aboutsummaryrefslogtreecommitdiff
path: root/src/layout/workspace.rs
diff options
context:
space:
mode:
authorIvan Molodetskikh <yalterz@gmail.com>2024-11-30 09:18:33 +0300
committerIvan Molodetskikh <yalterz@gmail.com>2024-12-01 22:24:21 -0800
commit8665003269d1fbe4efe3c477a71400392930cac9 (patch)
treec8c7bc727032c51259e4bdbd6e1541c859cc6e0f /src/layout/workspace.rs
parent1e76716819ecda33dca0e612d62a8f6c2892890d (diff)
downloadniri-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.rs3954
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