2023-08-13 12:46:53 +04:00
|
|
|
//! Window layout logic.
|
|
|
|
|
//!
|
|
|
|
|
//! Niri implements scrollable tiling with workspaces. There's one primary output, and potentially
|
|
|
|
|
//! multiple other outputs.
|
|
|
|
|
//!
|
|
|
|
|
//! Our layout has the following invariants:
|
|
|
|
|
//!
|
|
|
|
|
//! 1. Disconnecting and reconnecting the same output must not change the layout.
|
|
|
|
|
//! * This includes both secondary outputs and the primary output.
|
|
|
|
|
//! 2. Connecting an output must not change the layout for any workspaces that were never on that
|
|
|
|
|
//! output.
|
|
|
|
|
//!
|
|
|
|
|
//! Therefore, we implement the following logic: every workspace keeps track of which output it
|
|
|
|
|
//! originated on. When an output disconnects, its workspace (or workspaces, in case of the primary
|
|
|
|
|
//! output disconnecting) are appended to the (potentially new) primary output, but remember their
|
|
|
|
|
//! original output. Then, if the original output connects again, all workspaces originally from
|
|
|
|
|
//! there move back to that output.
|
|
|
|
|
//!
|
|
|
|
|
//! In order to avoid surprising behavior, if the user creates or moves any new windows onto a
|
|
|
|
|
//! workspace, it forgets its original output, and its current output becomes its original output.
|
|
|
|
|
//! Imagine a scenario: the user works with a laptop and a monitor at home, then takes their laptop
|
|
|
|
|
//! with them, disconnecting the monitor, and keeps working as normal, using the second monitor's
|
|
|
|
|
//! workspace just like any other. Then they come back, reconnect the second monitor, and now we
|
|
|
|
|
//! don't want an unassuming workspace to end up on it.
|
|
|
|
|
//!
|
|
|
|
|
//! ## Workspaces-only-on-primary considerations
|
|
|
|
|
//!
|
|
|
|
|
//! If this logic results in more than one workspace present on a secondary output, then as a
|
|
|
|
|
//! compromise we only keep the first workspace there, and move the rest to the primary output,
|
|
|
|
|
//! making the primary output their original output.
|
|
|
|
|
|
|
|
|
|
use std::cmp::{max, min};
|
2023-10-07 10:47:51 +04:00
|
|
|
use std::iter::zip;
|
2023-08-13 12:46:53 +04:00
|
|
|
use std::mem;
|
2023-10-05 09:25:07 +04:00
|
|
|
use std::rc::Rc;
|
2023-08-13 19:55:37 +04:00
|
|
|
use std::time::Duration;
|
2023-08-13 12:46:53 +04:00
|
|
|
|
2023-10-11 14:32:29 +04:00
|
|
|
use arrayvec::ArrayVec;
|
2023-09-24 18:10:43 +04:00
|
|
|
use smithay::backend::renderer::element::solid::{SolidColorBuffer, SolidColorRenderElement};
|
2023-08-13 19:55:37 +04:00
|
|
|
use smithay::backend::renderer::element::surface::WaylandSurfaceRenderElement;
|
2023-08-14 17:25:28 +04:00
|
|
|
use smithay::backend::renderer::element::utils::{
|
|
|
|
|
CropRenderElement, Relocate, RelocateRenderElement,
|
|
|
|
|
};
|
2023-09-24 18:10:43 +04:00
|
|
|
use smithay::backend::renderer::element::{AsRenderElements, Kind};
|
2023-08-13 19:55:37 +04:00
|
|
|
use smithay::backend::renderer::gles::GlesRenderer;
|
2023-09-24 18:10:43 +04:00
|
|
|
use smithay::backend::renderer::ImportAll;
|
2023-08-13 19:55:37 +04:00
|
|
|
use smithay::desktop::space::SpaceElement;
|
2023-09-26 20:12:04 +04:00
|
|
|
use smithay::desktop::{layer_map_for_output, Window};
|
2023-08-13 12:46:53 +04:00
|
|
|
use smithay::output::Output;
|
2023-10-11 14:32:29 +04:00
|
|
|
use smithay::reexports::wayland_protocols::xdg::decoration::zv1::server::zxdg_toplevel_decoration_v1;
|
2023-08-16 09:08:10 +04:00
|
|
|
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel;
|
2023-08-13 12:46:53 +04:00
|
|
|
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
|
2023-09-24 18:10:43 +04:00
|
|
|
use smithay::render_elements;
|
2023-11-01 15:07:45 +04:00
|
|
|
use smithay::utils::{Logical, Point, Rectangle, Scale, Size, Transform};
|
|
|
|
|
use smithay::wayland::compositor::{send_surface_state, with_states};
|
2023-08-16 09:59:27 +04:00
|
|
|
use smithay::wayland::shell::xdg::SurfaceCachedState;
|
2023-08-13 12:46:53 +04:00
|
|
|
|
2023-08-14 15:54:11 +04:00
|
|
|
use crate::animation::Animation;
|
2023-10-05 09:25:07 +04:00
|
|
|
use crate::config::{self, Color, Config, PresetWidth, SizeChange};
|
2023-08-14 15:54:11 +04:00
|
|
|
|
2023-08-13 12:46:53 +04:00
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
|
|
|
pub struct OutputId(String);
|
|
|
|
|
|
2023-09-24 18:10:43 +04:00
|
|
|
render_elements! {
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
|
pub WorkspaceRenderElement<R> where R: ImportAll;
|
|
|
|
|
Wayland = WaylandSurfaceRenderElement<R>,
|
|
|
|
|
FocusRing = SolidColorRenderElement,
|
|
|
|
|
}
|
2023-08-14 17:25:28 +04:00
|
|
|
pub type MonitorRenderElement<R> =
|
|
|
|
|
RelocateRenderElement<CropRenderElement<WorkspaceRenderElement<R>>>;
|
2023-08-14 16:19:43 +04:00
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
pub trait LayoutElement: SpaceElement + PartialEq + Clone {
|
|
|
|
|
fn request_size(&self, size: Size<i32, Logical>);
|
2023-08-16 09:08:10 +04:00
|
|
|
fn request_fullscreen(&self, size: Size<i32, Logical>);
|
2023-08-13 19:55:37 +04:00
|
|
|
fn min_size(&self) -> Size<i32, Logical>;
|
2023-08-16 10:03:24 +04:00
|
|
|
fn max_size(&self) -> Size<i32, Logical>;
|
2023-08-13 19:55:37 +04:00
|
|
|
fn is_wl_surface(&self, wl_surface: &WlSurface) -> bool;
|
2023-10-11 14:32:29 +04:00
|
|
|
fn has_ssd(&self) -> bool;
|
2023-11-01 15:07:45 +04:00
|
|
|
fn set_preferred_scale_transform(&self, scale: i32, transform: Transform);
|
2023-08-13 19:55:37 +04:00
|
|
|
}
|
|
|
|
|
|
2023-08-13 12:46:53 +04:00
|
|
|
#[derive(Debug)]
|
2023-10-05 09:25:07 +04:00
|
|
|
pub struct Layout<W: LayoutElement> {
|
|
|
|
|
/// Monitors and workspaes in the layout.
|
|
|
|
|
monitor_set: MonitorSet<W>,
|
|
|
|
|
/// Configurable properties of the layout.
|
|
|
|
|
options: Rc<Options>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
|
enum MonitorSet<W: LayoutElement> {
|
2023-08-13 12:46:53 +04:00
|
|
|
/// At least one output is connected.
|
|
|
|
|
Normal {
|
2023-08-13 19:55:37 +04:00
|
|
|
/// Connected monitors.
|
|
|
|
|
monitors: Vec<Monitor<W>>,
|
2023-08-13 12:46:53 +04:00
|
|
|
/// Index of the primary monitor.
|
|
|
|
|
primary_idx: usize,
|
|
|
|
|
/// Index of the active monitor.
|
|
|
|
|
active_monitor_idx: usize,
|
|
|
|
|
},
|
|
|
|
|
/// No outputs are connected, and these are the workspaces.
|
2023-10-05 09:25:07 +04:00
|
|
|
NoOutputs {
|
|
|
|
|
/// The workspaces.
|
|
|
|
|
workspaces: Vec<Workspace<W>>,
|
|
|
|
|
},
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
2023-08-13 19:55:37 +04:00
|
|
|
pub struct Monitor<W: LayoutElement> {
|
|
|
|
|
/// Output for this monitor.
|
2023-08-13 12:46:53 +04:00
|
|
|
output: Output,
|
|
|
|
|
// Must always contain at least one.
|
2023-08-13 19:55:37 +04:00
|
|
|
workspaces: Vec<Workspace<W>>,
|
2023-08-13 12:46:53 +04:00
|
|
|
/// Index of the currently active workspace.
|
|
|
|
|
active_workspace_idx: usize,
|
2023-10-08 09:57:59 +04:00
|
|
|
/// In-progress switch between workspaces.
|
|
|
|
|
workspace_switch: Option<WorkspaceSwitch>,
|
2023-10-05 09:25:07 +04:00
|
|
|
/// Configurable properties of the layout.
|
|
|
|
|
options: Rc<Options>,
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
2023-10-08 09:57:59 +04:00
|
|
|
#[derive(Debug)]
|
|
|
|
|
enum WorkspaceSwitch {
|
|
|
|
|
Animation(Animation),
|
|
|
|
|
Gesture(WorkspaceSwitchGesture),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
|
struct WorkspaceSwitchGesture {
|
|
|
|
|
/// Index of the workspace where the gesture was started.
|
|
|
|
|
center_idx: usize,
|
|
|
|
|
/// Current, fractional workspace index.
|
|
|
|
|
current_idx: f64,
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-13 12:46:53 +04:00
|
|
|
#[derive(Debug)]
|
2023-08-13 19:55:37 +04:00
|
|
|
pub struct Workspace<W: LayoutElement> {
|
2023-08-13 12:46:53 +04:00
|
|
|
/// The original output of this workspace.
|
|
|
|
|
///
|
|
|
|
|
/// Most of the time this will be the workspace's current output, however, after an output
|
|
|
|
|
/// disconnection, it may remain pointing to the disconnected output.
|
|
|
|
|
original_output: OutputId,
|
|
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
/// Current output of this workspace.
|
|
|
|
|
output: Option<Output>,
|
2023-08-13 12:46:53 +04:00
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
/// Latest known view size for this workspace.
|
|
|
|
|
///
|
|
|
|
|
/// This should be computed from the current workspace output size, or, if all outputs have
|
|
|
|
|
/// been disconnected, preserved until a new output is connected.
|
|
|
|
|
view_size: Size<i32, Logical>,
|
|
|
|
|
|
2023-09-26 20:12:04 +04:00
|
|
|
/// Latest known working area for this workspace.
|
|
|
|
|
///
|
|
|
|
|
/// This is similar to view size, but takes into account things like layer shell exclusive
|
|
|
|
|
/// zones.
|
|
|
|
|
working_area: Rectangle<i32, Logical>,
|
|
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
/// Columns of windows on this workspace.
|
|
|
|
|
columns: Vec<Column<W>>,
|
2023-08-13 12:46:53 +04:00
|
|
|
|
|
|
|
|
/// Index of the currently active column, if any.
|
|
|
|
|
active_column_idx: usize,
|
2023-08-13 19:55:37 +04:00
|
|
|
|
2023-09-26 13:09:33 +04:00
|
|
|
/// Focus ring buffer and parameters.
|
|
|
|
|
focus_ring: FocusRing,
|
2023-09-24 18:10:43 +04:00
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
/// Offset of the view computed from the active column.
|
2023-10-07 10:47:51 +04:00
|
|
|
///
|
2023-10-07 17:45:55 +04:00
|
|
|
/// Any gaps, including left padding from work area left exclusive zone, is handled
|
2023-10-07 10:47:51 +04:00
|
|
|
/// with this view offset (rather than added as a constant elsewhere in the code). This allows
|
|
|
|
|
/// for natural handling of fullscreen windows, which must ignore work area padding.
|
2023-08-13 19:55:37 +04:00
|
|
|
view_offset: i32,
|
2023-08-14 15:54:11 +04:00
|
|
|
|
|
|
|
|
/// Animation of the view offset, if one is currently ongoing.
|
|
|
|
|
view_offset_anim: Option<Animation>,
|
2023-09-24 10:08:43 +04:00
|
|
|
|
|
|
|
|
/// Whether to activate the previous, rather than the next, column upon column removal.
|
|
|
|
|
///
|
|
|
|
|
/// When a new column is created and removed with no focus changes in-between, it is more
|
|
|
|
|
/// natural to activate the previously-focused column. This variable tracks that.
|
|
|
|
|
///
|
|
|
|
|
/// Since we only create-and-activate columns immediately to the right of the active column (in
|
|
|
|
|
/// contrast to tabs in Firefox, for example), we can track this as a bool, rather than an
|
|
|
|
|
/// index of the previous column to activate.
|
|
|
|
|
activate_prev_column_on_removal: bool,
|
2023-10-05 09:25:07 +04:00
|
|
|
|
|
|
|
|
/// Configurable properties of the layout.
|
|
|
|
|
options: Rc<Options>,
|
2023-08-13 19:55:37 +04:00
|
|
|
}
|
|
|
|
|
|
2023-09-26 13:09:33 +04:00
|
|
|
#[derive(Debug)]
|
|
|
|
|
struct FocusRing {
|
2023-10-11 14:32:29 +04:00
|
|
|
buffers: [SolidColorBuffer; 4],
|
|
|
|
|
locations: [Point<i32, Logical>; 4],
|
2023-09-26 13:09:33 +04:00
|
|
|
is_off: bool,
|
2023-10-11 14:32:29 +04:00
|
|
|
is_border: bool,
|
2023-09-26 13:09:33 +04:00
|
|
|
width: i32,
|
|
|
|
|
active_color: Color,
|
|
|
|
|
inactive_color: Color,
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-05 09:25:07 +04:00
|
|
|
#[derive(Debug, PartialEq)]
|
|
|
|
|
struct Options {
|
2023-10-07 17:45:55 +04:00
|
|
|
/// Padding around windows in logical pixels.
|
|
|
|
|
gaps: i32,
|
2023-10-05 09:25:07 +04:00
|
|
|
focus_ring: config::FocusRing,
|
|
|
|
|
/// Column widths that `toggle_width()` switches between.
|
|
|
|
|
preset_widths: Vec<ColumnWidth>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Default for Options {
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
Self {
|
2023-10-07 17:45:55 +04:00
|
|
|
gaps: 16,
|
2023-10-05 09:25:07 +04:00
|
|
|
focus_ring: Default::default(),
|
|
|
|
|
preset_widths: vec![
|
|
|
|
|
ColumnWidth::Proportion(1. / 3.),
|
|
|
|
|
ColumnWidth::Proportion(0.5),
|
|
|
|
|
ColumnWidth::Proportion(2. / 3.),
|
|
|
|
|
],
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Options {
|
|
|
|
|
fn from_config(config: &Config) -> Self {
|
|
|
|
|
let preset_column_widths = &config.preset_column_widths;
|
|
|
|
|
|
|
|
|
|
let preset_widths = if preset_column_widths.is_empty() {
|
|
|
|
|
Options::default().preset_widths
|
|
|
|
|
} else {
|
|
|
|
|
preset_column_widths
|
|
|
|
|
.iter()
|
|
|
|
|
.copied()
|
|
|
|
|
.map(ColumnWidth::from)
|
|
|
|
|
.collect()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Self {
|
2023-10-07 17:45:55 +04:00
|
|
|
gaps: config.gaps.into(),
|
2023-10-05 09:25:07 +04:00
|
|
|
focus_ring: config.focus_ring,
|
|
|
|
|
preset_widths,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
/// Width of a column.
|
2023-10-05 09:25:07 +04:00
|
|
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
2023-08-13 19:55:37 +04:00
|
|
|
enum ColumnWidth {
|
|
|
|
|
/// Proportion of the current view width.
|
|
|
|
|
Proportion(f64),
|
2023-08-14 18:29:50 +04:00
|
|
|
/// One of the proportion presets.
|
|
|
|
|
///
|
|
|
|
|
/// This is separate from Proportion in order to be able to reliably cycle between preset
|
|
|
|
|
/// proportions.
|
2023-10-05 09:25:07 +04:00
|
|
|
Preset(usize),
|
2023-08-13 19:55:37 +04:00
|
|
|
/// Fixed width in logical pixels.
|
|
|
|
|
Fixed(i32),
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
2023-10-05 09:25:07 +04:00
|
|
|
impl From<PresetWidth> for ColumnWidth {
|
|
|
|
|
fn from(value: PresetWidth) -> Self {
|
|
|
|
|
match value {
|
|
|
|
|
PresetWidth::Proportion(p) => Self::Proportion(p.clamp(0., 10000.)),
|
|
|
|
|
PresetWidth::Fixed(f) => Self::Fixed(f.clamp(1, 100000)),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-13 12:46:53 +04:00
|
|
|
#[derive(Debug)]
|
2023-08-13 19:55:37 +04:00
|
|
|
struct Column<W: LayoutElement> {
|
|
|
|
|
/// Windows in this column.
|
|
|
|
|
///
|
|
|
|
|
/// Must be non-empty.
|
|
|
|
|
windows: Vec<W>,
|
|
|
|
|
|
2023-08-13 12:46:53 +04:00
|
|
|
/// Index of the currently active window.
|
|
|
|
|
active_window_idx: usize,
|
2023-08-13 19:55:37 +04:00
|
|
|
|
|
|
|
|
/// Desired width of this column.
|
|
|
|
|
width: ColumnWidth,
|
2023-08-16 09:08:10 +04:00
|
|
|
|
|
|
|
|
/// Whether this column contains a single full-screened window.
|
|
|
|
|
is_fullscreen: bool,
|
2023-10-04 15:45:30 +04:00
|
|
|
|
|
|
|
|
/// Latest known view size for this column's workspace.
|
|
|
|
|
view_size: Size<i32, Logical>,
|
|
|
|
|
|
|
|
|
|
/// Latest known working area for this column's workspace.
|
|
|
|
|
working_area: Rectangle<i32, Logical>,
|
2023-10-05 09:25:07 +04:00
|
|
|
|
|
|
|
|
/// Configurable properties of the layout.
|
|
|
|
|
options: Rc<Options>,
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl OutputId {
|
|
|
|
|
pub fn new(output: &Output) -> Self {
|
|
|
|
|
Self(output.name())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
impl LayoutElement for Window {
|
|
|
|
|
fn request_size(&self, size: Size<i32, Logical>) {
|
2023-08-16 09:08:10 +04:00
|
|
|
self.toplevel().with_pending_state(|state| {
|
|
|
|
|
state.size = Some(size);
|
|
|
|
|
state.states.unset(xdg_toplevel::State::Fullscreen);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn request_fullscreen(&self, size: Size<i32, Logical>) {
|
|
|
|
|
self.toplevel().with_pending_state(|state| {
|
|
|
|
|
state.size = Some(size);
|
|
|
|
|
state.states.set(xdg_toplevel::State::Fullscreen);
|
|
|
|
|
});
|
2023-08-13 19:55:37 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn min_size(&self) -> Size<i32, Logical> {
|
|
|
|
|
with_states(self.toplevel().wl_surface(), |state| {
|
2023-08-16 09:59:27 +04:00
|
|
|
let curr = state.cached_state.current::<SurfaceCachedState>();
|
|
|
|
|
curr.min_size
|
2023-08-13 19:55:37 +04:00
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-16 10:03:24 +04:00
|
|
|
fn max_size(&self) -> Size<i32, Logical> {
|
|
|
|
|
with_states(self.toplevel().wl_surface(), |state| {
|
|
|
|
|
let curr = state.cached_state.current::<SurfaceCachedState>();
|
|
|
|
|
curr.max_size
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
fn is_wl_surface(&self, wl_surface: &WlSurface) -> bool {
|
|
|
|
|
self.toplevel().wl_surface() == wl_surface
|
|
|
|
|
}
|
2023-10-11 14:32:29 +04:00
|
|
|
|
2023-11-01 15:07:45 +04:00
|
|
|
fn set_preferred_scale_transform(&self, scale: i32, transform: Transform) {
|
|
|
|
|
self.with_surfaces(|surface, data| {
|
|
|
|
|
send_surface_state(surface, data, scale, transform);
|
|
|
|
|
});
|
2023-10-26 00:15:46 +04:00
|
|
|
}
|
|
|
|
|
|
2023-10-11 14:32:29 +04:00
|
|
|
fn has_ssd(&self) -> bool {
|
|
|
|
|
self.toplevel().current_state().decoration_mode
|
|
|
|
|
== Some(zxdg_toplevel_decoration_v1::Mode::ServerSide)
|
|
|
|
|
}
|
2023-08-13 19:55:37 +04:00
|
|
|
}
|
|
|
|
|
|
2023-09-26 13:09:33 +04:00
|
|
|
impl FocusRing {
|
2023-10-11 14:32:29 +04:00
|
|
|
fn update(
|
|
|
|
|
&mut self,
|
|
|
|
|
win_pos: Point<i32, Logical>,
|
|
|
|
|
win_size: Size<i32, Logical>,
|
|
|
|
|
is_border: bool,
|
|
|
|
|
) {
|
|
|
|
|
if is_border {
|
|
|
|
|
self.buffers[0].resize((win_size.w + self.width * 2, self.width));
|
|
|
|
|
self.buffers[1].resize((win_size.w + self.width * 2, self.width));
|
|
|
|
|
self.buffers[2].resize((self.width, win_size.h));
|
|
|
|
|
self.buffers[3].resize((self.width, win_size.h));
|
|
|
|
|
|
|
|
|
|
self.locations[0] = win_pos + Point::from((-self.width, -self.width));
|
|
|
|
|
self.locations[1] = win_pos + Point::from((-self.width, win_size.h));
|
|
|
|
|
self.locations[2] = win_pos + Point::from((-self.width, 0));
|
|
|
|
|
self.locations[3] = win_pos + Point::from((win_size.w, 0));
|
|
|
|
|
} else {
|
|
|
|
|
let size = win_size + Size::from((self.width * 2, self.width * 2));
|
|
|
|
|
self.buffers[0].resize(size);
|
|
|
|
|
self.locations[0] = win_pos - Point::from((self.width, self.width));
|
|
|
|
|
}
|
2023-09-26 13:09:33 +04:00
|
|
|
|
2023-10-11 14:32:29 +04:00
|
|
|
self.is_border = is_border;
|
2023-10-11 11:56:38 +04:00
|
|
|
}
|
|
|
|
|
|
2023-09-26 13:09:33 +04:00
|
|
|
fn set_active(&mut self, is_active: bool) {
|
2023-10-11 14:32:29 +04:00
|
|
|
let color = if is_active {
|
2023-09-26 13:09:33 +04:00
|
|
|
self.active_color.into()
|
|
|
|
|
} else {
|
|
|
|
|
self.inactive_color.into()
|
2023-10-11 14:32:29 +04:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for buf in &mut self.buffers {
|
|
|
|
|
buf.set_color(color);
|
|
|
|
|
}
|
2023-09-26 13:09:33 +04:00
|
|
|
}
|
|
|
|
|
|
2023-10-11 14:32:29 +04:00
|
|
|
fn render(&self, scale: Scale<f64>) -> impl Iterator<Item = SolidColorRenderElement> {
|
|
|
|
|
let mut rv = ArrayVec::<_, 4>::new();
|
|
|
|
|
|
2023-09-26 13:09:33 +04:00
|
|
|
if self.is_off {
|
2023-10-11 14:32:29 +04:00
|
|
|
return rv.into_iter();
|
2023-09-26 13:09:33 +04:00
|
|
|
}
|
|
|
|
|
|
2023-10-11 14:32:29 +04:00
|
|
|
let mut push = |buffer, location: Point<i32, Logical>| {
|
|
|
|
|
let elem = SolidColorRenderElement::from_buffer(
|
|
|
|
|
buffer,
|
|
|
|
|
location.to_physical_precise_round(scale),
|
|
|
|
|
scale,
|
|
|
|
|
1.,
|
|
|
|
|
Kind::Unspecified,
|
|
|
|
|
);
|
|
|
|
|
rv.push(elem);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if self.is_border {
|
|
|
|
|
for (buf, loc) in zip(&self.buffers, self.locations) {
|
|
|
|
|
push(buf, loc);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
push(&self.buffers[0], self.locations[0]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rv.into_iter()
|
2023-09-26 13:09:33 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-05 09:25:07 +04:00
|
|
|
impl FocusRing {
|
|
|
|
|
fn new(config: config::FocusRing) -> Self {
|
2023-09-26 13:09:33 +04:00
|
|
|
Self {
|
2023-10-11 14:32:29 +04:00
|
|
|
buffers: Default::default(),
|
|
|
|
|
locations: Default::default(),
|
2023-10-05 09:25:07 +04:00
|
|
|
is_off: config.off,
|
2023-10-11 14:32:29 +04:00
|
|
|
is_border: false,
|
2023-10-05 09:25:07 +04:00
|
|
|
width: config.width.into(),
|
|
|
|
|
active_color: config.active_color,
|
|
|
|
|
inactive_color: config.inactive_color,
|
2023-09-26 13:09:33 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-08 09:57:59 +04:00
|
|
|
impl WorkspaceSwitch {
|
|
|
|
|
fn current_idx(&self) -> f64 {
|
|
|
|
|
match self {
|
|
|
|
|
WorkspaceSwitch::Animation(anim) => anim.value(),
|
|
|
|
|
WorkspaceSwitch::Gesture(gesture) => gesture.current_idx,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Returns `true` if the workspace switch is [`Animation`].
|
|
|
|
|
///
|
|
|
|
|
/// [`Animation`]: WorkspaceSwitch::Animation
|
|
|
|
|
#[must_use]
|
|
|
|
|
fn is_animation(&self) -> bool {
|
|
|
|
|
matches!(self, Self::Animation(..))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
impl ColumnWidth {
|
2023-10-05 09:25:07 +04:00
|
|
|
fn resolve(self, options: &Options, view_width: i32) -> i32 {
|
2023-08-13 19:55:37 +04:00
|
|
|
match self {
|
2023-10-05 09:29:39 +04:00
|
|
|
ColumnWidth::Proportion(proportion) => {
|
2023-10-07 17:45:55 +04:00
|
|
|
((view_width - options.gaps) as f64 * proportion).floor() as i32 - options.gaps
|
2023-10-05 09:29:39 +04:00
|
|
|
}
|
2023-10-05 09:25:07 +04:00
|
|
|
ColumnWidth::Preset(idx) => options.preset_widths[idx].resolve(options, view_width),
|
2023-10-05 09:29:39 +04:00
|
|
|
ColumnWidth::Fixed(width) => width,
|
2023-08-13 19:55:37 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Default for ColumnWidth {
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
Self::Proportion(0.5)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-05 09:25:07 +04:00
|
|
|
impl<W: LayoutElement> Layout<W> {
|
|
|
|
|
pub fn new(config: &Config) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
monitor_set: MonitorSet::NoOutputs { workspaces: vec![] },
|
|
|
|
|
options: Rc::new(Options::from_config(config)),
|
|
|
|
|
}
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn add_output(&mut self, output: Output) {
|
|
|
|
|
let id = OutputId::new(&output);
|
|
|
|
|
|
2023-10-05 09:25:07 +04:00
|
|
|
self.monitor_set = match mem::take(&mut self.monitor_set) {
|
2023-08-13 12:46:53 +04:00
|
|
|
MonitorSet::Normal {
|
|
|
|
|
mut monitors,
|
|
|
|
|
primary_idx,
|
|
|
|
|
active_monitor_idx,
|
|
|
|
|
} => {
|
|
|
|
|
let primary = &mut monitors[primary_idx];
|
|
|
|
|
|
|
|
|
|
let mut workspaces = vec![];
|
|
|
|
|
for i in (0..primary.workspaces.len()).rev() {
|
|
|
|
|
if primary.workspaces[i].original_output == id {
|
2023-08-13 19:55:37 +04:00
|
|
|
let ws = primary.workspaces.remove(i);
|
2023-10-14 20:32:53 +04:00
|
|
|
|
|
|
|
|
// The user could've closed a window while remaining on this workspace, on
|
|
|
|
|
// another monitor. However, we will add an empty workspace in the end
|
|
|
|
|
// instead.
|
|
|
|
|
if ws.has_windows() {
|
|
|
|
|
workspaces.push(ws);
|
|
|
|
|
}
|
2023-09-26 20:36:48 +04:00
|
|
|
|
|
|
|
|
if i <= primary.active_workspace_idx {
|
|
|
|
|
primary.active_workspace_idx =
|
|
|
|
|
primary.active_workspace_idx.saturating_sub(1);
|
|
|
|
|
}
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
workspaces.reverse();
|
2023-10-14 20:32:53 +04:00
|
|
|
|
|
|
|
|
// Make sure there's always an empty workspace.
|
|
|
|
|
workspaces.push(Workspace::new(output.clone(), self.options.clone()));
|
2023-08-13 12:46:53 +04:00
|
|
|
|
|
|
|
|
for ws in &mut workspaces {
|
2023-08-13 19:55:37 +04:00
|
|
|
ws.set_output(Some(output.clone()));
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
2023-10-05 09:25:07 +04:00
|
|
|
monitors.push(Monitor::new(output, workspaces, self.options.clone()));
|
2023-08-13 12:46:53 +04:00
|
|
|
MonitorSet::Normal {
|
|
|
|
|
monitors,
|
|
|
|
|
primary_idx,
|
|
|
|
|
active_monitor_idx,
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-10-05 09:25:07 +04:00
|
|
|
MonitorSet::NoOutputs { mut workspaces } => {
|
2023-08-13 19:55:37 +04:00
|
|
|
// We know there are no empty workspaces there, so add one.
|
2023-10-05 09:25:07 +04:00
|
|
|
workspaces.push(Workspace::new(output.clone(), self.options.clone()));
|
2023-08-13 12:46:53 +04:00
|
|
|
|
|
|
|
|
for workspace in &mut workspaces {
|
2023-08-13 19:55:37 +04:00
|
|
|
workspace.set_output(Some(output.clone()));
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
2023-10-05 09:25:07 +04:00
|
|
|
let monitor = Monitor::new(output, workspaces, self.options.clone());
|
|
|
|
|
|
2023-08-13 12:46:53 +04:00
|
|
|
MonitorSet::Normal {
|
|
|
|
|
monitors: vec![monitor],
|
|
|
|
|
primary_idx: 0,
|
|
|
|
|
active_monitor_idx: 0,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn remove_output(&mut self, output: &Output) {
|
2023-10-05 09:25:07 +04:00
|
|
|
self.monitor_set = match mem::take(&mut self.monitor_set) {
|
2023-08-13 12:46:53 +04:00
|
|
|
MonitorSet::Normal {
|
|
|
|
|
mut monitors,
|
|
|
|
|
mut primary_idx,
|
|
|
|
|
mut active_monitor_idx,
|
|
|
|
|
} => {
|
|
|
|
|
let idx = monitors
|
|
|
|
|
.iter()
|
|
|
|
|
.position(|mon| &mon.output == output)
|
|
|
|
|
.expect("trying to remove non-existing output");
|
|
|
|
|
let monitor = monitors.remove(idx);
|
|
|
|
|
let mut workspaces = monitor.workspaces;
|
|
|
|
|
|
|
|
|
|
for ws in &mut workspaces {
|
2023-08-13 19:55:37 +04:00
|
|
|
ws.set_output(None);
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get rid of empty workspaces.
|
2023-08-13 19:55:37 +04:00
|
|
|
workspaces.retain(|ws| ws.has_windows());
|
2023-08-13 12:46:53 +04:00
|
|
|
|
|
|
|
|
if monitors.is_empty() {
|
|
|
|
|
// Removed the last monitor.
|
2023-10-05 09:25:07 +04:00
|
|
|
MonitorSet::NoOutputs { workspaces }
|
2023-08-13 12:46:53 +04:00
|
|
|
} else {
|
|
|
|
|
if primary_idx >= idx {
|
|
|
|
|
// Update primary_idx to either still point at the same monitor, or at some
|
|
|
|
|
// other monitor if the primary has been removed.
|
|
|
|
|
primary_idx = primary_idx.saturating_sub(1);
|
|
|
|
|
}
|
|
|
|
|
if active_monitor_idx >= idx {
|
|
|
|
|
// Update active_monitor_idx to either still point at the same monitor, or
|
|
|
|
|
// at some other monitor if the active monitor has
|
|
|
|
|
// been removed.
|
|
|
|
|
active_monitor_idx = active_monitor_idx.saturating_sub(1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let primary = &mut monitors[primary_idx];
|
|
|
|
|
for ws in &mut workspaces {
|
2023-08-13 19:55:37 +04:00
|
|
|
ws.set_output(Some(primary.output.clone()));
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
2023-08-13 19:55:37 +04:00
|
|
|
|
2023-10-14 19:24:52 +04:00
|
|
|
let empty_was_focused =
|
|
|
|
|
primary.active_workspace_idx == primary.workspaces.len() - 1;
|
|
|
|
|
|
|
|
|
|
// Push the workspaces from the removed monitor in the end, right before the
|
|
|
|
|
// last, empty, workspace.
|
2023-08-13 19:55:37 +04:00
|
|
|
let empty = primary.workspaces.remove(primary.workspaces.len() - 1);
|
2023-08-13 12:46:53 +04:00
|
|
|
primary.workspaces.extend(workspaces);
|
2023-08-13 19:55:37 +04:00
|
|
|
primary.workspaces.push(empty);
|
2023-08-13 12:46:53 +04:00
|
|
|
|
2023-10-14 19:24:52 +04:00
|
|
|
// If the empty workspace was focused on the primary monitor, keep it focused.
|
|
|
|
|
if empty_was_focused {
|
|
|
|
|
primary.active_workspace_idx = primary.workspaces.len() - 1;
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-13 12:46:53 +04:00
|
|
|
MonitorSet::Normal {
|
|
|
|
|
monitors,
|
|
|
|
|
primary_idx,
|
|
|
|
|
active_monitor_idx,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-10-05 09:25:07 +04:00
|
|
|
MonitorSet::NoOutputs { .. } => {
|
2023-08-13 12:46:53 +04:00
|
|
|
panic!("tried to remove output when there were already none")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-12 19:41:50 +04:00
|
|
|
pub fn add_window_by_idx(
|
2023-08-13 12:46:53 +04:00
|
|
|
&mut self,
|
|
|
|
|
monitor_idx: usize,
|
|
|
|
|
workspace_idx: usize,
|
2023-08-13 19:55:37 +04:00
|
|
|
window: W,
|
2023-08-13 12:46:53 +04:00
|
|
|
activate: bool,
|
|
|
|
|
) {
|
|
|
|
|
let MonitorSet::Normal {
|
|
|
|
|
monitors,
|
|
|
|
|
active_monitor_idx,
|
|
|
|
|
..
|
2023-10-05 09:25:07 +04:00
|
|
|
} = &mut self.monitor_set
|
2023-08-13 12:46:53 +04:00
|
|
|
else {
|
|
|
|
|
panic!()
|
|
|
|
|
};
|
|
|
|
|
|
2023-08-14 16:19:43 +04:00
|
|
|
monitors[monitor_idx].add_window(workspace_idx, window, activate);
|
2023-08-13 12:46:53 +04:00
|
|
|
|
|
|
|
|
if activate {
|
|
|
|
|
*active_monitor_idx = monitor_idx;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-12 19:41:50 +04:00
|
|
|
/// Adds a new window to the layout.
|
|
|
|
|
///
|
|
|
|
|
/// Returns an output that the window was added to, if there were any outputs.
|
|
|
|
|
pub fn add_window(&mut self, window: W, activate: bool) -> Option<&Output> {
|
2023-10-05 09:25:07 +04:00
|
|
|
match &mut self.monitor_set {
|
2023-09-12 19:41:50 +04:00
|
|
|
MonitorSet::Normal {
|
|
|
|
|
monitors,
|
|
|
|
|
active_monitor_idx,
|
|
|
|
|
..
|
|
|
|
|
} => {
|
|
|
|
|
let mon = &mut monitors[*active_monitor_idx];
|
|
|
|
|
mon.add_window(mon.active_workspace_idx, window, activate);
|
|
|
|
|
Some(&mon.output)
|
|
|
|
|
}
|
2023-10-05 09:25:07 +04:00
|
|
|
MonitorSet::NoOutputs { workspaces } => {
|
2023-09-12 19:41:50 +04:00
|
|
|
let ws = if let Some(ws) = workspaces.get_mut(0) {
|
|
|
|
|
ws
|
|
|
|
|
} else {
|
2023-10-05 09:25:07 +04:00
|
|
|
workspaces.push(Workspace::new_no_outputs(self.options.clone()));
|
2023-09-12 19:41:50 +04:00
|
|
|
&mut workspaces[0]
|
|
|
|
|
};
|
|
|
|
|
ws.add_window(window, activate);
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
pub fn remove_window(&mut self, window: &W) {
|
2023-10-05 09:25:07 +04:00
|
|
|
match &mut self.monitor_set {
|
2023-08-13 19:55:37 +04:00
|
|
|
MonitorSet::Normal { monitors, .. } => {
|
|
|
|
|
for mon in monitors {
|
|
|
|
|
for (idx, ws) in mon.workspaces.iter_mut().enumerate() {
|
|
|
|
|
if ws.has_window(window) {
|
|
|
|
|
ws.remove_window(window);
|
|
|
|
|
|
|
|
|
|
// Clean up empty workspaces that are not active and not last.
|
|
|
|
|
if !ws.has_windows()
|
|
|
|
|
&& idx != mon.active_workspace_idx
|
|
|
|
|
&& idx != mon.workspaces.len() - 1
|
|
|
|
|
{
|
|
|
|
|
mon.workspaces.remove(idx);
|
2023-09-26 20:42:16 +04:00
|
|
|
|
|
|
|
|
if idx < mon.active_workspace_idx {
|
|
|
|
|
mon.active_workspace_idx -= 1;
|
|
|
|
|
}
|
2023-08-13 19:55:37 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-10-05 09:25:07 +04:00
|
|
|
MonitorSet::NoOutputs { workspaces, .. } => {
|
2023-08-13 19:55:37 +04:00
|
|
|
for (idx, ws) in workspaces.iter_mut().enumerate() {
|
|
|
|
|
if ws.has_window(window) {
|
|
|
|
|
ws.remove_window(window);
|
2023-08-13 12:46:53 +04:00
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
// Clean up empty workspaces.
|
|
|
|
|
if !ws.has_windows() {
|
|
|
|
|
workspaces.remove(idx);
|
|
|
|
|
}
|
2023-08-13 12:46:53 +04:00
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-08-13 12:46:53 +04:00
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
pub fn update_window(&mut self, window: &W) {
|
2023-10-05 09:25:07 +04:00
|
|
|
match &mut self.monitor_set {
|
2023-08-13 19:55:37 +04:00
|
|
|
MonitorSet::Normal { monitors, .. } => {
|
|
|
|
|
for mon in monitors {
|
|
|
|
|
for ws in &mut mon.workspaces {
|
|
|
|
|
if ws.has_window(window) {
|
|
|
|
|
ws.update_window(window);
|
2023-08-16 09:08:10 +04:00
|
|
|
return;
|
2023-08-13 19:55:37 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-10-05 09:25:07 +04:00
|
|
|
MonitorSet::NoOutputs { workspaces, .. } => {
|
2023-08-13 19:55:37 +04:00
|
|
|
for ws in workspaces {
|
|
|
|
|
if ws.has_window(window) {
|
|
|
|
|
ws.update_window(window);
|
2023-08-16 09:08:10 +04:00
|
|
|
return;
|
2023-08-13 19:55:37 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
2023-10-01 07:21:53 +04:00
|
|
|
pub fn find_window_and_output(&self, wl_surface: &WlSurface) -> Option<(W, Output)> {
|
2023-10-05 09:25:07 +04:00
|
|
|
if let MonitorSet::Normal { monitors, .. } = &self.monitor_set {
|
2023-08-13 19:55:37 +04:00
|
|
|
for mon in monitors {
|
2023-10-01 07:21:53 +04:00
|
|
|
for ws in &mon.workspaces {
|
2023-08-13 19:55:37 +04:00
|
|
|
if let Some(window) = ws.find_wl_surface(wl_surface) {
|
|
|
|
|
return Some((window.clone(), mon.output.clone()));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-26 20:12:04 +04:00
|
|
|
pub fn update_output_size(&mut self, output: &Output) {
|
2023-10-05 09:25:07 +04:00
|
|
|
let MonitorSet::Normal { monitors, .. } = &mut self.monitor_set else {
|
2023-08-13 19:55:37 +04:00
|
|
|
panic!()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for mon in monitors {
|
|
|
|
|
if &mon.output == output {
|
2023-09-26 20:12:04 +04:00
|
|
|
let view_size = output_size(output);
|
|
|
|
|
let working_area = layer_map_for_output(output).non_exclusive_zone();
|
|
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
for ws in &mut mon.workspaces {
|
2023-09-26 20:12:04 +04:00
|
|
|
ws.set_view_size(view_size, working_area);
|
2023-08-13 19:55:37 +04:00
|
|
|
}
|
2023-09-26 20:12:04 +04:00
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn activate_window(&mut self, window: &W) {
|
2023-08-13 12:46:53 +04:00
|
|
|
let MonitorSet::Normal {
|
|
|
|
|
monitors,
|
|
|
|
|
active_monitor_idx,
|
|
|
|
|
..
|
2023-10-05 09:25:07 +04:00
|
|
|
} = &mut self.monitor_set
|
2023-08-13 12:46:53 +04:00
|
|
|
else {
|
|
|
|
|
todo!()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for (monitor_idx, mon) in monitors.iter_mut().enumerate() {
|
2023-10-21 20:46:00 +04:00
|
|
|
for (_workspace_idx, ws) in mon.workspaces.iter_mut().enumerate() {
|
2023-08-13 19:55:37 +04:00
|
|
|
if ws.has_window(window) {
|
2023-08-13 12:46:53 +04:00
|
|
|
*active_monitor_idx = monitor_idx;
|
2023-10-21 20:46:00 +04:00
|
|
|
// FIXME: switch to this workspace if not already switching.
|
2023-08-13 19:55:37 +04:00
|
|
|
ws.activate_window(window);
|
2023-08-13 12:46:53 +04:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn activate_output(&mut self, output: &Output) {
|
|
|
|
|
let MonitorSet::Normal {
|
|
|
|
|
monitors,
|
|
|
|
|
active_monitor_idx,
|
|
|
|
|
..
|
2023-10-05 09:25:07 +04:00
|
|
|
} = &mut self.monitor_set
|
2023-08-13 12:46:53 +04:00
|
|
|
else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let idx = monitors
|
|
|
|
|
.iter()
|
|
|
|
|
.position(|mon| &mon.output == output)
|
|
|
|
|
.unwrap();
|
|
|
|
|
*active_monitor_idx = idx;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn active_output(&self) -> Option<&Output> {
|
|
|
|
|
let MonitorSet::Normal {
|
|
|
|
|
monitors,
|
|
|
|
|
active_monitor_idx,
|
|
|
|
|
..
|
2023-10-05 09:25:07 +04:00
|
|
|
} = &self.monitor_set
|
2023-08-13 12:46:53 +04:00
|
|
|
else {
|
|
|
|
|
return None;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Some(&monitors[*active_monitor_idx].output)
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-05 09:25:07 +04:00
|
|
|
pub fn active_workspace(&self) -> Option<&Workspace<W>> {
|
|
|
|
|
let MonitorSet::Normal {
|
|
|
|
|
monitors,
|
|
|
|
|
active_monitor_idx,
|
|
|
|
|
..
|
|
|
|
|
} = &self.monitor_set
|
|
|
|
|
else {
|
|
|
|
|
return None;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let mon = &monitors[*active_monitor_idx];
|
|
|
|
|
Some(&mon.workspaces[mon.active_workspace_idx])
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-10 12:42:24 +04:00
|
|
|
pub fn active_window(&self) -> Option<(W, Output)> {
|
|
|
|
|
let MonitorSet::Normal {
|
|
|
|
|
monitors,
|
|
|
|
|
active_monitor_idx,
|
|
|
|
|
..
|
|
|
|
|
} = &self.monitor_set
|
|
|
|
|
else {
|
|
|
|
|
return None;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let mon = &monitors[*active_monitor_idx];
|
|
|
|
|
let ws = &mon.workspaces[mon.active_workspace_idx];
|
|
|
|
|
|
|
|
|
|
if ws.columns.is_empty() {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let col = &ws.columns[ws.active_column_idx];
|
|
|
|
|
Some((
|
|
|
|
|
col.windows[col.active_window_idx].clone(),
|
|
|
|
|
mon.output.clone(),
|
|
|
|
|
))
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-16 10:59:34 +04:00
|
|
|
pub fn windows_for_output(&self, output: &Output) -> impl Iterator<Item = &W> + '_ {
|
2023-10-05 09:25:07 +04:00
|
|
|
let MonitorSet::Normal { monitors, .. } = &self.monitor_set else {
|
2023-08-16 10:59:34 +04:00
|
|
|
panic!()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let mon = monitors.iter().find(|mon| &mon.output == output).unwrap();
|
|
|
|
|
mon.workspaces.iter().flat_map(|ws| ws.windows())
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
fn active_monitor(&mut self) -> Option<&mut Monitor<W>> {
|
2023-08-13 12:46:53 +04:00
|
|
|
let MonitorSet::Normal {
|
|
|
|
|
monitors,
|
|
|
|
|
active_monitor_idx,
|
|
|
|
|
..
|
2023-10-05 09:25:07 +04:00
|
|
|
} = &mut self.monitor_set
|
2023-08-13 12:46:53 +04:00
|
|
|
else {
|
|
|
|
|
return None;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Some(&mut monitors[*active_monitor_idx])
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-27 17:44:22 +04:00
|
|
|
pub fn monitor_for_output(&self, output: &Output) -> Option<&Monitor<W>> {
|
2023-10-05 09:25:07 +04:00
|
|
|
let MonitorSet::Normal { monitors, .. } = &self.monitor_set else {
|
2023-08-27 17:44:22 +04:00
|
|
|
return None;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
monitors.iter().find(|monitor| &monitor.output == output)
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-15 12:49:26 +04:00
|
|
|
pub fn outputs(&self) -> impl Iterator<Item = &Output> + '_ {
|
2023-10-05 09:25:07 +04:00
|
|
|
let monitors = if let MonitorSet::Normal { monitors, .. } = &self.monitor_set {
|
2023-08-15 12:49:26 +04:00
|
|
|
&monitors[..]
|
|
|
|
|
} else {
|
|
|
|
|
&[][..]
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
monitors.iter().map(|mon| &mon.output)
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-13 12:46:53 +04:00
|
|
|
pub fn move_left(&mut self) {
|
2023-08-14 16:19:43 +04:00
|
|
|
let Some(monitor) = self.active_monitor() else {
|
2023-08-13 12:46:53 +04:00
|
|
|
return;
|
|
|
|
|
};
|
2023-08-14 16:19:43 +04:00
|
|
|
monitor.move_left();
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn move_right(&mut self) {
|
2023-08-14 16:19:43 +04:00
|
|
|
let Some(monitor) = self.active_monitor() else {
|
2023-08-13 12:46:53 +04:00
|
|
|
return;
|
|
|
|
|
};
|
2023-08-14 16:19:43 +04:00
|
|
|
monitor.move_right();
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn move_down(&mut self) {
|
2023-08-14 16:19:43 +04:00
|
|
|
let Some(monitor) = self.active_monitor() else {
|
2023-08-13 12:46:53 +04:00
|
|
|
return;
|
|
|
|
|
};
|
2023-08-14 16:19:43 +04:00
|
|
|
monitor.move_down();
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn move_up(&mut self) {
|
2023-08-14 16:19:43 +04:00
|
|
|
let Some(monitor) = self.active_monitor() else {
|
2023-08-13 12:46:53 +04:00
|
|
|
return;
|
|
|
|
|
};
|
2023-08-14 16:19:43 +04:00
|
|
|
monitor.move_up();
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn focus_left(&mut self) {
|
2023-08-14 16:19:43 +04:00
|
|
|
let Some(monitor) = self.active_monitor() else {
|
2023-08-13 12:46:53 +04:00
|
|
|
return;
|
|
|
|
|
};
|
2023-08-14 16:19:43 +04:00
|
|
|
monitor.focus_left();
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn focus_right(&mut self) {
|
2023-08-14 16:19:43 +04:00
|
|
|
let Some(monitor) = self.active_monitor() else {
|
2023-08-13 12:46:53 +04:00
|
|
|
return;
|
|
|
|
|
};
|
2023-08-14 16:19:43 +04:00
|
|
|
monitor.focus_right();
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn focus_down(&mut self) {
|
2023-08-14 16:19:43 +04:00
|
|
|
let Some(monitor) = self.active_monitor() else {
|
2023-08-13 12:46:53 +04:00
|
|
|
return;
|
|
|
|
|
};
|
2023-08-14 16:19:43 +04:00
|
|
|
monitor.focus_down();
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn focus_up(&mut self) {
|
2023-08-14 16:19:43 +04:00
|
|
|
let Some(monitor) = self.active_monitor() else {
|
2023-08-13 12:46:53 +04:00
|
|
|
return;
|
|
|
|
|
};
|
2023-08-14 16:19:43 +04:00
|
|
|
monitor.focus_up();
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn move_to_workspace_up(&mut self) {
|
2023-08-14 16:19:43 +04:00
|
|
|
let Some(monitor) = self.active_monitor() else {
|
2023-08-13 12:46:53 +04:00
|
|
|
return;
|
|
|
|
|
};
|
2023-08-14 16:19:43 +04:00
|
|
|
monitor.move_to_workspace_up();
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn move_to_workspace_down(&mut self) {
|
2023-08-14 16:19:43 +04:00
|
|
|
let Some(monitor) = self.active_monitor() else {
|
2023-08-13 12:46:53 +04:00
|
|
|
return;
|
|
|
|
|
};
|
2023-08-14 16:19:43 +04:00
|
|
|
monitor.move_to_workspace_down();
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
2023-09-16 12:14:02 +04:00
|
|
|
pub fn move_to_workspace(&mut self, idx: u8) {
|
|
|
|
|
let Some(monitor) = self.active_monitor() else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
monitor.move_to_workspace(idx);
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-13 12:46:53 +04:00
|
|
|
pub fn switch_workspace_up(&mut self) {
|
|
|
|
|
let Some(monitor) = self.active_monitor() else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
2023-08-14 16:19:43 +04:00
|
|
|
monitor.switch_workspace_up();
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn switch_workspace_down(&mut self) {
|
|
|
|
|
let Some(monitor) = self.active_monitor() else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
2023-08-14 16:19:43 +04:00
|
|
|
monitor.switch_workspace_down();
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
2023-09-16 12:14:02 +04:00
|
|
|
pub fn switch_workspace(&mut self, idx: u8) {
|
|
|
|
|
let Some(monitor) = self.active_monitor() else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
monitor.switch_workspace(idx);
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-13 12:46:53 +04:00
|
|
|
pub fn consume_into_column(&mut self) {
|
2023-08-14 16:19:43 +04:00
|
|
|
let Some(monitor) = self.active_monitor() else {
|
2023-08-13 12:46:53 +04:00
|
|
|
return;
|
|
|
|
|
};
|
2023-08-14 16:19:43 +04:00
|
|
|
monitor.consume_into_column();
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn expel_from_column(&mut self) {
|
2023-08-14 16:19:43 +04:00
|
|
|
let Some(monitor) = self.active_monitor() else {
|
2023-08-13 12:46:53 +04:00
|
|
|
return;
|
|
|
|
|
};
|
2023-08-14 16:19:43 +04:00
|
|
|
monitor.expel_from_column();
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
pub fn focus(&self) -> Option<&W> {
|
2023-08-13 12:46:53 +04:00
|
|
|
let MonitorSet::Normal {
|
|
|
|
|
monitors,
|
|
|
|
|
active_monitor_idx,
|
|
|
|
|
..
|
2023-10-05 09:25:07 +04:00
|
|
|
} = &self.monitor_set
|
2023-08-13 12:46:53 +04:00
|
|
|
else {
|
|
|
|
|
return None;
|
|
|
|
|
};
|
|
|
|
|
|
2023-08-14 16:19:43 +04:00
|
|
|
monitors[*active_monitor_idx].focus()
|
2023-08-14 15:54:11 +04:00
|
|
|
}
|
|
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
pub fn window_under(
|
|
|
|
|
&self,
|
|
|
|
|
output: &Output,
|
|
|
|
|
pos_within_output: Point<f64, Logical>,
|
|
|
|
|
) -> Option<(&W, Point<i32, Logical>)> {
|
2023-10-21 20:49:29 +04:00
|
|
|
let MonitorSet::Normal { monitors, .. } = &self.monitor_set else {
|
|
|
|
|
return None;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let mon = monitors.iter().find(|mon| &mon.output == output)?;
|
|
|
|
|
mon.window_under(pos_within_output)
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
2023-09-12 19:44:17 +04:00
|
|
|
#[cfg(test)]
|
2023-08-13 12:46:53 +04:00
|
|
|
fn verify_invariants(&self) {
|
2023-10-05 09:25:07 +04:00
|
|
|
let (monitors, &primary_idx, &active_monitor_idx) = match &self.monitor_set {
|
2023-08-13 12:46:53 +04:00
|
|
|
MonitorSet::Normal {
|
|
|
|
|
monitors,
|
|
|
|
|
primary_idx,
|
|
|
|
|
active_monitor_idx,
|
|
|
|
|
} => (monitors, primary_idx, active_monitor_idx),
|
2023-10-05 09:25:07 +04:00
|
|
|
MonitorSet::NoOutputs { workspaces } => {
|
2023-08-13 12:46:53 +04:00
|
|
|
for workspace in workspaces {
|
|
|
|
|
assert!(
|
2023-09-12 19:42:34 +04:00
|
|
|
workspace.has_windows(),
|
2023-08-13 12:46:53 +04:00
|
|
|
"with no outputs there cannot be empty workspaces"
|
|
|
|
|
);
|
|
|
|
|
|
2023-10-05 09:25:07 +04:00
|
|
|
assert_eq!(
|
|
|
|
|
workspace.options, self.options,
|
|
|
|
|
"workspace options must be synchronized with layout"
|
|
|
|
|
);
|
|
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
workspace.verify_invariants();
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2023-09-26 20:35:08 +04:00
|
|
|
assert!(primary_idx < monitors.len());
|
|
|
|
|
assert!(active_monitor_idx < monitors.len());
|
2023-08-13 12:46:53 +04:00
|
|
|
|
|
|
|
|
for (idx, monitor) in monitors.iter().enumerate() {
|
|
|
|
|
assert!(
|
|
|
|
|
!monitor.workspaces.is_empty(),
|
2023-10-14 19:24:12 +04:00
|
|
|
"monitor must have at least one workspace"
|
2023-08-13 12:46:53 +04:00
|
|
|
);
|
2023-09-26 20:35:08 +04:00
|
|
|
assert!(monitor.active_workspace_idx < monitor.workspaces.len());
|
2023-08-13 12:46:53 +04:00
|
|
|
|
2023-10-05 09:25:07 +04:00
|
|
|
assert_eq!(
|
|
|
|
|
monitor.options, self.options,
|
|
|
|
|
"monitor options must be synchronized with layout"
|
|
|
|
|
);
|
|
|
|
|
|
2023-08-13 12:46:53 +04:00
|
|
|
let monitor_id = OutputId::new(&monitor.output);
|
|
|
|
|
|
|
|
|
|
if idx == primary_idx {
|
|
|
|
|
} else {
|
|
|
|
|
assert!(
|
|
|
|
|
monitor
|
|
|
|
|
.workspaces
|
|
|
|
|
.iter()
|
|
|
|
|
.any(|workspace| workspace.original_output == monitor_id),
|
|
|
|
|
"secondary monitor must have all own workspaces"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-14 20:32:53 +04:00
|
|
|
assert!(
|
|
|
|
|
monitor.workspaces.last().unwrap().columns.is_empty(),
|
|
|
|
|
"monitor must have an empty workspace in the end"
|
|
|
|
|
);
|
|
|
|
|
|
2023-10-14 20:39:44 +04:00
|
|
|
// If there's no workspace switch in progress, there can't be any non-last non-active
|
|
|
|
|
// empty workspaces.
|
|
|
|
|
if monitor.workspace_switch.is_none() {
|
|
|
|
|
for (idx, ws) in monitor.workspaces.iter().enumerate().rev().skip(1) {
|
|
|
|
|
if idx != monitor.active_workspace_idx {
|
|
|
|
|
assert!(
|
|
|
|
|
!ws.columns.is_empty(),
|
|
|
|
|
"non-active workspace can't be empty except the last one"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-13 12:46:53 +04:00
|
|
|
// FIXME: verify that primary doesn't have any workspaces for which their own monitor
|
|
|
|
|
// exists.
|
|
|
|
|
|
|
|
|
|
for workspace in &monitor.workspaces {
|
2023-10-05 09:25:07 +04:00
|
|
|
assert_eq!(
|
|
|
|
|
workspace.options, self.options,
|
|
|
|
|
"workspace options must be synchronized with layout"
|
|
|
|
|
);
|
|
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
workspace.verify_invariants();
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-08-14 17:40:15 +04:00
|
|
|
|
|
|
|
|
pub fn advance_animations(&mut self, current_time: Duration) {
|
2023-10-11 14:53:53 +04:00
|
|
|
let _span = tracy_client::span!("Layout::advance_animations");
|
|
|
|
|
|
2023-10-05 09:25:07 +04:00
|
|
|
match &mut self.monitor_set {
|
2023-09-24 18:10:43 +04:00
|
|
|
MonitorSet::Normal {
|
|
|
|
|
monitors,
|
|
|
|
|
active_monitor_idx,
|
|
|
|
|
..
|
|
|
|
|
} => {
|
|
|
|
|
for (idx, mon) in monitors.iter_mut().enumerate() {
|
|
|
|
|
mon.advance_animations(current_time, idx == *active_monitor_idx);
|
2023-08-14 17:40:15 +04:00
|
|
|
}
|
|
|
|
|
}
|
2023-10-05 09:25:07 +04:00
|
|
|
MonitorSet::NoOutputs { workspaces, .. } => {
|
2023-08-14 17:40:15 +04:00
|
|
|
for ws in workspaces {
|
2023-09-24 18:10:43 +04:00
|
|
|
ws.advance_animations(current_time, false);
|
2023-08-14 17:40:15 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-08-14 18:29:50 +04:00
|
|
|
|
2023-09-26 13:09:33 +04:00
|
|
|
pub fn update_config(&mut self, config: &Config) {
|
2023-10-05 09:25:07 +04:00
|
|
|
let options = Rc::new(Options::from_config(config));
|
|
|
|
|
|
|
|
|
|
match &mut self.monitor_set {
|
2023-09-26 13:09:33 +04:00
|
|
|
MonitorSet::Normal { monitors, .. } => {
|
|
|
|
|
for mon in monitors {
|
2023-10-05 09:25:07 +04:00
|
|
|
mon.update_config(options.clone());
|
2023-09-26 13:09:33 +04:00
|
|
|
}
|
|
|
|
|
}
|
2023-10-05 09:25:07 +04:00
|
|
|
MonitorSet::NoOutputs { workspaces } => {
|
2023-09-26 13:09:33 +04:00
|
|
|
for ws in workspaces {
|
2023-10-05 09:25:07 +04:00
|
|
|
ws.update_config(options.clone());
|
2023-09-26 13:09:33 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-10-05 09:25:07 +04:00
|
|
|
|
|
|
|
|
self.options = options;
|
2023-09-26 13:09:33 +04:00
|
|
|
}
|
|
|
|
|
|
2023-08-14 18:29:50 +04:00
|
|
|
pub fn toggle_width(&mut self) {
|
|
|
|
|
let Some(monitor) = self.active_monitor() else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
monitor.toggle_width();
|
|
|
|
|
}
|
2023-08-14 18:34:39 +04:00
|
|
|
|
|
|
|
|
pub fn toggle_full_width(&mut self) {
|
|
|
|
|
let Some(monitor) = self.active_monitor() else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
monitor.toggle_full_width();
|
|
|
|
|
}
|
2023-08-16 08:03:20 +04:00
|
|
|
|
2023-10-03 11:38:42 +04:00
|
|
|
pub fn set_column_width(&mut self, change: SizeChange) {
|
|
|
|
|
let Some(monitor) = self.active_monitor() else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
monitor.set_column_width(change);
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-16 08:03:20 +04:00
|
|
|
pub fn focus_output(&mut self, output: &Output) {
|
|
|
|
|
if let MonitorSet::Normal {
|
|
|
|
|
monitors,
|
|
|
|
|
active_monitor_idx,
|
|
|
|
|
..
|
2023-10-05 09:25:07 +04:00
|
|
|
} = &mut self.monitor_set
|
2023-08-16 08:03:20 +04:00
|
|
|
{
|
|
|
|
|
for (idx, mon) in monitors.iter().enumerate() {
|
|
|
|
|
if &mon.output == output {
|
|
|
|
|
*active_monitor_idx = idx;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn move_to_output(&mut self, output: &Output) {
|
|
|
|
|
if let MonitorSet::Normal {
|
|
|
|
|
monitors,
|
|
|
|
|
active_monitor_idx,
|
|
|
|
|
..
|
2023-10-05 09:25:07 +04:00
|
|
|
} = &mut self.monitor_set
|
2023-08-16 08:03:20 +04:00
|
|
|
{
|
|
|
|
|
let new_idx = monitors
|
|
|
|
|
.iter()
|
|
|
|
|
.position(|mon| &mon.output == output)
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
let current = &mut monitors[*active_monitor_idx];
|
|
|
|
|
let ws = current.active_workspace();
|
|
|
|
|
if !ws.has_windows() {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
let column = &ws.columns[ws.active_column_idx];
|
|
|
|
|
let window = column.windows[column.active_window_idx].clone();
|
|
|
|
|
ws.remove_window(&window);
|
|
|
|
|
|
|
|
|
|
let workspace_idx = monitors[new_idx].active_workspace_idx;
|
2023-09-12 19:41:50 +04:00
|
|
|
self.add_window_by_idx(new_idx, workspace_idx, window, true);
|
2023-08-16 08:03:20 +04:00
|
|
|
}
|
|
|
|
|
}
|
2023-08-16 09:08:10 +04:00
|
|
|
|
|
|
|
|
pub fn move_window_to_output(&mut self, window: W, output: &Output) {
|
|
|
|
|
self.remove_window(&window);
|
|
|
|
|
|
2023-10-05 09:25:07 +04:00
|
|
|
if let MonitorSet::Normal { monitors, .. } = &mut self.monitor_set {
|
2023-08-16 09:08:10 +04:00
|
|
|
let new_idx = monitors
|
|
|
|
|
.iter()
|
|
|
|
|
.position(|mon| &mon.output == output)
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
let workspace_idx = monitors[new_idx].active_workspace_idx;
|
|
|
|
|
// FIXME: activate only if it was already active and focused.
|
2023-09-12 19:41:50 +04:00
|
|
|
self.add_window_by_idx(new_idx, workspace_idx, window, true);
|
2023-08-16 09:08:10 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn set_fullscreen(&mut self, window: &W, is_fullscreen: bool) {
|
2023-10-05 09:25:07 +04:00
|
|
|
match &mut self.monitor_set {
|
2023-08-16 09:08:10 +04:00
|
|
|
MonitorSet::Normal { monitors, .. } => {
|
|
|
|
|
for mon in monitors {
|
|
|
|
|
for ws in &mut mon.workspaces {
|
|
|
|
|
if ws.has_window(window) {
|
|
|
|
|
ws.set_fullscreen(window, is_fullscreen);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-10-05 09:25:07 +04:00
|
|
|
MonitorSet::NoOutputs { workspaces, .. } => {
|
2023-08-16 09:08:10 +04:00
|
|
|
for ws in workspaces {
|
|
|
|
|
if ws.has_window(window) {
|
|
|
|
|
ws.set_fullscreen(window, is_fullscreen);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn toggle_fullscreen(&mut self, window: &W) {
|
2023-10-05 09:25:07 +04:00
|
|
|
match &mut self.monitor_set {
|
2023-08-16 09:08:10 +04:00
|
|
|
MonitorSet::Normal { monitors, .. } => {
|
|
|
|
|
for mon in monitors {
|
|
|
|
|
for ws in &mut mon.workspaces {
|
|
|
|
|
if ws.has_window(window) {
|
|
|
|
|
ws.toggle_fullscreen(window);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-10-05 09:25:07 +04:00
|
|
|
MonitorSet::NoOutputs { workspaces, .. } => {
|
2023-08-16 09:08:10 +04:00
|
|
|
for ws in workspaces {
|
|
|
|
|
if ws.has_window(window) {
|
|
|
|
|
ws.toggle_fullscreen(window);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-10-08 09:57:59 +04:00
|
|
|
|
|
|
|
|
pub fn workspace_switch_gesture_begin(&mut self, output: &Output) {
|
|
|
|
|
let monitors = match &mut self.monitor_set {
|
|
|
|
|
MonitorSet::Normal { monitors, .. } => monitors,
|
|
|
|
|
MonitorSet::NoOutputs { .. } => unreachable!(),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for monitor in monitors {
|
|
|
|
|
// Cancel the gesture on other outputs.
|
|
|
|
|
if &monitor.output != output {
|
|
|
|
|
if let Some(WorkspaceSwitch::Gesture(_)) = monitor.workspace_switch {
|
|
|
|
|
monitor.workspace_switch = None;
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let center_idx = monitor.active_workspace_idx;
|
|
|
|
|
let current_idx = monitor
|
|
|
|
|
.workspace_switch
|
|
|
|
|
.as_ref()
|
|
|
|
|
.map(|s| s.current_idx())
|
|
|
|
|
.unwrap_or(center_idx as f64);
|
|
|
|
|
|
|
|
|
|
let gesture = WorkspaceSwitchGesture {
|
|
|
|
|
center_idx,
|
|
|
|
|
current_idx,
|
|
|
|
|
};
|
|
|
|
|
monitor.workspace_switch = Some(WorkspaceSwitch::Gesture(gesture));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn workspace_switch_gesture_update(&mut self, delta_y: f64) -> Option<Option<Output>> {
|
|
|
|
|
let monitors = match &mut self.monitor_set {
|
|
|
|
|
MonitorSet::Normal { monitors, .. } => monitors,
|
|
|
|
|
MonitorSet::NoOutputs { .. } => return None,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for monitor in monitors {
|
|
|
|
|
if let Some(WorkspaceSwitch::Gesture(gesture)) = &mut monitor.workspace_switch {
|
|
|
|
|
// Normalize like GNOME Shell's workspace switching.
|
|
|
|
|
let delta_y = -delta_y / 400.;
|
|
|
|
|
|
|
|
|
|
let min = gesture.center_idx.saturating_sub(1) as f64;
|
|
|
|
|
let max = (gesture.center_idx + 1).min(monitor.workspaces.len() - 1) as f64;
|
|
|
|
|
let new_idx = (gesture.current_idx + delta_y).clamp(min, max);
|
|
|
|
|
|
|
|
|
|
if gesture.current_idx == new_idx {
|
|
|
|
|
return Some(None);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
gesture.current_idx = new_idx;
|
|
|
|
|
return Some(Some(monitor.output.clone()));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn workspace_switch_gesture_end(&mut self, cancelled: bool) -> Option<Output> {
|
|
|
|
|
let monitors = match &mut self.monitor_set {
|
|
|
|
|
MonitorSet::Normal { monitors, .. } => monitors,
|
|
|
|
|
MonitorSet::NoOutputs { .. } => return None,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for monitor in monitors {
|
|
|
|
|
if let Some(WorkspaceSwitch::Gesture(gesture)) = &mut monitor.workspace_switch {
|
|
|
|
|
if cancelled {
|
|
|
|
|
monitor.workspace_switch = None;
|
|
|
|
|
return Some(monitor.output.clone());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// FIXME: keep track of gesture velocity and use it to compute the final point and
|
|
|
|
|
// to animate to it.
|
|
|
|
|
let current_idx = gesture.current_idx;
|
|
|
|
|
let idx = current_idx.round() as usize;
|
|
|
|
|
|
|
|
|
|
monitor.active_workspace_idx = idx;
|
|
|
|
|
monitor.workspace_switch = Some(WorkspaceSwitch::Animation(Animation::new(
|
|
|
|
|
current_idx,
|
|
|
|
|
idx as f64,
|
|
|
|
|
Duration::from_millis(250),
|
|
|
|
|
)));
|
|
|
|
|
|
|
|
|
|
return Some(monitor.output.clone());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
None
|
|
|
|
|
}
|
2023-10-14 20:42:10 +04:00
|
|
|
|
|
|
|
|
pub fn move_workspace_down(&mut self) {
|
|
|
|
|
let Some(monitor) = self.active_monitor() else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
monitor.move_workspace_down();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn move_workspace_up(&mut self) {
|
|
|
|
|
let Some(monitor) = self.active_monitor() else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
monitor.move_workspace_up();
|
|
|
|
|
}
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
2023-10-05 09:25:07 +04:00
|
|
|
impl Layout<Window> {
|
2023-08-14 14:48:10 +04:00
|
|
|
pub fn refresh(&self) {
|
|
|
|
|
let _span = tracy_client::span!("MonitorSet::refresh");
|
|
|
|
|
|
2023-10-05 09:25:07 +04:00
|
|
|
match &self.monitor_set {
|
2023-08-14 14:48:10 +04:00
|
|
|
MonitorSet::Normal { monitors, .. } => {
|
|
|
|
|
for mon in monitors {
|
|
|
|
|
for ws in &mon.workspaces {
|
|
|
|
|
ws.refresh();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-10-05 09:25:07 +04:00
|
|
|
MonitorSet::NoOutputs { workspaces, .. } => {
|
2023-08-14 14:48:10 +04:00
|
|
|
for ws in workspaces {
|
|
|
|
|
ws.refresh();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-08-13 19:55:37 +04:00
|
|
|
}
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
impl<W: LayoutElement> Default for MonitorSet<W> {
|
2023-08-13 12:46:53 +04:00
|
|
|
fn default() -> Self {
|
2023-10-05 09:25:07 +04:00
|
|
|
Self::NoOutputs { workspaces: vec![] }
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-14 16:19:43 +04:00
|
|
|
impl<W: LayoutElement> Monitor<W> {
|
2023-10-05 09:25:07 +04:00
|
|
|
fn new(output: Output, workspaces: Vec<Workspace<W>>, options: Rc<Options>) -> Self {
|
2023-08-14 17:25:28 +04:00
|
|
|
Self {
|
|
|
|
|
output,
|
|
|
|
|
workspaces,
|
|
|
|
|
active_workspace_idx: 0,
|
2023-10-08 09:57:59 +04:00
|
|
|
workspace_switch: None,
|
2023-10-05 09:25:07 +04:00
|
|
|
options,
|
2023-08-14 17:25:28 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-14 16:19:43 +04:00
|
|
|
fn active_workspace(&mut self) -> &mut Workspace<W> {
|
|
|
|
|
&mut self.workspaces[self.active_workspace_idx]
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-14 17:25:28 +04:00
|
|
|
fn activate_workspace(&mut self, idx: usize) {
|
|
|
|
|
if self.active_workspace_idx == idx {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let current_idx = self
|
2023-10-08 09:57:59 +04:00
|
|
|
.workspace_switch
|
2023-08-14 17:25:28 +04:00
|
|
|
.as_ref()
|
2023-10-08 09:57:59 +04:00
|
|
|
.map(|s| s.current_idx())
|
2023-08-14 17:25:28 +04:00
|
|
|
.unwrap_or(self.active_workspace_idx as f64);
|
|
|
|
|
|
|
|
|
|
self.active_workspace_idx = idx;
|
|
|
|
|
|
2023-10-08 09:57:59 +04:00
|
|
|
self.workspace_switch = Some(WorkspaceSwitch::Animation(Animation::new(
|
2023-08-14 17:25:28 +04:00
|
|
|
current_idx,
|
|
|
|
|
idx as f64,
|
|
|
|
|
Duration::from_millis(250),
|
2023-10-08 09:57:59 +04:00
|
|
|
)));
|
2023-08-14 17:25:28 +04:00
|
|
|
}
|
|
|
|
|
|
2023-08-14 16:19:43 +04:00
|
|
|
pub fn add_window(&mut self, workspace_idx: usize, window: W, activate: bool) {
|
|
|
|
|
let workspace = &mut self.workspaces[workspace_idx];
|
|
|
|
|
|
|
|
|
|
workspace.add_window(window.clone(), activate);
|
|
|
|
|
|
2023-08-14 17:32:56 +04:00
|
|
|
// After adding a new window, workspace becomes this output's own.
|
|
|
|
|
workspace.original_output = OutputId::new(&self.output);
|
|
|
|
|
|
2023-08-14 16:19:43 +04:00
|
|
|
if workspace_idx == self.workspaces.len() - 1 {
|
|
|
|
|
// Insert a new empty workspace.
|
2023-10-05 09:25:07 +04:00
|
|
|
let ws = Workspace::new(self.output.clone(), self.options.clone());
|
2023-08-14 16:19:43 +04:00
|
|
|
self.workspaces.push(ws);
|
|
|
|
|
}
|
2023-08-14 17:25:28 +04:00
|
|
|
|
|
|
|
|
if activate {
|
|
|
|
|
self.activate_workspace(workspace_idx);
|
|
|
|
|
}
|
2023-08-14 16:19:43 +04:00
|
|
|
}
|
|
|
|
|
|
2023-08-14 16:26:50 +04:00
|
|
|
fn clean_up_workspaces(&mut self) {
|
2023-10-08 09:57:59 +04:00
|
|
|
assert!(self.workspace_switch.is_none());
|
2023-08-14 17:25:28 +04:00
|
|
|
|
2023-08-14 16:26:50 +04:00
|
|
|
for idx in (0..self.workspaces.len() - 1).rev() {
|
|
|
|
|
if self.active_workspace_idx == idx {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !self.workspaces[idx].has_windows() {
|
|
|
|
|
self.workspaces.remove(idx);
|
|
|
|
|
if self.active_workspace_idx > idx {
|
|
|
|
|
self.active_workspace_idx -= 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-14 16:19:43 +04:00
|
|
|
pub fn move_left(&mut self) {
|
|
|
|
|
self.active_workspace().move_left();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn move_right(&mut self) {
|
|
|
|
|
self.active_workspace().move_right();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn move_down(&mut self) {
|
|
|
|
|
self.active_workspace().move_down();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn move_up(&mut self) {
|
|
|
|
|
self.active_workspace().move_up();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn focus_left(&mut self) {
|
|
|
|
|
self.active_workspace().focus_left();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn focus_right(&mut self) {
|
|
|
|
|
self.active_workspace().focus_right();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn focus_down(&mut self) {
|
|
|
|
|
self.active_workspace().focus_down();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn focus_up(&mut self) {
|
|
|
|
|
self.active_workspace().focus_up();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn move_to_workspace_up(&mut self) {
|
|
|
|
|
let source_workspace_idx = self.active_workspace_idx;
|
|
|
|
|
|
|
|
|
|
let new_idx = source_workspace_idx.saturating_sub(1);
|
|
|
|
|
if new_idx == source_workspace_idx {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let workspace = &mut self.workspaces[source_workspace_idx];
|
|
|
|
|
if workspace.columns.is_empty() {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let column = &mut workspace.columns[workspace.active_column_idx];
|
|
|
|
|
let window = column.windows[column.active_window_idx].clone();
|
|
|
|
|
workspace.remove_window(&window);
|
|
|
|
|
|
|
|
|
|
self.add_window(new_idx, window, true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn move_to_workspace_down(&mut self) {
|
|
|
|
|
let source_workspace_idx = self.active_workspace_idx;
|
|
|
|
|
|
2023-08-14 16:26:50 +04:00
|
|
|
let new_idx = min(source_workspace_idx + 1, self.workspaces.len() - 1);
|
2023-08-14 16:19:43 +04:00
|
|
|
if new_idx == source_workspace_idx {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let workspace = &mut self.workspaces[source_workspace_idx];
|
|
|
|
|
if workspace.columns.is_empty() {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let column = &mut workspace.columns[workspace.active_column_idx];
|
|
|
|
|
let window = column.windows[column.active_window_idx].clone();
|
|
|
|
|
workspace.remove_window(&window);
|
|
|
|
|
|
|
|
|
|
self.add_window(new_idx, window, true);
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-16 12:14:02 +04:00
|
|
|
pub fn move_to_workspace(&mut self, idx: u8) {
|
|
|
|
|
let source_workspace_idx = self.active_workspace_idx;
|
|
|
|
|
|
|
|
|
|
let new_idx = min(idx.saturating_sub(1) as usize, self.workspaces.len() - 1);
|
|
|
|
|
if new_idx == source_workspace_idx {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let workspace = &mut self.workspaces[source_workspace_idx];
|
|
|
|
|
if workspace.columns.is_empty() {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let column = &mut workspace.columns[workspace.active_column_idx];
|
|
|
|
|
let window = column.windows[column.active_window_idx].clone();
|
|
|
|
|
workspace.remove_window(&window);
|
|
|
|
|
|
|
|
|
|
self.add_window(new_idx, window, true);
|
|
|
|
|
|
|
|
|
|
// Don't animate this action.
|
2023-10-08 09:57:59 +04:00
|
|
|
self.workspace_switch = None;
|
2023-10-14 20:18:09 +04:00
|
|
|
|
|
|
|
|
self.clean_up_workspaces();
|
2023-09-16 12:14:02 +04:00
|
|
|
}
|
|
|
|
|
|
2023-08-14 16:19:43 +04:00
|
|
|
pub fn switch_workspace_up(&mut self) {
|
2023-08-14 17:25:28 +04:00
|
|
|
self.activate_workspace(self.active_workspace_idx.saturating_sub(1));
|
2023-08-14 16:19:43 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn switch_workspace_down(&mut self) {
|
2023-08-14 17:25:28 +04:00
|
|
|
self.activate_workspace(min(
|
|
|
|
|
self.active_workspace_idx + 1,
|
|
|
|
|
self.workspaces.len() - 1,
|
|
|
|
|
));
|
2023-08-14 16:19:43 +04:00
|
|
|
}
|
|
|
|
|
|
2023-09-16 12:14:02 +04:00
|
|
|
pub fn switch_workspace(&mut self, idx: u8) {
|
|
|
|
|
self.activate_workspace(min(
|
|
|
|
|
idx.saturating_sub(1) as usize,
|
|
|
|
|
self.workspaces.len() - 1,
|
|
|
|
|
));
|
|
|
|
|
// Don't animate this action.
|
2023-10-08 09:57:59 +04:00
|
|
|
self.workspace_switch = None;
|
2023-10-14 20:18:09 +04:00
|
|
|
|
|
|
|
|
self.clean_up_workspaces();
|
2023-09-16 12:14:02 +04:00
|
|
|
}
|
|
|
|
|
|
2023-08-14 16:19:43 +04:00
|
|
|
pub fn consume_into_column(&mut self) {
|
|
|
|
|
self.active_workspace().consume_into_column();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn expel_from_column(&mut self) {
|
|
|
|
|
self.active_workspace().expel_from_column();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn focus(&self) -> Option<&W> {
|
|
|
|
|
let workspace = &self.workspaces[self.active_workspace_idx];
|
|
|
|
|
if !workspace.has_windows() {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let column = &workspace.columns[workspace.active_column_idx];
|
|
|
|
|
Some(&column.windows[column.active_window_idx])
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-24 18:10:43 +04:00
|
|
|
pub fn advance_animations(&mut self, current_time: Duration, is_active: bool) {
|
2023-10-08 09:57:59 +04:00
|
|
|
if let Some(WorkspaceSwitch::Animation(anim)) = &mut self.workspace_switch {
|
2023-08-14 17:25:28 +04:00
|
|
|
anim.set_current_time(current_time);
|
|
|
|
|
if anim.is_done() {
|
2023-10-08 09:57:59 +04:00
|
|
|
self.workspace_switch = None;
|
2023-08-14 17:25:28 +04:00
|
|
|
self.clean_up_workspaces();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-14 16:19:43 +04:00
|
|
|
for ws in &mut self.workspaces {
|
2023-09-24 18:10:43 +04:00
|
|
|
ws.advance_animations(current_time, is_active);
|
2023-08-14 16:19:43 +04:00
|
|
|
}
|
|
|
|
|
}
|
2023-08-14 18:29:50 +04:00
|
|
|
|
2023-09-30 17:13:56 +04:00
|
|
|
pub fn are_animations_ongoing(&self) -> bool {
|
2023-10-08 09:57:59 +04:00
|
|
|
self.workspace_switch
|
|
|
|
|
.as_ref()
|
|
|
|
|
.is_some_and(|s| s.is_animation())
|
2023-09-30 17:13:56 +04:00
|
|
|
|| self.workspaces.iter().any(|ws| ws.are_animations_ongoing())
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-21 20:46:41 +04:00
|
|
|
pub fn are_transitions_ongoing(&self) -> bool {
|
|
|
|
|
self.workspace_switch.is_some()
|
|
|
|
|
|| self.workspaces.iter().any(|ws| ws.are_animations_ongoing())
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-05 09:25:07 +04:00
|
|
|
fn update_config(&mut self, options: Rc<Options>) {
|
2023-09-26 13:09:33 +04:00
|
|
|
for ws in &mut self.workspaces {
|
2023-10-05 09:25:07 +04:00
|
|
|
ws.update_config(options.clone());
|
2023-09-26 13:09:33 +04:00
|
|
|
}
|
2023-10-05 09:25:07 +04:00
|
|
|
|
|
|
|
|
self.options = options;
|
2023-09-26 13:09:33 +04:00
|
|
|
}
|
|
|
|
|
|
2023-08-14 18:29:50 +04:00
|
|
|
fn toggle_width(&mut self) {
|
|
|
|
|
self.active_workspace().toggle_width();
|
|
|
|
|
}
|
2023-08-14 18:34:39 +04:00
|
|
|
|
|
|
|
|
fn toggle_full_width(&mut self) {
|
|
|
|
|
self.active_workspace().toggle_full_width();
|
|
|
|
|
}
|
2023-10-03 11:38:42 +04:00
|
|
|
|
|
|
|
|
fn set_column_width(&mut self, change: SizeChange) {
|
|
|
|
|
self.active_workspace().set_column_width(change);
|
|
|
|
|
}
|
2023-10-14 20:42:10 +04:00
|
|
|
|
|
|
|
|
fn move_workspace_down(&mut self) {
|
|
|
|
|
let new_idx = min(self.active_workspace_idx + 1, self.workspaces.len() - 1);
|
|
|
|
|
if new_idx == self.active_workspace_idx {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.workspaces.swap(self.active_workspace_idx, new_idx);
|
|
|
|
|
|
|
|
|
|
if new_idx == self.workspaces.len() - 1 {
|
|
|
|
|
// Insert a new empty workspace.
|
|
|
|
|
let ws = Workspace::new(self.output.clone(), self.options.clone());
|
|
|
|
|
self.workspaces.push(ws);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.activate_workspace(new_idx);
|
|
|
|
|
self.workspace_switch = None;
|
|
|
|
|
|
|
|
|
|
self.clean_up_workspaces();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn move_workspace_up(&mut self) {
|
|
|
|
|
let new_idx = self.active_workspace_idx.saturating_sub(1);
|
|
|
|
|
if new_idx == self.active_workspace_idx {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.workspaces.swap(self.active_workspace_idx, new_idx);
|
|
|
|
|
|
|
|
|
|
if self.active_workspace_idx == self.workspaces.len() - 1 {
|
|
|
|
|
// Insert a new empty workspace.
|
|
|
|
|
let ws = Workspace::new(self.output.clone(), self.options.clone());
|
|
|
|
|
self.workspaces.push(ws);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.activate_workspace(new_idx);
|
|
|
|
|
self.workspace_switch = None;
|
|
|
|
|
|
|
|
|
|
self.clean_up_workspaces();
|
|
|
|
|
}
|
2023-10-21 20:49:29 +04:00
|
|
|
|
|
|
|
|
pub fn window_under(
|
|
|
|
|
&self,
|
|
|
|
|
pos_within_output: Point<f64, Logical>,
|
|
|
|
|
) -> Option<(&W, Point<i32, Logical>)> {
|
|
|
|
|
match &self.workspace_switch {
|
|
|
|
|
Some(switch) => {
|
|
|
|
|
let size = output_size(&self.output);
|
|
|
|
|
|
|
|
|
|
let render_idx = switch.current_idx();
|
|
|
|
|
let before_idx = render_idx.floor() as usize;
|
|
|
|
|
let after_idx = render_idx.ceil() as usize;
|
|
|
|
|
|
|
|
|
|
let offset = ((render_idx - before_idx as f64) * size.h as f64).round() as i32;
|
|
|
|
|
|
|
|
|
|
let (idx, ws_offset) = if pos_within_output.y < (size.h - offset) as f64 {
|
|
|
|
|
(before_idx, Point::from((0, offset)))
|
|
|
|
|
} else {
|
|
|
|
|
(after_idx, Point::from((0, -size.h + offset)))
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let ws = &self.workspaces[idx];
|
|
|
|
|
let (win, win_pos) = ws.window_under(pos_within_output + ws_offset.to_f64())?;
|
|
|
|
|
Some((win, win_pos - ws_offset))
|
|
|
|
|
}
|
|
|
|
|
None => {
|
|
|
|
|
let ws = &self.workspaces[self.active_workspace_idx];
|
|
|
|
|
ws.window_under(pos_within_output)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-08-14 16:19:43 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Monitor<Window> {
|
|
|
|
|
pub fn render_elements(
|
|
|
|
|
&self,
|
|
|
|
|
renderer: &mut GlesRenderer,
|
2023-08-14 17:25:28 +04:00
|
|
|
) -> Vec<MonitorRenderElement<GlesRenderer>> {
|
2023-10-11 14:53:53 +04:00
|
|
|
let _span = tracy_client::span!("Monitor::render_elements");
|
|
|
|
|
|
2023-09-21 13:48:32 +04:00
|
|
|
let output_scale = Scale::from(self.output.current_scale().fractional_scale());
|
2023-08-14 17:25:28 +04:00
|
|
|
let output_transform = self.output.current_transform();
|
|
|
|
|
let output_mode = self.output.current_mode().unwrap();
|
2023-10-21 19:47:49 +04:00
|
|
|
let size = output_transform.transform_size(output_mode.size);
|
2023-08-14 17:25:28 +04:00
|
|
|
|
2023-10-08 09:57:59 +04:00
|
|
|
match &self.workspace_switch {
|
|
|
|
|
Some(switch) => {
|
|
|
|
|
let render_idx = switch.current_idx();
|
2023-10-21 19:49:18 +04:00
|
|
|
let before_idx = render_idx.floor() as usize;
|
|
|
|
|
let after_idx = render_idx.ceil() as usize;
|
2023-08-14 17:25:28 +04:00
|
|
|
|
2023-10-21 19:49:18 +04:00
|
|
|
let offset = ((render_idx - before_idx as f64) * size.h as f64).round() as i32;
|
2023-08-14 17:25:28 +04:00
|
|
|
|
2023-10-21 19:49:18 +04:00
|
|
|
let before = self.workspaces[before_idx].render_elements(renderer);
|
|
|
|
|
let after = self.workspaces[after_idx].render_elements(renderer);
|
2023-08-14 17:25:28 +04:00
|
|
|
|
2023-10-21 19:49:18 +04:00
|
|
|
let before = before.into_iter().filter_map(|elem| {
|
2023-08-14 17:25:28 +04:00
|
|
|
Some(RelocateRenderElement::from_element(
|
|
|
|
|
CropRenderElement::from_element(
|
|
|
|
|
elem,
|
2023-09-21 13:48:32 +04:00
|
|
|
output_scale,
|
2023-10-21 19:47:49 +04:00
|
|
|
Rectangle::from_extemities((0, offset), (size.w, size.h)),
|
2023-08-14 17:25:28 +04:00
|
|
|
)?,
|
|
|
|
|
(0, -offset),
|
|
|
|
|
Relocate::Relative,
|
|
|
|
|
))
|
|
|
|
|
});
|
2023-10-21 19:49:18 +04:00
|
|
|
let after = after.into_iter().filter_map(|elem| {
|
2023-08-14 17:25:28 +04:00
|
|
|
Some(RelocateRenderElement::from_element(
|
|
|
|
|
CropRenderElement::from_element(
|
|
|
|
|
elem,
|
2023-09-21 13:48:32 +04:00
|
|
|
output_scale,
|
2023-10-21 19:47:49 +04:00
|
|
|
Rectangle::from_extemities((0, 0), (size.w, offset)),
|
2023-08-14 17:25:28 +04:00
|
|
|
)?,
|
2023-10-21 19:47:49 +04:00
|
|
|
(0, -offset + size.h),
|
2023-08-14 17:25:28 +04:00
|
|
|
Relocate::Relative,
|
|
|
|
|
))
|
|
|
|
|
});
|
2023-10-21 19:49:18 +04:00
|
|
|
before.chain(after).collect()
|
2023-08-14 17:25:28 +04:00
|
|
|
}
|
|
|
|
|
None => {
|
|
|
|
|
let elements = self.workspaces[self.active_workspace_idx].render_elements(renderer);
|
|
|
|
|
elements
|
|
|
|
|
.into_iter()
|
|
|
|
|
.filter_map(|elem| {
|
|
|
|
|
Some(RelocateRenderElement::from_element(
|
|
|
|
|
CropRenderElement::from_element(
|
|
|
|
|
elem,
|
2023-09-21 13:48:32 +04:00
|
|
|
output_scale,
|
2023-10-21 19:47:49 +04:00
|
|
|
Rectangle::from_loc_and_size((0, 0), size),
|
2023-08-14 17:25:28 +04:00
|
|
|
)?,
|
|
|
|
|
(0, 0),
|
|
|
|
|
Relocate::Relative,
|
|
|
|
|
))
|
|
|
|
|
})
|
|
|
|
|
.collect()
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-08-14 16:19:43 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
impl<W: LayoutElement> Workspace<W> {
|
2023-10-05 09:25:07 +04:00
|
|
|
fn new(output: Output, options: Rc<Options>) -> Self {
|
2023-09-27 17:38:57 +04:00
|
|
|
let working_area = layer_map_for_output(&output).non_exclusive_zone();
|
2023-08-13 12:46:53 +04:00
|
|
|
Self {
|
2023-08-13 19:55:37 +04:00
|
|
|
original_output: OutputId::new(&output),
|
|
|
|
|
view_size: output_size(&output),
|
2023-09-27 17:38:57 +04:00
|
|
|
working_area,
|
2023-08-13 19:55:37 +04:00
|
|
|
output: Some(output),
|
2023-08-13 12:46:53 +04:00
|
|
|
columns: vec![],
|
|
|
|
|
active_column_idx: 0,
|
2023-10-05 09:25:07 +04:00
|
|
|
focus_ring: FocusRing::new(options.focus_ring),
|
2023-08-13 19:55:37 +04:00
|
|
|
view_offset: 0,
|
2023-08-14 15:54:11 +04:00
|
|
|
view_offset_anim: None,
|
2023-09-24 10:08:43 +04:00
|
|
|
activate_prev_column_on_removal: false,
|
2023-10-05 09:25:07 +04:00
|
|
|
options,
|
2023-08-14 15:54:11 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-05 09:25:07 +04:00
|
|
|
fn new_no_outputs(options: Rc<Options>) -> Self {
|
2023-09-12 19:41:50 +04:00
|
|
|
Self {
|
|
|
|
|
output: None,
|
|
|
|
|
original_output: OutputId(String::new()),
|
|
|
|
|
view_size: Size::from((1280, 720)),
|
2023-09-26 20:12:04 +04:00
|
|
|
working_area: Rectangle::from_loc_and_size((0, 0), (1280, 720)),
|
2023-09-12 19:41:50 +04:00
|
|
|
columns: vec![],
|
|
|
|
|
active_column_idx: 0,
|
2023-10-05 09:25:07 +04:00
|
|
|
focus_ring: FocusRing::new(options.focus_ring),
|
2023-09-12 19:41:50 +04:00
|
|
|
view_offset: 0,
|
|
|
|
|
view_offset_anim: None,
|
2023-09-24 10:08:43 +04:00
|
|
|
activate_prev_column_on_removal: false,
|
2023-10-05 09:25:07 +04:00
|
|
|
options,
|
2023-09-12 19:41:50 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-24 18:10:43 +04:00
|
|
|
pub fn advance_animations(&mut self, current_time: Duration, is_active: bool) {
|
2023-08-14 15:54:11 +04:00
|
|
|
match &mut self.view_offset_anim {
|
|
|
|
|
Some(anim) => {
|
|
|
|
|
anim.set_current_time(current_time);
|
|
|
|
|
self.view_offset = anim.value().round() as i32;
|
|
|
|
|
if anim.is_done() {
|
|
|
|
|
self.view_offset_anim = None;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
None => (),
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
2023-09-24 18:10:43 +04:00
|
|
|
|
2023-10-11 11:56:38 +04:00
|
|
|
let view_pos = self.view_pos();
|
|
|
|
|
|
2023-09-24 18:10:43 +04:00
|
|
|
// This shall one day become a proper animation.
|
|
|
|
|
if !self.columns.is_empty() {
|
|
|
|
|
let col = &self.columns[self.active_column_idx];
|
|
|
|
|
let active_win = &col.windows[col.active_window_idx];
|
|
|
|
|
let geom = active_win.geometry();
|
2023-10-11 14:32:29 +04:00
|
|
|
let has_ssd = active_win.has_ssd();
|
2023-10-11 11:56:38 +04:00
|
|
|
|
|
|
|
|
let win_pos = Point::from((
|
|
|
|
|
self.column_x(self.active_column_idx) - view_pos,
|
|
|
|
|
col.window_y(col.active_window_idx),
|
|
|
|
|
));
|
|
|
|
|
|
2023-10-11 14:32:29 +04:00
|
|
|
self.focus_ring.update(win_pos, geom.size, has_ssd);
|
2023-09-26 13:09:33 +04:00
|
|
|
self.focus_ring.set_active(is_active);
|
2023-09-24 18:10:43 +04:00
|
|
|
}
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
2023-09-30 17:13:56 +04:00
|
|
|
pub fn are_animations_ongoing(&self) -> bool {
|
|
|
|
|
self.view_offset_anim.is_some()
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-05 09:25:07 +04:00
|
|
|
fn update_config(&mut self, options: Rc<Options>) {
|
|
|
|
|
let c = &options.focus_ring;
|
2023-09-26 13:09:33 +04:00
|
|
|
self.focus_ring.is_off = c.off;
|
|
|
|
|
self.focus_ring.width = c.width.into();
|
|
|
|
|
self.focus_ring.active_color = c.active_color;
|
|
|
|
|
self.focus_ring.inactive_color = c.inactive_color;
|
|
|
|
|
// The focus ring buffer will be updated in a subsequent update_animations call.
|
2023-10-05 09:25:07 +04:00
|
|
|
|
|
|
|
|
for column in &mut self.columns {
|
|
|
|
|
column.update_config(options.clone());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.options = options;
|
2023-09-26 13:09:33 +04:00
|
|
|
}
|
|
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
fn windows(&self) -> impl Iterator<Item = &W> + '_ {
|
|
|
|
|
self.columns.iter().flat_map(|col| col.windows.iter())
|
|
|
|
|
}
|
2023-08-13 12:46:53 +04:00
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
fn set_output(&mut self, output: Option<Output>) {
|
|
|
|
|
if self.output == output {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let Some(output) = self.output.take() {
|
|
|
|
|
for win in self.windows() {
|
|
|
|
|
win.output_leave(&output);
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-21 13:39:55 +04:00
|
|
|
self.output = output;
|
2023-08-13 19:55:37 +04:00
|
|
|
|
2023-09-21 13:39:55 +04:00
|
|
|
if let Some(output) = &self.output {
|
2023-09-26 20:12:04 +04:00
|
|
|
let working_area = layer_map_for_output(output).non_exclusive_zone();
|
|
|
|
|
self.set_view_size(output_size(output), working_area);
|
2023-08-13 19:55:37 +04:00
|
|
|
|
|
|
|
|
for win in self.windows() {
|
|
|
|
|
self.enter_output_for_window(win);
|
|
|
|
|
}
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
fn enter_output_for_window(&self, window: &W) {
|
|
|
|
|
if let Some(output) = &self.output {
|
2023-11-01 15:07:45 +04:00
|
|
|
prepare_for_output(window, output);
|
|
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
// FIXME: proper overlap.
|
|
|
|
|
window.output_enter(
|
|
|
|
|
output,
|
|
|
|
|
Rectangle::from_loc_and_size((0, 0), (i32::MAX, i32::MAX)),
|
|
|
|
|
);
|
|
|
|
|
}
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
2023-09-26 20:12:04 +04:00
|
|
|
fn set_view_size(&mut self, size: Size<i32, Logical>, working_area: Rectangle<i32, Logical>) {
|
|
|
|
|
if self.view_size == size && self.working_area == working_area {
|
2023-08-13 19:55:37 +04:00
|
|
|
return;
|
|
|
|
|
}
|
2023-08-13 12:46:53 +04:00
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
self.view_size = size;
|
2023-09-26 20:12:04 +04:00
|
|
|
self.working_area = working_area;
|
|
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
for col in &mut self.columns {
|
2023-10-04 15:45:30 +04:00
|
|
|
col.set_view_size(self.view_size, self.working_area);
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
2023-08-13 19:55:37 +04:00
|
|
|
}
|
2023-08-13 12:46:53 +04:00
|
|
|
|
2023-10-07 17:50:45 +04:00
|
|
|
fn toplevel_bounds(&self) -> Size<i32, Logical> {
|
|
|
|
|
Size::from((
|
|
|
|
|
max(self.working_area.size.w - self.options.gaps * 2, 1),
|
|
|
|
|
max(self.working_area.size.h - self.options.gaps * 2, 1),
|
|
|
|
|
))
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-05 09:25:07 +04:00
|
|
|
pub fn configure_new_window(&self, window: &Window) {
|
2023-10-05 09:29:39 +04:00
|
|
|
let width = ColumnWidth::default().resolve(&self.options, self.working_area.size.w);
|
2023-10-07 17:45:55 +04:00
|
|
|
let height = self.working_area.size.h - self.options.gaps * 2;
|
2023-10-05 09:25:07 +04:00
|
|
|
let size = Size::from((max(width, 1), max(height, 1)));
|
|
|
|
|
|
2023-10-07 17:50:45 +04:00
|
|
|
let bounds = self.toplevel_bounds();
|
2023-10-05 09:25:07 +04:00
|
|
|
|
2023-10-26 00:15:46 +04:00
|
|
|
if let Some(output) = self.output.as_ref() {
|
2023-11-01 15:07:45 +04:00
|
|
|
prepare_for_output(window, output);
|
2023-10-26 00:15:46 +04:00
|
|
|
}
|
|
|
|
|
|
2023-10-05 09:25:07 +04:00
|
|
|
window.toplevel().with_pending_state(|state| {
|
|
|
|
|
state.size = Some(size);
|
|
|
|
|
state.bounds = Some(bounds);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-07 10:47:51 +04:00
|
|
|
fn compute_new_view_offset_for_column(&self, current_x: i32, idx: usize) -> i32 {
|
|
|
|
|
if self.columns[idx].is_fullscreen {
|
|
|
|
|
return 0;
|
2023-08-14 15:54:11 +04:00
|
|
|
}
|
|
|
|
|
|
2023-10-07 10:47:51 +04:00
|
|
|
let new_col_x = self.column_x(idx);
|
2023-08-14 15:54:11 +04:00
|
|
|
|
2023-09-30 17:27:25 +04:00
|
|
|
let final_x = if let Some(anim) = &self.view_offset_anim {
|
|
|
|
|
current_x - self.view_offset + anim.to().round() as i32
|
|
|
|
|
} else {
|
|
|
|
|
current_x
|
|
|
|
|
};
|
|
|
|
|
|
2023-10-07 10:47:51 +04:00
|
|
|
let new_offset = compute_new_view_offset(
|
|
|
|
|
final_x + self.working_area.loc.x,
|
2023-09-26 20:12:04 +04:00
|
|
|
self.working_area.size.w,
|
2023-10-07 10:47:51 +04:00
|
|
|
new_col_x,
|
|
|
|
|
self.columns[idx].width(),
|
2023-10-07 17:45:55 +04:00
|
|
|
self.options.gaps,
|
2023-09-13 16:57:12 +04:00
|
|
|
);
|
|
|
|
|
|
2023-10-07 10:47:51 +04:00
|
|
|
// Non-fullscreen windows are always offset at least by the working area position.
|
|
|
|
|
new_offset - self.working_area.loc.x
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn animate_view_offset_to_column(&mut self, current_x: i32, idx: usize) {
|
|
|
|
|
let new_view_offset = self.compute_new_view_offset_for_column(current_x, idx);
|
|
|
|
|
|
|
|
|
|
let new_col_x = self.column_x(idx);
|
|
|
|
|
let from_view_offset = current_x - new_col_x;
|
|
|
|
|
self.view_offset = from_view_offset;
|
|
|
|
|
|
|
|
|
|
// If we're already animating towards that, don't restart it.
|
|
|
|
|
if let Some(anim) = &self.view_offset_anim {
|
|
|
|
|
if anim.value().round() as i32 == self.view_offset
|
|
|
|
|
&& anim.to().round() as i32 == new_view_offset
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If our view offset is already this, we don't need to do anything.
|
|
|
|
|
if self.view_offset == new_view_offset {
|
|
|
|
|
self.view_offset_anim = None;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-14 15:54:11 +04:00
|
|
|
self.view_offset_anim = Some(Animation::new(
|
2023-10-07 10:47:51 +04:00
|
|
|
self.view_offset as f64,
|
2023-09-13 16:57:12 +04:00
|
|
|
new_view_offset as f64,
|
2023-08-14 15:54:11 +04:00
|
|
|
Duration::from_millis(250),
|
|
|
|
|
));
|
2023-10-07 10:47:51 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn activate_column(&mut self, idx: usize) {
|
|
|
|
|
if self.active_column_idx == idx {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let current_x = self.view_pos();
|
|
|
|
|
self.animate_view_offset_to_column(current_x, idx);
|
|
|
|
|
|
|
|
|
|
self.active_column_idx = idx;
|
|
|
|
|
|
|
|
|
|
// A different column was activated; reset the flag.
|
|
|
|
|
self.activate_prev_column_on_removal = false;
|
2023-08-14 15:54:11 +04:00
|
|
|
}
|
|
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
fn has_windows(&self) -> bool {
|
|
|
|
|
self.windows().next().is_some()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn has_window(&self, window: &W) -> bool {
|
|
|
|
|
self.windows().any(|win| win == window)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn find_wl_surface(&self, wl_surface: &WlSurface) -> Option<&W> {
|
|
|
|
|
self.windows().find(|win| win.is_wl_surface(wl_surface))
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Computes the X position of the windows in the given column, in logical coordinates.
|
|
|
|
|
fn column_x(&self, column_idx: usize) -> i32 {
|
2023-10-07 10:47:51 +04:00
|
|
|
let mut x = 0;
|
2023-08-13 12:46:53 +04:00
|
|
|
|
|
|
|
|
for column in self.columns.iter().take(column_idx) {
|
2023-10-07 17:45:55 +04:00
|
|
|
x += column.width() + self.options.gaps;
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
x
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
fn add_window(&mut self, window: W, activate: bool) {
|
|
|
|
|
self.enter_output_for_window(&window);
|
|
|
|
|
|
2023-10-07 10:47:51 +04:00
|
|
|
let was_empty = self.columns.is_empty();
|
|
|
|
|
|
2023-08-13 12:46:53 +04:00
|
|
|
let idx = if self.columns.is_empty() {
|
|
|
|
|
0
|
|
|
|
|
} else {
|
|
|
|
|
self.active_column_idx + 1
|
|
|
|
|
};
|
|
|
|
|
|
2023-10-05 09:25:07 +04:00
|
|
|
let column = Column::new(
|
|
|
|
|
window,
|
|
|
|
|
self.view_size,
|
|
|
|
|
self.working_area,
|
|
|
|
|
self.options.clone(),
|
|
|
|
|
);
|
2023-08-13 12:46:53 +04:00
|
|
|
self.columns.insert(idx, column);
|
|
|
|
|
|
|
|
|
|
if activate {
|
2023-10-07 10:47:51 +04:00
|
|
|
// If this is the first window on an empty workspace, skip the animation from whatever
|
|
|
|
|
// view_offset was left over.
|
|
|
|
|
if was_empty {
|
|
|
|
|
// Try to make the code produce a left-aligned offset, even in presence of left
|
|
|
|
|
// exclusive zones.
|
|
|
|
|
self.view_offset = self.compute_new_view_offset_for_column(self.column_x(0), 0);
|
|
|
|
|
self.view_offset_anim = None;
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-14 15:54:11 +04:00
|
|
|
self.activate_column(idx);
|
2023-09-24 10:08:43 +04:00
|
|
|
self.activate_prev_column_on_removal = true;
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
fn remove_window(&mut self, window: &W) {
|
|
|
|
|
if let Some(output) = &self.output {
|
|
|
|
|
window.output_leave(output);
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-13 12:46:53 +04:00
|
|
|
let column_idx = self
|
|
|
|
|
.columns
|
|
|
|
|
.iter()
|
2023-08-13 19:55:37 +04:00
|
|
|
.position(|col| col.contains(window))
|
2023-08-13 12:46:53 +04:00
|
|
|
.unwrap();
|
|
|
|
|
let column = &mut self.columns[column_idx];
|
|
|
|
|
|
|
|
|
|
let window_idx = column.windows.iter().position(|win| win == window).unwrap();
|
|
|
|
|
column.windows.remove(window_idx);
|
|
|
|
|
if column.windows.is_empty() {
|
2023-09-24 10:08:43 +04:00
|
|
|
if column_idx + 1 == self.active_column_idx {
|
|
|
|
|
// The previous column, that we were going to activate upon removal of the active
|
|
|
|
|
// column, has just been itself removed.
|
|
|
|
|
self.activate_prev_column_on_removal = false;
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-24 09:42:49 +04:00
|
|
|
// FIXME: activate_column below computes current view position to compute the new view
|
|
|
|
|
// position, which can include the column we're removing here. This leads to unwanted
|
|
|
|
|
// view jumps.
|
2023-08-13 12:46:53 +04:00
|
|
|
self.columns.remove(column_idx);
|
|
|
|
|
if self.columns.is_empty() {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-24 10:08:43 +04:00
|
|
|
if self.active_column_idx > column_idx
|
|
|
|
|
|| (self.active_column_idx == column_idx && self.activate_prev_column_on_removal)
|
|
|
|
|
{
|
2023-09-24 09:42:49 +04:00
|
|
|
// A column to the left was removed; preserve the current position.
|
2023-09-24 10:08:43 +04:00
|
|
|
// FIXME: preserve activate_prev_column_on_removal.
|
|
|
|
|
// Or, the active column was removed, and we needed to activate the previous column.
|
|
|
|
|
self.activate_column(self.active_column_idx.saturating_sub(1));
|
2023-09-24 09:42:49 +04:00
|
|
|
} else {
|
|
|
|
|
self.activate_column(min(self.active_column_idx, self.columns.len() - 1));
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-13 12:46:53 +04:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
column.active_window_idx = min(column.active_window_idx, column.windows.len() - 1);
|
2023-10-04 15:45:30 +04:00
|
|
|
column.update_window_sizes();
|
2023-08-13 19:55:37 +04:00
|
|
|
}
|
2023-08-13 12:46:53 +04:00
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
fn update_window(&mut self, window: &W) {
|
2023-09-13 16:57:12 +04:00
|
|
|
let (idx, column) = self
|
2023-08-13 19:55:37 +04:00
|
|
|
.columns
|
|
|
|
|
.iter_mut()
|
2023-09-13 16:57:12 +04:00
|
|
|
.enumerate()
|
|
|
|
|
.find(|(_, col)| col.contains(window))
|
2023-08-13 19:55:37 +04:00
|
|
|
.unwrap();
|
2023-10-04 15:45:30 +04:00
|
|
|
column.update_window_sizes();
|
2023-09-13 16:57:12 +04:00
|
|
|
|
|
|
|
|
if idx == self.active_column_idx {
|
|
|
|
|
// We might need to move the view to ensure the resized window is still visible.
|
|
|
|
|
let current_x = self.view_pos();
|
2023-10-07 10:47:51 +04:00
|
|
|
self.animate_view_offset_to_column(current_x, idx);
|
2023-09-13 16:57:12 +04:00
|
|
|
}
|
2023-08-13 19:55:37 +04:00
|
|
|
}
|
2023-08-13 12:46:53 +04:00
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
fn activate_window(&mut self, window: &W) {
|
2023-08-13 12:46:53 +04:00
|
|
|
let column_idx = self
|
|
|
|
|
.columns
|
|
|
|
|
.iter()
|
2023-08-13 19:55:37 +04:00
|
|
|
.position(|col| col.contains(window))
|
2023-08-13 12:46:53 +04:00
|
|
|
.unwrap();
|
|
|
|
|
let column = &mut self.columns[column_idx];
|
|
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
column.activate_window(window);
|
2023-08-14 15:54:11 +04:00
|
|
|
self.activate_column(column_idx);
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
2023-09-12 19:44:17 +04:00
|
|
|
#[cfg(test)]
|
2023-08-13 12:46:53 +04:00
|
|
|
fn verify_invariants(&self) {
|
2023-08-13 19:55:37 +04:00
|
|
|
assert!(self.view_size.w > 0);
|
|
|
|
|
assert!(self.view_size.h > 0);
|
|
|
|
|
|
2023-08-14 15:54:11 +04:00
|
|
|
if !self.columns.is_empty() {
|
|
|
|
|
assert!(self.active_column_idx < self.columns.len());
|
2023-08-13 19:55:37 +04:00
|
|
|
|
2023-08-14 15:54:11 +04:00
|
|
|
for column in &self.columns {
|
|
|
|
|
column.verify_invariants();
|
|
|
|
|
}
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn focus_left(&mut self) {
|
2023-08-14 15:54:11 +04:00
|
|
|
self.activate_column(self.active_column_idx.saturating_sub(1));
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn focus_right(&mut self) {
|
|
|
|
|
if self.columns.is_empty() {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-14 15:54:11 +04:00
|
|
|
self.activate_column(min(self.active_column_idx + 1, self.columns.len() - 1));
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn focus_down(&mut self) {
|
|
|
|
|
if self.columns.is_empty() {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.columns[self.active_column_idx].focus_down();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn focus_up(&mut self) {
|
|
|
|
|
if self.columns.is_empty() {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.columns[self.active_column_idx].focus_up();
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
fn move_left(&mut self) {
|
2023-08-13 12:46:53 +04:00
|
|
|
let new_idx = self.active_column_idx.saturating_sub(1);
|
|
|
|
|
if self.active_column_idx == new_idx {
|
2023-08-13 19:55:37 +04:00
|
|
|
return;
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
2023-09-13 19:37:14 +04:00
|
|
|
let current_x = self.view_pos();
|
|
|
|
|
|
2023-08-13 12:46:53 +04:00
|
|
|
self.columns.swap(self.active_column_idx, new_idx);
|
2023-09-13 19:37:14 +04:00
|
|
|
|
2023-10-07 10:47:51 +04:00
|
|
|
self.view_offset =
|
|
|
|
|
self.compute_new_view_offset_for_column(current_x, self.active_column_idx);
|
2023-09-13 19:37:14 +04:00
|
|
|
|
2023-08-14 15:54:11 +04:00
|
|
|
self.activate_column(new_idx);
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
fn move_right(&mut self) {
|
2023-08-13 12:46:53 +04:00
|
|
|
if self.columns.is_empty() {
|
2023-08-13 19:55:37 +04:00
|
|
|
return;
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let new_idx = min(self.active_column_idx + 1, self.columns.len() - 1);
|
|
|
|
|
if self.active_column_idx == new_idx {
|
2023-08-13 19:55:37 +04:00
|
|
|
return;
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
2023-09-13 19:37:14 +04:00
|
|
|
let current_x = self.view_pos();
|
|
|
|
|
|
2023-08-13 12:46:53 +04:00
|
|
|
self.columns.swap(self.active_column_idx, new_idx);
|
2023-09-13 19:37:14 +04:00
|
|
|
|
2023-10-07 10:47:51 +04:00
|
|
|
self.view_offset =
|
|
|
|
|
self.compute_new_view_offset_for_column(current_x, self.active_column_idx);
|
2023-09-13 19:37:14 +04:00
|
|
|
|
2023-08-14 15:54:11 +04:00
|
|
|
self.activate_column(new_idx);
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
fn move_down(&mut self) {
|
2023-08-13 12:46:53 +04:00
|
|
|
if self.columns.is_empty() {
|
2023-08-13 19:55:37 +04:00
|
|
|
return;
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
self.columns[self.active_column_idx].move_down();
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
fn move_up(&mut self) {
|
2023-08-13 12:46:53 +04:00
|
|
|
if self.columns.is_empty() {
|
2023-08-13 19:55:37 +04:00
|
|
|
return;
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
self.columns[self.active_column_idx].move_up();
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
fn consume_into_column(&mut self) {
|
2023-08-13 12:46:53 +04:00
|
|
|
if self.columns.len() < 2 {
|
2023-08-13 19:55:37 +04:00
|
|
|
return;
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if self.active_column_idx == self.columns.len() - 1 {
|
2023-08-13 19:55:37 +04:00
|
|
|
return;
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let source_column_idx = self.active_column_idx + 1;
|
|
|
|
|
|
|
|
|
|
let source_column = &mut self.columns[source_column_idx];
|
|
|
|
|
let window = source_column.windows[0].clone();
|
2023-08-13 19:55:37 +04:00
|
|
|
self.remove_window(&window);
|
2023-08-13 12:46:53 +04:00
|
|
|
|
|
|
|
|
let target_column = &mut self.columns[self.active_column_idx];
|
2023-10-04 15:45:30 +04:00
|
|
|
target_column.add_window(window);
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
fn expel_from_column(&mut self) {
|
2023-08-13 12:46:53 +04:00
|
|
|
if self.columns.is_empty() {
|
2023-08-13 19:55:37 +04:00
|
|
|
return;
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let source_column = &mut self.columns[self.active_column_idx];
|
|
|
|
|
if source_column.windows.len() == 1 {
|
2023-08-13 19:55:37 +04:00
|
|
|
return;
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let window = source_column.windows[source_column.active_window_idx].clone();
|
2023-08-13 19:55:37 +04:00
|
|
|
self.remove_window(&window);
|
2023-08-13 12:46:53 +04:00
|
|
|
|
|
|
|
|
self.add_window(window, true);
|
2023-08-13 19:55:37 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn view_pos(&self) -> i32 {
|
2023-10-07 10:47:51 +04:00
|
|
|
self.column_x(self.active_column_idx) + self.view_offset
|
2023-08-13 19:55:37 +04:00
|
|
|
}
|
|
|
|
|
|
2023-10-07 10:47:51 +04:00
|
|
|
fn window_under(&self, pos: Point<f64, Logical>) -> Option<(&W, Point<i32, Logical>)> {
|
2023-08-16 09:22:46 +04:00
|
|
|
if self.columns.is_empty() {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
let view_pos = self.view_pos();
|
|
|
|
|
|
2023-08-16 09:22:46 +04:00
|
|
|
// Prefer the active window since it's drawn on top.
|
|
|
|
|
let col = &self.columns[self.active_column_idx];
|
|
|
|
|
let active_win = &col.windows[col.active_window_idx];
|
|
|
|
|
let geom = active_win.geometry();
|
2023-10-07 10:47:51 +04:00
|
|
|
let buf_pos = Point::from((
|
|
|
|
|
self.column_x(self.active_column_idx) - view_pos,
|
2023-08-16 09:22:46 +04:00
|
|
|
col.window_y(col.active_window_idx),
|
|
|
|
|
)) - geom.loc;
|
2023-10-07 10:47:51 +04:00
|
|
|
if active_win.is_in_input_region(&(pos - buf_pos.to_f64())) {
|
|
|
|
|
return Some((active_win, buf_pos));
|
2023-08-16 09:22:46 +04:00
|
|
|
}
|
|
|
|
|
|
2023-10-07 10:47:51 +04:00
|
|
|
let mut x = -view_pos;
|
2023-08-13 19:55:37 +04:00
|
|
|
for col in &self.columns {
|
2023-10-07 10:47:51 +04:00
|
|
|
for (win, y) in zip(&col.windows, col.window_ys()) {
|
|
|
|
|
if win == active_win {
|
|
|
|
|
// Already handled it above.
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2023-08-13 19:55:37 +04:00
|
|
|
|
2023-09-05 18:14:42 +04:00
|
|
|
let geom = win.geometry();
|
2023-10-07 10:47:51 +04:00
|
|
|
let buf_pos = Point::from((x, y)) - geom.loc;
|
|
|
|
|
if win.is_in_input_region(&(pos - buf_pos.to_f64())) {
|
|
|
|
|
return Some((win, buf_pos));
|
2023-08-13 19:55:37 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-07 17:45:55 +04:00
|
|
|
x += col.width() + self.options.gaps;
|
2023-08-13 19:55:37 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
None
|
|
|
|
|
}
|
2023-08-14 18:29:50 +04:00
|
|
|
|
|
|
|
|
fn toggle_width(&mut self) {
|
|
|
|
|
if self.columns.is_empty() {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-04 15:45:30 +04:00
|
|
|
self.columns[self.active_column_idx].toggle_width();
|
2023-08-14 18:29:50 +04:00
|
|
|
}
|
2023-08-14 18:34:39 +04:00
|
|
|
|
|
|
|
|
fn toggle_full_width(&mut self) {
|
|
|
|
|
if self.columns.is_empty() {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-04 15:45:30 +04:00
|
|
|
self.columns[self.active_column_idx].toggle_full_width();
|
2023-08-14 18:34:39 +04:00
|
|
|
}
|
2023-08-16 09:08:10 +04:00
|
|
|
|
2023-10-03 11:38:42 +04:00
|
|
|
fn set_column_width(&mut self, change: SizeChange) {
|
|
|
|
|
if self.columns.is_empty() {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-04 15:45:30 +04:00
|
|
|
self.columns[self.active_column_idx].set_column_width(change);
|
2023-10-03 11:38:42 +04:00
|
|
|
}
|
|
|
|
|
|
2023-08-16 09:08:10 +04:00
|
|
|
pub fn set_fullscreen(&mut self, window: &W, is_fullscreen: bool) {
|
|
|
|
|
let (mut col_idx, win_idx) = self
|
|
|
|
|
.columns
|
|
|
|
|
.iter()
|
|
|
|
|
.enumerate()
|
|
|
|
|
.find_map(|(col_idx, col)| {
|
|
|
|
|
col.windows
|
|
|
|
|
.iter()
|
|
|
|
|
.position(|w| w == window)
|
|
|
|
|
.map(|win_idx| (col_idx, win_idx))
|
|
|
|
|
})
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
let mut col = &mut self.columns[col_idx];
|
|
|
|
|
|
|
|
|
|
if is_fullscreen && col.windows.len() > 1 {
|
|
|
|
|
// This wasn't the only window in its column; extract it into a separate column.
|
|
|
|
|
let target_window_was_focused =
|
|
|
|
|
self.active_column_idx == col_idx && col.active_window_idx == win_idx;
|
|
|
|
|
let window = col.windows.remove(win_idx);
|
|
|
|
|
col.active_window_idx = min(col.active_window_idx, col.windows.len() - 1);
|
2023-10-04 15:45:30 +04:00
|
|
|
col.update_window_sizes();
|
2023-08-16 09:08:10 +04:00
|
|
|
|
|
|
|
|
col_idx += 1;
|
2023-09-26 20:12:04 +04:00
|
|
|
self.columns.insert(
|
|
|
|
|
col_idx,
|
2023-10-05 09:25:07 +04:00
|
|
|
Column::new(
|
|
|
|
|
window,
|
|
|
|
|
self.view_size,
|
|
|
|
|
self.working_area,
|
|
|
|
|
self.options.clone(),
|
|
|
|
|
),
|
2023-09-26 20:12:04 +04:00
|
|
|
);
|
2023-08-16 09:08:10 +04:00
|
|
|
if self.active_column_idx >= col_idx || target_window_was_focused {
|
|
|
|
|
self.active_column_idx += 1;
|
|
|
|
|
}
|
|
|
|
|
col = &mut self.columns[col_idx];
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-04 15:45:30 +04:00
|
|
|
col.set_fullscreen(is_fullscreen);
|
2023-08-16 09:08:10 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn toggle_fullscreen(&mut self, window: &W) {
|
|
|
|
|
let col = self
|
|
|
|
|
.columns
|
|
|
|
|
.iter_mut()
|
|
|
|
|
.find(|col| col.windows.contains(window))
|
|
|
|
|
.unwrap();
|
|
|
|
|
let value = !col.is_fullscreen;
|
|
|
|
|
self.set_fullscreen(window, value);
|
|
|
|
|
}
|
2023-08-13 19:55:37 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Workspace<Window> {
|
2023-08-14 14:48:10 +04:00
|
|
|
fn refresh(&self) {
|
2023-10-07 17:50:45 +04:00
|
|
|
let bounds = self.toplevel_bounds();
|
|
|
|
|
|
2023-08-14 14:48:10 +04:00
|
|
|
for (col_idx, col) in self.columns.iter().enumerate() {
|
|
|
|
|
for (win_idx, win) in col.windows.iter().enumerate() {
|
|
|
|
|
let active = self.active_column_idx == col_idx && col.active_window_idx == win_idx;
|
|
|
|
|
win.set_activated(active);
|
2023-10-07 17:50:45 +04:00
|
|
|
|
|
|
|
|
win.toplevel().with_pending_state(|state| {
|
|
|
|
|
state.bounds = Some(bounds);
|
|
|
|
|
});
|
|
|
|
|
|
2023-08-14 14:48:10 +04:00
|
|
|
win.toplevel().send_pending_configure();
|
2023-09-28 08:26:17 +04:00
|
|
|
win.refresh();
|
2023-08-14 14:48:10 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn render_elements(
|
2023-08-13 19:55:37 +04:00
|
|
|
&self,
|
|
|
|
|
renderer: &mut GlesRenderer,
|
2023-08-14 16:19:43 +04:00
|
|
|
) -> Vec<WorkspaceRenderElement<GlesRenderer>> {
|
2023-08-16 09:22:46 +04:00
|
|
|
if self.columns.is_empty() {
|
|
|
|
|
return vec![];
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-21 13:48:32 +04:00
|
|
|
// FIXME: workspaces should probably cache their last used scale so they can be correctly
|
|
|
|
|
// rendered even with no outputs connected.
|
|
|
|
|
let output_scale = self
|
|
|
|
|
.output
|
|
|
|
|
.as_ref()
|
|
|
|
|
.map(|o| Scale::from(o.current_scale().fractional_scale()))
|
|
|
|
|
.unwrap_or(Scale::from(1.));
|
|
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
let mut rv = vec![];
|
|
|
|
|
let view_pos = self.view_pos();
|
|
|
|
|
|
2023-08-16 09:22:46 +04:00
|
|
|
// Draw the active window on top.
|
|
|
|
|
let col = &self.columns[self.active_column_idx];
|
|
|
|
|
let active_win = &col.windows[col.active_window_idx];
|
2023-10-07 10:47:51 +04:00
|
|
|
let win_pos = Point::from((
|
2023-08-16 09:22:46 +04:00
|
|
|
self.column_x(self.active_column_idx) - view_pos,
|
|
|
|
|
col.window_y(col.active_window_idx),
|
2023-10-07 10:47:51 +04:00
|
|
|
));
|
2023-09-24 18:10:43 +04:00
|
|
|
|
|
|
|
|
// Draw the window itself.
|
2023-10-07 10:47:51 +04:00
|
|
|
let geom = active_win.geometry();
|
|
|
|
|
let buf_pos = win_pos - geom.loc;
|
2023-08-16 09:22:46 +04:00
|
|
|
rv.extend(active_win.render_elements(
|
|
|
|
|
renderer,
|
2023-10-07 10:47:51 +04:00
|
|
|
buf_pos.to_physical_precise_round(output_scale),
|
2023-09-21 13:48:32 +04:00
|
|
|
output_scale,
|
2023-08-16 09:22:46 +04:00
|
|
|
1.,
|
|
|
|
|
));
|
|
|
|
|
|
2023-09-24 18:10:43 +04:00
|
|
|
// Draw the focus ring.
|
2023-10-11 11:56:38 +04:00
|
|
|
rv.extend(self.focus_ring.render(output_scale).map(Into::into));
|
2023-09-24 18:10:43 +04:00
|
|
|
|
2023-10-07 10:47:51 +04:00
|
|
|
let mut x = -view_pos;
|
2023-08-13 19:55:37 +04:00
|
|
|
for col in &self.columns {
|
2023-10-07 10:47:51 +04:00
|
|
|
for (win, y) in zip(&col.windows, col.window_ys()) {
|
|
|
|
|
if win == active_win {
|
|
|
|
|
// Already handled it above.
|
|
|
|
|
continue;
|
2023-08-16 09:08:10 +04:00
|
|
|
}
|
2023-09-05 18:14:42 +04:00
|
|
|
|
2023-10-07 10:47:51 +04:00
|
|
|
let geom = win.geometry();
|
|
|
|
|
let buf_pos = Point::from((x, y)) - geom.loc;
|
|
|
|
|
rv.extend(win.render_elements(
|
|
|
|
|
renderer,
|
|
|
|
|
buf_pos.to_physical_precise_round(output_scale),
|
|
|
|
|
output_scale,
|
|
|
|
|
1.,
|
|
|
|
|
));
|
2023-08-13 19:55:37 +04:00
|
|
|
}
|
|
|
|
|
|
2023-10-07 17:45:55 +04:00
|
|
|
x += col.width() + self.options.gaps;
|
2023-08-13 19:55:37 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rv
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
impl<W: LayoutElement> Column<W> {
|
2023-09-26 20:12:04 +04:00
|
|
|
fn new(
|
|
|
|
|
window: W,
|
|
|
|
|
view_size: Size<i32, Logical>,
|
|
|
|
|
working_area: Rectangle<i32, Logical>,
|
2023-10-05 09:25:07 +04:00
|
|
|
options: Rc<Options>,
|
2023-09-26 20:12:04 +04:00
|
|
|
) -> Self {
|
2023-08-13 19:55:37 +04:00
|
|
|
let mut rv = Self {
|
|
|
|
|
windows: vec![],
|
|
|
|
|
active_window_idx: 0,
|
|
|
|
|
width: ColumnWidth::default(),
|
2023-08-16 09:08:10 +04:00
|
|
|
is_fullscreen: false,
|
2023-10-04 15:45:30 +04:00
|
|
|
view_size,
|
|
|
|
|
working_area,
|
2023-10-05 09:25:07 +04:00
|
|
|
options,
|
2023-08-13 19:55:37 +04:00
|
|
|
};
|
|
|
|
|
|
2023-10-04 15:45:30 +04:00
|
|
|
rv.add_window(window);
|
2023-08-13 19:55:37 +04:00
|
|
|
|
|
|
|
|
rv
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-04 15:45:30 +04:00
|
|
|
fn set_view_size(&mut self, size: Size<i32, Logical>, working_area: Rectangle<i32, Logical>) {
|
|
|
|
|
if self.view_size == size && self.working_area == working_area {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.view_size = size;
|
|
|
|
|
self.working_area = working_area;
|
|
|
|
|
|
|
|
|
|
self.update_window_sizes();
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-05 09:25:07 +04:00
|
|
|
fn update_config(&mut self, options: Rc<Options>) {
|
2023-10-07 17:45:55 +04:00
|
|
|
let mut update_sizes = false;
|
|
|
|
|
|
2023-10-05 09:25:07 +04:00
|
|
|
// If preset widths changed, make our width non-preset.
|
|
|
|
|
if self.options.preset_widths != options.preset_widths {
|
|
|
|
|
if let ColumnWidth::Preset(idx) = self.width {
|
|
|
|
|
self.width = self.options.preset_widths[idx];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-07 17:45:55 +04:00
|
|
|
if self.options.gaps != options.gaps {
|
|
|
|
|
update_sizes = true;
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-05 09:25:07 +04:00
|
|
|
self.options = options;
|
2023-10-07 17:45:55 +04:00
|
|
|
|
|
|
|
|
if update_sizes {
|
|
|
|
|
self.update_window_sizes();
|
|
|
|
|
}
|
2023-10-05 09:25:07 +04:00
|
|
|
}
|
|
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
fn window_count(&self) -> usize {
|
|
|
|
|
self.windows.len()
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-04 15:45:30 +04:00
|
|
|
fn set_width(&mut self, width: ColumnWidth) {
|
2023-08-13 19:55:37 +04:00
|
|
|
self.width = width;
|
2023-10-04 15:45:30 +04:00
|
|
|
self.update_window_sizes();
|
2023-08-13 19:55:37 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn contains(&self, window: &W) -> bool {
|
|
|
|
|
self.windows.iter().any(|win| win == window)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn activate_window(&mut self, window: &W) {
|
|
|
|
|
let idx = self.windows.iter().position(|win| win == window).unwrap();
|
|
|
|
|
self.active_window_idx = idx;
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-04 15:45:30 +04:00
|
|
|
fn add_window(&mut self, window: W) {
|
2023-08-16 09:08:10 +04:00
|
|
|
self.is_fullscreen = false;
|
2023-08-13 19:55:37 +04:00
|
|
|
self.windows.push(window);
|
2023-10-04 15:45:30 +04:00
|
|
|
self.update_window_sizes();
|
2023-08-13 19:55:37 +04:00
|
|
|
}
|
|
|
|
|
|
2023-10-04 15:45:30 +04:00
|
|
|
fn update_window_sizes(&mut self) {
|
2023-08-16 09:08:10 +04:00
|
|
|
if self.is_fullscreen {
|
2023-10-04 15:45:30 +04:00
|
|
|
self.windows[0].request_fullscreen(self.view_size);
|
2023-08-16 09:08:10 +04:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
let min_width = self
|
|
|
|
|
.windows
|
|
|
|
|
.iter()
|
|
|
|
|
.filter_map(|win| {
|
|
|
|
|
let w = win.min_size().w;
|
|
|
|
|
if w == 0 {
|
|
|
|
|
None
|
|
|
|
|
} else {
|
|
|
|
|
Some(w)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.max()
|
|
|
|
|
.unwrap_or(1);
|
2023-08-16 10:03:24 +04:00
|
|
|
let max_width = self
|
|
|
|
|
.windows
|
|
|
|
|
.iter()
|
|
|
|
|
.filter_map(|win| {
|
|
|
|
|
let w = win.max_size().w;
|
|
|
|
|
if w == 0 {
|
|
|
|
|
None
|
|
|
|
|
} else {
|
|
|
|
|
Some(w)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.min()
|
|
|
|
|
.unwrap_or(i32::MAX);
|
|
|
|
|
let max_width = max(max_width, min_width);
|
|
|
|
|
|
2023-10-05 09:29:39 +04:00
|
|
|
let width = self.width.resolve(&self.options, self.working_area.size.w);
|
2023-10-07 17:45:55 +04:00
|
|
|
let height = (self.working_area.size.h - self.options.gaps) / self.window_count() as i32
|
|
|
|
|
- self.options.gaps;
|
2023-08-16 10:03:24 +04:00
|
|
|
let size = Size::from((max(min(width, max_width), min_width), max(height, 1)));
|
2023-08-13 19:55:37 +04:00
|
|
|
|
|
|
|
|
for win in &self.windows {
|
|
|
|
|
win.request_size(size);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-07 10:47:51 +04:00
|
|
|
fn width(&self) -> i32 {
|
|
|
|
|
self.windows
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|win| win.geometry().size.w)
|
|
|
|
|
.max()
|
|
|
|
|
.unwrap()
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
fn focus_up(&mut self) {
|
2023-08-14 14:48:10 +04:00
|
|
|
self.active_window_idx = self.active_window_idx.saturating_sub(1);
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn focus_down(&mut self) {
|
2023-08-14 14:48:10 +04:00
|
|
|
self.active_window_idx = min(self.active_window_idx + 1, self.windows.len() - 1);
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
fn move_up(&mut self) {
|
2023-08-13 12:46:53 +04:00
|
|
|
let new_idx = self.active_window_idx.saturating_sub(1);
|
|
|
|
|
if self.active_window_idx == new_idx {
|
2023-08-13 19:55:37 +04:00
|
|
|
return;
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.windows.swap(self.active_window_idx, new_idx);
|
|
|
|
|
self.active_window_idx = new_idx;
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
fn move_down(&mut self) {
|
2023-08-13 12:46:53 +04:00
|
|
|
let new_idx = min(self.active_window_idx + 1, self.windows.len() - 1);
|
|
|
|
|
if self.active_window_idx == new_idx {
|
2023-08-13 19:55:37 +04:00
|
|
|
return;
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.windows.swap(self.active_window_idx, new_idx);
|
|
|
|
|
self.active_window_idx = new_idx;
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-12 19:44:17 +04:00
|
|
|
#[cfg(test)]
|
2023-08-13 12:46:53 +04:00
|
|
|
fn verify_invariants(&self) {
|
|
|
|
|
assert!(!self.windows.is_empty(), "columns can't be empty");
|
2023-08-13 19:55:37 +04:00
|
|
|
assert!(self.active_window_idx < self.windows.len());
|
2023-08-16 09:08:10 +04:00
|
|
|
|
|
|
|
|
if self.is_fullscreen {
|
|
|
|
|
assert_eq!(self.windows.len(), 1);
|
|
|
|
|
}
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
2023-08-14 18:29:50 +04:00
|
|
|
|
2023-10-04 15:45:30 +04:00
|
|
|
fn toggle_width(&mut self) {
|
2023-08-14 18:29:50 +04:00
|
|
|
let idx = match self.width {
|
2023-10-05 09:25:07 +04:00
|
|
|
ColumnWidth::Preset(idx) => (idx + 1) % self.options.preset_widths.len(),
|
2023-08-14 18:29:50 +04:00
|
|
|
_ => {
|
2023-10-07 10:47:51 +04:00
|
|
|
let current = self.width();
|
2023-10-05 09:25:07 +04:00
|
|
|
self.options
|
|
|
|
|
.preset_widths
|
|
|
|
|
.iter()
|
2023-09-26 20:12:04 +04:00
|
|
|
.position(|prop| {
|
2023-10-05 09:29:39 +04:00
|
|
|
prop.resolve(&self.options, self.working_area.size.w) > current
|
2023-09-26 20:12:04 +04:00
|
|
|
})
|
2023-08-14 18:29:50 +04:00
|
|
|
.unwrap_or(0)
|
|
|
|
|
}
|
|
|
|
|
};
|
2023-10-05 09:25:07 +04:00
|
|
|
let width = ColumnWidth::Preset(idx);
|
2023-10-04 15:45:30 +04:00
|
|
|
self.set_width(width);
|
2023-08-14 18:29:50 +04:00
|
|
|
}
|
2023-08-14 18:34:39 +04:00
|
|
|
|
2023-10-04 15:45:30 +04:00
|
|
|
fn toggle_full_width(&mut self) {
|
2023-08-14 18:34:39 +04:00
|
|
|
let width = match self.width {
|
2023-08-16 20:29:57 +04:00
|
|
|
ColumnWidth::Proportion(x) if x == 1. => {
|
2023-08-14 18:34:39 +04:00
|
|
|
// FIXME: would be good to restore to previous width here.
|
|
|
|
|
ColumnWidth::default()
|
|
|
|
|
}
|
|
|
|
|
_ => ColumnWidth::Proportion(1.),
|
|
|
|
|
};
|
2023-10-04 15:45:30 +04:00
|
|
|
self.set_width(width);
|
2023-08-14 18:34:39 +04:00
|
|
|
}
|
2023-08-16 09:08:10 +04:00
|
|
|
|
2023-10-04 15:45:30 +04:00
|
|
|
fn set_column_width(&mut self, change: SizeChange) {
|
2023-10-05 09:29:39 +04:00
|
|
|
let current_px = self.width.resolve(&self.options, self.working_area.size.w);
|
2023-10-03 11:38:42 +04:00
|
|
|
|
|
|
|
|
let current = match self.width {
|
2023-10-05 09:25:07 +04:00
|
|
|
ColumnWidth::Preset(idx) => self.options.preset_widths[idx],
|
2023-10-03 11:38:42 +04:00
|
|
|
current => current,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// FIXME: fix overflows then remove limits.
|
|
|
|
|
const MAX_PX: i32 = 100000;
|
|
|
|
|
const MAX_F: f64 = 10000.;
|
|
|
|
|
|
|
|
|
|
let width = match (current, change) {
|
|
|
|
|
(_, SizeChange::SetFixed(fixed)) => ColumnWidth::Fixed(fixed.clamp(1, MAX_PX)),
|
|
|
|
|
(_, SizeChange::SetProportion(proportion)) => {
|
|
|
|
|
ColumnWidth::Proportion((proportion / 100.).clamp(0., MAX_F))
|
|
|
|
|
}
|
|
|
|
|
(_, SizeChange::AdjustFixed(delta)) => {
|
|
|
|
|
let width = current_px.saturating_add(delta).clamp(1, MAX_PX);
|
|
|
|
|
ColumnWidth::Fixed(width)
|
|
|
|
|
}
|
|
|
|
|
(ColumnWidth::Proportion(current), SizeChange::AdjustProportion(delta)) => {
|
|
|
|
|
let proportion = (current + delta / 100.).clamp(0., MAX_F);
|
|
|
|
|
ColumnWidth::Proportion(proportion)
|
|
|
|
|
}
|
|
|
|
|
(ColumnWidth::Fixed(_), SizeChange::AdjustProportion(delta)) => {
|
2023-10-07 17:45:55 +04:00
|
|
|
let current = (current_px + self.options.gaps) as f64
|
|
|
|
|
/ (self.working_area.size.w - self.options.gaps) as f64;
|
2023-10-03 11:38:42 +04:00
|
|
|
let proportion = (current + delta / 100.).clamp(0., MAX_F);
|
|
|
|
|
ColumnWidth::Proportion(proportion)
|
|
|
|
|
}
|
2023-10-05 09:25:07 +04:00
|
|
|
(ColumnWidth::Preset(_), _) => unreachable!(),
|
2023-10-03 11:38:42 +04:00
|
|
|
};
|
|
|
|
|
|
2023-10-04 15:45:30 +04:00
|
|
|
self.set_width(width);
|
2023-10-03 11:38:42 +04:00
|
|
|
}
|
|
|
|
|
|
2023-10-04 15:45:30 +04:00
|
|
|
fn set_fullscreen(&mut self, is_fullscreen: bool) {
|
2023-08-16 09:08:10 +04:00
|
|
|
assert_eq!(self.windows.len(), 1);
|
|
|
|
|
self.is_fullscreen = is_fullscreen;
|
2023-10-04 15:45:30 +04:00
|
|
|
self.update_window_sizes();
|
2023-08-16 09:08:10 +04:00
|
|
|
}
|
2023-08-16 09:22:46 +04:00
|
|
|
|
|
|
|
|
fn window_y(&self, window_idx: usize) -> i32 {
|
2023-10-07 10:47:51 +04:00
|
|
|
self.window_ys().nth(window_idx).unwrap()
|
|
|
|
|
}
|
2023-08-16 09:22:46 +04:00
|
|
|
|
2023-10-07 10:47:51 +04:00
|
|
|
fn window_ys(&self) -> impl Iterator<Item = i32> + '_ {
|
|
|
|
|
let mut y = 0;
|
2023-08-16 09:22:46 +04:00
|
|
|
|
2023-10-07 10:47:51 +04:00
|
|
|
if !self.is_fullscreen {
|
2023-10-07 17:45:55 +04:00
|
|
|
y = self.working_area.loc.y + self.options.gaps;
|
2023-08-16 09:22:46 +04:00
|
|
|
}
|
|
|
|
|
|
2023-10-07 10:47:51 +04:00
|
|
|
self.windows.iter().map(move |win| {
|
|
|
|
|
let pos = y;
|
2023-10-07 17:45:55 +04:00
|
|
|
y += win.geometry().size.h + self.options.gaps;
|
2023-10-07 10:47:51 +04:00
|
|
|
pos
|
|
|
|
|
})
|
2023-08-16 09:22:46 +04:00
|
|
|
}
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
2023-08-13 19:55:37 +04:00
|
|
|
|
|
|
|
|
pub fn output_size(output: &Output) -> Size<i32, Logical> {
|
|
|
|
|
let output_scale = output.current_scale().integer_scale();
|
|
|
|
|
let output_transform = output.current_transform();
|
|
|
|
|
let output_mode = output.current_mode().unwrap();
|
|
|
|
|
|
|
|
|
|
output_transform
|
|
|
|
|
.transform_size(output_mode.size)
|
|
|
|
|
.to_logical(output_scale)
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-07 17:45:55 +04:00
|
|
|
fn compute_new_view_offset(
|
|
|
|
|
cur_x: i32,
|
|
|
|
|
view_width: i32,
|
|
|
|
|
new_col_x: i32,
|
|
|
|
|
new_col_width: i32,
|
|
|
|
|
gaps: i32,
|
|
|
|
|
) -> i32 {
|
2023-09-13 16:57:12 +04:00
|
|
|
// If the column is wider than the view, always left-align it.
|
2023-10-07 10:47:51 +04:00
|
|
|
if view_width <= new_col_width {
|
2023-09-13 16:57:12 +04:00
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-07 10:47:51 +04:00
|
|
|
// Compute the padding in case it needs to be smaller due to large window width.
|
2023-10-07 17:45:55 +04:00
|
|
|
let padding = ((view_width - new_col_width) / 2).clamp(0, gaps);
|
2023-10-07 10:47:51 +04:00
|
|
|
|
|
|
|
|
// Compute the desired new X with padding.
|
|
|
|
|
let new_x = new_col_x - padding;
|
|
|
|
|
let new_right_x = new_col_x + new_col_width + padding;
|
|
|
|
|
|
2023-09-13 16:57:12 +04:00
|
|
|
// If the column is already fully visible, leave the view as is.
|
2023-10-07 10:47:51 +04:00
|
|
|
if cur_x <= new_x && new_right_x <= cur_x + view_width {
|
|
|
|
|
return -(new_col_x - cur_x);
|
2023-09-13 16:57:12 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Otherwise, prefer the aligment that results in less motion from the current position.
|
|
|
|
|
let dist_to_left = cur_x.abs_diff(new_x);
|
2023-10-07 10:47:51 +04:00
|
|
|
let dist_to_right = (cur_x + view_width).abs_diff(new_right_x);
|
2023-09-13 16:57:12 +04:00
|
|
|
if dist_to_left <= dist_to_right {
|
2023-10-07 10:47:51 +04:00
|
|
|
-padding
|
2023-09-13 16:57:12 +04:00
|
|
|
} else {
|
2023-10-07 10:47:51 +04:00
|
|
|
-(view_width - padding - new_col_width)
|
2023-09-13 16:57:12 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-01 15:07:45 +04:00
|
|
|
fn prepare_for_output(window: &impl LayoutElement, output: &Output) {
|
|
|
|
|
let scale = output.current_scale().integer_scale();
|
|
|
|
|
let transform = output.current_transform();
|
|
|
|
|
window.set_preferred_scale_transform(scale, transform);
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-12 19:44:17 +04:00
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use std::cell::Cell;
|
|
|
|
|
use std::rc::Rc;
|
|
|
|
|
|
2023-09-27 13:35:02 +04:00
|
|
|
use proptest::prelude::*;
|
|
|
|
|
use proptest_derive::Arbitrary;
|
2023-09-12 19:44:17 +04:00
|
|
|
use smithay::output::{Mode, PhysicalProperties, Subpixel};
|
|
|
|
|
use smithay::utils::IsAlive;
|
|
|
|
|
|
|
|
|
|
use super::*;
|
|
|
|
|
|
2023-10-05 09:25:07 +04:00
|
|
|
impl<W: LayoutElement> Default for Layout<W> {
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
monitor_set: MonitorSet::NoOutputs { workspaces: vec![] },
|
|
|
|
|
options: Rc::new(Options::default()),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-12 19:44:17 +04:00
|
|
|
#[derive(Debug)]
|
|
|
|
|
struct TestWindowInner {
|
|
|
|
|
id: usize,
|
|
|
|
|
bbox: Cell<Rectangle<i32, Logical>>,
|
2023-09-28 08:59:45 +04:00
|
|
|
initial_bbox: Rectangle<i32, Logical>,
|
|
|
|
|
requested_size: Cell<Option<Size<i32, Logical>>>,
|
2023-09-12 19:44:17 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
struct TestWindow(Rc<TestWindowInner>);
|
|
|
|
|
|
|
|
|
|
impl TestWindow {
|
|
|
|
|
fn new(id: usize, bbox: Rectangle<i32, Logical>) -> Self {
|
|
|
|
|
Self(Rc::new(TestWindowInner {
|
|
|
|
|
id,
|
|
|
|
|
bbox: Cell::new(bbox),
|
2023-09-28 08:59:45 +04:00
|
|
|
initial_bbox: bbox,
|
|
|
|
|
requested_size: Cell::new(None),
|
2023-09-12 19:44:17 +04:00
|
|
|
}))
|
|
|
|
|
}
|
2023-09-28 08:59:45 +04:00
|
|
|
|
|
|
|
|
fn communicate(&self) -> bool {
|
|
|
|
|
if let Some(size) = self.0.requested_size.take() {
|
|
|
|
|
assert!(size.w >= 0);
|
|
|
|
|
assert!(size.h >= 0);
|
|
|
|
|
|
|
|
|
|
let mut new_bbox = self.0.initial_bbox;
|
|
|
|
|
if size.w != 0 {
|
|
|
|
|
new_bbox.size.w = size.w;
|
|
|
|
|
}
|
|
|
|
|
if size.h != 0 {
|
|
|
|
|
new_bbox.size.h = size.h;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if self.0.bbox.get() != new_bbox {
|
|
|
|
|
self.0.bbox.set(new_bbox);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
false
|
|
|
|
|
}
|
2023-09-12 19:44:17 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl PartialEq for TestWindow {
|
|
|
|
|
fn eq(&self, other: &Self) -> bool {
|
|
|
|
|
self.0.id == other.0.id
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl IsAlive for TestWindow {
|
|
|
|
|
fn alive(&self) -> bool {
|
|
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl SpaceElement for TestWindow {
|
|
|
|
|
fn bbox(&self) -> Rectangle<i32, Logical> {
|
|
|
|
|
self.0.bbox.get()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn is_in_input_region(&self, _point: &Point<f64, Logical>) -> bool {
|
|
|
|
|
false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn set_activate(&self, _activated: bool) {}
|
|
|
|
|
fn output_enter(&self, _output: &Output, _overlap: Rectangle<i32, Logical>) {}
|
|
|
|
|
fn output_leave(&self, _output: &Output) {}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl LayoutElement for TestWindow {
|
2023-09-28 08:59:45 +04:00
|
|
|
fn request_size(&self, size: Size<i32, Logical>) {
|
|
|
|
|
self.0.requested_size.set(Some(size));
|
|
|
|
|
}
|
2023-09-12 19:44:17 +04:00
|
|
|
|
|
|
|
|
fn request_fullscreen(&self, _size: Size<i32, Logical>) {}
|
|
|
|
|
|
|
|
|
|
fn min_size(&self) -> Size<i32, Logical> {
|
|
|
|
|
Size::from((0, 0))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn max_size(&self) -> Size<i32, Logical> {
|
|
|
|
|
Size::from((0, 0))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn is_wl_surface(&self, _wl_surface: &WlSurface) -> bool {
|
|
|
|
|
false
|
|
|
|
|
}
|
2023-10-11 14:32:29 +04:00
|
|
|
|
2023-11-01 15:07:45 +04:00
|
|
|
fn set_preferred_scale_transform(&self, _scale: i32, _transform: Transform) {}
|
2023-10-26 00:15:46 +04:00
|
|
|
|
2023-10-11 14:32:29 +04:00
|
|
|
fn has_ssd(&self) -> bool {
|
|
|
|
|
false
|
|
|
|
|
}
|
2023-09-12 19:44:17 +04:00
|
|
|
}
|
|
|
|
|
|
2023-09-27 13:35:02 +04:00
|
|
|
fn arbitrary_bbox() -> impl Strategy<Value = Rectangle<i32, Logical>> {
|
|
|
|
|
any::<(i16, i16, u16, u16)>().prop_map(|(x, y, w, h)| {
|
|
|
|
|
let loc: Point<i32, _> = Point::from((x.into(), y.into()));
|
|
|
|
|
let size: Size<i32, _> = Size::from((w.into(), h.into()));
|
|
|
|
|
Rectangle::from_loc_and_size(loc, size)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-03 11:38:42 +04:00
|
|
|
fn arbitrary_size_change() -> impl Strategy<Value = SizeChange> {
|
|
|
|
|
prop_oneof![
|
|
|
|
|
(0..).prop_map(SizeChange::SetFixed),
|
|
|
|
|
(0f64..).prop_map(SizeChange::SetProportion),
|
|
|
|
|
any::<i32>().prop_map(SizeChange::AdjustFixed),
|
|
|
|
|
any::<f64>().prop_map(SizeChange::AdjustProportion),
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-27 13:35:02 +04:00
|
|
|
#[derive(Debug, Clone, Copy, Arbitrary)]
|
2023-09-12 19:44:17 +04:00
|
|
|
enum Op {
|
2023-09-27 13:35:02 +04:00
|
|
|
AddOutput(#[proptest(strategy = "1..=5usize")] usize),
|
|
|
|
|
RemoveOutput(#[proptest(strategy = "1..=5usize")] usize),
|
|
|
|
|
FocusOutput(#[proptest(strategy = "1..=5usize")] usize),
|
2023-09-12 19:44:17 +04:00
|
|
|
AddWindow {
|
2023-09-27 13:35:02 +04:00
|
|
|
#[proptest(strategy = "1..=5usize")]
|
2023-09-12 19:44:17 +04:00
|
|
|
id: usize,
|
2023-09-27 13:35:02 +04:00
|
|
|
#[proptest(strategy = "arbitrary_bbox()")]
|
2023-09-12 19:44:17 +04:00
|
|
|
bbox: Rectangle<i32, Logical>,
|
|
|
|
|
activate: bool,
|
|
|
|
|
},
|
2023-09-27 13:35:02 +04:00
|
|
|
CloseWindow(#[proptest(strategy = "1..=5usize")] usize),
|
2023-09-27 13:45:49 +04:00
|
|
|
FullscreenWindow(#[proptest(strategy = "1..=5usize")] usize),
|
2023-09-12 19:44:17 +04:00
|
|
|
FocusColumnLeft,
|
|
|
|
|
FocusColumnRight,
|
2023-09-27 13:45:49 +04:00
|
|
|
FocusWindowDown,
|
|
|
|
|
FocusWindowUp,
|
2023-09-12 19:44:17 +04:00
|
|
|
MoveColumnLeft,
|
|
|
|
|
MoveColumnRight,
|
2023-09-27 13:45:49 +04:00
|
|
|
MoveWindowDown,
|
|
|
|
|
MoveWindowUp,
|
2023-09-12 19:44:17 +04:00
|
|
|
ConsumeWindowIntoColumn,
|
|
|
|
|
ExpelWindowFromColumn,
|
|
|
|
|
FocusWorkspaceDown,
|
|
|
|
|
FocusWorkspaceUp,
|
2023-09-27 13:35:02 +04:00
|
|
|
FocusWorkspace(#[proptest(strategy = "1..=5u8")] u8),
|
2023-09-12 19:44:17 +04:00
|
|
|
MoveWindowToWorkspaceDown,
|
|
|
|
|
MoveWindowToWorkspaceUp,
|
2023-09-27 13:35:02 +04:00
|
|
|
MoveWindowToWorkspace(#[proptest(strategy = "1..=5u8")] u8),
|
2023-10-14 20:42:10 +04:00
|
|
|
MoveWorkspaceDown,
|
|
|
|
|
MoveWorkspaceUp,
|
2023-09-27 13:45:49 +04:00
|
|
|
MoveWindowToOutput(#[proptest(strategy = "1..=5u8")] u8),
|
|
|
|
|
SwitchPresetColumnWidth,
|
|
|
|
|
MaximizeColumn,
|
2023-10-03 11:38:42 +04:00
|
|
|
SetColumnWidth(#[proptest(strategy = "arbitrary_size_change()")] SizeChange),
|
2023-09-28 08:59:45 +04:00
|
|
|
Communicate(#[proptest(strategy = "1..=5usize")] usize),
|
2023-09-12 19:44:17 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Op {
|
2023-10-05 09:25:07 +04:00
|
|
|
fn apply(self, layout: &mut Layout<TestWindow>) {
|
2023-09-12 19:44:17 +04:00
|
|
|
match self {
|
|
|
|
|
Op::AddOutput(id) => {
|
|
|
|
|
let name = format!("output{id}");
|
2023-10-05 09:25:07 +04:00
|
|
|
if layout.outputs().any(|o| o.name() == name) {
|
2023-09-12 19:44:17 +04:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let output = Output::new(
|
|
|
|
|
name,
|
|
|
|
|
PhysicalProperties {
|
|
|
|
|
size: Size::from((1280, 720)),
|
|
|
|
|
subpixel: Subpixel::Unknown,
|
|
|
|
|
make: String::new(),
|
|
|
|
|
model: String::new(),
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
output.change_current_state(
|
|
|
|
|
Some(Mode {
|
|
|
|
|
size: Size::from((1280, 720)),
|
|
|
|
|
refresh: 60,
|
|
|
|
|
}),
|
|
|
|
|
None,
|
|
|
|
|
None,
|
|
|
|
|
None,
|
|
|
|
|
);
|
2023-10-05 09:25:07 +04:00
|
|
|
layout.add_output(output.clone());
|
2023-09-12 19:44:17 +04:00
|
|
|
}
|
|
|
|
|
Op::RemoveOutput(id) => {
|
|
|
|
|
let name = format!("output{id}");
|
2023-10-05 09:25:07 +04:00
|
|
|
let Some(output) = layout.outputs().find(|o| o.name() == name).cloned() else {
|
2023-09-12 19:44:17 +04:00
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
|
2023-10-05 09:25:07 +04:00
|
|
|
layout.remove_output(&output);
|
2023-09-12 19:44:17 +04:00
|
|
|
}
|
|
|
|
|
Op::FocusOutput(id) => {
|
|
|
|
|
let name = format!("output{id}");
|
2023-10-05 09:25:07 +04:00
|
|
|
let Some(output) = layout.outputs().find(|o| o.name() == name).cloned() else {
|
2023-09-12 19:44:17 +04:00
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
|
2023-10-05 09:25:07 +04:00
|
|
|
layout.focus_output(&output);
|
2023-09-12 19:44:17 +04:00
|
|
|
}
|
|
|
|
|
Op::AddWindow { id, bbox, activate } => {
|
2023-10-14 20:42:10 +04:00
|
|
|
match &mut layout.monitor_set {
|
|
|
|
|
MonitorSet::Normal { monitors, .. } => {
|
|
|
|
|
for mon in monitors {
|
|
|
|
|
for ws in &mut mon.workspaces {
|
|
|
|
|
for win in ws.windows() {
|
|
|
|
|
if win.0.id == id {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
MonitorSet::NoOutputs { workspaces, .. } => {
|
|
|
|
|
for ws in workspaces {
|
|
|
|
|
for win in ws.windows() {
|
|
|
|
|
if win.0.id == id {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-12 19:44:17 +04:00
|
|
|
let win = TestWindow::new(id, bbox);
|
2023-10-05 09:25:07 +04:00
|
|
|
layout.add_window(win, activate);
|
2023-09-12 19:44:17 +04:00
|
|
|
}
|
|
|
|
|
Op::CloseWindow(id) => {
|
|
|
|
|
let dummy = TestWindow::new(id, Rectangle::default());
|
2023-10-05 09:25:07 +04:00
|
|
|
layout.remove_window(&dummy);
|
2023-09-12 19:44:17 +04:00
|
|
|
}
|
2023-09-27 13:45:49 +04:00
|
|
|
Op::FullscreenWindow(id) => {
|
|
|
|
|
let dummy = TestWindow::new(id, Rectangle::default());
|
2023-10-05 09:25:07 +04:00
|
|
|
layout.toggle_fullscreen(&dummy);
|
2023-09-27 13:45:49 +04:00
|
|
|
}
|
2023-10-05 09:25:07 +04:00
|
|
|
Op::FocusColumnLeft => layout.focus_left(),
|
|
|
|
|
Op::FocusColumnRight => layout.focus_right(),
|
|
|
|
|
Op::FocusWindowDown => layout.focus_down(),
|
|
|
|
|
Op::FocusWindowUp => layout.focus_up(),
|
|
|
|
|
Op::MoveColumnLeft => layout.move_left(),
|
|
|
|
|
Op::MoveColumnRight => layout.move_right(),
|
|
|
|
|
Op::MoveWindowDown => layout.move_down(),
|
|
|
|
|
Op::MoveWindowUp => layout.move_up(),
|
|
|
|
|
Op::ConsumeWindowIntoColumn => layout.consume_into_column(),
|
|
|
|
|
Op::ExpelWindowFromColumn => layout.expel_from_column(),
|
|
|
|
|
Op::FocusWorkspaceDown => layout.switch_workspace_down(),
|
|
|
|
|
Op::FocusWorkspaceUp => layout.switch_workspace_up(),
|
|
|
|
|
Op::FocusWorkspace(idx) => layout.switch_workspace(idx),
|
|
|
|
|
Op::MoveWindowToWorkspaceDown => layout.move_to_workspace_down(),
|
|
|
|
|
Op::MoveWindowToWorkspaceUp => layout.move_to_workspace_up(),
|
|
|
|
|
Op::MoveWindowToWorkspace(idx) => layout.move_to_workspace(idx),
|
2023-09-27 13:45:49 +04:00
|
|
|
Op::MoveWindowToOutput(id) => {
|
|
|
|
|
let name = format!("output{id}");
|
2023-10-05 09:25:07 +04:00
|
|
|
let Some(output) = layout.outputs().find(|o| o.name() == name).cloned() else {
|
2023-09-27 13:45:49 +04:00
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
|
2023-10-05 09:25:07 +04:00
|
|
|
layout.move_to_output(&output);
|
2023-09-27 13:45:49 +04:00
|
|
|
}
|
2023-10-14 20:42:10 +04:00
|
|
|
Op::MoveWorkspaceDown => layout.move_workspace_down(),
|
|
|
|
|
Op::MoveWorkspaceUp => layout.move_workspace_up(),
|
2023-10-05 09:25:07 +04:00
|
|
|
Op::SwitchPresetColumnWidth => layout.toggle_width(),
|
|
|
|
|
Op::MaximizeColumn => layout.toggle_full_width(),
|
|
|
|
|
Op::SetColumnWidth(change) => layout.set_column_width(change),
|
2023-09-28 08:59:45 +04:00
|
|
|
Op::Communicate(id) => {
|
|
|
|
|
let mut window = None;
|
2023-10-05 09:25:07 +04:00
|
|
|
match &mut layout.monitor_set {
|
2023-09-28 08:59:45 +04:00
|
|
|
MonitorSet::Normal { monitors, .. } => {
|
|
|
|
|
'outer: for mon in monitors {
|
|
|
|
|
for ws in &mut mon.workspaces {
|
|
|
|
|
for win in ws.windows() {
|
|
|
|
|
if win.0.id == id {
|
|
|
|
|
if win.communicate() {
|
|
|
|
|
window = Some(win.clone());
|
|
|
|
|
}
|
|
|
|
|
break 'outer;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-10-05 09:25:07 +04:00
|
|
|
MonitorSet::NoOutputs { workspaces, .. } => {
|
2023-09-28 08:59:45 +04:00
|
|
|
'outer: for ws in workspaces {
|
|
|
|
|
for win in ws.windows() {
|
|
|
|
|
if win.0.id == id {
|
|
|
|
|
if win.communicate() {
|
|
|
|
|
window = Some(win.clone());
|
|
|
|
|
}
|
|
|
|
|
break 'outer;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let Some(win) = window {
|
2023-10-05 09:25:07 +04:00
|
|
|
layout.update_window(&win);
|
2023-09-28 08:59:45 +04:00
|
|
|
}
|
|
|
|
|
}
|
2023-09-12 19:44:17 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-26 20:41:55 +04:00
|
|
|
#[track_caller]
|
|
|
|
|
fn check_ops(ops: &[Op]) {
|
2023-10-05 09:25:07 +04:00
|
|
|
let mut layout = Layout::default();
|
2023-09-26 20:41:55 +04:00
|
|
|
for op in ops {
|
2023-10-05 09:25:07 +04:00
|
|
|
op.apply(&mut layout);
|
|
|
|
|
layout.verify_invariants();
|
2023-09-26 20:41:55 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-12 19:44:17 +04:00
|
|
|
#[test]
|
2023-10-05 09:59:28 +04:00
|
|
|
fn operations_dont_panic() {
|
2023-09-12 19:44:17 +04:00
|
|
|
let every_op = [
|
|
|
|
|
Op::AddOutput(0),
|
|
|
|
|
Op::AddOutput(1),
|
|
|
|
|
Op::AddOutput(2),
|
|
|
|
|
Op::RemoveOutput(0),
|
|
|
|
|
Op::RemoveOutput(1),
|
|
|
|
|
Op::RemoveOutput(2),
|
|
|
|
|
Op::FocusOutput(0),
|
|
|
|
|
Op::FocusOutput(1),
|
|
|
|
|
Op::FocusOutput(2),
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 0,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
|
|
|
|
activate: true,
|
|
|
|
|
},
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 1,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
|
|
|
|
activate: true,
|
|
|
|
|
},
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 2,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
|
|
|
|
activate: true,
|
|
|
|
|
},
|
|
|
|
|
Op::CloseWindow(0),
|
|
|
|
|
Op::CloseWindow(1),
|
|
|
|
|
Op::CloseWindow(2),
|
|
|
|
|
Op::FocusColumnLeft,
|
|
|
|
|
Op::FocusColumnRight,
|
|
|
|
|
Op::MoveColumnLeft,
|
|
|
|
|
Op::MoveColumnRight,
|
|
|
|
|
Op::ConsumeWindowIntoColumn,
|
|
|
|
|
Op::ExpelWindowFromColumn,
|
|
|
|
|
Op::FocusWorkspaceDown,
|
|
|
|
|
Op::FocusWorkspaceUp,
|
2023-09-16 12:14:02 +04:00
|
|
|
Op::FocusWorkspace(1),
|
|
|
|
|
Op::FocusWorkspace(2),
|
|
|
|
|
Op::FocusWorkspace(3),
|
2023-09-12 19:44:17 +04:00
|
|
|
Op::MoveWindowToWorkspaceDown,
|
|
|
|
|
Op::MoveWindowToWorkspaceUp,
|
2023-09-16 12:14:02 +04:00
|
|
|
Op::MoveWindowToWorkspace(1),
|
|
|
|
|
Op::MoveWindowToWorkspace(2),
|
|
|
|
|
Op::MoveWindowToWorkspace(3),
|
2023-10-14 20:42:10 +04:00
|
|
|
Op::MoveWindowDown,
|
|
|
|
|
Op::MoveWindowUp,
|
2023-09-12 19:44:17 +04:00
|
|
|
];
|
|
|
|
|
|
|
|
|
|
for third in every_op {
|
|
|
|
|
for second in every_op {
|
|
|
|
|
for first in every_op {
|
2023-10-05 09:59:28 +04:00
|
|
|
// eprintln!("{first:?}, {second:?}, {third:?}");
|
2023-09-12 19:44:17 +04:00
|
|
|
|
2023-10-05 09:25:07 +04:00
|
|
|
let mut layout = Layout::default();
|
|
|
|
|
first.apply(&mut layout);
|
|
|
|
|
layout.verify_invariants();
|
|
|
|
|
second.apply(&mut layout);
|
|
|
|
|
layout.verify_invariants();
|
|
|
|
|
third.apply(&mut layout);
|
|
|
|
|
layout.verify_invariants();
|
2023-09-12 19:44:17 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-09-26 20:36:48 +04:00
|
|
|
|
2023-10-05 09:59:28 +04:00
|
|
|
#[test]
|
|
|
|
|
fn operations_from_starting_state_dont_panic() {
|
|
|
|
|
if std::env::var_os("RUN_SLOW_TESTS").is_none() {
|
|
|
|
|
eprintln!("ignoring slow test");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Running every op from an empty state doesn't get us to all the interesting states. So,
|
|
|
|
|
// also run it from a manually-created starting state with more things going on to exercise
|
|
|
|
|
// more code paths.
|
|
|
|
|
let setup_ops = [
|
|
|
|
|
Op::AddOutput(1),
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 1,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
|
|
|
|
activate: true,
|
|
|
|
|
},
|
|
|
|
|
Op::MoveWindowToWorkspaceDown,
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 2,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
|
|
|
|
activate: true,
|
|
|
|
|
},
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 3,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
|
|
|
|
activate: true,
|
|
|
|
|
},
|
|
|
|
|
Op::FocusColumnLeft,
|
|
|
|
|
Op::ConsumeWindowIntoColumn,
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 4,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
|
|
|
|
activate: true,
|
|
|
|
|
},
|
|
|
|
|
Op::AddOutput(2),
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 5,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
|
|
|
|
activate: true,
|
|
|
|
|
},
|
|
|
|
|
Op::MoveWindowToOutput(2),
|
|
|
|
|
Op::FocusOutput(1),
|
|
|
|
|
Op::Communicate(1),
|
|
|
|
|
Op::Communicate(2),
|
|
|
|
|
Op::Communicate(3),
|
|
|
|
|
Op::Communicate(4),
|
|
|
|
|
Op::Communicate(5),
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
let every_op = [
|
|
|
|
|
Op::AddOutput(0),
|
|
|
|
|
Op::AddOutput(1),
|
|
|
|
|
Op::AddOutput(2),
|
|
|
|
|
Op::RemoveOutput(0),
|
|
|
|
|
Op::RemoveOutput(1),
|
|
|
|
|
Op::RemoveOutput(2),
|
|
|
|
|
Op::FocusOutput(0),
|
|
|
|
|
Op::FocusOutput(1),
|
|
|
|
|
Op::FocusOutput(2),
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 0,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
|
|
|
|
activate: true,
|
|
|
|
|
},
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 1,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
|
|
|
|
activate: true,
|
|
|
|
|
},
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 2,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
|
|
|
|
activate: true,
|
|
|
|
|
},
|
|
|
|
|
Op::CloseWindow(0),
|
|
|
|
|
Op::CloseWindow(1),
|
|
|
|
|
Op::CloseWindow(2),
|
|
|
|
|
Op::FocusColumnLeft,
|
|
|
|
|
Op::FocusColumnRight,
|
|
|
|
|
Op::MoveColumnLeft,
|
|
|
|
|
Op::MoveColumnRight,
|
|
|
|
|
Op::ConsumeWindowIntoColumn,
|
|
|
|
|
Op::ExpelWindowFromColumn,
|
|
|
|
|
Op::FocusWorkspaceDown,
|
|
|
|
|
Op::FocusWorkspaceUp,
|
|
|
|
|
Op::FocusWorkspace(1),
|
|
|
|
|
Op::FocusWorkspace(2),
|
|
|
|
|
Op::FocusWorkspace(3),
|
|
|
|
|
Op::MoveWindowToWorkspaceDown,
|
|
|
|
|
Op::MoveWindowToWorkspaceUp,
|
|
|
|
|
Op::MoveWindowToWorkspace(1),
|
|
|
|
|
Op::MoveWindowToWorkspace(2),
|
|
|
|
|
Op::MoveWindowToWorkspace(3),
|
2023-10-14 20:42:10 +04:00
|
|
|
Op::MoveWindowDown,
|
|
|
|
|
Op::MoveWindowUp,
|
2023-10-05 09:59:28 +04:00
|
|
|
];
|
|
|
|
|
|
|
|
|
|
for third in every_op {
|
|
|
|
|
for second in every_op {
|
|
|
|
|
for first in every_op {
|
|
|
|
|
// eprintln!("{first:?}, {second:?}, {third:?}");
|
|
|
|
|
|
|
|
|
|
let mut layout = Layout::default();
|
|
|
|
|
for op in setup_ops {
|
|
|
|
|
op.apply(&mut layout);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
first.apply(&mut layout);
|
|
|
|
|
layout.verify_invariants();
|
|
|
|
|
second.apply(&mut layout);
|
|
|
|
|
layout.verify_invariants();
|
|
|
|
|
third.apply(&mut layout);
|
|
|
|
|
layout.verify_invariants();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-26 20:36:48 +04:00
|
|
|
#[test]
|
|
|
|
|
fn primary_active_workspace_idx_not_updated_on_output_add() {
|
|
|
|
|
let ops = [
|
|
|
|
|
Op::AddOutput(1),
|
|
|
|
|
Op::AddOutput(2),
|
|
|
|
|
Op::FocusOutput(1),
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 0,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
|
|
|
|
activate: true,
|
|
|
|
|
},
|
|
|
|
|
Op::FocusOutput(2),
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 1,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
|
|
|
|
activate: true,
|
|
|
|
|
},
|
|
|
|
|
Op::RemoveOutput(2),
|
|
|
|
|
Op::FocusWorkspace(3),
|
|
|
|
|
Op::AddOutput(2),
|
|
|
|
|
];
|
|
|
|
|
|
2023-09-26 20:41:55 +04:00
|
|
|
check_ops(&ops);
|
2023-09-26 20:36:48 +04:00
|
|
|
}
|
2023-09-26 20:42:16 +04:00
|
|
|
|
|
|
|
|
#[test]
|
2023-09-27 13:09:39 +04:00
|
|
|
fn window_closed_on_previous_workspace() {
|
2023-09-26 20:42:16 +04:00
|
|
|
let ops = [
|
|
|
|
|
Op::AddOutput(1),
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 0,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
|
|
|
|
activate: true,
|
|
|
|
|
},
|
|
|
|
|
Op::FocusWorkspaceDown,
|
|
|
|
|
Op::CloseWindow(0),
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
check_ops(&ops);
|
|
|
|
|
}
|
2023-09-27 13:35:02 +04:00
|
|
|
|
2023-10-14 19:24:52 +04:00
|
|
|
#[test]
|
|
|
|
|
fn removing_output_must_keep_empty_focus_on_primary() {
|
|
|
|
|
let ops = [
|
|
|
|
|
Op::AddOutput(1),
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 0,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
|
|
|
|
activate: true,
|
|
|
|
|
},
|
|
|
|
|
Op::AddOutput(2),
|
|
|
|
|
Op::RemoveOutput(1),
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
let mut layout = Layout::default();
|
|
|
|
|
for op in ops {
|
|
|
|
|
op.apply(&mut layout);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let MonitorSet::Normal { monitors, .. } = layout.monitor_set else {
|
|
|
|
|
unreachable!()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// The workspace from the removed output was inserted at position 0, so the active workspace
|
|
|
|
|
// must change to 1 to keep the focus on the empty workspace.
|
|
|
|
|
assert_eq!(monitors[0].active_workspace_idx, 1);
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-14 20:18:09 +04:00
|
|
|
#[test]
|
|
|
|
|
fn move_to_workspace_by_idx_does_not_leave_empty_workspaces() {
|
|
|
|
|
let ops = [
|
|
|
|
|
Op::AddOutput(1),
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 0,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
|
|
|
|
activate: true,
|
|
|
|
|
},
|
|
|
|
|
Op::MoveWindowToWorkspace(2),
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
let mut layout = Layout::default();
|
|
|
|
|
for op in ops {
|
|
|
|
|
op.apply(&mut layout);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let MonitorSet::Normal { monitors, .. } = layout.monitor_set else {
|
|
|
|
|
unreachable!()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
assert!(monitors[0].workspaces[0].has_windows());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn focus_workspace_by_idx_does_not_leave_empty_workspaces() {
|
|
|
|
|
let ops = [
|
|
|
|
|
Op::AddOutput(1),
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 0,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
|
|
|
|
activate: true,
|
|
|
|
|
},
|
|
|
|
|
Op::FocusWorkspaceDown,
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 1,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
|
|
|
|
activate: true,
|
|
|
|
|
},
|
|
|
|
|
Op::FocusWorkspaceUp,
|
|
|
|
|
Op::CloseWindow(0),
|
|
|
|
|
Op::FocusWorkspace(3),
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
let mut layout = Layout::default();
|
|
|
|
|
for op in ops {
|
|
|
|
|
op.apply(&mut layout);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let MonitorSet::Normal { monitors, .. } = layout.monitor_set else {
|
|
|
|
|
unreachable!()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
assert!(monitors[0].workspaces[0].has_windows());
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-14 20:32:53 +04:00
|
|
|
#[test]
|
|
|
|
|
fn empty_workspaces_dont_move_back_to_original_output() {
|
|
|
|
|
let ops = [
|
|
|
|
|
Op::AddOutput(1),
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 1,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
|
|
|
|
activate: true,
|
|
|
|
|
},
|
|
|
|
|
Op::FocusWorkspaceDown,
|
|
|
|
|
Op::AddWindow {
|
|
|
|
|
id: 2,
|
|
|
|
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
|
|
|
|
activate: true,
|
|
|
|
|
},
|
|
|
|
|
Op::AddOutput(2),
|
|
|
|
|
Op::RemoveOutput(1),
|
|
|
|
|
Op::FocusWorkspace(1),
|
|
|
|
|
Op::CloseWindow(1),
|
|
|
|
|
Op::AddOutput(1),
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
check_ops(&ops);
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-27 13:35:02 +04:00
|
|
|
proptest! {
|
|
|
|
|
#![proptest_config(ProptestConfig {
|
|
|
|
|
cases: if std::env::var_os("RUN_SLOW_TESTS").is_none() {
|
|
|
|
|
eprintln!("ignoring slow test");
|
|
|
|
|
0
|
|
|
|
|
} else {
|
|
|
|
|
ProptestConfig::default().cases
|
|
|
|
|
},
|
|
|
|
|
..ProptestConfig::default()
|
|
|
|
|
})]
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn random_operations_dont_panic(ops: Vec<Op>) {
|
|
|
|
|
// eprintln!("{ops:?}");
|
|
|
|
|
check_ops(&ops);
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-09-12 19:44:17 +04:00
|
|
|
}
|