Resolve animation defaults during parsing

This commit is contained in:
Ivan Molodetskikh
2024-04-17 14:06:32 +04:00
parent c40d4f3268
commit 73e9ef5fe2
6 changed files with 289 additions and 237 deletions
+261 -143
View File
@@ -9,6 +9,7 @@ use std::time::Duration;
use bitflags::bitflags;
use knuffel::errors::DecodeError;
use knuffel::Decode as _;
use miette::{miette, Context, IntoDiagnostic, NarratableReportHandler};
use niri_ipc::{LayoutSwitchTarget, SizeChange, Transform};
use regex::Regex;
@@ -482,20 +483,20 @@ pub struct Animations {
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_movement())]
pub window_movement: Animation,
#[knuffel(child, default = Animation::default_window_open())]
pub window_open: Animation,
#[knuffel(child, default = Animation::default_window_close())]
pub window_close: Animation,
#[knuffel(child, default = Animation::default_window_resize())]
pub window_resize: Animation,
#[knuffel(child, default = Animation::default_config_notification_open_close())]
pub config_notification_open_close: Animation,
#[knuffel(child, default)]
pub workspace_switch: WorkspaceSwitchAnim,
#[knuffel(child, default)]
pub horizontal_view_movement: HorizontalViewMovementAnim,
#[knuffel(child, default)]
pub window_movement: WindowMovementAnim,
#[knuffel(child, default)]
pub window_open: WindowOpenAnim,
#[knuffel(child, default)]
pub window_close: WindowCloseAnim,
#[knuffel(child, default)]
pub window_resize: WindowResizeAnim,
#[knuffel(child, default)]
pub config_notification_open_close: ConfigNotificationOpenCloseAnim,
}
impl Default for Animations {
@@ -503,114 +504,133 @@ impl Default for Animations {
Self {
off: false,
slowdown: 1.,
workspace_switch: Animation::default_workspace_switch(),
horizontal_view_movement: Animation::default_horizontal_view_movement(),
window_movement: Animation::default_window_movement(),
window_open: Animation::default_window_open(),
window_close: Animation::default_window_close(),
window_resize: Animation::default_window_resize(),
config_notification_open_close: Animation::default_config_notification_open_close(),
workspace_switch: Default::default(),
horizontal_view_movement: Default::default(),
window_movement: Default::default(),
window_open: Default::default(),
window_close: Default::default(),
window_resize: Default::default(),
config_notification_open_close: Default::default(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct WorkspaceSwitchAnim(pub Animation);
impl Default for WorkspaceSwitchAnim {
fn default() -> Self {
Self(Animation {
off: false,
kind: AnimationKind::Spring(SpringParams {
damping_ratio: 1.,
stiffness: 1000,
epsilon: 0.0001,
}),
})
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct HorizontalViewMovementAnim(pub Animation);
impl Default for HorizontalViewMovementAnim {
fn default() -> Self {
Self(Animation {
off: false,
kind: AnimationKind::Spring(SpringParams {
damping_ratio: 1.,
stiffness: 800,
epsilon: 0.0001,
}),
})
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct WindowMovementAnim(pub Animation);
impl Default for WindowMovementAnim {
fn default() -> Self {
Self(Animation {
off: false,
kind: AnimationKind::Spring(SpringParams {
damping_ratio: 1.,
stiffness: 800,
epsilon: 0.0001,
}),
})
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ConfigNotificationOpenCloseAnim(pub Animation);
impl Default for ConfigNotificationOpenCloseAnim {
fn default() -> Self {
Self(Animation {
off: false,
kind: AnimationKind::Spring(SpringParams {
damping_ratio: 0.6,
stiffness: 1000,
epsilon: 0.001,
}),
})
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct WindowOpenAnim(pub Animation);
impl Default for WindowOpenAnim {
fn default() -> Self {
Self(Animation {
off: false,
kind: AnimationKind::Easing(EasingParams {
duration_ms: 150,
curve: AnimationCurve::EaseOutExpo,
}),
})
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct WindowCloseAnim(pub Animation);
impl Default for WindowCloseAnim {
fn default() -> Self {
Self(Animation {
off: false,
kind: AnimationKind::Easing(EasingParams {
duration_ms: 150,
curve: AnimationCurve::EaseOutQuad,
}),
})
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct WindowResizeAnim(pub Animation);
impl Default for WindowResizeAnim {
fn default() -> Self {
Self(Animation {
off: false,
kind: AnimationKind::Spring(SpringParams {
damping_ratio: 1.,
stiffness: 800,
epsilon: 0.0001,
}),
})
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Animation {
pub off: bool,
pub kind: AnimationKind,
}
impl Animation {
pub const fn unfilled() -> Self {
Self {
off: false,
kind: AnimationKind::Easing(EasingParams::unfilled()),
}
}
pub const fn default() -> Self {
Self {
off: false,
kind: AnimationKind::Easing(EasingParams::default()),
}
}
pub const fn default_workspace_switch() -> Self {
Self {
off: false,
kind: AnimationKind::Spring(SpringParams {
damping_ratio: 1.,
stiffness: 1000,
epsilon: 0.0001,
}),
}
}
pub const fn default_horizontal_view_movement() -> Self {
Self {
off: false,
kind: AnimationKind::Spring(SpringParams {
damping_ratio: 1.,
stiffness: 800,
epsilon: 0.0001,
}),
}
}
pub const fn default_window_movement() -> Self {
Self {
off: false,
kind: AnimationKind::Spring(SpringParams {
damping_ratio: 1.,
stiffness: 800,
epsilon: 0.0001,
}),
}
}
pub const fn default_config_notification_open_close() -> Self {
Self {
off: false,
kind: AnimationKind::Spring(SpringParams {
damping_ratio: 0.6,
stiffness: 1000,
epsilon: 0.001,
}),
}
}
pub const fn default_window_open() -> Self {
Self {
off: false,
kind: AnimationKind::Easing(EasingParams {
duration_ms: Some(150),
curve: Some(AnimationCurve::EaseOutExpo),
}),
}
}
pub const fn default_window_close() -> Self {
Self {
off: false,
kind: AnimationKind::Easing(EasingParams {
duration_ms: Some(150),
curve: Some(AnimationCurve::EaseOutQuad),
}),
}
}
pub const fn default_window_resize() -> Self {
Self {
off: false,
kind: AnimationKind::Spring(SpringParams {
damping_ratio: 1.,
stiffness: 800,
epsilon: 0.0001,
}),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum AnimationKind {
Easing(EasingParams),
@@ -619,24 +639,8 @@ pub enum AnimationKind {
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct EasingParams {
pub duration_ms: Option<u32>,
pub curve: Option<AnimationCurve>,
}
impl EasingParams {
pub const fn unfilled() -> Self {
Self {
duration_ms: None,
curve: None,
}
}
pub const fn default() -> Self {
Self {
duration_ms: Some(250),
curve: Some(AnimationCurve::EaseOutCubic),
}
}
pub duration_ms: u32,
pub curve: AnimationCurve,
}
#[derive(knuffel::DecodeScalar, Debug, Clone, Copy, PartialEq)]
@@ -1177,7 +1181,7 @@ fn parse_arg_node<S: knuffel::traits::ErrorSpan, T: knuffel::traits::DecodeScala
Ok(value)
}
impl<S> knuffel::Decode<S> for Animation
impl<S> knuffel::Decode<S> for WorkspaceSwitchAnim
where
S: knuffel::traits::ErrorSpan,
{
@@ -1185,10 +1189,105 @@ where
node: &knuffel::ast::SpannedNode<S>,
ctx: &mut knuffel::decode::Context<S>,
) -> Result<Self, DecodeError<S>> {
let default = Self::default().0;
Ok(Self(Animation::decode_node(node, ctx, default)?))
}
}
impl<S> knuffel::Decode<S> for HorizontalViewMovementAnim
where
S: knuffel::traits::ErrorSpan,
{
fn decode_node(
node: &knuffel::ast::SpannedNode<S>,
ctx: &mut knuffel::decode::Context<S>,
) -> Result<Self, DecodeError<S>> {
let default = Self::default().0;
Ok(Self(Animation::decode_node(node, ctx, default)?))
}
}
impl<S> knuffel::Decode<S> for WindowMovementAnim
where
S: knuffel::traits::ErrorSpan,
{
fn decode_node(
node: &knuffel::ast::SpannedNode<S>,
ctx: &mut knuffel::decode::Context<S>,
) -> Result<Self, DecodeError<S>> {
let default = Self::default().0;
Ok(Self(Animation::decode_node(node, ctx, default)?))
}
}
impl<S> knuffel::Decode<S> for WindowOpenAnim
where
S: knuffel::traits::ErrorSpan,
{
fn decode_node(
node: &knuffel::ast::SpannedNode<S>,
ctx: &mut knuffel::decode::Context<S>,
) -> Result<Self, DecodeError<S>> {
let default = Self::default().0;
Ok(Self(Animation::decode_node(node, ctx, default)?))
}
}
impl<S> knuffel::Decode<S> for WindowCloseAnim
where
S: knuffel::traits::ErrorSpan,
{
fn decode_node(
node: &knuffel::ast::SpannedNode<S>,
ctx: &mut knuffel::decode::Context<S>,
) -> Result<Self, DecodeError<S>> {
let default = Self::default().0;
Ok(Self(Animation::decode_node(node, ctx, default)?))
}
}
impl<S> knuffel::Decode<S> for WindowResizeAnim
where
S: knuffel::traits::ErrorSpan,
{
fn decode_node(
node: &knuffel::ast::SpannedNode<S>,
ctx: &mut knuffel::decode::Context<S>,
) -> Result<Self, DecodeError<S>> {
let default = Self::default().0;
Ok(Self(Animation::decode_node(node, ctx, default)?))
}
}
impl<S> knuffel::Decode<S> for ConfigNotificationOpenCloseAnim
where
S: knuffel::traits::ErrorSpan,
{
fn decode_node(
node: &knuffel::ast::SpannedNode<S>,
ctx: &mut knuffel::decode::Context<S>,
) -> Result<Self, DecodeError<S>> {
let default = Self::default().0;
Ok(Self(Animation::decode_node(node, ctx, default)?))
}
}
impl Animation {
fn decode_node<S: knuffel::traits::ErrorSpan>(
node: &knuffel::ast::SpannedNode<S>,
ctx: &mut knuffel::decode::Context<S>,
default: Self,
) -> Result<Self, DecodeError<S>> {
#[derive(Default, PartialEq)]
struct OptionalEasingParams {
duration_ms: Option<u32>,
curve: Option<AnimationCurve>,
}
expect_only_children(node, ctx);
let mut off = false;
let mut easing_params = EasingParams::unfilled();
let mut easing_params = OptionalEasingParams::default();
let mut spring_params = None;
for child in node.children() {
@@ -1206,7 +1305,7 @@ where
}
}
"spring" => {
if easing_params != EasingParams::unfilled() {
if easing_params != OptionalEasingParams::default() {
ctx.emit_error(DecodeError::unexpected(
child,
"node",
@@ -1270,9 +1369,28 @@ where
}
let kind = if let Some(spring_params) = spring_params {
// Configured spring.
AnimationKind::Spring(spring_params)
} else if easing_params == OptionalEasingParams::default() {
// Did not configure anything.
default.kind
} else {
AnimationKind::Easing(easing_params)
// Configured easing.
let default = if let AnimationKind::Easing(easing) = default.kind {
easing
} else {
// Generic fallback values for when the default animation is spring, but the user
// configured an easing animation.
EasingParams {
duration_ms: 250,
curve: AnimationCurve::EaseOutCubic,
}
};
AnimationKind::Easing(EasingParams {
duration_ms: easing_params.duration_ms.unwrap_or(default.duration_ms),
curve: easing_params.curve.unwrap_or(default.curve),
})
};
Ok(Self { off, kind })
@@ -1948,25 +2066,25 @@ mod tests {
},
animations: Animations {
slowdown: 2.,
workspace_switch: Animation {
workspace_switch: WorkspaceSwitchAnim(Animation {
off: false,
kind: AnimationKind::Spring(SpringParams {
damping_ratio: 1.,
stiffness: 1000,
epsilon: 0.0001,
}),
},
horizontal_view_movement: Animation {
}),
horizontal_view_movement: HorizontalViewMovementAnim(Animation {
off: false,
kind: AnimationKind::Easing(EasingParams {
duration_ms: Some(100),
curve: Some(AnimationCurve::EaseOutExpo),
duration_ms: 100,
curve: AnimationCurve::EaseOutExpo,
}),
},
window_open: Animation {
}),
window_open: WindowOpenAnim(Animation {
off: true,
..Animation::unfilled()
},
..WindowOpenAnim::default().0
}),
..Default::default()
},
environment: Environment(vec![
+12 -40
View File
@@ -46,52 +46,27 @@ pub enum Curve {
}
impl Animation {
pub fn new(
from: f64,
to: f64,
initial_velocity: f64,
config: niri_config::Animation,
default: niri_config::Animation,
) -> Self {
pub fn new(from: f64, to: f64, initial_velocity: f64, config: niri_config::Animation) -> Self {
let mut rv = Self::ease(from, to, initial_velocity, 0, Curve::EaseOutCubic);
if config.off {
return rv;
}
rv.replace_config(config, default);
rv.replace_config(config);
rv
}
pub fn replace_config(
&mut self,
config: niri_config::Animation,
default: niri_config::Animation,
) {
pub fn replace_config(&mut self, config: niri_config::Animation) {
if config.off {
self.duration = Duration::ZERO;
self.clamped_duration = Duration::ZERO;
return;
}
let start_time = self.start_time;
let current_time = self.current_time;
// Resolve defaults.
let (kind, easing_defaults) = match (config.kind, default.kind) {
// Configured spring.
(configured @ niri_config::AnimationKind::Spring(_), _) => (configured, None),
// Configured nothing, defaults spring.
(
niri_config::AnimationKind::Easing(easing),
defaults @ niri_config::AnimationKind::Spring(_),
) if easing == niri_config::EasingParams::unfilled() => (defaults, None),
// Configured easing or nothing, defaults easing.
(
configured @ niri_config::AnimationKind::Easing(_),
niri_config::AnimationKind::Easing(defaults),
) => (configured, Some(defaults)),
// Configured easing, defaults spring.
(
configured @ niri_config::AnimationKind::Easing(_),
niri_config::AnimationKind::Spring(_),
) => (configured, None),
};
match kind {
match config.kind {
niri_config::AnimationKind::Spring(p) => {
let params = SpringParams::new(p.damping_ratio, f64::from(p.stiffness), p.epsilon);
@@ -104,15 +79,12 @@ impl Animation {
*self = Self::spring(spring);
}
niri_config::AnimationKind::Easing(p) => {
let defaults = easing_defaults.unwrap_or(niri_config::EasingParams::default());
let duration_ms = p.duration_ms.or(defaults.duration_ms).unwrap();
let curve = Curve::from(p.curve.or(defaults.curve).unwrap());
*self = Self::ease(
self.from,
self.to,
self.initial_velocity,
u64::from(duration_ms),
curve,
u64::from(p.duration_ms),
Curve::from(p.curve),
);
}
}
+2 -4
View File
@@ -127,8 +127,7 @@ impl<W: LayoutElement> Monitor<W> {
current_idx,
idx as f64,
0.,
self.options.animations.workspace_switch,
niri_config::Animation::default_workspace_switch(),
self.options.animations.workspace_switch.0,
)));
}
@@ -882,8 +881,7 @@ impl<W: LayoutElement> Monitor<W> {
gesture.current_idx,
new_idx as f64,
velocity,
self.options.animations.workspace_switch,
niri_config::Animation::default_workspace_switch(),
self.options.animations.workspace_switch.0,
)));
true
+4 -16
View File
@@ -150,13 +150,7 @@ impl<W: LayoutElement> Tile<W> {
let change = self.window.size().to_point() - size_from.to_point();
let change = max(change.x.abs(), change.y.abs());
if change > RESIZE_ANIMATION_THRESHOLD {
let anim = Animation::new(
0.,
1.,
0.,
self.options.animations.window_resize,
niri_config::Animation::default_window_resize(),
);
let anim = Animation::new(0., 1., 0., self.options.animations.window_resize.0);
self.resize_animation = Some(ResizeAnimation {
anim,
size_from,
@@ -230,8 +224,7 @@ impl<W: LayoutElement> Tile<W> {
0.,
1.,
0.,
self.options.animations.window_open,
niri_config::Animation::default_window_open(),
self.options.animations.window_open.0,
));
}
@@ -244,23 +237,18 @@ impl<W: LayoutElement> Tile<W> {
}
pub fn animate_move_from(&mut self, from: Point<i32, Logical>) {
self.animate_move_from_with_config(
from,
self.options.animations.window_movement,
niri_config::Animation::default_window_movement(),
);
self.animate_move_from_with_config(from, self.options.animations.window_movement.0);
}
pub fn animate_move_from_with_config(
&mut self,
from: Point<i32, Logical>,
config: niri_config::Animation,
default: niri_config::Animation,
) {
let current_offset = self.render_offset();
self.move_animation = Some(MoveAnimation {
anim: Animation::new(1., 0., 0., config, default),
anim: Animation::new(1., 0., 0., config),
from: from + current_offset,
});
}
+9 -27
View File
@@ -525,8 +525,7 @@ impl<W: LayoutElement> Workspace<W> {
self.view_offset as f64,
new_view_offset as f64,
0.,
self.options.animations.horizontal_view_movement,
niri_config::Animation::default_horizontal_view_movement(),
self.options.animations.horizontal_view_movement.0,
)));
}
@@ -857,8 +856,7 @@ impl<W: LayoutElement> Workspace<W> {
for tile in &mut column.tiles[window_idx + 1..] {
tile.animate_move_from_with_config(
Point::from((0, offset_y)),
self.options.animations.window_resize,
niri_config::Animation::default_window_resize(),
self.options.animations.window_resize.0,
);
}
@@ -1034,16 +1032,14 @@ impl<W: LayoutElement> Workspace<W> {
for col in &mut self.columns[col_idx + 1..] {
col.animate_move_from_with_config(
offset,
self.options.animations.window_resize,
niri_config::Animation::default_window_resize(),
self.options.animations.window_resize.0,
);
}
} else {
for col in &mut self.columns[..=col_idx] {
col.animate_move_from_with_config(
-offset,
self.options.animations.window_resize,
niri_config::Animation::default_window_resize(),
self.options.animations.window_resize.0,
);
}
}
@@ -1074,10 +1070,7 @@ impl<W: LayoutElement> Workspace<W> {
// offset animation if the target was the same; maybe we shouldn't replace in this
// case?
if started_animation {
anim.replace_config(
self.options.animations.window_resize,
niri_config::Animation::default_window_resize(),
);
anim.replace_config(self.options.animations.window_resize.0);
}
}
}
@@ -1149,13 +1142,7 @@ impl<W: LayoutElement> Workspace<W> {
(1., 1.)
};
let anim = Animation::new(
1.,
0.,
0.,
self.options.animations.window_close,
niri_config::Animation::default_window_close(),
);
let anim = Animation::new(1., 0., 0., self.options.animations.window_close.0);
let res = ClosingWindow::new(
renderer,
@@ -2021,8 +2008,7 @@ impl<W: LayoutElement> Workspace<W> {
current_view_offset + delta,
target_view_offset as f64,
velocity,
self.options.animations.horizontal_view_movement,
niri_config::Animation::default_horizontal_view_movement(),
self.options.animations.horizontal_view_movement.0,
)));
// HACK: deal with things like snapping to the right edge of a larger-than-view window.
@@ -2188,8 +2174,7 @@ impl<W: LayoutElement> Column<W> {
pub fn animate_move_from(&mut self, from_x_offset: i32) {
self.animate_move_from_with_config(
from_x_offset,
self.options.animations.window_movement,
niri_config::Animation::default_window_movement(),
self.options.animations.window_movement.0,
);
}
@@ -2197,7 +2182,6 @@ impl<W: LayoutElement> Column<W> {
&mut self,
from_x_offset: i32,
config: niri_config::Animation,
default: niri_config::Animation,
) {
let current_offset = self.move_animation.as_ref().map_or(0., Animation::value);
@@ -2206,7 +2190,6 @@ impl<W: LayoutElement> Column<W> {
0.,
0.,
config,
default,
));
}
@@ -2257,8 +2240,7 @@ impl<W: LayoutElement> Column<W> {
for tile in &mut self.tiles[tile_idx + 1..] {
tile.animate_move_from_with_config(
Point::from((0, offset)),
self.options.animations.window_resize,
niri_config::Animation::default_window_resize(),
self.options.animations.window_resize.0,
);
}
}
+1 -7
View File
@@ -59,13 +59,7 @@ impl ConfigErrorNotification {
fn animation(&self, from: f64, to: f64) -> Animation {
let c = self.config.borrow();
Animation::new(
from,
to,
0.,
c.animations.config_notification_open_close,
niri_config::Animation::default_config_notification_open_close(),
)
Animation::new(from, to, 0., c.animations.config_notification_open_close.0)
}
pub fn show_created(&mut self, created_path: Option<PathBuf>) {