diff options
| author | Ivan Molodetskikh <yalterz@gmail.com> | 2024-11-23 11:27:27 +0300 |
|---|---|---|
| committer | Ivan Molodetskikh <yalterz@gmail.com> | 2024-11-25 04:07:59 -0800 |
| commit | 93cee2994ab9ccf59a09f61d5b8acf6cd937d654 (patch) | |
| tree | fb00cdb266e9e94891f226558cd93a09e0091d69 /src/layout | |
| parent | 9c7e8d04d27d2f914ad3e9a54c64b64c34aea4d4 (diff) | |
| download | niri-93cee2994ab9ccf59a09f61d5b8acf6cd937d654.tar.gz niri-93cee2994ab9ccf59a09f61d5b8acf6cd937d654.tar.bz2 niri-93cee2994ab9ccf59a09f61d5b8acf6cd937d654.zip | |
Refactor animations to take explicit current time
Diffstat (limited to 'src/layout')
| -rw-r--r-- | src/layout/closing_window.rs | 3 | ||||
| -rw-r--r-- | src/layout/mod.rs | 120 | ||||
| -rw-r--r-- | src/layout/monitor.rs | 20 | ||||
| -rw-r--r-- | src/layout/tile.rs | 25 | ||||
| -rw-r--r-- | src/layout/workspace.rs | 46 |
5 files changed, 175 insertions, 39 deletions
diff --git a/src/layout/closing_window.rs b/src/layout/closing_window.rs index 8bbf0258..744c099e 100644 --- a/src/layout/closing_window.rs +++ b/src/layout/closing_window.rs @@ -142,8 +142,7 @@ impl ClosingWindow { match &mut self.anim_state { AnimationState::Waiting { blocker, anim } => { if blocker.state() != BlockerState::Pending { - let mut anim = anim.restarted(0., 1., 0.); - anim.set_current_time(current_time); + let anim = anim.restarted(current_time, 0., 1., 0.); self.anim_state = AnimationState::Animating(anim); } } diff --git a/src/layout/mod.rs b/src/layout/mod.rs index e5cfea67..f4e59af3 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -52,6 +52,7 @@ use workspace::WorkspaceId; pub use self::monitor::MonitorRenderElement; use self::monitor::{Monitor, WorkspaceSwitch}; use self::workspace::{compute_working_area, Column, ColumnWidth, InsertHint, OutputId, Workspace}; +use crate::animation::Clock; use crate::layout::workspace::InsertPosition; use crate::niri_render_elements; use crate::render_helpers::renderer::NiriRenderer; @@ -216,6 +217,8 @@ pub struct Layout<W: LayoutElement> { last_active_workspace_id: HashMap<String, WorkspaceId>, /// Ongoing interactive move. interactive_move: Option<InteractiveMoveState<W>>, + /// Clock for driving animations. + clock: Clock, /// Configurable properties of the layout. options: Rc<Options>, } @@ -433,27 +436,30 @@ impl Options { } impl<W: LayoutElement> Layout<W> { - pub fn new(config: &Config) -> Self { - Self::with_options_and_workspaces(config, Options::from_config(config)) + pub fn new(clock: Clock, config: &Config) -> Self { + Self::with_options_and_workspaces(clock, config, Options::from_config(config)) } - pub fn with_options(options: Options) -> Self { + pub fn with_options(clock: Clock, options: Options) -> Self { Self { monitor_set: MonitorSet::NoOutputs { workspaces: vec![] }, is_active: true, last_active_workspace_id: HashMap::new(), interactive_move: None, + clock, options: Rc::new(options), } } - fn with_options_and_workspaces(config: &Config, options: Options) -> Self { + fn with_options_and_workspaces(clock: Clock, config: &Config, options: Options) -> Self { let opts = Rc::new(options); let workspaces = config .workspaces .iter() - .map(|ws| Workspace::new_with_config_no_outputs(Some(ws.clone()), opts.clone())) + .map(|ws| { + Workspace::new_with_config_no_outputs(Some(ws.clone()), clock.clone(), opts.clone()) + }) .collect(); Self { @@ -461,6 +467,7 @@ impl<W: LayoutElement> Layout<W> { is_active: true, last_active_workspace_id: HashMap::new(), interactive_move: None, + clock, options: opts, } } @@ -522,13 +529,18 @@ impl<W: LayoutElement> Layout<W> { } // Make sure there's always an empty workspace. - workspaces.push(Workspace::new(output.clone(), self.options.clone())); + workspaces.push(Workspace::new( + output.clone(), + self.clock.clone(), + self.options.clone(), + )); for ws in &mut workspaces { ws.set_output(Some(output.clone())); } - let mut monitor = Monitor::new(output, workspaces, self.options.clone()); + let mut monitor = + Monitor::new(output, workspaces, self.clock.clone(), self.options.clone()); monitor.active_workspace_idx = active_workspace_idx.unwrap_or(0); monitors.push(monitor); @@ -540,7 +552,11 @@ impl<W: LayoutElement> Layout<W> { } MonitorSet::NoOutputs { mut workspaces } => { // We know there are no empty workspaces there, so add one. - workspaces.push(Workspace::new(output.clone(), self.options.clone())); + workspaces.push(Workspace::new( + output.clone(), + self.clock.clone(), + self.options.clone(), + )); let ws_id_to_activate = self.last_active_workspace_id.remove(&output.name()); let mut active_workspace_idx = 0; @@ -553,7 +569,8 @@ impl<W: LayoutElement> Layout<W> { } } - let mut monitor = Monitor::new(output, workspaces, self.options.clone()); + let mut monitor = + Monitor::new(output, workspaces, self.clock.clone(), self.options.clone()); monitor.active_workspace_idx = active_workspace_idx; MonitorSet::Normal { @@ -785,7 +802,10 @@ impl<W: LayoutElement> Layout<W> { let ws = if let Some(ws) = workspaces.get_mut(0) { ws } else { - workspaces.push(Workspace::new_no_outputs(self.options.clone())); + workspaces.push(Workspace::new_no_outputs( + self.clock.clone(), + self.options.clone(), + )); &mut workspaces[0] }; ws.add_window(None, window, true, width, is_full_width); @@ -1992,6 +2012,8 @@ impl<W: LayoutElement> Layout<W> { move_win_id = Some(window_id.clone()); } InteractiveMoveState::Moving(move_) => { + assert_eq!(self.clock, move_.tile.clock); + let scale = move_.output.current_scale().fractional_scale(); let options = Options::clone(&self.options).adjusted_for_scale(scale); assert_eq!( @@ -2026,6 +2048,8 @@ impl<W: LayoutElement> Layout<W> { "with no outputs there cannot be empty unnamed workspaces" ); + assert_eq!(self.clock, workspace.clock); + assert_eq!( workspace.base_options, self.options, "workspace base options must be synchronized with layout" @@ -2070,6 +2094,7 @@ impl<W: LayoutElement> Layout<W> { ); assert!(monitor.active_workspace_idx < monitor.workspaces.len()); + assert_eq!(self.clock, monitor.clock); assert_eq!( monitor.options, self.options, "monitor options must be synchronized with layout" @@ -2135,6 +2160,8 @@ impl<W: LayoutElement> Layout<W> { // exists. for workspace in &monitor.workspaces { + assert_eq!(self.clock, workspace.clock); + assert_eq!( workspace.base_options, self.options, "workspace options must be synchronized with layout" @@ -2326,6 +2353,7 @@ impl<W: LayoutElement> Layout<W> { return; } + let clock = self.clock.clone(); let options = self.options.clone(); match &mut self.monitor_set { @@ -2349,6 +2377,7 @@ impl<W: LayoutElement> Layout<W> { let ws = Workspace::new_with_config( mon.output.clone(), Some(ws_config.clone()), + clock, options, ); mon.workspaces.insert(0, ws); @@ -2357,7 +2386,8 @@ impl<W: LayoutElement> Layout<W> { mon.clean_up_workspaces(); } MonitorSet::NoOutputs { workspaces } => { - let ws = Workspace::new_with_config_no_outputs(Some(ws_config.clone()), options); + let ws = + Workspace::new_with_config_no_outputs(Some(ws_config.clone()), clock, options); workspaces.insert(0, ws); } } @@ -3161,7 +3191,10 @@ impl<W: LayoutElement> Layout<W> { let ws = if let Some(ws) = workspaces.get_mut(0) { ws } else { - workspaces.push(Workspace::new_no_outputs(self.options.clone())); + workspaces.push(Workspace::new_no_outputs( + self.clock.clone(), + self.options.clone(), + )); &mut workspaces[0] }; @@ -3620,7 +3653,7 @@ mod tests { impl<W: LayoutElement> Default for Layout<W> { fn default() -> Self { - Self::with_options(Default::default()) + Self::with_options(Clock::with_override(Duration::ZERO), Default::default()) } } @@ -3854,6 +3887,16 @@ mod tests { prop_oneof![Just(1.), Just(1.5), Just(2.),] } + fn arbitrary_msec_delta() -> impl Strategy<Value = i32> { + prop_oneof![ + 1 => Just(-1000), + 2 => Just(-10), + 1 => Just(0), + 2 => Just(10), + 6 => Just(1000), + ] + } + #[derive(Debug, Clone, Copy, Arbitrary)] enum Op { AddOutput(#[proptest(strategy = "1..=5usize")] usize), @@ -3997,6 +4040,10 @@ mod tests { Refresh { is_active: bool, }, + AdvanceAnimations { + #[proptest(strategy = "arbitrary_msec_delta()")] + msec_delta: i32, + }, MoveWorkspaceToOutput(#[proptest(strategy = "1..=5u8")] u8), ViewOffsetGestureBegin { #[proptest(strategy = "1..=5usize")] @@ -4505,6 +4552,16 @@ mod tests { Op::Refresh { is_active } => { layout.refresh(is_active); } + Op::AdvanceAnimations { msec_delta } => { + let mut now = layout.clock.now(); + if msec_delta >= 0 { + now = now.saturating_add(Duration::from_millis(msec_delta as u64)); + } else { + now = now.saturating_sub(Duration::from_millis(-msec_delta as u64)); + } + layout.clock.set_time_override(Some(now)); + layout.advance_animations(now); + } Op::MoveWorkspaceToOutput(id) => { let name = format!("output{id}"); let Some(output) = layout.outputs().find(|o| o.name() == name).cloned() else { @@ -4617,7 +4674,7 @@ mod tests { #[track_caller] fn check_ops_with_options(options: Options, ops: &[Op]) { - let mut layout = Layout::with_options(options); + let mut layout = Layout::with_options(Clock::with_override(Duration::ZERO), options); for op in ops { op.apply(&mut layout); @@ -5440,7 +5497,7 @@ mod tests { config.layout.border.off = false; config.layout.border.width = FloatOrInt(2.); - let mut layout = Layout::new(&config); + let mut layout = Layout::new(Clock::default(), &config); Op::AddWindow { id: 1, @@ -5460,7 +5517,7 @@ mod tests { let mut config = Config::default(); config.layout.preset_window_heights = vec![PresetSize::Fixed(1), PresetSize::Fixed(2)]; - let mut layout = Layout::new(&config); + let mut layout = Layout::new(Clock::default(), &config); let ops = [ Op::AddOutput(1), @@ -5753,6 +5810,37 @@ mod tests { } #[test] + fn interactive_move_onto_last_workspace() { + let ops = [ + Op::AddOutput(1), + Op::AddWindow { + id: 0, + bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)), + min_max_size: Default::default(), + }, + Op::InteractiveMoveBegin { + window: 0, + output_idx: 1, + px: 0., + py: 0., + }, + Op::InteractiveMoveUpdate { + window: 0, + dx: 1000., + dy: 0., + output_idx: 1, + px: 0., + py: 0., + }, + Op::FocusWorkspaceDown, + Op::AdvanceAnimations { msec_delta: 1000 }, + Op::InteractiveMoveEnd { window: 0 }, + ]; + + check_ops(&ops); + } + + #[test] fn output_active_workspace_is_preserved() { let ops = [ Op::AddOutput(1), diff --git a/src/layout/monitor.rs b/src/layout/monitor.rs index aecad889..aad921bb 100644 --- a/src/layout/monitor.rs +++ b/src/layout/monitor.rs @@ -15,7 +15,7 @@ use super::workspace::{ WorkspaceRenderElement, }; use super::{LayoutElement, Options}; -use crate::animation::Animation; +use crate::animation::{Animation, Clock}; use crate::input::swipe_tracker::SwipeTracker; use crate::render_helpers::renderer::NiriRenderer; use crate::render_helpers::RenderTarget; @@ -45,6 +45,8 @@ pub struct Monitor<W: LayoutElement> { pub(super) previous_workspace_id: Option<WorkspaceId>, /// In-progress switch between workspaces. pub(super) workspace_switch: Option<WorkspaceSwitch>, + /// Clock for driving animations. + pub(super) clock: Clock, /// Configurable properties of the layout. pub(super) options: Rc<Options>, } @@ -94,7 +96,12 @@ impl WorkspaceSwitch { } impl<W: LayoutElement> Monitor<W> { - pub fn new(output: Output, workspaces: Vec<Workspace<W>>, options: Rc<Options>) -> Self { + pub fn new( + output: Output, + workspaces: Vec<Workspace<W>>, + clock: Clock, + options: Rc<Options>, + ) -> Self { Self { output_name: output.name(), output, @@ -102,6 +109,7 @@ impl<W: LayoutElement> Monitor<W> { active_workspace_idx: 0, previous_workspace_id: None, workspace_switch: None, + clock, options, } } @@ -151,7 +159,11 @@ impl<W: LayoutElement> Monitor<W> { } pub fn add_workspace_bottom(&mut self) { - let ws = Workspace::new(self.output.clone(), self.options.clone()); + let ws = Workspace::new( + self.output.clone(), + self.clock.clone(), + self.options.clone(), + ); self.workspaces.push(ws); } @@ -172,6 +184,7 @@ impl<W: LayoutElement> Monitor<W> { self.active_workspace_idx = idx; self.workspace_switch = Some(WorkspaceSwitch::Animation(Animation::new( + self.clock.now(), current_idx, idx as f64, 0., @@ -1099,6 +1112,7 @@ impl<W: LayoutElement> Monitor<W> { self.active_workspace_idx = new_idx; self.workspace_switch = Some(WorkspaceSwitch::Animation(Animation::new( + self.clock.now(), gesture.current_idx, new_idx as f64, velocity, diff --git a/src/layout/tile.rs b/src/layout/tile.rs index ddb9bd8c..dd3d50af 100644 --- a/src/layout/tile.rs +++ b/src/layout/tile.rs @@ -13,7 +13,7 @@ use super::{ LayoutElement, LayoutElementRenderElement, LayoutElementRenderSnapshot, Options, RESIZE_ANIMATION_THRESHOLD, }; -use crate::animation::Animation; +use crate::animation::{Animation, Clock}; use crate::niri_render_elements; use crate::render_helpers::border::BorderRenderElement; use crate::render_helpers::clipped_surface::{ClippedSurfaceRenderElement, RoundedCornerDamage}; @@ -76,6 +76,9 @@ pub struct Tile<W: LayoutElement> { /// Scale of the output the tile is on (and rounds its sizes to). scale: f64, + /// Clock for driving animations. + pub(super) clock: Clock, + /// Configurable properties of the layout. pub(super) options: Rc<Options>, } @@ -110,7 +113,7 @@ struct MoveAnimation { } impl<W: LayoutElement> Tile<W> { - pub fn new(window: W, scale: f64, options: Rc<Options>) -> Self { + pub fn new(window: W, scale: f64, clock: Clock, options: Rc<Options>) -> Self { let rules = window.rules(); let border_config = rules.border.resolve_against(options.border); let focus_ring_config = rules.focus_ring.resolve_against(options.focus_ring.into()); @@ -130,6 +133,7 @@ impl<W: LayoutElement> Tile<W> { unmap_snapshot: None, rounded_corner_damage: Default::default(), scale, + clock, options, } } @@ -180,7 +184,13 @@ impl<W: LayoutElement> Tile<W> { let change = self.window.size().to_f64().to_point() - size_from.to_point(); let change = f64::max(change.x.abs(), change.y.abs()); if change > RESIZE_ANIMATION_THRESHOLD { - let anim = Animation::new(0., 1., 0., self.options.animations.window_resize.anim); + let anim = Animation::new( + self.clock.now(), + 0., + 1., + 0., + self.options.animations.window_resize.anim, + ); self.resize_animation = Some(ResizeAnimation { anim, size_from, @@ -316,6 +326,7 @@ impl<W: LayoutElement> Tile<W> { pub fn start_open_animation(&mut self) { self.open_animation = Some(OpenAnimation::new(Animation::new( + self.clock.now(), 0., 1., 0., @@ -342,8 +353,8 @@ impl<W: LayoutElement> Tile<W> { // Preserve the previous config if ongoing. let anim = self.move_x_animation.take().map(|move_| move_.anim); let anim = anim - .map(|anim| anim.restarted(1., 0., 0.)) - .unwrap_or_else(|| Animation::new(1., 0., 0., config)); + .map(|anim| anim.restarted(self.clock.now(), 1., 0., 0.)) + .unwrap_or_else(|| Animation::new(self.clock.now(), 1., 0., 0., config)); self.move_x_animation = Some(MoveAnimation { anim, @@ -361,8 +372,8 @@ impl<W: LayoutElement> Tile<W> { // Preserve the previous config if ongoing. let anim = self.move_y_animation.take().map(|move_| move_.anim); let anim = anim - .map(|anim| anim.restarted(1., 0., 0.)) - .unwrap_or_else(|| Animation::new(1., 0., 0., config)); + .map(|anim| anim.restarted(self.clock.now(), 1., 0., 0.)) + .unwrap_or_else(|| Animation::new(self.clock.now(), 1., 0., 0., config)); self.move_y_animation = Some(MoveAnimation { anim, diff --git a/src/layout/workspace.rs b/src/layout/workspace.rs index b26dfcc1..6432ca8b 100644 --- a/src/layout/workspace.rs +++ b/src/layout/workspace.rs @@ -19,7 +19,7 @@ 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; +use crate::animation::{Animation, Clock}; use crate::input::swipe_tracker::SwipeTracker; use crate::niri_render_elements; use crate::render_helpers::renderer::NiriRenderer; @@ -113,6 +113,9 @@ pub struct Workspace<W: LayoutElement> { /// Insert hint element for rendering. insert_hint_element: InsertHintElement, + /// Clock for driving animations. + pub(super) clock: Clock, + /// Configurable properties of the layout as received from the parent monitor. pub(super) base_options: Rc<Options>, @@ -293,6 +296,9 @@ pub struct Column<W: LayoutElement> { /// 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>, } @@ -403,13 +409,14 @@ impl TileData { } impl<W: LayoutElement> Workspace<W> { - pub fn new(output: Output, options: Rc<Options>) -> Self { - Self::new_with_config(output, None, options) + pub fn new(output: Output, clock: Clock, options: Rc<Options>) -> Self { + Self::new_with_config(output, None, clock, options) } pub fn new_with_config( output: Output, config: Option<WorkspaceConfig>, + clock: Clock, base_options: Rc<Options>, ) -> Self { let original_output = config @@ -442,6 +449,7 @@ impl<W: LayoutElement> Workspace<W> { closing_windows: vec![], insert_hint: None, insert_hint_element: InsertHintElement::new(options.insert_hint), + clock, base_options, options, name: config.map(|c| c.name.0), @@ -451,6 +459,7 @@ impl<W: LayoutElement> Workspace<W> { pub fn new_with_config_no_outputs( config: Option<WorkspaceConfig>, + clock: Clock, base_options: Rc<Options>, ) -> Self { let original_output = OutputId( @@ -482,6 +491,7 @@ impl<W: LayoutElement> Workspace<W> { closing_windows: vec![], insert_hint: None, insert_hint_element: InsertHintElement::new(options.insert_hint), + clock, base_options, options, name: config.map(|c| c.name.0), @@ -489,8 +499,8 @@ impl<W: LayoutElement> Workspace<W> { } } - pub fn new_no_outputs(options: Rc<Options>) -> Self { - Self::new_with_config_no_outputs(None, options) + pub fn new_no_outputs(clock: Clock, options: Rc<Options>) -> Self { + Self::new_with_config_no_outputs(None, clock, options) } pub fn id(&self) -> WorkspaceId { @@ -941,6 +951,7 @@ impl<W: LayoutElement> Workspace<W> { // FIXME: also compute and use current velocity. self.view_offset_adj = Some(ViewOffsetAdjustment::Animation(Animation::new( + self.clock.now(), self.view_offset, new_view_offset, 0., @@ -1100,7 +1111,12 @@ impl<W: LayoutElement> Workspace<W> { width: ColumnWidth, is_full_width: bool, ) { - let tile = Tile::new(window, self.scale.fractional_scale(), self.options.clone()); + let tile = Tile::new( + window, + self.scale.fractional_scale(), + self.clock.clone(), + self.options.clone(), + ); self.add_tile(col_idx, tile, activate, width, is_full_width, None); } @@ -1118,7 +1134,6 @@ impl<W: LayoutElement> Workspace<W> { self.view_size, self.working_area, self.scale.fractional_scale(), - self.options.clone(), width, is_full_width, true, @@ -1682,7 +1697,13 @@ impl<W: LayoutElement> Workspace<W> { ) { let output_scale = Scale::from(self.scale.fractional_scale()); - let anim = Animation::new(0., 1., 0., self.options.animations.window_close.anim); + let anim = Animation::new( + self.clock.now(), + 0., + 1., + 0., + self.options.animations.window_close.anim, + ); let blocker = if self.options.disable_transactions { TransactionBlocker::completed() @@ -1725,6 +1746,7 @@ impl<W: LayoutElement> Workspace<W> { for (column, data) in zip(&self.columns, &self.data) { assert!(Rc::ptr_eq(&self.options, &column.options)); + assert_eq!(self.clock, column.clock); assert_eq!(self.scale.fractional_scale(), column.scale); column.verify_invariants(); @@ -2619,7 +2641,6 @@ impl<W: LayoutElement> Workspace<W> { self.view_size, self.working_area, self.scale.fractional_scale(), - self.options.clone(), removed.width, removed.is_full_width, false, @@ -2969,6 +2990,7 @@ impl<W: LayoutElement> Workspace<W> { let target_view_offset = target_snap.view_pos - new_col_x; self.view_offset_adj = Some(ViewOffsetAdjustment::Animation(Animation::new( + self.clock.now(), current_view_offset + delta, target_view_offset, velocity, @@ -3174,7 +3196,6 @@ impl<W: LayoutElement> Column<W> { view_size: Size<f64, Logical>, working_area: Rectangle<f64, Logical>, scale: f64, - options: Rc<Options>, width: ColumnWidth, is_full_width: bool, animate_resize: bool, @@ -3190,7 +3211,8 @@ impl<W: LayoutElement> Column<W> { view_size, working_area, scale, - options, + clock: tile.clock.clone(), + options: tile.options.clone(), }; let is_pending_fullscreen = tile.window().is_pending_fullscreen(); @@ -3313,6 +3335,7 @@ impl<W: LayoutElement> Column<W> { let current_offset = self.move_animation.as_ref().map_or(0., Animation::value); self.move_animation = Some(Animation::new( + self.clock.now(), from_x_offset + current_offset, 0., 0., @@ -3716,6 +3739,7 @@ impl<W: LayoutElement> Column<W> { let mut total_min_height = 0.; for (tile, data) in zip(&self.tiles, &self.data) { assert!(Rc::ptr_eq(&self.options, &tile.options)); + assert_eq!(self.clock, tile.clock); assert_eq!(self.scale, tile.scale()); assert_eq!(self.is_fullscreen, tile.window().is_pending_fullscreen()); |
