feat: configure animation policy
This commit is contained in:
1 parent
a5aee06a10
commit
7f92b6d634
3 files changed
+172
-15
No files matched your search
+138
-1
@@ -5,18 +5,64 @@ use std::fs;
|
|||||||
use std::io;
|
use std::io;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use ferese_animation::SpringConfig;
|
||||||
use ferese_core::LayoutMode;
|
use ferese_core::LayoutMode;
|
||||||
use ferese_layout::ColumnWidth;
|
use ferese_layout::ColumnWidth;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
#[derive(Debug, Default, Deserialize)]
|
#[derive(Debug, Default, Deserialize)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
|
#[serde(default)]
|
||||||
|
animations: AnimationsConfig,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
layout: LayoutConfig,
|
layout: LayoutConfig,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
scrolling: ScrollingConfig,
|
scrolling: ScrollingConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct AnimationsConfig {
|
||||||
|
#[serde(default = "enabled_by_default")]
|
||||||
|
enabled: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
reduced_motion: bool,
|
||||||
|
#[serde(default = "default_animation_speed")]
|
||||||
|
speed: f64,
|
||||||
|
#[serde(default)]
|
||||||
|
spring: SpringSettings,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for AnimationsConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
enabled: true,
|
||||||
|
reduced_motion: false,
|
||||||
|
speed: 1.0,
|
||||||
|
spring: SpringSettings::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct SpringSettings {
|
||||||
|
#[serde(default = "default_spring_mass")]
|
||||||
|
mass: f64,
|
||||||
|
#[serde(default = "default_spring_stiffness")]
|
||||||
|
stiffness: f64,
|
||||||
|
#[serde(default = "default_spring_damping")]
|
||||||
|
damping: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for SpringSettings {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
mass: default_spring_mass(),
|
||||||
|
stiffness: default_spring_stiffness(),
|
||||||
|
damping: default_spring_damping(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Deserialize)]
|
#[derive(Debug, Default, Deserialize)]
|
||||||
struct LayoutConfig {
|
struct LayoutConfig {
|
||||||
mode: Option<LayoutModeValue>,
|
mode: Option<LayoutModeValue>,
|
||||||
@@ -57,6 +103,10 @@ pub enum ConfigError {
|
|||||||
field: &'static str,
|
field: &'static str,
|
||||||
value: String,
|
value: String,
|
||||||
},
|
},
|
||||||
|
InvalidAnimationValue {
|
||||||
|
field: &'static str,
|
||||||
|
value: f64,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for ConfigError {
|
impl fmt::Display for ConfigError {
|
||||||
@@ -72,6 +122,9 @@ impl fmt::Display for ConfigError {
|
|||||||
formatter,
|
formatter,
|
||||||
"invalid scrolling.{field} {value}; expected a positive number or \"full\""
|
"invalid scrolling.{field} {value}; expected a positive number or \"full\""
|
||||||
),
|
),
|
||||||
|
Self::InvalidAnimationValue { field, value } => {
|
||||||
|
write!(formatter, "invalid animations.{field} value {value}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -81,7 +134,7 @@ impl Error for ConfigError {
|
|||||||
match self {
|
match self {
|
||||||
Self::Read { source, .. } => Some(source),
|
Self::Read { source, .. } => Some(source),
|
||||||
Self::Parse { source, .. } => Some(source),
|
Self::Parse { source, .. } => Some(source),
|
||||||
Self::InvalidColumnWidth { .. } => None,
|
Self::InvalidColumnWidth { .. } | Self::InvalidAnimationValue { .. } => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -133,6 +186,62 @@ impl Config {
|
|||||||
.map(|value| parse_column_width(value, "width_presets"))
|
.map(|value| parse_column_width(value, "width_presets"))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn animations_enabled(&self) -> bool {
|
||||||
|
self.animations.enabled && !self.animations.reduced_motion
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn animation_speed(&self) -> Result<f64, ConfigError> {
|
||||||
|
positive_animation_value(self.animations.speed, "speed")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn spring_config(&self) -> Result<SpringConfig, ConfigError> {
|
||||||
|
let mass = positive_animation_value(self.animations.spring.mass, "spring.mass")?;
|
||||||
|
let stiffness =
|
||||||
|
positive_animation_value(self.animations.spring.stiffness, "spring.stiffness")?;
|
||||||
|
let damping = self.animations.spring.damping;
|
||||||
|
if !damping.is_finite() || damping < 0.0 {
|
||||||
|
return Err(ConfigError::InvalidAnimationValue {
|
||||||
|
field: "spring.damping",
|
||||||
|
value: damping,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(SpringConfig {
|
||||||
|
mass,
|
||||||
|
stiffness,
|
||||||
|
damping,
|
||||||
|
..SpringConfig::default()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn positive_animation_value(value: f64, field: &'static str) -> Result<f64, ConfigError> {
|
||||||
|
if value.is_finite() && value > 0.0 {
|
||||||
|
Ok(value)
|
||||||
|
} else {
|
||||||
|
Err(ConfigError::InvalidAnimationValue { field, value })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn enabled_by_default() -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn default_animation_speed() -> f64 {
|
||||||
|
1.0
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn default_spring_mass() -> f64 {
|
||||||
|
1.0
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn default_spring_stiffness() -> f64 {
|
||||||
|
700.0
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn default_spring_damping() -> f64 {
|
||||||
|
53.0
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_column_width(
|
fn parse_column_width(
|
||||||
@@ -222,4 +331,32 @@ mod tests {
|
|||||||
assert!(zero.default_column_width().is_err());
|
assert!(zero.default_column_width().is_err());
|
||||||
assert!(unknown.default_column_width().is_err());
|
assert!(unknown.default_column_width().is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parses_animation_policy_and_reduced_motion() {
|
||||||
|
let config = parse(
|
||||||
|
"[animations]\nspeed = 1.5\nreduced_motion = true\n\n[animations.spring]\nmass = 2.0\nstiffness = 500.0\ndamping = 40.0",
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(!config.animations_enabled());
|
||||||
|
assert_eq!(config.animation_speed().unwrap(), 1.5);
|
||||||
|
assert_eq!(
|
||||||
|
config.spring_config().unwrap(),
|
||||||
|
SpringConfig {
|
||||||
|
mass: 2.0,
|
||||||
|
stiffness: 500.0,
|
||||||
|
damping: 40.0,
|
||||||
|
..SpringConfig::default()
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rejects_invalid_animation_numbers() {
|
||||||
|
let speed = parse("[animations]\nspeed = 0.0");
|
||||||
|
let damping = parse("[animations.spring]\ndamping = -1.0");
|
||||||
|
|
||||||
|
assert!(speed.animation_speed().is_err());
|
||||||
|
assert!(damping.spring_config().is_err());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+10
-8
@@ -12,7 +12,7 @@ mod winit;
|
|||||||
use std::{error::Error, io, process::Command};
|
use std::{error::Error, io, process::Command};
|
||||||
|
|
||||||
use smithay::reexports::{calloop::EventLoop, wayland_server::Display};
|
use smithay::reexports::{calloop::EventLoop, wayland_server::Display};
|
||||||
pub use state::Ferese;
|
pub use state::{Ferese, RuntimeConfig};
|
||||||
use tracing::{info, warn};
|
use tracing::{info, warn};
|
||||||
|
|
||||||
use crate::backends::LaunchConfig;
|
use crate::backends::LaunchConfig;
|
||||||
@@ -26,13 +26,15 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
let config = Config::load()?;
|
let config = Config::load()?;
|
||||||
let mut event_loop = EventLoop::try_new()?;
|
let mut event_loop = EventLoop::try_new()?;
|
||||||
let display = Display::new()?;
|
let display = Display::new()?;
|
||||||
let mut state = Ferese::new(
|
let runtime = RuntimeConfig {
|
||||||
&mut event_loop,
|
layout_mode: config.layout_mode(),
|
||||||
display,
|
default_column_width: config.default_column_width()?,
|
||||||
config.layout_mode(),
|
column_width_presets: config.width_presets()?,
|
||||||
config.default_column_width()?,
|
animations_enabled: config.animations_enabled(),
|
||||||
config.width_presets()?,
|
animation_speed: config.animation_speed()?,
|
||||||
)?;
|
spring_config: config.spring_config()?,
|
||||||
|
};
|
||||||
|
let mut state = Ferese::new(&mut event_loop, display, runtime)?;
|
||||||
backends::init(launch.backend, &mut event_loop, &mut state)?;
|
backends::init(launch.backend, &mut event_loop, &mut state)?;
|
||||||
|
|
||||||
// SAFETY: the backend has already read the host display and Ferese is
|
// SAFETY: the backend has already read the host display and Ferese is
|
||||||
|
|||||||
+24
-6
@@ -85,6 +85,9 @@ pub struct Ferese {
|
|||||||
pub window_geometry: HashMap<WindowId, WindowGeometry>,
|
pub window_geometry: HashMap<WindowId, WindowGeometry>,
|
||||||
pub focused_window: Option<WindowId>,
|
pub focused_window: Option<WindowId>,
|
||||||
column_width_presets: Vec<ColumnWidth>,
|
column_width_presets: Vec<ColumnWidth>,
|
||||||
|
animations_enabled: bool,
|
||||||
|
animation_speed: f64,
|
||||||
|
spring_config: SpringConfig,
|
||||||
pub cursor_status: CursorImageStatus,
|
pub cursor_status: CursorImageStatus,
|
||||||
pub(crate) cursor_theme: xcursor::CursorTheme,
|
pub(crate) cursor_theme: xcursor::CursorTheme,
|
||||||
pub(crate) named_cursors: HashMap<CursorIcon, crate::cursor::NamedCursor>,
|
pub(crate) named_cursors: HashMap<CursorIcon, crate::cursor::NamedCursor>,
|
||||||
@@ -125,6 +128,15 @@ pub struct Ferese {
|
|||||||
pub xdg_shell_state: XdgShellState,
|
pub xdg_shell_state: XdgShellState,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct RuntimeConfig {
|
||||||
|
pub layout_mode: LayoutMode,
|
||||||
|
pub default_column_width: ColumnWidth,
|
||||||
|
pub column_width_presets: Vec<ColumnWidth>,
|
||||||
|
pub animations_enabled: bool,
|
||||||
|
pub animation_speed: f64,
|
||||||
|
pub spring_config: SpringConfig,
|
||||||
|
}
|
||||||
|
|
||||||
impl Ferese {
|
impl Ferese {
|
||||||
pub fn register_output(&mut self, output: &Output, identity: String) {
|
pub fn register_output(&mut self, output: &Output, identity: String) {
|
||||||
let Some(geometry) = self.space.output_geometry(output) else {
|
let Some(geometry) = self.space.output_geometry(output) else {
|
||||||
@@ -272,9 +284,7 @@ impl Ferese {
|
|||||||
pub fn new(
|
pub fn new(
|
||||||
event_loop: &mut EventLoop<'static, Self>,
|
event_loop: &mut EventLoop<'static, Self>,
|
||||||
display: Display<Self>,
|
display: Display<Self>,
|
||||||
default_layout_mode: LayoutMode,
|
config: RuntimeConfig,
|
||||||
default_column_width: ColumnWidth,
|
|
||||||
column_width_presets: Vec<ColumnWidth>,
|
|
||||||
) -> Result<Self, Box<dyn Error>> {
|
) -> Result<Self, Box<dyn Error>> {
|
||||||
let display_handle = display.handle();
|
let display_handle = display.handle();
|
||||||
crate::handlers::screencopy::init_global(&display_handle);
|
crate::handlers::screencopy::init_global(&display_handle);
|
||||||
@@ -323,14 +333,17 @@ impl Ferese {
|
|||||||
display_handle,
|
display_handle,
|
||||||
loop_signal: event_loop.get_signal(),
|
loop_signal: event_loop.get_signal(),
|
||||||
space: Space::default(),
|
space: Space::default(),
|
||||||
workspaces: WorkspaceSet::new(default_layout_mode, default_column_width),
|
workspaces: WorkspaceSet::new(config.layout_mode, config.default_column_width),
|
||||||
output_workspaces: OutputWorkspaceMap::default(),
|
output_workspaces: OutputWorkspaceMap::default(),
|
||||||
output_ids: HashMap::new(),
|
output_ids: HashMap::new(),
|
||||||
output_identity_ids: HashMap::new(),
|
output_identity_ids: HashMap::new(),
|
||||||
window_ids: HashMap::new(),
|
window_ids: HashMap::new(),
|
||||||
window_geometry: HashMap::new(),
|
window_geometry: HashMap::new(),
|
||||||
focused_window: None,
|
focused_window: None,
|
||||||
column_width_presets,
|
column_width_presets: config.column_width_presets,
|
||||||
|
animations_enabled: config.animations_enabled,
|
||||||
|
animation_speed: config.animation_speed,
|
||||||
|
spring_config: config.spring_config,
|
||||||
cursor_status: CursorImageStatus::default_named(),
|
cursor_status: CursorImageStatus::default_named(),
|
||||||
cursor_theme,
|
cursor_theme,
|
||||||
named_cursors,
|
named_cursors,
|
||||||
@@ -733,6 +746,9 @@ impl Ferese {
|
|||||||
.entry(id)
|
.entry(id)
|
||||||
.or_insert_with(|| WindowGeometry::new(rect, committed_size));
|
.or_insert_with(|| WindowGeometry::new(rect, committed_size));
|
||||||
let requested_size = geometry.set_logical_target(rect, now);
|
let requested_size = geometry.set_logical_target(rect, now);
|
||||||
|
if !self.animations_enabled {
|
||||||
|
geometry.advance(Duration::ZERO, self.spring_config, false);
|
||||||
|
}
|
||||||
let visual = geometry.visual.current;
|
let visual = geometry.visual.current;
|
||||||
let location = (visual.x.round() as i32, visual.y.round() as i32);
|
let location = (visual.x.round() as i32, visual.y.round() as i32);
|
||||||
|
|
||||||
@@ -805,6 +821,7 @@ impl Ferese {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn advance_animations_by(&mut self, delta: std::time::Duration) -> bool {
|
fn advance_animations_by(&mut self, delta: std::time::Duration) -> bool {
|
||||||
|
let delta = delta.mul_f64(self.animation_speed);
|
||||||
let windows = self
|
let windows = self
|
||||||
.space
|
.space
|
||||||
.elements()
|
.elements()
|
||||||
@@ -822,7 +839,8 @@ impl Ferese {
|
|||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
active_animation |= geometry.advance(delta, SpringConfig::default(), true);
|
active_animation |=
|
||||||
|
geometry.advance(delta, self.spring_config, self.animations_enabled);
|
||||||
if let Some(size) = geometry.client.expire_wait(self.start_time.elapsed()) {
|
if let Some(size) = geometry.client.expire_wait(self.start_time.elapsed()) {
|
||||||
tracing::warn!(
|
tracing::warn!(
|
||||||
?id,
|
?id,
|
||||||
|
|||||||
Reference in new issue
Block a user