mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-23 02:05:33 +07:00
Make all animations configurable
This commit is contained in:
+110
-19
@@ -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<u32>,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub curve: Option<AnimationCurve>,
|
||||
}
|
||||
|
||||
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<Bind>);
|
||||
|
||||
@@ -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<PathBuf>,
|
||||
}
|
||||
|
||||
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<Self> {
|
||||
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()
|
||||
},
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
+25
-8
@@ -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<niri_config::AnimationCurve> for Curve {
|
||||
fn from(value: niri_config::AnimationCurve) -> Self {
|
||||
match value {
|
||||
niri_config::AnimationCurve::EaseOutCubic => Curve::EaseOutCubic,
|
||||
niri_config::AnimationCurve::EaseOutExpo => Curve::EaseOutExpo,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<PathBuf>,
|
||||
|
||||
config: Rc<RefCell<Config>>,
|
||||
}
|
||||
|
||||
enum State {
|
||||
@@ -44,21 +48,32 @@ pub type ConfigErrorNotificationRenderElement<R> =
|
||||
RelocateRenderElement<MemoryRenderBufferRenderElement<R>>;
|
||||
|
||||
impl ConfigErrorNotification {
|
||||
pub fn new() -> Self {
|
||||
pub fn new(config: Rc<RefCell<Config>>) -> 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<PathBuf>) {
|
||||
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<MemoryRenderBuffer> {
|
||||
let _span = tracy_client::span!("config_error_notification::render");
|
||||
|
||||
|
||||
+5
-1
@@ -158,6 +158,7 @@ pub struct Options {
|
||||
pub preset_widths: Vec<ColumnWidth>,
|
||||
/// Initial width for new columns.
|
||||
pub default_width: Option<ColumnWidth>,
|
||||
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<W: LayoutElement> Layout<W> {
|
||||
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());
|
||||
|
||||
@@ -96,7 +96,8 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
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(),
|
||||
)));
|
||||
}
|
||||
|
||||
|
||||
+7
-2
@@ -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<W: LayoutElement> Tile<W> {
|
||||
}
|
||||
|
||||
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 {
|
||||
|
||||
@@ -401,7 +401,8 @@ impl<W: LayoutElement> Workspace<W> {
|
||||
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(),
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
+7
-1
@@ -177,7 +177,13 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
})
|
||||
.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.
|
||||
|
||||
+8
-2
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user