From 6424a2738db6349de62dab150d5d6f1d431ca6c4 Mon Sep 17 00:00:00 2001 From: Ivan Molodetskikh Date: Wed, 7 Feb 2024 17:05:15 +0400 Subject: Make all animations configurable --- niri-config/src/lib.rs | 129 +++++++++++++++++++++++++++++++++------ resources/default-config.kdl | 49 ++++++++++++++- src/animation.rs | 33 +++++++--- src/config_error_notification.rs | 29 ++++++--- src/layout/mod.rs | 6 +- src/layout/monitor.rs | 3 +- src/layout/tile.rs | 9 ++- src/layout/workspace.rs | 3 +- src/main.rs | 8 ++- src/niri.rs | 10 ++- 10 files changed, 231 insertions(+), 48 deletions(-) diff --git a/niri-config/src/lib.rs b/niri-config/src/lib.rs index bd8b6438..333a018a 100644 --- a/niri-config/src/lib.rs +++ b/niri-config/src/lib.rs @@ -36,6 +36,8 @@ pub struct Config { #[knuffel(child, default)] pub hotkey_overlay: HotkeyOverlay, #[knuffel(child, default)] + pub animations: Animations, + #[knuffel(child, default)] pub binds: Binds, #[knuffel(child, default)] pub debug: DebugConfig, @@ -399,6 +401,89 @@ pub struct HotkeyOverlay { pub skip_at_startup: bool, } +#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)] +pub struct Animations { + #[knuffel(child)] + pub off: bool, + #[knuffel(child, unwrap(argument), default = 1.)] + pub slowdown: f64, + #[knuffel(child, default = Animation::default_workspace_switch())] + pub workspace_switch: Animation, + #[knuffel(child, default = Animation::default_horizontal_view_movement())] + pub horizontal_view_movement: Animation, + #[knuffel(child, default = Animation::default_window_open())] + pub window_open: Animation, + #[knuffel(child, default = Animation::default_config_notification_open_close())] + pub config_notification_open_close: Animation, +} + +impl Default for Animations { + fn default() -> Self { + Self { + off: false, + slowdown: 1., + workspace_switch: Animation::default_workspace_switch(), + horizontal_view_movement: Animation::default_horizontal_view_movement(), + window_open: Animation::default_window_open(), + config_notification_open_close: Animation::default_config_notification_open_close(), + } + } +} + +#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)] +pub struct Animation { + #[knuffel(child)] + pub off: bool, + #[knuffel(child, unwrap(argument))] + pub duration_ms: Option, + #[knuffel(child, unwrap(argument))] + pub curve: Option, +} + +impl Animation { + pub const fn unfilled() -> Self { + Self { + off: false, + duration_ms: None, + curve: None, + } + } + + pub const fn default() -> Self { + Self { + off: false, + duration_ms: Some(250), + curve: Some(AnimationCurve::EaseOutCubic), + } + } + + pub const fn default_workspace_switch() -> Self { + Self::default() + } + + pub const fn default_horizontal_view_movement() -> Self { + Self::default() + } + + pub const fn default_config_notification_open_close() -> Self { + Self::default() + } + + pub const fn default_window_open() -> Self { + Self { + duration_ms: Some(150), + curve: Some(AnimationCurve::EaseOutExpo), + ..Self::default() + } + } +} + +#[derive(knuffel::DecodeScalar, Debug, Clone, Copy, PartialEq)] +pub enum AnimationCurve { + EaseOutCubic, + EaseOutExpo, +} + #[derive(knuffel::Decode, Debug, Default, PartialEq)] pub struct Binds(#[knuffel(children)] pub Vec); @@ -515,10 +600,8 @@ pub enum LayoutAction { Prev, } -#[derive(knuffel::Decode, Debug, PartialEq)] +#[derive(knuffel::Decode, Debug, Default, PartialEq)] pub struct DebugConfig { - #[knuffel(child, unwrap(argument), default = 1.)] - pub animation_slowdown: f64, #[knuffel(child)] pub dbus_interfaces_in_non_session_instances: bool, #[knuffel(child)] @@ -533,20 +616,6 @@ pub struct DebugConfig { pub render_drm_device: Option, } -impl Default for DebugConfig { - fn default() -> Self { - Self { - animation_slowdown: 1., - dbus_interfaces_in_non_session_instances: false, - wait_for_frame_completion_before_queueing: false, - enable_color_transformations_capability: false, - enable_overlay_planes: false, - disable_cursor_plane: false, - render_drm_device: None, - } - } -} - impl Config { pub fn load(path: &Path) -> miette::Result { let _span = tracy_client::span!("Config::load"); @@ -841,6 +910,17 @@ mod tests { skip-at-startup } + animations { + slowdown 2.0 + + workspace-switch { off; } + + horizontal-view-movement { + duration-ms 100 + curve "ease-out-expo" + } + } + binds { Mod+T { spawn "alacritty"; } Mod+Q { close-window; } @@ -851,7 +931,6 @@ mod tests { } debug { - animation-slowdown 2.0 render-drm-device "/dev/dri/renderD129" } "#, @@ -961,6 +1040,19 @@ mod tests { hotkey_overlay: HotkeyOverlay { skip_at_startup: true, }, + animations: Animations { + slowdown: 2., + workspace_switch: Animation { + off: true, + ..Animation::unfilled() + }, + horizontal_view_movement: Animation { + duration_ms: Some(100), + curve: Some(AnimationCurve::EaseOutExpo), + ..Animation::unfilled() + }, + ..Default::default() + }, binds: Binds(vec![ Bind { key: Key { @@ -1006,7 +1098,6 @@ mod tests { }, ]), debug: DebugConfig { - animation_slowdown: 2., render_drm_device: Some(PathBuf::from("/dev/dri/renderD129")), ..Default::default() }, diff --git a/resources/default-config.kdl b/resources/default-config.kdl index 268f3fd8..499a9c81 100644 --- a/resources/default-config.kdl +++ b/resources/default-config.kdl @@ -198,6 +198,52 @@ hotkey-overlay { // skip-at-startup } +// Animation settings. +animations { + // Uncomment to turn off all animations. + // off + + // Slow down all animations by this factor. Values below 1 speed them up instead. + // slowdown 3.0 + + // You can configure all individual animations. + // Available settings are the same for all of them. + + // Animation when switching workspaces up and down, + // including after the touchpad gesture. + workspace-switch { + // off + // duration-ms 250 + // curve "ease-out-cubic" + } + + // All horizontal camera view movement: + // - When a window off-screen is focused and the camera scrolls to it. + // - When a new window appears off-screen and the camera scrolls to it. + // - When a window resizes bigger and the camera scrolls to show it in full. + // - And so on. + horizontal-view-movement { + // off + // duration-ms 250 + // curve "ease-out-cubic" + } + + // Window opening animation. Note that this one has different defaults. + window-open { + // off + // duration-ms 150 + // curve "ease-out-expo" + } + + // Config parse error and new default config creation notification + // open/close animation. + config-notification-open-close { + // off + // duration-ms 250 + // curve "ease-out-cubic" + } +} + binds { // Keys consist of modifiers separated by + signs, followed by an XKB key name // in the end. To find an XKB name for a particular key, you may use a program @@ -385,9 +431,6 @@ debug { // The cursor will be rendered together with the rest of the frame. // disable-cursor-plane - // Slow down animations by this factor. - // animation-slowdown 3.0 - // Override the DRM device that niri will use for all rendering. // render-drm-device "/dev/dri/renderD129" } diff --git a/src/animation.rs b/src/animation.rs index 404c3d85..bb22b75c 100644 --- a/src/animation.rs +++ b/src/animation.rs @@ -25,28 +25,36 @@ pub enum Curve { } impl Animation { - pub fn new(from: f64, to: f64, over_ms: u32) -> Self { + pub fn new( + from: f64, + to: f64, + config: niri_config::Animation, + default: niri_config::Animation, + ) -> Self { // FIXME: ideally we shouldn't use current time here because animations started within the // same frame cycle should have the same start time to be synchronized. let now = get_monotonic_time(); - let duration = Duration::from_millis(u64::from(over_ms)) + let duration_ms = if config.off { + 0 + } else { + config.duration_ms.unwrap_or(default.duration_ms.unwrap()) + }; + let duration = Duration::from_millis(u64::from(duration_ms)) .mul_f64(ANIMATION_SLOWDOWN.load(Ordering::Relaxed)); + + let curve = Curve::from(config.curve.unwrap_or(default.curve.unwrap())); + Self { from, to, duration, start_time: now, current_time: now, - curve: Curve::EaseOutCubic, + curve, } } - pub fn with_curve(mut self, curve: Curve) -> Self { - self.curve = curve; - self - } - pub fn set_current_time(&mut self, time: Duration) { self.current_time = time; } @@ -80,3 +88,12 @@ impl Curve { } } } + +impl From for Curve { + fn from(value: niri_config::AnimationCurve) -> Self { + match value { + niri_config::AnimationCurve::EaseOutCubic => Curve::EaseOutCubic, + niri_config::AnimationCurve::EaseOutExpo => Curve::EaseOutExpo, + } + } +} diff --git a/src/config_error_notification.rs b/src/config_error_notification.rs index b80e8d27..12d92b85 100644 --- a/src/config_error_notification.rs +++ b/src/config_error_notification.rs @@ -1,8 +1,10 @@ use std::cell::RefCell; use std::collections::HashMap; use std::path::{Path, PathBuf}; +use std::rc::Rc; use std::time::Duration; +use niri_config::Config; use pangocairo::cairo::{self, ImageSurface}; use pangocairo::pango::FontDescription; use smithay::backend::renderer::element::memory::{ @@ -31,6 +33,8 @@ pub struct ConfigErrorNotification { // If set, this is a "Created config at {path}" notification. If unset, this is a config error // notification. created_path: Option, + + config: Rc>, } enum State { @@ -44,21 +48,32 @@ pub type ConfigErrorNotificationRenderElement = RelocateRenderElement>; impl ConfigErrorNotification { - pub fn new() -> Self { + pub fn new(config: Rc>) -> Self { Self { state: State::Hidden, buffers: RefCell::new(HashMap::new()), created_path: None, + config, } } + fn animation(&self, from: f64, to: f64) -> Animation { + let c = self.config.borrow(); + Animation::new( + from, + to, + c.animations.config_notification_open_close, + niri_config::Animation::default_config_notification_open_close(), + ) + } + pub fn show_created(&mut self, created_path: Option) { if self.created_path != created_path { self.created_path = created_path; self.buffers.borrow_mut().clear(); } - self.state = State::Showing(Animation::new(0., 1., 250)); + self.state = State::Showing(self.animation(0., 1.)); } pub fn show(&mut self) { @@ -68,7 +83,7 @@ impl ConfigErrorNotification { } // Show from scratch even if already showing to bring attention. - self.state = State::Showing(Animation::new(0., 1., 250)); + self.state = State::Showing(self.animation(0., 1.)); } pub fn hide(&mut self) { @@ -76,7 +91,7 @@ impl ConfigErrorNotification { return; } - self.state = State::Hiding(Animation::new(1., 0., 250)); + self.state = State::Hiding(self.animation(1., 0.)); } pub fn advance_animations(&mut self, target_presentation_time: Duration) { @@ -167,12 +182,6 @@ impl ConfigErrorNotification { } } -impl Default for ConfigErrorNotification { - fn default() -> Self { - Self::new() - } -} - fn render(scale: i32, created_path: Option<&Path>) -> anyhow::Result { let _span = tracy_client::span!("config_error_notification::render"); diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 7c381358..c4bcc61f 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -158,6 +158,7 @@ pub struct Options { pub preset_widths: Vec, /// Initial width for new columns. pub default_width: Option, + pub animations: niri_config::Animations, } impl Default for Options { @@ -174,6 +175,7 @@ impl Default for Options { ColumnWidth::Proportion(2. / 3.), ], default_width: None, + animations: Default::default(), } } } @@ -209,6 +211,7 @@ impl Options { center_focused_column: layout.center_focused_column, preset_widths, default_width, + animations: config.animations, } } } @@ -1577,7 +1580,8 @@ impl Layout { monitor.workspace_switch = Some(WorkspaceSwitch::Animation(Animation::new( current_idx, idx as f64, - 250, + self.options.animations.workspace_switch, + niri_config::Animation::default_workspace_switch(), ))); return Some(monitor.output.clone()); diff --git a/src/layout/monitor.rs b/src/layout/monitor.rs index accce645..e67f5bf2 100644 --- a/src/layout/monitor.rs +++ b/src/layout/monitor.rs @@ -96,7 +96,8 @@ impl Monitor { self.workspace_switch = Some(WorkspaceSwitch::Animation(Animation::new( current_idx, idx as f64, - 250, + self.options.animations.workspace_switch, + niri_config::Animation::default_workspace_switch(), ))); } diff --git a/src/layout/tile.rs b/src/layout/tile.rs index be96f6e6..62f79a77 100644 --- a/src/layout/tile.rs +++ b/src/layout/tile.rs @@ -11,7 +11,7 @@ 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::animation::Animation; use crate::niri_render_elements; use crate::render_helpers::offscreen::OffscreenRenderElement; use crate::render_helpers::renderer::NiriRenderer; @@ -114,7 +114,12 @@ impl Tile { } pub fn start_open_animation(&mut self) { - self.open_animation = Some(Animation::new(0., 1., 150).with_curve(Curve::EaseOutExpo)); + self.open_animation = Some(Animation::new( + 0., + 1., + self.options.animations.window_open, + niri_config::Animation::default_window_open(), + )); } pub fn window(&self) -> &W { diff --git a/src/layout/workspace.rs b/src/layout/workspace.rs index 9165d7e0..0e576b7a 100644 --- a/src/layout/workspace.rs +++ b/src/layout/workspace.rs @@ -401,7 +401,8 @@ impl Workspace { self.view_offset_anim = Some(Animation::new( self.view_offset as f64, new_view_offset as f64, - 250, + self.options.animations.horizontal_view_movement, + niri_config::Animation::default_horizontal_view_movement(), )); } diff --git a/src/main.rs b/src/main.rs index 3e38b6d0..3dfacfa1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -177,7 +177,13 @@ fn main() -> Result<(), Box> { }) .unwrap_or_default(); - animation::ANIMATION_SLOWDOWN.store(config.debug.animation_slowdown, Ordering::Relaxed); + let slowdown = if config.animations.off { + 0. + } else { + config.animations.slowdown + }; + animation::ANIMATION_SLOWDOWN.store(slowdown, Ordering::Relaxed); + let spawn_at_startup = mem::take(&mut config.spawn_at_startup); // Create the compositor. diff --git a/src/niri.rs b/src/niri.rs index 3cb01091..aa1df4d9 100644 --- a/src/niri.rs +++ b/src/niri.rs @@ -581,7 +581,13 @@ impl State { self.niri.config_error_notification.hide(); self.niri.layout.update_config(&config); - animation::ANIMATION_SLOWDOWN.store(config.debug.animation_slowdown, Ordering::Relaxed); + + let slowdown = if config.animations.off { + 0. + } else { + config.animations.slowdown + }; + animation::ANIMATION_SLOWDOWN.store(slowdown, Ordering::Relaxed); let mut reload_xkb = None; let mut libinput_config_changed = false; @@ -902,7 +908,7 @@ impl Niri { }); let screenshot_ui = ScreenshotUi::new(); - let config_error_notification = ConfigErrorNotification::new(); + let config_error_notification = ConfigErrorNotification::new(config.clone()); let mut hotkey_overlay = HotkeyOverlay::new(config.clone(), backend.mod_key()); if !config_.hotkey_overlay.skip_at_startup { -- cgit