diff options
| author | Ivan Molodetskikh <yalterz@gmail.com> | 2024-02-07 11:32:02 +0400 |
|---|---|---|
| committer | Ivan Molodetskikh <yalterz@gmail.com> | 2024-02-07 13:16:54 +0400 |
| commit | f9085db5648bc6bad7fb0abf45e2a11f2e03d1af (patch) | |
| tree | 56b32abe55f9857649f0296a0e36c30cd20c38e5 /src/layout | |
| parent | 49ce791d13031ba5396b2ee8dbbffe128d64ff0f (diff) | |
| download | niri-f9085db5648bc6bad7fb0abf45e2a11f2e03d1af.tar.gz niri-f9085db5648bc6bad7fb0abf45e2a11f2e03d1af.tar.bz2 niri-f9085db5648bc6bad7fb0abf45e2a11f2e03d1af.zip | |
Implement window open animations
Diffstat (limited to 'src/layout')
| -rw-r--r-- | src/layout/mod.rs | 44 | ||||
| -rw-r--r-- | src/layout/tile.rs | 94 | ||||
| -rw-r--r-- | src/layout/workspace.rs | 30 |
3 files changed, 159 insertions, 9 deletions
diff --git a/src/layout/mod.rs b/src/layout/mod.rs index b9e298cf..36f60d12 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -37,7 +37,7 @@ use std::time::Duration; use niri_config::{self, CenterFocusedColumn, Config, SizeChange, Struts}; use smithay::backend::renderer::element::solid::SolidColorRenderElement; use smithay::backend::renderer::element::surface::WaylandSurfaceRenderElement; -use smithay::backend::renderer::element::AsRenderElements; +use smithay::backend::renderer::element::{AsRenderElements, Id}; use smithay::desktop::space::SpaceElement; use smithay::desktop::Window; use smithay::output::Output; @@ -52,6 +52,7 @@ pub use self::monitor::MonitorRenderElement; use self::monitor::{Monitor, WorkspaceSwitch, WorkspaceSwitchGesture}; use self::workspace::{compute_working_area, Column, ColumnWidth, OutputId, Workspace}; use crate::animation::Animation; +use crate::niri::WindowOffscreenId; use crate::niri_render_elements; use crate::render_helpers::renderer::NiriRenderer; use crate::utils::output_size; @@ -105,6 +106,7 @@ pub trait LayoutElement: PartialEq { fn set_preferred_scale_transform(&self, scale: i32, transform: Transform); fn output_enter(&self, output: &Output); fn output_leave(&self, output: &Output); + fn set_offscreen_element_id(&self, id: Option<Id>); /// Whether the element is currently fullscreen. /// @@ -292,6 +294,11 @@ impl LayoutElement for Window { SpaceElement::output_leave(self, output) } + fn set_offscreen_element_id(&self, id: Option<Id>) { + let data = self.user_data().get_or_insert(WindowOffscreenId::default); + data.0.replace(id); + } + fn is_fullscreen(&self) -> bool { self.toplevel() .current_state() @@ -1593,6 +1600,39 @@ impl<W: LayoutElement> Layout<W> { }; monitor.move_workspace_up(); } + + pub fn start_open_animation_for_window(&mut self, window: &W) { + match &mut self.monitor_set { + MonitorSet::Normal { monitors, .. } => { + for mon in monitors { + for ws in &mut mon.workspaces { + for col in &mut ws.columns { + for tile in &mut col.tiles { + if tile.window() == window { + tile.start_open_animation(); + return; + } + } + } + } + } + } + MonitorSet::NoOutputs { workspaces, .. } => { + for ws in workspaces { + if ws.has_window(window) { + for col in &mut ws.columns { + for tile in &mut col.tiles { + if tile.window() == window { + tile.start_open_animation(); + return; + } + } + } + } + } + } + } + } } impl Layout<Window> { @@ -1757,6 +1797,8 @@ mod tests { fn output_leave(&self, _output: &Output) {} + fn set_offscreen_element_id(&self, _id: Option<Id>) {} + fn is_fullscreen(&self) -> bool { false } diff --git a/src/layout/tile.rs b/src/layout/tile.rs index 80797130..dfda6fbb 100644 --- a/src/layout/tile.rs +++ b/src/layout/tile.rs @@ -3,13 +3,17 @@ use std::rc::Rc; use std::time::Duration; use smithay::backend::renderer::element::solid::{SolidColorBuffer, SolidColorRenderElement}; -use smithay::backend::renderer::element::utils::{Relocate, RelocateRenderElement}; -use smithay::backend::renderer::element::Kind; +use smithay::backend::renderer::element::utils::{ + Relocate, RelocateRenderElement, RescaleRenderElement, +}; +use smithay::backend::renderer::element::{Element, Kind}; use smithay::utils::{Logical, Point, Rectangle, Scale, Size}; use super::focus_ring::FocusRing; use super::{LayoutElement, LayoutElementRenderElement, Options}; +use crate::animation::{Animation, Curve}; use crate::niri_render_elements; +use crate::render_helpers::offscreen::OffscreenRenderElement; use crate::render_helpers::renderer::NiriRenderer; /// Toplevel window with decorations. @@ -39,6 +43,9 @@ pub struct Tile<W: LayoutElement> { /// The size we were requested to fullscreen into. fullscreen_size: Size<i32, Logical>, + /// The animation upon opening a window. + open_animation: Option<Animation>, + /// Configurable properties of the layout. options: Rc<Options>, } @@ -47,6 +54,7 @@ niri_render_elements! { TileRenderElement => { LayoutElement = LayoutElementRenderElement<R>, SolidColor = RelocateRenderElement<SolidColorRenderElement>, + Offscreen = RescaleRenderElement<OffscreenRenderElement>, } } @@ -59,6 +67,7 @@ impl<W: LayoutElement> Tile<W> { is_fullscreen: false, // FIXME: up-to-date fullscreen right away, but we need size. fullscreen_backdrop: SolidColorBuffer::new((0, 0), [0., 0., 0., 1.]), fullscreen_size: Default::default(), + open_animation: None, options, } } @@ -76,7 +85,7 @@ impl<W: LayoutElement> Tile<W> { } } - pub fn advance_animations(&mut self, _current_time: Duration, is_active: bool) { + pub fn advance_animations(&mut self, current_time: Duration, is_active: bool) { let width = self.border.width(); self.border.update( (width, width).into(), @@ -88,6 +97,25 @@ impl<W: LayoutElement> Tile<W> { self.focus_ring .update((0, 0).into(), self.tile_size(), self.has_ssd()); self.focus_ring.set_active(is_active); + + match &mut self.open_animation { + Some(anim) => { + anim.set_current_time(current_time); + if anim.is_done() { + self.open_animation = None; + } + } + None => (), + } + } + + pub fn are_animations_ongoing(&self) -> bool { + self.open_animation.is_some() + } + + pub fn start_open_animation(&mut self) { + self.open_animation = + Some(Animation::new(0., 1., Duration::from_millis(150)).with_curve(Curve::EaseOutExpo)); } pub fn window(&self) -> &W { @@ -160,6 +188,22 @@ impl<W: LayoutElement> Tile<W> { self.window.size() } + /// Returns an animated size of the tile for rendering and input. + /// + /// During the window opening animation, windows to the right should gradually slide further to + /// the right. This is what this visual size is used for. Other things like window resizes or + /// transactions or new view position calculation always use the real size, instead of this + /// visual size. + pub fn visual_tile_size(&self) -> Size<i32, Logical> { + let size = self.tile_size(); + let v = self + .open_animation + .as_ref() + .map(|anim| anim.value()) + .unwrap_or(1.); + Size::from(((f64::from(size.w) * v).round() as i32, size.h)) + } + pub fn buf_loc(&self) -> Point<i32, Logical> { let mut loc = Point::from((0, 0)); loc += self.window_loc(); @@ -251,7 +295,7 @@ impl<W: LayoutElement> Tile<W> { self.effective_border_width().is_some() || self.window.has_ssd() } - pub fn render<R: NiriRenderer>( + fn render_inner<R: NiriRenderer>( &self, renderer: &mut R, location: Point<i32, Logical>, @@ -300,4 +344,46 @@ impl<W: LayoutElement> Tile<W> { }); rv.chain(elem) } + + pub fn render<R: NiriRenderer>( + &self, + renderer: &mut R, + location: Point<i32, Logical>, + scale: Scale<f64>, + focus_ring: bool, + ) -> impl Iterator<Item = TileRenderElement<R>> { + if let Some(anim) = &self.open_animation { + let renderer = renderer.as_gles_renderer(); + let elements = self.render_inner(renderer, location, scale, focus_ring); + let elements = elements.collect::<Vec<TileRenderElement<_>>>(); + + let elem = OffscreenRenderElement::new( + renderer, + scale.x as i32, + &elements, + anim.value() as f32, + ); + self.window() + .set_offscreen_element_id(Some(elem.id().clone())); + + let mut center = location; + center.x += self.tile_size().w / 2; + center.y += self.tile_size().h / 2; + + Some(TileRenderElement::Offscreen( + RescaleRenderElement::from_element( + elem, + center.to_physical_precise_round(scale), + (anim.value() / 2. + 0.5).min(1.), + ), + )) + .into_iter() + .chain(None.into_iter().flatten()) + } else { + self.window().set_offscreen_element_id(None); + + let elements = self.render_inner(renderer, location, scale, focus_ring); + None.into_iter().chain(Some(elements).into_iter().flatten()) + } + } } diff --git a/src/layout/workspace.rs b/src/layout/workspace.rs index 07a94321..24372074 100644 --- a/src/layout/workspace.rs +++ b/src/layout/workspace.rs @@ -230,7 +230,7 @@ impl<W: LayoutElement> Workspace<W> { } pub fn are_animations_ongoing(&self) -> bool { - self.view_offset_anim.is_some() + self.view_offset_anim.is_some() || self.columns.iter().any(Column::are_animations_ongoing) } pub fn update_config(&mut self, options: Rc<Options>) { @@ -507,6 +507,16 @@ impl<W: LayoutElement> Workspace<W> { x } + fn visual_column_x(&self, column_idx: usize) -> i32 { + let mut x = 0; + + for column in self.columns.iter().take(column_idx) { + x += column.visual_width() + self.options.gaps; + } + + x + } + pub fn add_window( &mut self, window: W, @@ -978,13 +988,13 @@ impl<W: LayoutElement> Workspace<W> { } fn tiles_in_render_order(&self) -> impl Iterator<Item = (&'_ Tile<W>, Point<i32, Logical>)> { - let view_pos = self.view_pos(); + let view_pos = self.visual_column_x(self.active_column_idx) + self.view_offset; // Start with the active window since it's drawn on top. let col = &self.columns[self.active_column_idx]; let tile = &col.tiles[col.active_tile_idx]; let tile_pos = Point::from(( - self.column_x(self.active_column_idx) - view_pos, + self.visual_column_x(self.active_column_idx) - view_pos, col.tile_y(col.active_tile_idx), )); let first = iter::once((tile, tile_pos)); @@ -997,7 +1007,7 @@ impl<W: LayoutElement> Workspace<W> { // Keep track of column X position. .map(move |(col_idx, col)| { let rv = (col_idx, col, x); - x += col.width() + self.options.gaps; + x += col.visual_width() + self.options.gaps; rv }) .flat_map(move |(col_idx, col, x)| { @@ -1279,6 +1289,10 @@ impl<W: LayoutElement> Column<W> { } } + pub fn are_animations_ongoing(&self) -> bool { + self.tiles.iter().any(Tile::are_animations_ongoing) + } + pub fn contains(&self, window: &W) -> bool { self.tiles.iter().map(Tile::window).any(|win| win == window) } @@ -1477,6 +1491,14 @@ impl<W: LayoutElement> Column<W> { .unwrap() } + fn visual_width(&self) -> i32 { + self.tiles + .iter() + .map(|tile| tile.visual_tile_size().w) + .max() + .unwrap() + } + fn focus_up(&mut self) { self.active_tile_idx = self.active_tile_idx.saturating_sub(1); } |
