aboutsummaryrefslogtreecommitdiff
path: root/src/layout
diff options
context:
space:
mode:
authorIvan Molodetskikh <yalterz@gmail.com>2024-02-07 11:32:02 +0400
committerIvan Molodetskikh <yalterz@gmail.com>2024-02-07 13:16:54 +0400
commitf9085db5648bc6bad7fb0abf45e2a11f2e03d1af (patch)
tree56b32abe55f9857649f0296a0e36c30cd20c38e5 /src/layout
parent49ce791d13031ba5396b2ee8dbbffe128d64ff0f (diff)
downloadniri-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.rs44
-rw-r--r--src/layout/tile.rs94
-rw-r--r--src/layout/workspace.rs30
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);
}