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};
|
|
|
|
|
use std::mem;
|
2023-08-13 19:55:37 +04:00
|
|
|
use std::time::Duration;
|
2023-08-13 12:46:53 +04:00
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
use smithay::backend::renderer::element::surface::WaylandSurfaceRenderElement;
|
|
|
|
|
use smithay::backend::renderer::element::AsRenderElements;
|
|
|
|
|
use smithay::backend::renderer::gles::GlesRenderer;
|
|
|
|
|
use smithay::desktop::space::SpaceElement;
|
|
|
|
|
use smithay::desktop::Window;
|
2023-08-13 12:46:53 +04:00
|
|
|
use smithay::output::Output;
|
|
|
|
|
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
|
2023-08-13 19:55:37 +04:00
|
|
|
use smithay::utils::{Logical, Point, Rectangle, Scale, Size};
|
|
|
|
|
use smithay::wayland::compositor::{with_states, SurfaceData};
|
|
|
|
|
use smithay::wayland::shell::xdg::XdgToplevelSurfaceData;
|
2023-08-13 12:46:53 +04:00
|
|
|
|
2023-08-14 15:54:11 +04:00
|
|
|
use crate::animation::Animation;
|
|
|
|
|
|
2023-08-13 12:46:53 +04:00
|
|
|
const PADDING: i32 = 16;
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
|
|
|
pub struct OutputId(String);
|
|
|
|
|
|
2023-08-14 16:19:43 +04:00
|
|
|
pub type WorkspaceRenderElement<R> = WaylandSurfaceRenderElement<R>;
|
|
|
|
|
pub type MonitorRenderElement<R> = WorkspaceRenderElement<R>;
|
|
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
pub trait LayoutElement: SpaceElement + PartialEq + Clone {
|
|
|
|
|
fn request_size(&self, size: Size<i32, Logical>);
|
|
|
|
|
fn min_size(&self) -> Size<i32, Logical>;
|
|
|
|
|
fn is_wl_surface(&self, wl_surface: &WlSurface) -> bool;
|
|
|
|
|
fn send_frame<T, F>(
|
|
|
|
|
&self,
|
|
|
|
|
output: &Output,
|
|
|
|
|
time: T,
|
|
|
|
|
throttle: Option<Duration>,
|
|
|
|
|
primary_scan_out_output: F,
|
|
|
|
|
) where
|
|
|
|
|
T: Into<Duration>,
|
|
|
|
|
F: FnMut(&WlSurface, &SurfaceData) -> Option<Output> + Copy;
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-13 12:46:53 +04:00
|
|
|
#[derive(Debug)]
|
2023-08-13 19:55:37 +04:00
|
|
|
pub 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-08-13 19:55:37 +04:00
|
|
|
NoOutputs(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,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[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>,
|
|
|
|
|
|
|
|
|
|
/// 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
|
|
|
|
|
|
|
|
/// Offset of the view computed from the active column.
|
|
|
|
|
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-08-13 19:55:37 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Width of a column.
|
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
|
|
|
enum ColumnWidth {
|
|
|
|
|
/// Proportion of the current view width.
|
|
|
|
|
Proportion(f64),
|
|
|
|
|
/// Fixed width in logical pixels.
|
|
|
|
|
Fixed(i32),
|
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-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-14 14:48:10 +04:00
|
|
|
self.toplevel()
|
|
|
|
|
.with_pending_state(|state| state.size = Some(size));
|
2023-08-13 19:55:37 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn min_size(&self) -> Size<i32, Logical> {
|
|
|
|
|
with_states(self.toplevel().wl_surface(), |state| {
|
|
|
|
|
state
|
|
|
|
|
.data_map
|
|
|
|
|
.get::<XdgToplevelSurfaceData>()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.lock()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.min_size
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn is_wl_surface(&self, wl_surface: &WlSurface) -> bool {
|
|
|
|
|
self.toplevel().wl_surface() == wl_surface
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn send_frame<T, F>(
|
|
|
|
|
&self,
|
|
|
|
|
output: &Output,
|
|
|
|
|
time: T,
|
|
|
|
|
throttle: Option<Duration>,
|
|
|
|
|
primary_scan_out_output: F,
|
|
|
|
|
) where
|
|
|
|
|
T: Into<Duration>,
|
|
|
|
|
F: FnMut(&WlSurface, &SurfaceData) -> Option<Output> + Copy,
|
|
|
|
|
{
|
|
|
|
|
self.send_frame(output, time, throttle, primary_scan_out_output);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl ColumnWidth {
|
|
|
|
|
fn resolve(self, view_width: i32) -> i32 {
|
|
|
|
|
match self {
|
|
|
|
|
ColumnWidth::Proportion(proportion) => (view_width as f64 * proportion).floor() as i32,
|
|
|
|
|
ColumnWidth::Fixed(width) => width,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Default for ColumnWidth {
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
Self::Proportion(0.5)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<W: LayoutElement> MonitorSet<W> {
|
2023-08-13 12:46:53 +04:00
|
|
|
pub fn new() -> Self {
|
|
|
|
|
Self::NoOutputs(vec![])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn add_output(&mut self, output: Output) {
|
|
|
|
|
let id = OutputId::new(&output);
|
|
|
|
|
|
|
|
|
|
*self = match mem::take(self) {
|
|
|
|
|
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-08-13 12:46:53 +04:00
|
|
|
workspaces.push(ws);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
workspaces.reverse();
|
2023-08-13 19:55:37 +04:00
|
|
|
if workspaces.iter().all(|ws| ws.has_windows()) {
|
2023-08-13 12:46:53 +04:00
|
|
|
// Make sure there's always an empty workspace.
|
2023-08-13 19:55:37 +04:00
|
|
|
workspaces.push(Workspace::new(output.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
|
|
|
}
|
|
|
|
|
|
|
|
|
|
monitors.push(Monitor {
|
|
|
|
|
output,
|
|
|
|
|
workspaces,
|
|
|
|
|
active_workspace_idx: 0,
|
|
|
|
|
});
|
|
|
|
|
MonitorSet::Normal {
|
|
|
|
|
monitors,
|
|
|
|
|
primary_idx,
|
|
|
|
|
active_monitor_idx,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
MonitorSet::NoOutputs(mut workspaces) => {
|
2023-08-13 19:55:37 +04:00
|
|
|
// We know there are no empty workspaces there, so add one.
|
|
|
|
|
workspaces.push(Workspace::new(output.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
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let monitor = Monitor {
|
|
|
|
|
output,
|
|
|
|
|
workspaces,
|
|
|
|
|
active_workspace_idx: 0,
|
|
|
|
|
};
|
|
|
|
|
MonitorSet::Normal {
|
|
|
|
|
monitors: vec![monitor],
|
|
|
|
|
primary_idx: 0,
|
|
|
|
|
active_monitor_idx: 0,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn remove_output(&mut self, output: &Output) {
|
|
|
|
|
*self = match mem::take(self) {
|
|
|
|
|
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.
|
|
|
|
|
MonitorSet::NoOutputs(workspaces)
|
|
|
|
|
} 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
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
MonitorSet::Normal {
|
|
|
|
|
monitors,
|
|
|
|
|
primary_idx,
|
|
|
|
|
active_monitor_idx,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
MonitorSet::NoOutputs(_) => {
|
|
|
|
|
panic!("tried to remove output when there were already none")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn add_window(
|
|
|
|
|
&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,
|
|
|
|
|
..
|
|
|
|
|
} = self
|
|
|
|
|
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-08-13 19:55:37 +04:00
|
|
|
pub fn add_window_to_output(&mut self, output: &Output, window: W, activate: bool) {
|
2023-08-13 12:46:53 +04:00
|
|
|
let MonitorSet::Normal { monitors, .. } = self else {
|
|
|
|
|
panic!()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let (monitor_idx, monitor) = monitors
|
|
|
|
|
.iter()
|
|
|
|
|
.enumerate()
|
|
|
|
|
.find(|(_, mon)| &mon.output == output)
|
|
|
|
|
.unwrap();
|
|
|
|
|
let workspace_idx = monitor.active_workspace_idx;
|
|
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
self.add_window(monitor_idx, workspace_idx, window, activate);
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
pub fn remove_window(&mut self, window: &W) {
|
|
|
|
|
match self {
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
MonitorSet::NoOutputs(workspaces) => {
|
|
|
|
|
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) {
|
|
|
|
|
match self {
|
|
|
|
|
MonitorSet::Normal { monitors, .. } => {
|
|
|
|
|
for mon in monitors {
|
|
|
|
|
for ws in &mut mon.workspaces {
|
|
|
|
|
if ws.has_window(window) {
|
|
|
|
|
ws.update_window(window);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
MonitorSet::NoOutputs(workspaces) => {
|
|
|
|
|
for ws in workspaces {
|
|
|
|
|
if ws.has_window(window) {
|
|
|
|
|
ws.update_window(window);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
pub fn send_frame(&self, output: &Output, time: Duration) {
|
|
|
|
|
if let MonitorSet::Normal { monitors, .. } = self {
|
|
|
|
|
for mon in monitors {
|
|
|
|
|
if &mon.output == output {
|
|
|
|
|
mon.workspaces[mon.active_workspace_idx].send_frame(time);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
pub fn find_window_and_output(&mut self, wl_surface: &WlSurface) -> Option<(W, Output)> {
|
|
|
|
|
if let MonitorSet::Normal { monitors, .. } = self {
|
|
|
|
|
for mon in monitors {
|
|
|
|
|
for ws in &mut mon.workspaces {
|
|
|
|
|
if let Some(window) = ws.find_wl_surface(wl_surface) {
|
|
|
|
|
return Some((window.clone(), mon.output.clone()));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn update_output(&mut self, output: &Output) {
|
|
|
|
|
let MonitorSet::Normal { monitors, .. } = self else {
|
|
|
|
|
panic!()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for mon in monitors {
|
|
|
|
|
if &mon.output == output {
|
|
|
|
|
for ws in &mut mon.workspaces {
|
|
|
|
|
ws.set_view_size(output_size(output));
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn activate_window(&mut self, window: &W) {
|
2023-08-13 12:46:53 +04:00
|
|
|
let MonitorSet::Normal {
|
|
|
|
|
monitors,
|
|
|
|
|
active_monitor_idx,
|
|
|
|
|
..
|
|
|
|
|
} = self
|
|
|
|
|
else {
|
|
|
|
|
todo!()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for (monitor_idx, mon) in monitors.iter_mut().enumerate() {
|
|
|
|
|
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;
|
|
|
|
|
mon.active_workspace_idx = workspace_idx;
|
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,
|
|
|
|
|
..
|
|
|
|
|
} = self
|
|
|
|
|
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,
|
|
|
|
|
..
|
|
|
|
|
} = self
|
|
|
|
|
else {
|
|
|
|
|
return None;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Some(&monitors[*active_monitor_idx].output)
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-14 16:19:43 +04:00
|
|
|
pub fn workspace_for_output(&self, output: &Output) -> Option<&Workspace<W>> {
|
|
|
|
|
let MonitorSet::Normal { monitors, .. } = self else {
|
|
|
|
|
return None;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
monitors.iter().find_map(|monitor| {
|
|
|
|
|
if &monitor.output == output {
|
|
|
|
|
Some(&monitor.workspaces[monitor.active_workspace_idx])
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
})
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
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,
|
|
|
|
|
..
|
|
|
|
|
} = self
|
|
|
|
|
else {
|
|
|
|
|
return None;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Some(&mut monitors[*active_monitor_idx])
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-14 16:19:43 +04:00
|
|
|
pub fn monitor_for_output_mut(&mut self, output: &Output) -> Option<&mut Monitor<W>> {
|
|
|
|
|
let MonitorSet::Normal { monitors, .. } = self else {
|
|
|
|
|
return None;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
monitors
|
|
|
|
|
.iter_mut()
|
|
|
|
|
.find(|monitor| &monitor.output == 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
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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,
|
|
|
|
|
..
|
|
|
|
|
} = self
|
|
|
|
|
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>)> {
|
|
|
|
|
let ws = self.workspace_for_output(output).unwrap();
|
|
|
|
|
ws.window_under(pos_within_output)
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn verify_invariants(&self) {
|
|
|
|
|
let (monitors, &primary_idx, &active_monitor_idx) = match &self {
|
|
|
|
|
MonitorSet::Normal {
|
|
|
|
|
monitors,
|
|
|
|
|
primary_idx,
|
|
|
|
|
active_monitor_idx,
|
|
|
|
|
} => (monitors, primary_idx, active_monitor_idx),
|
|
|
|
|
MonitorSet::NoOutputs(workspaces) => {
|
|
|
|
|
for workspace in workspaces {
|
|
|
|
|
assert!(
|
2023-08-13 19:55:37 +04:00
|
|
|
!workspace.has_windows(),
|
2023-08-13 12:46:53 +04:00
|
|
|
"with no outputs there cannot be empty workspaces"
|
|
|
|
|
);
|
|
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
workspace.verify_invariants();
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
assert!(primary_idx <= monitors.len());
|
|
|
|
|
assert!(active_monitor_idx <= monitors.len());
|
|
|
|
|
|
|
|
|
|
for (idx, monitor) in monitors.iter().enumerate() {
|
|
|
|
|
assert!(
|
|
|
|
|
!monitor.workspaces.is_empty(),
|
|
|
|
|
"monitor monitor must have at least one workspace"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
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"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// FIXME: verify that primary doesn't have any workspaces for which their own monitor
|
|
|
|
|
// exists.
|
|
|
|
|
|
|
|
|
|
for workspace in &monitor.workspaces {
|
2023-08-13 19:55:37 +04:00
|
|
|
workspace.verify_invariants();
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
impl MonitorSet<Window> {
|
2023-08-14 14:48:10 +04:00
|
|
|
pub fn refresh(&self) {
|
|
|
|
|
let _span = tracy_client::span!("MonitorSet::refresh");
|
|
|
|
|
|
|
|
|
|
match self {
|
|
|
|
|
MonitorSet::Normal { monitors, .. } => {
|
|
|
|
|
for mon in monitors {
|
|
|
|
|
for ws in &mon.workspaces {
|
|
|
|
|
ws.refresh();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
MonitorSet::NoOutputs(workspaces) => {
|
|
|
|
|
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 {
|
|
|
|
|
Self::new()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-14 16:19:43 +04:00
|
|
|
impl<W: LayoutElement> Monitor<W> {
|
|
|
|
|
fn active_workspace(&mut self) -> &mut Workspace<W> {
|
|
|
|
|
&mut self.workspaces[self.active_workspace_idx]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn add_window(&mut self, workspace_idx: usize, window: W, activate: bool) {
|
|
|
|
|
let workspace = &mut self.workspaces[workspace_idx];
|
|
|
|
|
|
|
|
|
|
if activate {
|
|
|
|
|
self.active_workspace_idx = workspace_idx;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
workspace.add_window(window.clone(), activate);
|
|
|
|
|
|
|
|
|
|
if workspace_idx == self.workspaces.len() - 1 {
|
|
|
|
|
// Insert a new empty workspace.
|
|
|
|
|
let ws = Workspace::new(self.output.clone());
|
|
|
|
|
self.workspaces.push(ws);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-14 16:26:50 +04:00
|
|
|
fn clean_up_workspaces(&mut self) {
|
|
|
|
|
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);
|
2023-08-14 16:26:50 +04:00
|
|
|
|
|
|
|
|
self.clean_up_workspaces();
|
2023-08-14 16:19:43 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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-08-14 16:26:50 +04:00
|
|
|
|
|
|
|
|
self.clean_up_workspaces();
|
2023-08-14 16:19:43 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn switch_workspace_up(&mut self) {
|
2023-08-14 16:26:50 +04:00
|
|
|
self.active_workspace_idx = self.active_workspace_idx.saturating_sub(1);
|
|
|
|
|
self.clean_up_workspaces();
|
2023-08-14 16:19:43 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn switch_workspace_down(&mut self) {
|
2023-08-14 16:26:50 +04:00
|
|
|
self.active_workspace_idx = min(self.active_workspace_idx + 1, self.workspaces.len() - 1);
|
|
|
|
|
self.clean_up_workspaces();
|
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])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn advance_animations(&mut self, current_time: Duration) {
|
|
|
|
|
for ws in &mut self.workspaces {
|
|
|
|
|
ws.advance_animations(current_time);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Monitor<Window> {
|
|
|
|
|
pub fn render_elements(
|
|
|
|
|
&self,
|
|
|
|
|
renderer: &mut GlesRenderer,
|
|
|
|
|
) -> Vec<WorkspaceRenderElement<GlesRenderer>> {
|
|
|
|
|
self.workspaces[self.active_workspace_idx].render_elements(renderer)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
impl<W: LayoutElement> Workspace<W> {
|
|
|
|
|
fn new(output: Output) -> Self {
|
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),
|
|
|
|
|
output: Some(output),
|
2023-08-13 12:46:53 +04:00
|
|
|
columns: vec![],
|
|
|
|
|
active_column_idx: 0,
|
2023-08-13 19:55:37 +04:00
|
|
|
view_offset: 0,
|
2023-08-14 15:54:11 +04:00
|
|
|
view_offset_anim: None,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn advance_animations(&mut self, current_time: Duration) {
|
|
|
|
|
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-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-08-13 19:55:37 +04:00
|
|
|
if let Some(output) = output {
|
|
|
|
|
self.set_view_size(output_size(&output));
|
|
|
|
|
|
|
|
|
|
self.output = Some(output);
|
|
|
|
|
|
|
|
|
|
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 {
|
|
|
|
|
// 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-08-13 19:55:37 +04:00
|
|
|
fn set_view_size(&mut self, size: Size<i32, Logical>) {
|
|
|
|
|
if self.view_size == size {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2023-08-13 12:46:53 +04:00
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
self.view_size = size;
|
|
|
|
|
for col in &mut self.columns {
|
|
|
|
|
col.update_window_sizes(self.view_size);
|
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-08-14 15:54:11 +04:00
|
|
|
fn activate_column(&mut self, idx: usize) {
|
|
|
|
|
if self.active_column_idx == idx {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let current_x = self.view_pos();
|
|
|
|
|
|
|
|
|
|
self.active_column_idx = idx;
|
|
|
|
|
|
|
|
|
|
self.view_offset = 0;
|
|
|
|
|
let new_x = self.view_pos();
|
|
|
|
|
|
|
|
|
|
self.view_offset_anim = Some(Animation::new(
|
|
|
|
|
(current_x - new_x) as f64,
|
|
|
|
|
0.,
|
|
|
|
|
Duration::from_millis(250),
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
|
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 {
|
|
|
|
|
let mut x = PADDING;
|
|
|
|
|
|
|
|
|
|
for column in self.columns.iter().take(column_idx) {
|
|
|
|
|
x += column.size().w + PADDING;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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-08-13 12:46:53 +04:00
|
|
|
let idx = if self.columns.is_empty() {
|
|
|
|
|
0
|
|
|
|
|
} else {
|
|
|
|
|
self.active_column_idx + 1
|
|
|
|
|
};
|
|
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
let column = Column::new(window, self.view_size);
|
2023-08-13 12:46:53 +04:00
|
|
|
self.columns.insert(idx, column);
|
|
|
|
|
|
|
|
|
|
if activate {
|
2023-08-14 15:54:11 +04:00
|
|
|
self.activate_column(idx);
|
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() {
|
|
|
|
|
self.columns.remove(column_idx);
|
|
|
|
|
if self.columns.is_empty() {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-14 15:54:11 +04:00
|
|
|
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-08-13 19:55:37 +04:00
|
|
|
column.update_window_sizes(self.view_size);
|
|
|
|
|
}
|
2023-08-13 12:46:53 +04:00
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
fn update_window(&mut self, window: &W) {
|
|
|
|
|
let column = self
|
|
|
|
|
.columns
|
|
|
|
|
.iter_mut()
|
|
|
|
|
.find(|col| col.contains(window))
|
|
|
|
|
.unwrap();
|
|
|
|
|
column.update_window_sizes(self.view_size);
|
|
|
|
|
}
|
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
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.columns.swap(self.active_column_idx, new_idx);
|
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
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.columns.swap(self.active_column_idx, new_idx);
|
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-08-13 19:55:37 +04:00
|
|
|
target_column.add_window(self.view_size, 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 send_frame(&self, time: Duration) {
|
|
|
|
|
let output = self.output.as_ref().unwrap();
|
|
|
|
|
for win in self.windows() {
|
|
|
|
|
win.send_frame(output, time, None, |_, _| Some(output.clone()));
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-08-13 12:46:53 +04:00
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
fn view_pos(&self) -> i32 {
|
|
|
|
|
self.column_x(self.active_column_idx) + self.view_offset - PADDING
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn window_under(
|
|
|
|
|
&self,
|
|
|
|
|
pos_within_output: Point<f64, Logical>,
|
|
|
|
|
) -> Option<(&W, Point<i32, Logical>)> {
|
|
|
|
|
let view_pos = self.view_pos();
|
|
|
|
|
|
|
|
|
|
let mut pos = pos_within_output;
|
|
|
|
|
pos.x += view_pos as f64;
|
|
|
|
|
|
|
|
|
|
let mut x = PADDING;
|
|
|
|
|
for col in &self.columns {
|
|
|
|
|
let mut y = PADDING;
|
|
|
|
|
|
|
|
|
|
for win in &col.windows {
|
|
|
|
|
let geom = win.geometry();
|
|
|
|
|
|
|
|
|
|
// x, y point at the top-left of the window geometry.
|
|
|
|
|
let win_pos = Point::from((x, y)) - geom.loc;
|
|
|
|
|
if win.is_in_input_region(&(pos - win_pos.to_f64())) {
|
|
|
|
|
let mut win_pos_within_output = win_pos;
|
|
|
|
|
win_pos_within_output.x -= view_pos;
|
|
|
|
|
return Some((win, win_pos_within_output));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
y += geom.size.h + PADDING;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
x += col.size().w + PADDING;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Workspace<Window> {
|
2023-08-14 14:48:10 +04:00
|
|
|
fn refresh(&self) {
|
|
|
|
|
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);
|
|
|
|
|
win.toplevel().send_pending_configure();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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-13 19:55:37 +04:00
|
|
|
let mut rv = vec![];
|
|
|
|
|
let view_pos = self.view_pos();
|
|
|
|
|
|
|
|
|
|
let mut x = PADDING;
|
|
|
|
|
for col in &self.columns {
|
|
|
|
|
let mut y = PADDING;
|
|
|
|
|
|
|
|
|
|
for win in &col.windows {
|
|
|
|
|
let geom = win.geometry();
|
|
|
|
|
|
|
|
|
|
let win_pos = Point::from((x - view_pos, y)) - geom.loc;
|
|
|
|
|
rv.extend(win.render_elements(
|
|
|
|
|
renderer,
|
|
|
|
|
win_pos.to_physical(1),
|
|
|
|
|
Scale::from(1.),
|
|
|
|
|
1.,
|
|
|
|
|
));
|
|
|
|
|
y += win.geometry().size.h + PADDING;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
x += col.size().w + PADDING;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rv
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-13 19:55:37 +04:00
|
|
|
impl<W: LayoutElement> Column<W> {
|
|
|
|
|
fn new(window: W, view_size: Size<i32, Logical>) -> Self {
|
|
|
|
|
let mut rv = Self {
|
|
|
|
|
windows: vec![],
|
|
|
|
|
active_window_idx: 0,
|
|
|
|
|
width: ColumnWidth::default(),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
rv.add_window(view_size, window);
|
|
|
|
|
|
|
|
|
|
rv
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn window_count(&self) -> usize {
|
|
|
|
|
self.windows.len()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn set_width(&mut self, view_size: Size<i32, Logical>, width: ColumnWidth) {
|
|
|
|
|
self.width = width;
|
|
|
|
|
self.update_window_sizes(view_size);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn add_window(&mut self, view_size: Size<i32, Logical>, window: W) {
|
|
|
|
|
self.windows.push(window);
|
|
|
|
|
self.update_window_sizes(view_size);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn update_window_sizes(&mut self, view_size: Size<i32, Logical>) {
|
|
|
|
|
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);
|
|
|
|
|
let width = self.width.resolve(view_size.w - PADDING) - PADDING;
|
|
|
|
|
let height = (view_size.h - PADDING) / self.window_count() as i32 - PADDING;
|
|
|
|
|
let size = Size::from((max(width, min_width), max(height, 1)));
|
|
|
|
|
|
|
|
|
|
for win in &self.windows {
|
|
|
|
|
win.request_size(size);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-13 12:46:53 +04:00
|
|
|
/// Computes the size of the column including top and bottom padding.
|
|
|
|
|
fn size(&self) -> Size<i32, Logical> {
|
|
|
|
|
let mut total = Size::from((0, PADDING));
|
|
|
|
|
|
|
|
|
|
for window in &self.windows {
|
|
|
|
|
let size = window.geometry().size;
|
|
|
|
|
total.w = max(total.w, size.w);
|
|
|
|
|
total.h += size.h + PADDING;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
total
|
|
|
|
|
}
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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-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)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn configure_new_window(view_size: Size<i32, Logical>, window: &Window) {
|
|
|
|
|
let width = ColumnWidth::default().resolve(view_size.w - PADDING) - PADDING;
|
|
|
|
|
let height = view_size.h - PADDING * 2;
|
|
|
|
|
let size = Size::from((max(width, 1), max(height, 1)));
|
|
|
|
|
|
|
|
|
|
let bounds = Size::from((view_size.w - PADDING * 2, view_size.h - PADDING * 2));
|
|
|
|
|
|
|
|
|
|
window.toplevel().with_pending_state(|state| {
|
|
|
|
|
state.size = Some(size);
|
|
|
|
|
state.bounds = Some(bounds);
|
|
|
|
|
});
|
|
|
|
|
}
|