1
0
Fork 0
forked from wry/wry

Add custom animation curve config

This commit is contained in:
atagen 2026-05-21 17:19:46 +10:00
parent fa5c28ca3d
commit cf61c080b6
10 changed files with 281 additions and 57 deletions

View file

@ -270,7 +270,13 @@ pub struct UiDrag {
pub struct Animations {
pub enabled: Option<bool>,
pub duration_ms: Option<u32>,
pub curve: Option<String>,
pub curve: Option<AnimationCurveConfig>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum AnimationCurveConfig {
Preset(String),
CubicBezier([f32; 4]),
}
#[derive(Debug, Clone)]
@ -659,3 +665,16 @@ fn default_config_parses() {
let input = include_bytes!("default-config.toml");
parse_config(input, &Default::default(), |_| ()).unwrap();
}
#[test]
fn custom_animation_curve_parses() {
let input = b"
[animations]
curve = [0.25, 0.1, 0.25, 1.0]
";
let config = parse_config(input, &Default::default(), |_| ()).unwrap();
assert_eq!(
config.animations.curve,
Some(AnimationCurveConfig::CubicBezier([0.25, 0.1, 0.25, 1.0]))
);
}

View file

@ -1,13 +1,13 @@
use {
crate::{
config::{
Animations,
AnimationCurveConfig, Animations,
context::Context,
extractor::{Extractor, ExtractorError, bol, n32, opt, recover, str},
extractor::{Extractor, ExtractorError, bol, n32, opt, recover, val},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
},
toml::{
toml_span::{DespanExt, Span, Spanned},
toml_span::{DespanExt, Span, Spanned, SpannedExt},
toml_value::Value,
},
},
@ -21,6 +21,14 @@ pub enum AnimationsParserError {
Expected(#[from] UnexpectedDataType),
#[error(transparent)]
Extract(#[from] ExtractorError),
#[error("Expected animation curve to be a string or an array")]
CurveType,
#[error("Cubic-bezier animation curves must contain exactly four values")]
CubicBezierLen,
#[error("Cubic-bezier animation curve entries must be finite floats or integers")]
CubicBezierValue,
#[error("Cubic-bezier x control points must be between 0 and 1")]
CubicBezierXRange,
}
pub struct AnimationsParser<'a>(pub &'a Context<'a>);
@ -39,12 +47,51 @@ impl Parser for AnimationsParser<'_> {
let (enabled, duration_ms, curve) = ext.extract((
recover(opt(bol("enabled"))),
recover(opt(n32("duration-ms"))),
recover(opt(str("curve"))),
opt(val("curve")),
))?;
let curve = match curve {
Some(curve) => Some(parse_curve(curve)?),
None => None,
};
Ok(Animations {
enabled: enabled.despan(),
duration_ms: duration_ms.despan(),
curve: curve.despan().map(|s| s.to_string()),
curve,
})
}
}
fn parse_curve(
curve: Spanned<&Value>,
) -> Result<AnimationCurveConfig, Spanned<AnimationsParserError>> {
match curve.value {
Value::String(s) => Ok(AnimationCurveConfig::Preset(s.clone())),
Value::Array(values) => parse_cubic_bezier(curve.span, values),
_ => Err(AnimationsParserError::CurveType.spanned(curve.span)),
}
}
fn parse_cubic_bezier(
span: Span,
values: &[Spanned<Value>],
) -> Result<AnimationCurveConfig, Spanned<AnimationsParserError>> {
if values.len() != 4 {
return Err(AnimationsParserError::CubicBezierLen.spanned(span));
}
let mut points = [0.0; 4];
for (idx, value) in values.iter().enumerate() {
let f = match value.value {
Value::Float(f) => f,
Value::Integer(i) => i as f64,
_ => return Err(AnimationsParserError::CubicBezierValue.spanned(value.span)),
};
if !f.is_finite() {
return Err(AnimationsParserError::CubicBezierValue.spanned(value.span));
}
points[idx] = f as f32;
}
if !(0.0..=1.0).contains(&points[0]) || !(0.0..=1.0).contains(&points[2]) {
return Err(AnimationsParserError::CubicBezierXRange.spanned(span));
}
Ok(AnimationCurveConfig::CubicBezier(points))
}

View file

@ -13,9 +13,9 @@ mod toml;
use {
crate::{
config::{
Action, ClientRule, Config, ConfigConnector, ConfigDrmDevice, ConfigKeymap,
ConnectorMatch, DrmDeviceMatch, Exec, Input, InputMatch, Output, OutputMatch,
SimpleCommand, Status, Theme, WindowRule, parse_config,
Action, AnimationCurveConfig, ClientRule, Config, ConfigConnector, ConfigDrmDevice,
ConfigKeymap, ConnectorMatch, DrmDeviceMatch, Exec, Input, InputMatch, Output,
OutputMatch, SimpleCommand, Status, Theme, WindowRule, parse_config,
},
rules::{MatcherTemp, RuleMapper},
shortcuts::ModeState,
@ -37,8 +37,8 @@ use {
is_reload,
keyboard::Keymap,
logging::{clean_logs_older_than, set_log_level},
on_devices_enumerated, on_idle, on_unload, quit, reload, set_animation_curve,
set_animation_duration_ms, set_animations_enabled, set_autotile,
on_devices_enumerated, on_idle, on_unload, quit, reload, set_animation_cubic_bezier,
set_animation_curve, set_animation_duration_ms, set_animations_enabled, set_autotile,
set_color_management_enabled, set_corner_radius, set_default_workspace_capture,
set_explicit_sync_enabled, set_float_above_fullscreen, set_floating_titles, set_idle,
set_idle_grace_period, set_middle_click_paste_enabled, set_show_bar,
@ -1652,20 +1652,30 @@ fn load_config(initial_load: bool, auto_reload: bool, persistent: &Rc<Persistent
}
set_animations_enabled(config.animations.enabled.unwrap_or(false));
set_animation_duration_ms(config.animations.duration_ms.unwrap_or(160));
let curve_name = config.animations.curve.as_deref().unwrap_or("ease-out");
let curve = match curve_name {
"linear" => Some(AnimationCurve::LINEAR),
"ease" => Some(AnimationCurve::EASE),
"ease-in" => Some(AnimationCurve::EASE_IN),
"ease-out" => Some(AnimationCurve::EASE_OUT),
"ease-in-out" => Some(AnimationCurve::EASE_IN_OUT),
_ => {
log::warn!("Unknown animation curve: {curve_name}");
None
match config
.animations
.curve
.unwrap_or_else(|| AnimationCurveConfig::Preset("ease-out".to_string()))
{
AnimationCurveConfig::Preset(curve_name) => {
let curve = match curve_name.as_str() {
"linear" => Some(AnimationCurve::LINEAR),
"ease" => Some(AnimationCurve::EASE),
"ease-in" => Some(AnimationCurve::EASE_IN),
"ease-out" => Some(AnimationCurve::EASE_OUT),
"ease-in-out" => Some(AnimationCurve::EASE_IN_OUT),
_ => {
log::warn!("Unknown animation curve: {curve_name}");
None
}
};
if let Some(curve) = curve {
set_animation_curve(curve);
}
}
AnimationCurveConfig::CubicBezier([x1, y1, x2, y2]) => {
set_animation_cubic_bezier(x1, y1, x2, y2);
}
};
if let Some(curve) = curve {
set_animation_curve(curve);
}
if let Some(xwayland) = config.xwayland {
if let Some(enabled) = xwayland.enabled {