diff options
| author | Ivan Molodetskikh <yalterz@gmail.com> | 2024-11-30 09:18:33 +0300 |
|---|---|---|
| committer | Ivan Molodetskikh <yalterz@gmail.com> | 2024-12-01 22:24:21 -0800 |
| commit | 8665003269d1fbe4efe3c477a71400392930cac9 (patch) | |
| tree | c8c7bc727032c51259e4bdbd6e1541c859cc6e0f /src/layout/workspace.rs | |
| parent | 1e76716819ecda33dca0e612d62a8f6c2892890d (diff) | |
| download | niri-8665003269d1fbe4efe3c477a71400392930cac9.tar.gz niri-8665003269d1fbe4efe3c477a71400392930cac9.tar.bz2 niri-8665003269d1fbe4efe3c477a71400392930cac9.zip | |
layout: Extract ScrollingSpace
Leave the Workspace to do the workspace parts, and extract the scrolling parts
into a new file. This is a pre-requisite for things like the floating layer
(which will live in a workspace alongside the scrolling layer).
As part of this huge refactor, I found and fixed at least these issues:
- Wrong horizontal popup unconstraining for a smaller window in an
always-centered column.
- Wrong workspace switch in focus_up_or_right().
Diffstat (limited to 'src/layout/workspace.rs')
| -rw-r--r-- | src/layout/workspace.rs | 3954 |
1 files changed, 324 insertions, 3630 deletions
diff --git a/src/layout/workspace.rs b/src/layout/workspace.rs index 28a26a26..3a4c2628 100644 --- a/src/layout/workspace.rs +++ b/src/layout/workspace.rs @@ -1,13 +1,8 @@ -use std::cmp::{max, min}; -use std::iter::{self, zip}; use std::rc::Rc; use std::time::Duration; -use niri_config::{ - CenterFocusedColumn, CornerRadius, OutputName, PresetSize, Struts, Workspace as WorkspaceConfig, -}; +use niri_config::{OutputName, Workspace as WorkspaceConfig}; use niri_ipc::SizeChange; -use ordered_float::NotNan; use smithay::backend::renderer::gles::GlesRenderer; use smithay::desktop::{layer_map_for_output, Window}; use smithay::output::Output; @@ -15,12 +10,12 @@ use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel; use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface; use smithay::utils::{Logical, Point, Rectangle, Scale, Serial, Size, Transform}; -use super::closing_window::{ClosingWindow, ClosingWindowRenderElement}; -use super::insert_hint_element::{InsertHintElement, InsertHintRenderElement}; -use super::tile::{Tile, TileRenderElement, TileRenderSnapshot}; -use super::{ConfigureIntent, InteractiveResizeData, LayoutElement, Options, RemovedTile}; -use crate::animation::{Animation, Clock}; -use crate::input::swipe_tracker::SwipeTracker; +use super::scrolling::{ + Column, ColumnWidth, InsertHint, InsertPosition, ScrollingSpace, ScrollingSpaceRenderElement, +}; +use super::tile::{Tile, TileRenderSnapshot}; +use super::{InteractiveResizeData, LayoutElement, Options, RemovedTile}; +use crate::animation::Clock; use crate::niri_render_elements; use crate::render_helpers::renderer::NiriRenderer; use crate::render_helpers::RenderTarget; @@ -29,11 +24,11 @@ use crate::utils::transaction::{Transaction, TransactionBlocker}; use crate::utils::{output_size, send_scale_transform, ResizeEdge}; use crate::window::ResolvedWindowRules; -/// Amount of touchpad movement to scroll the view for the width of one working area. -const VIEW_GESTURE_WORKING_AREA_MOVEMENT: f64 = 1200.; - #[derive(Debug)] pub struct Workspace<W: LayoutElement> { + /// The scrollable-tiling layout. + scrolling: ScrollingSpace<W>, + /// The original output of this workspace. /// /// Most of the time this will be the workspace's current output, however, after an output @@ -63,53 +58,12 @@ pub struct Workspace<W: LayoutElement> { /// Latest known working area for this workspace. /// + /// Not rounded to physical pixels. + /// /// This is similar to view size, but takes into account things like layer shell exclusive /// zones. working_area: Rectangle<f64, Logical>, - /// Columns of windows on this workspace. - pub(super) columns: Vec<Column<W>>, - - /// Extra per-column data. - data: Vec<ColumnData>, - - /// Index of the currently active column, if any. - pub(super) active_column_idx: usize, - - /// Ongoing interactive resize. - interactive_resize: Option<InteractiveResize<W>>, - - /// Offset of the view computed from the active column. - /// - /// Any gaps, including left padding from work area left exclusive zone, is handled - /// 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. - view_offset: ViewOffset, - - /// 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. - /// - /// The value is the view offset that the previous column had before, to restore it. - activate_prev_column_on_removal: Option<f64>, - - /// View offset to restore after unfullscreening. - view_offset_before_fullscreen: Option<f64>, - - /// Windows in the closing animation. - closing_windows: Vec<ClosingWindow>, - - /// Indication where an interactively-moved window is about to be placed. - insert_hint: Option<InsertHint>, - - /// Insert hint element for rendering. - insert_hint_element: InsertHintElement, - /// Clock for driving animations. pub(super) clock: Clock, @@ -126,20 +80,6 @@ pub struct Workspace<W: LayoutElement> { id: WorkspaceId, } -#[derive(Debug, PartialEq)] -pub enum InsertPosition { - NewColumn(usize), - InColumn(usize, usize), -} - -#[derive(Debug)] -pub struct InsertHint { - pub position: InsertPosition, - pub width: ColumnWidth, - pub is_full_width: bool, - pub corner_radius: CornerRadius, -} - #[derive(Debug, Clone)] pub struct OutputId(String); @@ -171,82 +111,15 @@ impl WorkspaceId { niri_render_elements! { WorkspaceRenderElement<R> => { - Tile = TileRenderElement<R>, - ClosingWindow = ClosingWindowRenderElement, - InsertHint = InsertHintRenderElement, + Scrolling = ScrollingSpaceRenderElement<R>, } } -/// Extra per-column data. -#[derive(Debug, Clone, Copy, PartialEq)] -struct ColumnData { - /// Cached actual column width. - width: f64, -} - -#[derive(Debug)] -enum ViewOffset { - /// The view offset is static. - Static(f64), - /// The view offset is animating. - Animation(Animation), - /// The view offset is controlled by the ongoing gesture. - Gesture(ViewGesture), -} - #[derive(Debug)] -struct ViewGesture { - current_view_offset: f64, - tracker: SwipeTracker, - delta_from_tracker: f64, - // The view offset we'll use if needed for activate_prev_column_on_removal. - stationary_view_offset: f64, - /// Whether the gesture is controlled by the touchpad. - is_touchpad: bool, -} - -#[derive(Debug)] -struct InteractiveResize<W: LayoutElement> { - window: W::Id, - original_window_size: Size<f64, Logical>, - data: InteractiveResizeData, -} - -/// Width of a column. -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum ColumnWidth { - /// Proportion of the current view width. - Proportion(f64), - /// Fixed width in logical pixels. - Fixed(f64), - /// One of the preset widths. - Preset(usize), -} - -/// Height of a window in a column. -/// -/// Every window but one in a column must be `Auto`-sized so that the total height can add up to -/// the workspace height. Resizing a window converts all other windows to `Auto`, weighted to -/// preserve their visual heights at the moment of the conversion. -/// -/// In contrast to column widths, proportional height changes are converted to, and stored as, -/// fixed height right away. With column widths you frequently want e.g. two columns side-by-side -/// with 50% width each, and you want them to remain this way when moving to a differently sized -/// monitor. Windows in a column, however, already auto-size to fill the available height, giving -/// you this behavior. The main reason to set a different window height, then, is when you want -/// something in the window to fit exactly, e.g. to fit 30 lines in a terminal, which corresponds -/// to the `Fixed` variant. -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum WindowHeight { - /// Automatically computed *tile* height, distributed across the column according to weights. - /// - /// This controls the tile height rather than the window height because it's easier in the auto - /// height distribution algorithm. - Auto { weight: f64 }, - /// Fixed *window* height in logical pixels. - Fixed(f64), - /// One of the preset heights (tile or window). - Preset(usize), +pub(super) struct InteractiveResize<W: LayoutElement> { + pub window: W::Id, + pub original_window_size: Size<f64, Logical>, + pub data: InteractiveResizeData, } /// Resolved width or height in logical pixels. @@ -258,67 +131,6 @@ pub enum ResolvedSize { Window(f64), } -#[derive(Debug)] -pub struct Column<W: LayoutElement> { - /// Tiles in this column. - /// - /// Must be non-empty. - pub(super) tiles: Vec<Tile<W>>, - - /// Extra per-tile data. - /// - /// Must have the same number of elements as `tiles`. - data: Vec<TileData>, - - /// Index of the currently active tile. - pub(super) active_tile_idx: usize, - - /// Desired width of this column. - /// - /// If the column is full-width or full-screened, this is the width that should be restored - /// upon unfullscreening and untoggling full-width. - pub(super) width: ColumnWidth, - - /// Whether this column is full-width. - pub(super) is_full_width: bool, - - /// Whether this column contains a single full-screened window. - pub(super) is_fullscreen: bool, - - /// Animation of the render offset during window swapping. - move_animation: Option<Animation>, - - /// Latest known view size for this column's workspace. - view_size: Size<f64, Logical>, - - /// Latest known working area for this column's workspace. - working_area: Rectangle<f64, Logical>, - - /// Scale of the output the column is on (and rounds its sizes to). - scale: f64, - - /// Clock for driving animations. - clock: Clock, - - /// Configurable properties of the layout. - options: Rc<Options>, -} - -/// Extra per-tile data. -#[derive(Debug, Clone, Copy, PartialEq)] -struct TileData { - /// Requested height of the window. - /// - /// This is window height, not tile height, so it excludes tile decorations. - height: WindowHeight, - - /// Cached actual size of the tile. - size: Size<f64, Logical>, - - /// Cached whether the tile is being interactively resized by its left edge. - interactively_resizing_by_left_edge: bool, -} - impl OutputId { pub fn new(output: &Output) -> Self { let output_name = output.user_data().get::<OutputName>().unwrap(); @@ -326,142 +138,6 @@ impl OutputId { } } -impl ViewOffset { - /// Returns the current view offset. - pub fn current(&self) -> f64 { - match self { - ViewOffset::Static(offset) => *offset, - ViewOffset::Animation(anim) => anim.value(), - ViewOffset::Gesture(gesture) => gesture.current_view_offset, - } - } - - /// Returns the target view offset suitable for computing the new view offset. - pub fn target(&self) -> f64 { - match self { - ViewOffset::Static(offset) => *offset, - ViewOffset::Animation(anim) => anim.to(), - // This can be used for example if a gesture is interrupted. - ViewOffset::Gesture(gesture) => gesture.current_view_offset, - } - } - - /// Returns a view offset value suitable for saving and later restoration. - /// - /// This means that it shouldn't return an in-progress animation or gesture value. - fn stationary(&self) -> f64 { - match self { - ViewOffset::Static(offset) => *offset, - // For animations we can return the final value. - ViewOffset::Animation(anim) => anim.to(), - ViewOffset::Gesture(gesture) => gesture.stationary_view_offset, - } - } - - pub fn is_static(&self) -> bool { - matches!(self, Self::Static(_)) - } - - pub fn is_animation(&self) -> bool { - matches!(self, Self::Animation(_)) - } - - pub fn is_gesture(&self) -> bool { - matches!(self, Self::Gesture(_)) - } - - pub fn offset(&mut self, delta: f64) { - match self { - ViewOffset::Static(offset) => *offset += delta, - ViewOffset::Animation(anim) => anim.offset(delta), - ViewOffset::Gesture(_gesture) => { - // Is this needed? - error!("cancel gesture before offsetting"); - } - } - } - - pub fn cancel_gesture(&mut self) { - if let ViewOffset::Gesture(gesture) = self { - *self = ViewOffset::Static(gesture.current_view_offset); - } - } - - pub fn stop_anim_and_gesture(&mut self) { - *self = ViewOffset::Static(self.current()); - } -} - -impl ColumnData { - pub fn new<W: LayoutElement>(column: &Column<W>) -> Self { - let mut rv = Self { width: 0. }; - rv.update(column); - rv - } - - pub fn update<W: LayoutElement>(&mut self, column: &Column<W>) { - self.width = column.width(); - } -} - -impl ColumnWidth { - fn resolve(self, options: &Options, view_width: f64) -> f64 { - match self { - ColumnWidth::Proportion(proportion) => { - (view_width - options.gaps) * proportion - options.gaps - } - ColumnWidth::Preset(idx) => { - options.preset_column_widths[idx].resolve(options, view_width) - } - ColumnWidth::Fixed(width) => width, - } - } -} - -impl From<PresetSize> for ColumnWidth { - fn from(value: PresetSize) -> Self { - match value { - PresetSize::Proportion(p) => Self::Proportion(p.clamp(0., 10000.)), - PresetSize::Fixed(f) => Self::Fixed(f64::from(f.clamp(1, 100000))), - } - } -} - -fn resolve_preset_size(preset: PresetSize, options: &Options, view_size: f64) -> ResolvedSize { - match preset { - PresetSize::Proportion(proportion) => { - ResolvedSize::Tile((view_size - options.gaps) * proportion - options.gaps) - } - PresetSize::Fixed(width) => ResolvedSize::Window(f64::from(width)), - } -} - -impl WindowHeight { - const fn auto_1() -> Self { - Self::Auto { weight: 1. } - } -} - -impl TileData { - pub fn new<W: LayoutElement>(tile: &Tile<W>, height: WindowHeight) -> Self { - let mut rv = Self { - height, - size: Size::default(), - interactively_resizing_by_left_edge: false, - }; - rv.update(tile); - rv - } - - pub fn update<W: LayoutElement>(&mut self, tile: &Tile<W>) { - self.size = tile.tile_size(); - self.interactively_resizing_by_left_edge = tile - .window() - .interactive_resize_data() - .map_or(false, |data| data.edges.contains(ResizeEdge::LEFT)); - } -} - impl<W: LayoutElement> Workspace<W> { pub fn new(output: Output, clock: Clock, options: Rc<Options>) -> Self { Self::new_with_config(output, None, clock, options) @@ -483,25 +159,25 @@ impl<W: LayoutElement> Workspace<W> { let options = Rc::new(Options::clone(&base_options).adjusted_for_scale(scale.fractional_scale())); - let working_area = compute_working_area(&output, options.struts); + let view_size = output_size(&output); + let working_area = compute_working_area(&output); + + let scrolling = ScrollingSpace::new( + view_size, + working_area, + scale.fractional_scale(), + clock.clone(), + options.clone(), + ); Self { + scrolling, original_output, scale, transform: output.current_transform(), - view_size: output_size(&output), + view_size, working_area, output: Some(output), - columns: vec![], - data: vec![], - active_column_idx: 0, - interactive_resize: None, - view_offset: ViewOffset::Static(0.), - activate_prev_column_on_removal: None, - view_offset_before_fullscreen: None, - closing_windows: vec![], - insert_hint: None, - insert_hint_element: InsertHintElement::new(options.insert_hint), clock, base_options, options, @@ -526,23 +202,25 @@ impl<W: LayoutElement> Workspace<W> { let options = Rc::new(Options::clone(&base_options).adjusted_for_scale(scale.fractional_scale())); + let view_size = Size::from((1280., 720.)); + let working_area = Rectangle::from_loc_and_size((0., 0.), (1280., 720.)); + + let scrolling = ScrollingSpace::new( + view_size, + working_area, + scale.fractional_scale(), + clock.clone(), + options.clone(), + ); + Self { + scrolling, output: None, scale, transform: Transform::Normal, original_output, - view_size: Size::from((1280., 720.)), - working_area: Rectangle::from_loc_and_size((0., 0.), (1280., 720.)), - columns: vec![], - data: vec![], - active_column_idx: 0, - interactive_resize: None, - view_offset: ViewOffset::Static(0.), - activate_prev_column_on_removal: None, - view_offset_before_fullscreen: None, - closing_windows: vec![], - insert_hint: None, - insert_hint_element: InsertHintElement::new(options.insert_hint), + view_size, + working_area, clock, base_options, options, @@ -575,88 +253,39 @@ impl<W: LayoutElement> Workspace<W> { self.scale } - pub fn is_centering_focused_column(&self) -> bool { - self.options.center_focused_column == CenterFocusedColumn::Always - || (self.options.always_center_single_column && self.columns.len() <= 1) - } - pub fn advance_animations(&mut self) { - if let ViewOffset::Animation(anim) = &self.view_offset { - if anim.is_done() { - self.view_offset = ViewOffset::Static(anim.to()); - } - } - - for col in &mut self.columns { - col.advance_animations(); - } - - self.closing_windows.retain_mut(|closing| { - closing.advance_animations(); - closing.are_animations_ongoing() - }); + self.scrolling.advance_animations(); } pub fn are_animations_ongoing(&self) -> bool { - self.view_offset.is_animation() - || self.columns.iter().any(Column::are_animations_ongoing) - || !self.closing_windows.is_empty() + self.scrolling.are_animations_ongoing() } pub fn are_transitions_ongoing(&self) -> bool { - !self.view_offset.is_static() - || self.columns.iter().any(Column::are_animations_ongoing) - || !self.closing_windows.is_empty() + self.scrolling.are_transitions_ongoing() } pub fn update_render_elements(&mut self, is_active: bool) { - let view_pos = Point::from((self.view_pos(), 0.)); - let view_size = self.view_size(); - let active_idx = self.active_column_idx; - for (col_idx, (col, col_x)) in self.columns_mut().enumerate() { - let is_active = is_active && col_idx == active_idx; - let col_off = Point::from((col_x, 0.)); - let col_pos = view_pos - col_off - col.render_offset(); - let view_rect = Rectangle::from_loc_and_size(col_pos, view_size); - col.update_render_elements(is_active, view_rect); - } - - if let Some(insert_hint) = &self.insert_hint { - if let Some(area) = self.insert_hint_area(insert_hint) { - let view_rect = Rectangle::from_loc_and_size(area.loc.upscale(-1.), view_size); - self.insert_hint_element.update_render_elements( - area.size, - view_rect, - insert_hint.corner_radius, - self.scale.fractional_scale(), - ); - } - } + self.scrolling.update_render_elements(is_active); } pub fn update_config(&mut self, base_options: Rc<Options>) { let scale = self.scale.fractional_scale(); let options = Rc::new(Options::clone(&base_options).adjusted_for_scale(scale)); - for (column, data) in zip(&mut self.columns, &mut self.data) { - column.update_config(scale, options.clone()); - data.update(column); - } - - self.insert_hint_element.update_config(options.insert_hint); + self.scrolling.update_config( + self.view_size, + self.working_area, + self.scale.fractional_scale(), + options.clone(), + ); self.base_options = base_options; self.options = options; } pub fn update_shaders(&mut self) { - for col in &mut self.columns { - for tile in &mut col.tiles { - tile.update_shaders(); - } - } - - self.insert_hint_element.update_shaders(); + self.scrolling.update_shaders(); } pub fn windows(&self) -> impl Iterator<Item = &W> + '_ { @@ -668,11 +297,11 @@ impl<W: LayoutElement> Workspace<W> { } pub fn tiles(&self) -> impl Iterator<Item = &Tile<W>> + '_ { - self.columns.iter().flat_map(|col| col.tiles.iter()) + self.scrolling.tiles() } pub fn tiles_mut(&mut self) -> impl Iterator<Item = &mut Tile<W>> + '_ { - self.columns.iter_mut().flat_map(|col| col.tiles.iter_mut()) + self.scrolling.tiles_mut() } pub fn current_output(&self) -> Option<&Output> { @@ -680,12 +309,11 @@ impl<W: LayoutElement> Workspace<W> { } pub fn active_window(&self) -> Option<&W> { - if self.columns.is_empty() { - return None; - } + self.scrolling.active_window() + } - let col = &self.columns[self.active_column_idx]; - Some(col.tiles[col.active_tile_idx].window()) + pub fn is_active_fullscreen(&self) -> bool { + self.scrolling.is_active_fullscreen() } pub fn set_output(&mut self, output: Option<Output>) { @@ -707,10 +335,7 @@ impl<W: LayoutElement> Workspace<W> { self.original_output = OutputId::new(output); } - let scale = output.current_scale(); - let transform = output.current_transform(); - let working_area = compute_working_area(output, self.options.struts); - self.set_view_size(scale, transform, output_size(output), working_area); + self.update_output_size(); for win in self.windows() { self.enter_output_for_window(win); @@ -725,7 +350,16 @@ impl<W: LayoutElement> Workspace<W> { } } - pub fn set_view_size( + pub fn update_output_size(&mut self) { + let output = self.output.as_ref().unwrap(); + let scale = output.current_scale(); + let transform = output.current_transform(); + let view_size = output_size(output); + let working_area = compute_working_area(output); + self.set_view_size(scale, transform, view_size, working_area); + } + + fn set_view_size( &mut self, scale: smithay::output::Scale, transform: Transform, @@ -749,10 +383,14 @@ impl<W: LayoutElement> Workspace<W> { if fractional_scale_changed { // Options need to be recomputed for the new scale. self.update_config(self.base_options.clone()); - } - - for col in &mut self.columns { - col.set_view_size(self.view_size, self.working_area); + } else { + // Pass our existing options as is. + self.scrolling.update_config( + size, + working_area, + scale.fractional_scale(), + self.options.clone(), + ); } if scale_transform_changed { @@ -766,384 +404,8 @@ impl<W: LayoutElement> Workspace<W> { self.view_size } - fn toplevel_bounds(&self, rules: &ResolvedWindowRules) -> Size<i32, Logical> { - let border_config = rules.border.resolve_against(self.options.border); - compute_toplevel_bounds(border_config, self.working_area.size, self.options.gaps) - } - - pub fn resolve_default_width( - &self, - default_width: Option<Option<ColumnWidth>>, - ) -> Option<ColumnWidth> { - match default_width { - Some(Some(width)) => Some(width), - Some(None) => None, - None => self.options.default_column_width, - } - } - - pub fn new_window_size( - &self, - width: Option<ColumnWidth>, - rules: &ResolvedWindowRules, - ) -> Size<i32, Logical> { - let border = rules.border.resolve_against(self.options.border); - - let width = if let Some(width) = width { - let is_fixed = matches!(width, ColumnWidth::Fixed(_)); - - let mut width = width.resolve(&self.options, self.working_area.size.w); - - if !is_fixed && !border.off { - width -= border.width.0 * 2.; - } - - max(1, width.floor() as i32) - } else { - 0 - }; - - let mut height = self.working_area.size.h - self.options.gaps * 2.; - if !border.off { - height -= border.width.0 * 2.; - } - - Size::from((width, max(height.floor() as i32, 1))) - } - - pub fn configure_new_window( - &self, - window: &Window, - width: Option<ColumnWidth>, - rules: &ResolvedWindowRules, - ) { - window.with_surfaces(|surface, data| { - send_scale_transform(surface, data, self.scale, self.transform); - }); - - window - .toplevel() - .expect("no x11 support") - .with_pending_state(|state| { - if state.states.contains(xdg_toplevel::State::Fullscreen) { - state.size = Some(self.view_size.to_i32_round()); - } else { - state.size = Some(self.new_window_size(width, rules)); - } - - state.bounds = Some(self.toplevel_bounds(rules)); - }); - } - - fn compute_new_view_offset_fit( - &self, - target_x: Option<f64>, - col_x: f64, - width: f64, - is_fullscreen: bool, - ) -> f64 { - if is_fullscreen { - return 0.; - } - - let target_x = target_x.unwrap_or_else(|| self.target_view_pos()); - - let new_offset = compute_new_view_offset( - target_x + self.working_area.loc.x, - self.working_area.size.w, - col_x, - width, - self.options.gaps, - ); - - // Non-fullscreen windows are always offset at least by the working area position. - new_offset - self.working_area.loc.x - } - - fn compute_new_view_offset_centered( - &self, - target_x: Option<f64>, - col_x: f64, - width: f64, - is_fullscreen: bool, - ) -> f64 { - if is_fullscreen { - return self.compute_new_view_offset_fit(target_x, col_x, width, is_fullscreen); - } - - // Columns wider than the view are left-aligned (the fit code can deal with that). - if self.working_area.size.w <= width { - return self.compute_new_view_offset_fit(target_x, col_x, width, is_fullscreen); - } - - -(self.working_area.size.w - width) / 2. - self.working_area.loc.x - } - - fn compute_new_view_offset_for_column_fit(&self, target_x: Option<f64>, idx: usize) -> f64 { - let col = &self.columns[idx]; - self.compute_new_view_offset_fit( - target_x, - self.column_x(idx), - col.width(), - col.is_fullscreen, - ) - } - - fn compute_new_view_offset_for_column_centered( - &self, - target_x: Option<f64>, - idx: usize, - ) -> f64 { - let col = &self.columns[idx]; - self.compute_new_view_offset_centered( - target_x, - self.column_x(idx), - col.width(), - col.is_fullscreen, - ) - } - - fn compute_new_view_offset_for_column( - &self, - target_x: Option<f64>, - idx: usize, - prev_idx: Option<usize>, - ) -> f64 { - if self.is_centering_focused_column() { - return self.compute_new_view_offset_for_column_centered(target_x, idx); - } - - match self.options.center_focused_column { - CenterFocusedColumn::Always => { - self.compute_new_view_offset_for_column_centered(target_x, idx) - } - CenterFocusedColumn::OnOverflow => { - let Some(prev_idx) = prev_idx else { - return self.compute_new_view_offset_for_column_fit(target_x, idx); - }; - - // Always take the left or right neighbor of the target as the source. - let source_idx = if prev_idx > idx { - min(idx + 1, self.columns.len() - 1) - } else { - idx.saturating_sub(1) - }; - - let source_col_x = self.column_x(source_idx); - let source_col_width = self.columns[source_idx].width(); - - let target_col_x = self.column_x(idx); - let target_col_width = self.columns[idx].width(); - - let total_width = if source_col_x < target_col_x { - // Source is left from target. - target_col_x - source_col_x + target_col_width - } else { - // Source is right from target. - source_col_x - target_col_x + source_col_width - } + self.options.gaps * 2.; - - // If it fits together, do a normal animation, otherwise center the new column. - if total_width <= self.working_area.size.w { - self.compute_new_view_offset_for_column_fit(target_x, idx) - } else { - self.compute_new_view_offset_for_column_centered(target_x, idx) - } - } - CenterFocusedColumn::Never => { - self.compute_new_view_offset_for_column_fit(target_x, idx) - } - } - } - - fn animate_view_offset(&mut self, idx: usize, new_view_offset: f64) { - self.animate_view_offset_with_config( - idx, - new_view_offset, - self.options.animations.horizontal_view_movement.0, - ); - } - - fn animate_view_offset_with_config( - &mut self, - idx: usize, - new_view_offset: f64, - config: niri_config::Animation, - ) { - self.view_offset.cancel_gesture(); - - let new_col_x = self.column_x(idx); - let old_col_x = self.column_x(self.active_column_idx); - let offset_delta = old_col_x - new_col_x; - self.view_offset.offset(offset_delta); - - let pixel = 1. / self.scale.fractional_scale(); - - // If our view offset is already this or animating towards this, we don't need to do - // anything. - let to_diff = new_view_offset - self.view_offset.target(); - if to_diff.abs() < pixel { - // Correct for any inaccuracy. - self.view_offset.offset(to_diff); - return; - } - - // FIXME: also compute and use current velocity. - self.view_offset = ViewOffset::Animation(Animation::new( - self.clock.clone(), - self.view_offset.current(), - new_view_offset, - 0., - config, - )); - } - - fn animate_view_offset_to_column_centered( - &mut self, - target_x: Option<f64>, - idx: usize, - config: niri_config::Animation, - ) { - let new_view_offset = self.compute_new_view_offset_for_column_centered(target_x, idx); - self.animate_view_offset_with_config(idx, new_view_offset, config); - } - - fn animate_view_offset_to_column_with_config( - &mut self, - target_x: Option<f64>, - idx: usize, - prev_idx: Option<usize>, - config: niri_config::Animation, - ) { - let new_view_offset = self.compute_new_view_offset_for_column(target_x, idx, prev_idx); - self.animate_view_offset_with_config(idx, new_view_offset, config); - } - - fn animate_view_offset_to_column( - &mut self, - target_x: Option<f64>, - idx: usize, - prev_idx: Option<usize>, - ) { - self.animate_view_offset_to_column_with_config( - target_x, - idx, - prev_idx, - self.options.animations.horizontal_view_movement.0, - ) - } - - fn activate_column(&mut self, idx: usize) { - self.activate_column_with_anim_config( - idx, - self.options.animations.horizontal_view_movement.0, - ); - } - - fn activate_column_with_anim_config(&mut self, idx: usize, config: niri_config::Animation) { - if self.active_column_idx == idx { - return; - } - - self.animate_view_offset_to_column_with_config( - None, - idx, - Some(self.active_column_idx), - config, - ); - - self.active_column_idx = idx; - - // A different column was activated; reset the flag. - self.activate_prev_column_on_removal = None; - self.view_offset_before_fullscreen = None; - self.interactive_resize = None; - } - - pub fn has_windows(&self) -> bool { - self.windows().next().is_some() - } - - pub fn has_window(&self, window: &W::Id) -> bool { - self.windows().any(|win| win.id() == window) - } - - pub fn find_wl_surface(&self, wl_surface: &WlSurface) -> Option<&W> { - self.windows().find(|win| win.is_wl_surface(wl_surface)) - } - - pub fn find_wl_surface_mut(&mut self, wl_surface: &WlSurface) -> Option<&mut W> { - self.windows_mut().find(|win| win.is_wl_surface(wl_surface)) - } - - pub fn set_insert_hint(&mut self, insert_hint: InsertHint) { - if self.options.insert_hint.off { - return; - } - self.insert_hint = Some(insert_hint); - } - - pub fn clear_insert_hint(&mut self) { - self.insert_hint = None; - } - - pub fn get_in |
