mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-22 02:01:55 +07:00
Implement window open animations
This commit is contained in:
@@ -0,0 +1,228 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use niri::layout::workspace::ColumnWidth;
|
||||||
|
use niri::layout::Options;
|
||||||
|
use niri::utils::get_monotonic_time;
|
||||||
|
use niri_config::Color;
|
||||||
|
use smithay::backend::renderer::element::RenderElement;
|
||||||
|
use smithay::backend::renderer::gles::GlesRenderer;
|
||||||
|
use smithay::desktop::layer_map_for_output;
|
||||||
|
use smithay::output::{Mode, Output, PhysicalProperties, Subpixel};
|
||||||
|
use smithay::utils::{Logical, Physical, Size};
|
||||||
|
|
||||||
|
use super::TestCase;
|
||||||
|
use crate::test_window::TestWindow;
|
||||||
|
|
||||||
|
type DynStepFn = Box<dyn FnOnce(&mut Layout)>;
|
||||||
|
|
||||||
|
pub struct Layout {
|
||||||
|
output: Output,
|
||||||
|
windows: Vec<TestWindow>,
|
||||||
|
layout: niri::layout::Layout<TestWindow>,
|
||||||
|
start_time: Duration,
|
||||||
|
steps: HashMap<Duration, DynStepFn>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Layout {
|
||||||
|
pub fn new(size: Size<i32, Logical>) -> Self {
|
||||||
|
let output = Output::new(
|
||||||
|
String::new(),
|
||||||
|
PhysicalProperties {
|
||||||
|
size: Size::from((size.w, size.h)),
|
||||||
|
subpixel: Subpixel::Unknown,
|
||||||
|
make: String::new(),
|
||||||
|
model: String::new(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
let mode = Some(Mode {
|
||||||
|
size: size.to_physical(1),
|
||||||
|
refresh: 60000,
|
||||||
|
});
|
||||||
|
output.change_current_state(mode, None, None, None);
|
||||||
|
|
||||||
|
let options = Options {
|
||||||
|
focus_ring: niri_config::FocusRing {
|
||||||
|
off: true,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
border: niri_config::FocusRing {
|
||||||
|
off: false,
|
||||||
|
width: 4,
|
||||||
|
active_color: Color::new(255, 163, 72, 255),
|
||||||
|
inactive_color: Color::new(50, 50, 50, 255),
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let mut layout = niri::layout::Layout::with_options(options);
|
||||||
|
layout.add_output(output.clone());
|
||||||
|
|
||||||
|
Self {
|
||||||
|
output,
|
||||||
|
windows: Vec::new(),
|
||||||
|
layout,
|
||||||
|
start_time: get_monotonic_time(),
|
||||||
|
steps: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn open_in_between(size: Size<i32, Logical>) -> Self {
|
||||||
|
let mut rv = Self::new(size);
|
||||||
|
|
||||||
|
rv.add_window(TestWindow::freeform(0), Some(ColumnWidth::Proportion(0.3)));
|
||||||
|
rv.add_window(TestWindow::freeform(1), Some(ColumnWidth::Proportion(0.3)));
|
||||||
|
rv.layout.activate_window(&rv.windows[0]);
|
||||||
|
|
||||||
|
rv.add_step(500, |l| {
|
||||||
|
let win = TestWindow::freeform(2);
|
||||||
|
l.add_window(win.clone(), Some(ColumnWidth::Proportion(0.3)));
|
||||||
|
l.layout.start_open_animation_for_window(&win);
|
||||||
|
});
|
||||||
|
|
||||||
|
rv
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn open_multiple_quickly(size: Size<i32, Logical>) -> Self {
|
||||||
|
let mut rv = Self::new(size);
|
||||||
|
|
||||||
|
for delay in [100, 200, 300] {
|
||||||
|
rv.add_step(delay, move |l| {
|
||||||
|
let win = TestWindow::freeform(delay as usize);
|
||||||
|
l.add_window(win.clone(), Some(ColumnWidth::Proportion(0.3)));
|
||||||
|
l.layout.start_open_animation_for_window(&win);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
rv
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn open_multiple_quickly_big(size: Size<i32, Logical>) -> Self {
|
||||||
|
let mut rv = Self::new(size);
|
||||||
|
|
||||||
|
for delay in [100, 200, 300] {
|
||||||
|
rv.add_step(delay, move |l| {
|
||||||
|
let win = TestWindow::freeform(delay as usize);
|
||||||
|
l.add_window(win.clone(), Some(ColumnWidth::Proportion(0.5)));
|
||||||
|
l.layout.start_open_animation_for_window(&win);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
rv
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn open_to_the_left(size: Size<i32, Logical>) -> Self {
|
||||||
|
let mut rv = Self::new(size);
|
||||||
|
|
||||||
|
rv.add_window(TestWindow::freeform(0), Some(ColumnWidth::Proportion(0.3)));
|
||||||
|
rv.add_window(TestWindow::freeform(1), Some(ColumnWidth::Proportion(0.3)));
|
||||||
|
|
||||||
|
rv.add_step(500, |l| {
|
||||||
|
let win = TestWindow::freeform(2);
|
||||||
|
let right_of = l.windows[0].clone();
|
||||||
|
l.add_window_right_of(&right_of, win.clone(), Some(ColumnWidth::Proportion(0.3)));
|
||||||
|
l.layout.start_open_animation_for_window(&win);
|
||||||
|
});
|
||||||
|
|
||||||
|
rv
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn open_to_the_left_big(size: Size<i32, Logical>) -> Self {
|
||||||
|
let mut rv = Self::new(size);
|
||||||
|
|
||||||
|
rv.add_window(TestWindow::freeform(0), Some(ColumnWidth::Proportion(0.3)));
|
||||||
|
rv.add_window(TestWindow::freeform(1), Some(ColumnWidth::Proportion(0.8)));
|
||||||
|
|
||||||
|
rv.add_step(500, |l| {
|
||||||
|
let win = TestWindow::freeform(2);
|
||||||
|
let right_of = l.windows[0].clone();
|
||||||
|
l.add_window_right_of(&right_of, win.clone(), Some(ColumnWidth::Proportion(0.5)));
|
||||||
|
l.layout.start_open_animation_for_window(&win);
|
||||||
|
});
|
||||||
|
|
||||||
|
rv
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_window(&mut self, window: TestWindow, width: Option<ColumnWidth>) {
|
||||||
|
self.layout.add_window(window.clone(), width, false);
|
||||||
|
if window.communicate() {
|
||||||
|
self.layout.update_window(&window);
|
||||||
|
}
|
||||||
|
self.windows.push(window);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_window_right_of(
|
||||||
|
&mut self,
|
||||||
|
right_of: &TestWindow,
|
||||||
|
window: TestWindow,
|
||||||
|
width: Option<ColumnWidth>,
|
||||||
|
) {
|
||||||
|
self.layout
|
||||||
|
.add_window_right_of(right_of, window.clone(), width, false);
|
||||||
|
if window.communicate() {
|
||||||
|
self.layout.update_window(&window);
|
||||||
|
}
|
||||||
|
self.windows.push(window);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_step(&mut self, delay_ms: u64, f: impl FnOnce(&mut Self) + 'static) {
|
||||||
|
self.steps
|
||||||
|
.insert(Duration::from_millis(delay_ms), Box::new(f) as _);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestCase for Layout {
|
||||||
|
fn resize(&mut self, width: i32, height: i32) {
|
||||||
|
let mode = Some(Mode {
|
||||||
|
size: Size::from((width, height)),
|
||||||
|
refresh: 60000,
|
||||||
|
});
|
||||||
|
self.output.change_current_state(mode, None, None, None);
|
||||||
|
layer_map_for_output(&self.output).arrange();
|
||||||
|
self.layout.update_output_size(&self.output);
|
||||||
|
for win in &self.windows {
|
||||||
|
if win.communicate() {
|
||||||
|
self.layout.update_window(win);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn are_animations_ongoing(&self) -> bool {
|
||||||
|
self.layout
|
||||||
|
.monitor_for_output(&self.output)
|
||||||
|
.unwrap()
|
||||||
|
.are_animations_ongoing()
|
||||||
|
|| !self.steps.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn advance_animations(&mut self, mut current_time: Duration) {
|
||||||
|
let run = self
|
||||||
|
.steps
|
||||||
|
.keys()
|
||||||
|
.copied()
|
||||||
|
.filter(|delay| self.start_time + *delay <= current_time)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
for key in &run {
|
||||||
|
let f = self.steps.remove(key).unwrap();
|
||||||
|
f(self);
|
||||||
|
}
|
||||||
|
if !run.is_empty() {
|
||||||
|
current_time = get_monotonic_time();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.layout.advance_animations(current_time);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render(
|
||||||
|
&mut self,
|
||||||
|
renderer: &mut GlesRenderer,
|
||||||
|
_size: Size<i32, Physical>,
|
||||||
|
) -> Vec<Box<dyn RenderElement<GlesRenderer>>> {
|
||||||
|
self.layout
|
||||||
|
.monitor_for_output(&self.output)
|
||||||
|
.unwrap()
|
||||||
|
.render_elements(renderer)
|
||||||
|
.into_iter()
|
||||||
|
.map(|elem| Box::new(elem) as _)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ use smithay::backend::renderer::element::RenderElement;
|
|||||||
use smithay::backend::renderer::gles::GlesRenderer;
|
use smithay::backend::renderer::gles::GlesRenderer;
|
||||||
use smithay::utils::{Physical, Size};
|
use smithay::utils::{Physical, Size};
|
||||||
|
|
||||||
|
pub mod layout;
|
||||||
pub mod tile;
|
pub mod tile;
|
||||||
pub mod window;
|
pub mod window;
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,27 @@ impl Tile {
|
|||||||
rv
|
rv
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn freeform_open(size: Size<i32, Logical>) -> Self {
|
||||||
|
let mut rv = Self::freeform(size);
|
||||||
|
rv.window.set_color([0.1, 0.1, 0.1, 1.]);
|
||||||
|
rv.tile.start_open_animation();
|
||||||
|
rv
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fixed_size_open(size: Size<i32, Logical>) -> Self {
|
||||||
|
let mut rv = Self::fixed_size(size);
|
||||||
|
rv.window.set_color([0.1, 0.1, 0.1, 1.]);
|
||||||
|
rv.tile.start_open_animation();
|
||||||
|
rv
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fixed_size_with_csd_shadow_open(size: Size<i32, Logical>) -> Self {
|
||||||
|
let mut rv = Self::fixed_size_with_csd_shadow(size);
|
||||||
|
rv.window.set_color([0.1, 0.1, 0.1, 1.]);
|
||||||
|
rv.tile.start_open_animation();
|
||||||
|
rv
|
||||||
|
}
|
||||||
|
|
||||||
pub fn with_window(window: TestWindow) -> Self {
|
pub fn with_window(window: TestWindow) -> Self {
|
||||||
let options = Options {
|
let options = Options {
|
||||||
focus_ring: niri_config::FocusRing {
|
focus_ring: niri_config::FocusRing {
|
||||||
@@ -66,6 +87,10 @@ impl TestCase for Tile {
|
|||||||
self.window.communicate();
|
self.window.communicate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn are_animations_ongoing(&self) -> bool {
|
||||||
|
self.tile.are_animations_ongoing()
|
||||||
|
}
|
||||||
|
|
||||||
fn advance_animations(&mut self, current_time: Duration) {
|
fn advance_animations(&mut self, current_time: Duration) {
|
||||||
self.tile.advance_animations(current_time, true);
|
self.tile.advance_animations(current_time, true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ use smithay::utils::{Logical, Size};
|
|||||||
use smithay_view::SmithayView;
|
use smithay_view::SmithayView;
|
||||||
use tracing_subscriber::EnvFilter;
|
use tracing_subscriber::EnvFilter;
|
||||||
|
|
||||||
|
use crate::cases::layout::Layout;
|
||||||
use crate::cases::TestCase;
|
use crate::cases::TestCase;
|
||||||
|
|
||||||
mod cases;
|
mod cases;
|
||||||
@@ -85,6 +86,27 @@ fn build_ui(app: &adw::Application) {
|
|||||||
Tile::fixed_size_with_csd_shadow,
|
Tile::fixed_size_with_csd_shadow,
|
||||||
"Fixed Size Tile - CSD Shadow",
|
"Fixed Size Tile - CSD Shadow",
|
||||||
);
|
);
|
||||||
|
s.add(Tile::freeform_open, "Freeform Tile - Open");
|
||||||
|
s.add(Tile::fixed_size_open, "Fixed Size Tile - Open");
|
||||||
|
s.add(
|
||||||
|
Tile::fixed_size_with_csd_shadow_open,
|
||||||
|
"Fixed Size Tile - CSD Shadow - Open",
|
||||||
|
);
|
||||||
|
|
||||||
|
s.add(Layout::open_in_between, "Layout - Open In-Between");
|
||||||
|
s.add(
|
||||||
|
Layout::open_multiple_quickly,
|
||||||
|
"Layout - Open Multiple Quickly",
|
||||||
|
);
|
||||||
|
s.add(
|
||||||
|
Layout::open_multiple_quickly_big,
|
||||||
|
"Layout - Open Multiple Quickly - Big",
|
||||||
|
);
|
||||||
|
s.add(Layout::open_to_the_left, "Layout - Open To The Left");
|
||||||
|
s.add(
|
||||||
|
Layout::open_to_the_left_big,
|
||||||
|
"Layout - Open To The Left - Big",
|
||||||
|
);
|
||||||
|
|
||||||
let content_headerbar = adw::HeaderBar::new();
|
let content_headerbar = adw::HeaderBar::new();
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use std::rc::Rc;
|
|||||||
use niri::layout::{LayoutElement, LayoutElementRenderElement};
|
use niri::layout::{LayoutElement, LayoutElementRenderElement};
|
||||||
use niri::render_helpers::renderer::NiriRenderer;
|
use niri::render_helpers::renderer::NiriRenderer;
|
||||||
use smithay::backend::renderer::element::solid::{SolidColorBuffer, SolidColorRenderElement};
|
use smithay::backend::renderer::element::solid::{SolidColorBuffer, SolidColorRenderElement};
|
||||||
use smithay::backend::renderer::element::Kind;
|
use smithay::backend::renderer::element::{Id, Kind};
|
||||||
use smithay::output::Output;
|
use smithay::output::Output;
|
||||||
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
|
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
|
||||||
use smithay::utils::{Logical, Point, Scale, Size, Transform};
|
use smithay::utils::{Logical, Point, Scale, Size, Transform};
|
||||||
@@ -196,6 +196,8 @@ impl LayoutElement for TestWindow {
|
|||||||
|
|
||||||
fn output_leave(&self, _output: &Output) {}
|
fn output_leave(&self, _output: &Output) {}
|
||||||
|
|
||||||
|
fn set_offscreen_element_id(&self, _id: Option<Id>) {}
|
||||||
|
|
||||||
fn is_fullscreen(&self) -> bool {
|
fn is_fullscreen(&self) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|||||||
+23
-1
@@ -15,6 +15,13 @@ pub struct Animation {
|
|||||||
duration: Duration,
|
duration: Duration,
|
||||||
start_time: Duration,
|
start_time: Duration,
|
||||||
current_time: Duration,
|
current_time: Duration,
|
||||||
|
curve: Curve,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum Curve {
|
||||||
|
EaseOutCubic,
|
||||||
|
EaseOutExpo,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Animation {
|
impl Animation {
|
||||||
@@ -29,9 +36,15 @@ impl Animation {
|
|||||||
duration: over.mul_f64(ANIMATION_SLOWDOWN.load(Ordering::Relaxed)),
|
duration: over.mul_f64(ANIMATION_SLOWDOWN.load(Ordering::Relaxed)),
|
||||||
start_time: now,
|
start_time: now,
|
||||||
current_time: now,
|
current_time: now,
|
||||||
|
curve: Curve::EaseOutCubic,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn with_curve(mut self, curve: Curve) -> Self {
|
||||||
|
self.curve = curve;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_current_time(&mut self, time: Duration) {
|
pub fn set_current_time(&mut self, time: Duration) {
|
||||||
self.current_time = time;
|
self.current_time = time;
|
||||||
}
|
}
|
||||||
@@ -44,7 +57,7 @@ impl Animation {
|
|||||||
let passed = (self.current_time - self.start_time).as_secs_f64();
|
let passed = (self.current_time - self.start_time).as_secs_f64();
|
||||||
let total = self.duration.as_secs_f64();
|
let total = self.duration.as_secs_f64();
|
||||||
let x = (passed / total).clamp(0., 1.);
|
let x = (passed / total).clamp(0., 1.);
|
||||||
EaseOutCubic.y(x) * (self.to - self.from) + self.from
|
self.curve.y(x) * (self.to - self.from) + self.from
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to(&self) -> f64 {
|
pub fn to(&self) -> f64 {
|
||||||
@@ -56,3 +69,12 @@ impl Animation {
|
|||||||
self.from
|
self.from
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Curve {
|
||||||
|
pub fn y(self, x: f64) -> f64 {
|
||||||
|
match self {
|
||||||
|
Curve::EaseOutCubic => EaseOutCubic.y(x),
|
||||||
|
Curve::EaseOutExpo => 1. - 2f64.powf(-10. * x),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -120,6 +120,7 @@ impl CompositorHandler for State {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if let Some(output) = output.cloned() {
|
if let Some(output) = output.cloned() {
|
||||||
|
self.niri.layout.start_open_animation_for_window(&window);
|
||||||
self.niri.queue_redraw(output);
|
self.niri.queue_redraw(output);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
|||||||
+43
-1
@@ -37,7 +37,7 @@ use std::time::Duration;
|
|||||||
use niri_config::{self, CenterFocusedColumn, Config, SizeChange, Struts};
|
use niri_config::{self, CenterFocusedColumn, Config, SizeChange, Struts};
|
||||||
use smithay::backend::renderer::element::solid::SolidColorRenderElement;
|
use smithay::backend::renderer::element::solid::SolidColorRenderElement;
|
||||||
use smithay::backend::renderer::element::surface::WaylandSurfaceRenderElement;
|
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::space::SpaceElement;
|
||||||
use smithay::desktop::Window;
|
use smithay::desktop::Window;
|
||||||
use smithay::output::Output;
|
use smithay::output::Output;
|
||||||
@@ -52,6 +52,7 @@ pub use self::monitor::MonitorRenderElement;
|
|||||||
use self::monitor::{Monitor, WorkspaceSwitch, WorkspaceSwitchGesture};
|
use self::monitor::{Monitor, WorkspaceSwitch, WorkspaceSwitchGesture};
|
||||||
use self::workspace::{compute_working_area, Column, ColumnWidth, OutputId, Workspace};
|
use self::workspace::{compute_working_area, Column, ColumnWidth, OutputId, Workspace};
|
||||||
use crate::animation::Animation;
|
use crate::animation::Animation;
|
||||||
|
use crate::niri::WindowOffscreenId;
|
||||||
use crate::niri_render_elements;
|
use crate::niri_render_elements;
|
||||||
use crate::render_helpers::renderer::NiriRenderer;
|
use crate::render_helpers::renderer::NiriRenderer;
|
||||||
use crate::utils::output_size;
|
use crate::utils::output_size;
|
||||||
@@ -105,6 +106,7 @@ pub trait LayoutElement: PartialEq {
|
|||||||
fn set_preferred_scale_transform(&self, scale: i32, transform: Transform);
|
fn set_preferred_scale_transform(&self, scale: i32, transform: Transform);
|
||||||
fn output_enter(&self, output: &Output);
|
fn output_enter(&self, output: &Output);
|
||||||
fn output_leave(&self, output: &Output);
|
fn output_leave(&self, output: &Output);
|
||||||
|
fn set_offscreen_element_id(&self, id: Option<Id>);
|
||||||
|
|
||||||
/// Whether the element is currently fullscreen.
|
/// Whether the element is currently fullscreen.
|
||||||
///
|
///
|
||||||
@@ -292,6 +294,11 @@ impl LayoutElement for Window {
|
|||||||
SpaceElement::output_leave(self, output)
|
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 {
|
fn is_fullscreen(&self) -> bool {
|
||||||
self.toplevel()
|
self.toplevel()
|
||||||
.current_state()
|
.current_state()
|
||||||
@@ -1593,6 +1600,39 @@ impl<W: LayoutElement> Layout<W> {
|
|||||||
};
|
};
|
||||||
monitor.move_workspace_up();
|
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> {
|
impl Layout<Window> {
|
||||||
@@ -1757,6 +1797,8 @@ mod tests {
|
|||||||
|
|
||||||
fn output_leave(&self, _output: &Output) {}
|
fn output_leave(&self, _output: &Output) {}
|
||||||
|
|
||||||
|
fn set_offscreen_element_id(&self, _id: Option<Id>) {}
|
||||||
|
|
||||||
fn is_fullscreen(&self) -> bool {
|
fn is_fullscreen(&self) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|||||||
+90
-4
@@ -3,13 +3,17 @@ use std::rc::Rc;
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use smithay::backend::renderer::element::solid::{SolidColorBuffer, SolidColorRenderElement};
|
use smithay::backend::renderer::element::solid::{SolidColorBuffer, SolidColorRenderElement};
|
||||||
use smithay::backend::renderer::element::utils::{Relocate, RelocateRenderElement};
|
use smithay::backend::renderer::element::utils::{
|
||||||
use smithay::backend::renderer::element::Kind;
|
Relocate, RelocateRenderElement, RescaleRenderElement,
|
||||||
|
};
|
||||||
|
use smithay::backend::renderer::element::{Element, Kind};
|
||||||
use smithay::utils::{Logical, Point, Rectangle, Scale, Size};
|
use smithay::utils::{Logical, Point, Rectangle, Scale, Size};
|
||||||
|
|
||||||
use super::focus_ring::FocusRing;
|
use super::focus_ring::FocusRing;
|
||||||
use super::{LayoutElement, LayoutElementRenderElement, Options};
|
use super::{LayoutElement, LayoutElementRenderElement, Options};
|
||||||
|
use crate::animation::{Animation, Curve};
|
||||||
use crate::niri_render_elements;
|
use crate::niri_render_elements;
|
||||||
|
use crate::render_helpers::offscreen::OffscreenRenderElement;
|
||||||
use crate::render_helpers::renderer::NiriRenderer;
|
use crate::render_helpers::renderer::NiriRenderer;
|
||||||
|
|
||||||
/// Toplevel window with decorations.
|
/// Toplevel window with decorations.
|
||||||
@@ -39,6 +43,9 @@ pub struct Tile<W: LayoutElement> {
|
|||||||
/// The size we were requested to fullscreen into.
|
/// The size we were requested to fullscreen into.
|
||||||
fullscreen_size: Size<i32, Logical>,
|
fullscreen_size: Size<i32, Logical>,
|
||||||
|
|
||||||
|
/// The animation upon opening a window.
|
||||||
|
open_animation: Option<Animation>,
|
||||||
|
|
||||||
/// Configurable properties of the layout.
|
/// Configurable properties of the layout.
|
||||||
options: Rc<Options>,
|
options: Rc<Options>,
|
||||||
}
|
}
|
||||||
@@ -47,6 +54,7 @@ niri_render_elements! {
|
|||||||
TileRenderElement => {
|
TileRenderElement => {
|
||||||
LayoutElement = LayoutElementRenderElement<R>,
|
LayoutElement = LayoutElementRenderElement<R>,
|
||||||
SolidColor = RelocateRenderElement<SolidColorRenderElement>,
|
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.
|
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_backdrop: SolidColorBuffer::new((0, 0), [0., 0., 0., 1.]),
|
||||||
fullscreen_size: Default::default(),
|
fullscreen_size: Default::default(),
|
||||||
|
open_animation: None,
|
||||||
options,
|
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();
|
let width = self.border.width();
|
||||||
self.border.update(
|
self.border.update(
|
||||||
(width, width).into(),
|
(width, width).into(),
|
||||||
@@ -88,6 +97,25 @@ impl<W: LayoutElement> Tile<W> {
|
|||||||
self.focus_ring
|
self.focus_ring
|
||||||
.update((0, 0).into(), self.tile_size(), self.has_ssd());
|
.update((0, 0).into(), self.tile_size(), self.has_ssd());
|
||||||
self.focus_ring.set_active(is_active);
|
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 {
|
pub fn window(&self) -> &W {
|
||||||
@@ -160,6 +188,22 @@ impl<W: LayoutElement> Tile<W> {
|
|||||||
self.window.size()
|
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> {
|
pub fn buf_loc(&self) -> Point<i32, Logical> {
|
||||||
let mut loc = Point::from((0, 0));
|
let mut loc = Point::from((0, 0));
|
||||||
loc += self.window_loc();
|
loc += self.window_loc();
|
||||||
@@ -251,7 +295,7 @@ impl<W: LayoutElement> Tile<W> {
|
|||||||
self.effective_border_width().is_some() || self.window.has_ssd()
|
self.effective_border_width().is_some() || self.window.has_ssd()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render<R: NiriRenderer>(
|
fn render_inner<R: NiriRenderer>(
|
||||||
&self,
|
&self,
|
||||||
renderer: &mut R,
|
renderer: &mut R,
|
||||||
location: Point<i32, Logical>,
|
location: Point<i32, Logical>,
|
||||||
@@ -300,4 +344,46 @@ impl<W: LayoutElement> Tile<W> {
|
|||||||
});
|
});
|
||||||
rv.chain(elem)
|
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())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+26
-4
@@ -230,7 +230,7 @@ impl<W: LayoutElement> Workspace<W> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn are_animations_ongoing(&self) -> bool {
|
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>) {
|
pub fn update_config(&mut self, options: Rc<Options>) {
|
||||||
@@ -507,6 +507,16 @@ impl<W: LayoutElement> Workspace<W> {
|
|||||||
x
|
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(
|
pub fn add_window(
|
||||||
&mut self,
|
&mut self,
|
||||||
window: W,
|
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>)> {
|
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.
|
// Start with the active window since it's drawn on top.
|
||||||
let col = &self.columns[self.active_column_idx];
|
let col = &self.columns[self.active_column_idx];
|
||||||
let tile = &col.tiles[col.active_tile_idx];
|
let tile = &col.tiles[col.active_tile_idx];
|
||||||
let tile_pos = Point::from((
|
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),
|
col.tile_y(col.active_tile_idx),
|
||||||
));
|
));
|
||||||
let first = iter::once((tile, tile_pos));
|
let first = iter::once((tile, tile_pos));
|
||||||
@@ -997,7 +1007,7 @@ impl<W: LayoutElement> Workspace<W> {
|
|||||||
// Keep track of column X position.
|
// Keep track of column X position.
|
||||||
.map(move |(col_idx, col)| {
|
.map(move |(col_idx, col)| {
|
||||||
let rv = (col_idx, col, x);
|
let rv = (col_idx, col, x);
|
||||||
x += col.width() + self.options.gaps;
|
x += col.visual_width() + self.options.gaps;
|
||||||
rv
|
rv
|
||||||
})
|
})
|
||||||
.flat_map(move |(col_idx, col, x)| {
|
.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 {
|
pub fn contains(&self, window: &W) -> bool {
|
||||||
self.tiles.iter().map(Tile::window).any(|win| win == window)
|
self.tiles.iter().map(Tile::window).any(|win| win == window)
|
||||||
}
|
}
|
||||||
@@ -1477,6 +1491,14 @@ impl<W: LayoutElement> Column<W> {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn visual_width(&self) -> i32 {
|
||||||
|
self.tiles
|
||||||
|
.iter()
|
||||||
|
.map(|tile| tile.visual_tile_size().w)
|
||||||
|
.max()
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
fn focus_up(&mut self) {
|
fn focus_up(&mut self) {
|
||||||
self.active_tile_idx = self.active_tile_idx.saturating_sub(1);
|
self.active_tile_idx = self.active_tile_idx.saturating_sub(1);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user