mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-21 02:01:55 +07:00
config: Extract animations
This commit is contained in:
@@ -0,0 +1,667 @@
|
||||
use knuffel::errors::DecodeError;
|
||||
use knuffel::Decode as _;
|
||||
|
||||
use crate::{expect_only_children, parse_arg_node, FloatOrInt};
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Clone, PartialEq)]
|
||||
pub struct Animations {
|
||||
#[knuffel(child)]
|
||||
pub off: bool,
|
||||
#[knuffel(child, unwrap(argument), default = FloatOrInt(1.))]
|
||||
pub slowdown: FloatOrInt<0, { i32::MAX }>,
|
||||
#[knuffel(child, default)]
|
||||
pub workspace_switch: WorkspaceSwitchAnim,
|
||||
#[knuffel(child, default)]
|
||||
pub window_open: WindowOpenAnim,
|
||||
#[knuffel(child, default)]
|
||||
pub window_close: WindowCloseAnim,
|
||||
#[knuffel(child, default)]
|
||||
pub horizontal_view_movement: HorizontalViewMovementAnim,
|
||||
#[knuffel(child, default)]
|
||||
pub window_movement: WindowMovementAnim,
|
||||
#[knuffel(child, default)]
|
||||
pub window_resize: WindowResizeAnim,
|
||||
#[knuffel(child, default)]
|
||||
pub config_notification_open_close: ConfigNotificationOpenCloseAnim,
|
||||
#[knuffel(child, default)]
|
||||
pub exit_confirmation_open_close: ExitConfirmationOpenCloseAnim,
|
||||
#[knuffel(child, default)]
|
||||
pub screenshot_ui_open: ScreenshotUiOpenAnim,
|
||||
#[knuffel(child, default)]
|
||||
pub overview_open_close: OverviewOpenCloseAnim,
|
||||
}
|
||||
|
||||
impl Default for Animations {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
off: false,
|
||||
slowdown: FloatOrInt(1.),
|
||||
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(),
|
||||
exit_confirmation_open_close: Default::default(),
|
||||
screenshot_ui_open: Default::default(),
|
||||
overview_open_close: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Animation {
|
||||
pub off: bool,
|
||||
pub kind: AnimationKind,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum AnimationKind {
|
||||
Easing(EasingParams),
|
||||
Spring(SpringParams),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct EasingParams {
|
||||
pub duration_ms: u32,
|
||||
pub curve: AnimationCurve,
|
||||
}
|
||||
|
||||
#[derive(knuffel::DecodeScalar, Debug, Clone, Copy, PartialEq)]
|
||||
pub enum AnimationCurve {
|
||||
Linear,
|
||||
EaseOutQuad,
|
||||
EaseOutCubic,
|
||||
EaseOutExpo,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct SpringParams {
|
||||
pub damping_ratio: f64,
|
||||
pub stiffness: u32,
|
||||
pub epsilon: f64,
|
||||
}
|
||||
|
||||
#[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, PartialEq)]
|
||||
pub struct WindowOpenAnim {
|
||||
pub anim: Animation,
|
||||
pub custom_shader: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for WindowOpenAnim {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
anim: Animation {
|
||||
off: false,
|
||||
kind: AnimationKind::Easing(EasingParams {
|
||||
duration_ms: 150,
|
||||
curve: AnimationCurve::EaseOutExpo,
|
||||
}),
|
||||
},
|
||||
custom_shader: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct WindowCloseAnim {
|
||||
pub anim: Animation,
|
||||
pub custom_shader: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for WindowCloseAnim {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
anim: Animation {
|
||||
off: false,
|
||||
kind: AnimationKind::Easing(EasingParams {
|
||||
duration_ms: 150,
|
||||
curve: AnimationCurve::EaseOutQuad,
|
||||
}),
|
||||
},
|
||||
custom_shader: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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, PartialEq)]
|
||||
pub struct WindowResizeAnim {
|
||||
pub anim: Animation,
|
||||
pub custom_shader: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for WindowResizeAnim {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
anim: Animation {
|
||||
off: false,
|
||||
kind: AnimationKind::Spring(SpringParams {
|
||||
damping_ratio: 1.,
|
||||
stiffness: 800,
|
||||
epsilon: 0.0001,
|
||||
}),
|
||||
},
|
||||
custom_shader: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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 ExitConfirmationOpenCloseAnim(pub Animation);
|
||||
|
||||
impl Default for ExitConfirmationOpenCloseAnim {
|
||||
fn default() -> Self {
|
||||
Self(Animation {
|
||||
off: false,
|
||||
kind: AnimationKind::Spring(SpringParams {
|
||||
damping_ratio: 0.6,
|
||||
stiffness: 500,
|
||||
epsilon: 0.01,
|
||||
}),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct ScreenshotUiOpenAnim(pub Animation);
|
||||
|
||||
impl Default for ScreenshotUiOpenAnim {
|
||||
fn default() -> Self {
|
||||
Self(Animation {
|
||||
off: false,
|
||||
kind: AnimationKind::Easing(EasingParams {
|
||||
duration_ms: 200,
|
||||
curve: AnimationCurve::EaseOutQuad,
|
||||
}),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct OverviewOpenCloseAnim(pub Animation);
|
||||
|
||||
impl Default for OverviewOpenCloseAnim {
|
||||
fn default() -> Self {
|
||||
Self(Animation {
|
||||
off: false,
|
||||
kind: AnimationKind::Spring(SpringParams {
|
||||
damping_ratio: 1.,
|
||||
stiffness: 800,
|
||||
epsilon: 0.0001,
|
||||
}),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> knuffel::Decode<S> for WorkspaceSwitchAnim
|
||||
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, |_, _| {
|
||||
Ok(false)
|
||||
})?))
|
||||
}
|
||||
}
|
||||
|
||||
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, |_, _| {
|
||||
Ok(false)
|
||||
})?))
|
||||
}
|
||||
}
|
||||
|
||||
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, |_, _| {
|
||||
Ok(false)
|
||||
})?))
|
||||
}
|
||||
}
|
||||
|
||||
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().anim;
|
||||
let mut custom_shader = None;
|
||||
let anim = Animation::decode_node(node, ctx, default, |child, ctx| {
|
||||
if &**child.node_name == "custom-shader" {
|
||||
custom_shader = parse_arg_node("custom-shader", child, ctx)?;
|
||||
Ok(true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
})?;
|
||||
|
||||
Ok(Self {
|
||||
anim,
|
||||
custom_shader,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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().anim;
|
||||
let mut custom_shader = None;
|
||||
let anim = Animation::decode_node(node, ctx, default, |child, ctx| {
|
||||
if &**child.node_name == "custom-shader" {
|
||||
custom_shader = parse_arg_node("custom-shader", child, ctx)?;
|
||||
Ok(true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
})?;
|
||||
|
||||
Ok(Self {
|
||||
anim,
|
||||
custom_shader,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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().anim;
|
||||
let mut custom_shader = None;
|
||||
let anim = Animation::decode_node(node, ctx, default, |child, ctx| {
|
||||
if &**child.node_name == "custom-shader" {
|
||||
custom_shader = parse_arg_node("custom-shader", child, ctx)?;
|
||||
Ok(true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
})?;
|
||||
|
||||
Ok(Self {
|
||||
anim,
|
||||
custom_shader,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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, |_, _| {
|
||||
Ok(false)
|
||||
})?))
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> knuffel::Decode<S> for ExitConfirmationOpenCloseAnim
|
||||
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, |_, _| {
|
||||
Ok(false)
|
||||
})?))
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> knuffel::Decode<S> for ScreenshotUiOpenAnim
|
||||
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, |_, _| {
|
||||
Ok(false)
|
||||
})?))
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> knuffel::Decode<S> for OverviewOpenCloseAnim
|
||||
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, |_, _| {
|
||||
Ok(false)
|
||||
})?))
|
||||
}
|
||||
}
|
||||
|
||||
impl Animation {
|
||||
pub fn new_off() -> Self {
|
||||
Self {
|
||||
off: true,
|
||||
kind: AnimationKind::Easing(EasingParams {
|
||||
duration_ms: 0,
|
||||
curve: AnimationCurve::Linear,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn decode_node<S: knuffel::traits::ErrorSpan>(
|
||||
node: &knuffel::ast::SpannedNode<S>,
|
||||
ctx: &mut knuffel::decode::Context<S>,
|
||||
default: Self,
|
||||
mut process_children: impl FnMut(
|
||||
&knuffel::ast::SpannedNode<S>,
|
||||
&mut knuffel::decode::Context<S>,
|
||||
) -> Result<bool, DecodeError<S>>,
|
||||
) -> 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 = OptionalEasingParams::default();
|
||||
let mut spring_params = None;
|
||||
|
||||
for child in node.children() {
|
||||
match &**child.node_name {
|
||||
"off" => {
|
||||
knuffel::decode::check_flag_node(child, ctx);
|
||||
if off {
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
&child.node_name,
|
||||
"node",
|
||||
"duplicate node `off`, single node expected",
|
||||
));
|
||||
} else {
|
||||
off = true;
|
||||
}
|
||||
}
|
||||
"spring" => {
|
||||
if easing_params != OptionalEasingParams::default() {
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
child,
|
||||
"node",
|
||||
"cannot set both spring and easing parameters at once",
|
||||
));
|
||||
}
|
||||
if spring_params.is_some() {
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
&child.node_name,
|
||||
"node",
|
||||
"duplicate node `spring`, single node expected",
|
||||
));
|
||||
}
|
||||
|
||||
spring_params = Some(SpringParams::decode_node(child, ctx)?);
|
||||
}
|
||||
"duration-ms" => {
|
||||
if spring_params.is_some() {
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
child,
|
||||
"node",
|
||||
"cannot set both spring and easing parameters at once",
|
||||
));
|
||||
}
|
||||
if easing_params.duration_ms.is_some() {
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
&child.node_name,
|
||||
"node",
|
||||
"duplicate node `duration-ms`, single node expected",
|
||||
));
|
||||
}
|
||||
|
||||
easing_params.duration_ms = Some(parse_arg_node("duration-ms", child, ctx)?);
|
||||
}
|
||||
"curve" => {
|
||||
if spring_params.is_some() {
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
child,
|
||||
"node",
|
||||
"cannot set both spring and easing parameters at once",
|
||||
));
|
||||
}
|
||||
if easing_params.curve.is_some() {
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
&child.node_name,
|
||||
"node",
|
||||
"duplicate node `curve`, single node expected",
|
||||
));
|
||||
}
|
||||
|
||||
easing_params.curve = Some(parse_arg_node("curve", child, ctx)?);
|
||||
}
|
||||
name_str => {
|
||||
if !process_children(child, ctx)? {
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
child,
|
||||
"node",
|
||||
format!("unexpected node `{}`", name_str.escape_default()),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
// 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 })
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> knuffel::Decode<S> for SpringParams
|
||||
where
|
||||
S: knuffel::traits::ErrorSpan,
|
||||
{
|
||||
fn decode_node(
|
||||
node: &knuffel::ast::SpannedNode<S>,
|
||||
ctx: &mut knuffel::decode::Context<S>,
|
||||
) -> Result<Self, DecodeError<S>> {
|
||||
if let Some(type_name) = &node.type_name {
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
type_name,
|
||||
"type name",
|
||||
"no type name expected for this node",
|
||||
));
|
||||
}
|
||||
if let Some(val) = node.arguments.first() {
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
&val.literal,
|
||||
"argument",
|
||||
"unexpected argument",
|
||||
));
|
||||
}
|
||||
for child in node.children() {
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
child,
|
||||
"node",
|
||||
format!("unexpected node `{}`", child.node_name.escape_default()),
|
||||
));
|
||||
}
|
||||
|
||||
let mut damping_ratio = None;
|
||||
let mut stiffness = None;
|
||||
let mut epsilon = None;
|
||||
for (name, val) in &node.properties {
|
||||
match &***name {
|
||||
"damping-ratio" => {
|
||||
damping_ratio = Some(knuffel::traits::DecodeScalar::decode(val, ctx)?);
|
||||
}
|
||||
"stiffness" => {
|
||||
stiffness = Some(knuffel::traits::DecodeScalar::decode(val, ctx)?);
|
||||
}
|
||||
"epsilon" => {
|
||||
epsilon = Some(knuffel::traits::DecodeScalar::decode(val, ctx)?);
|
||||
}
|
||||
name_str => {
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
name,
|
||||
"property",
|
||||
format!("unexpected property `{}`", name_str.escape_default()),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
let damping_ratio = damping_ratio
|
||||
.ok_or_else(|| DecodeError::missing(node, "property `damping-ratio` is required"))?;
|
||||
let stiffness = stiffness
|
||||
.ok_or_else(|| DecodeError::missing(node, "property `stiffness` is required"))?;
|
||||
let epsilon =
|
||||
epsilon.ok_or_else(|| DecodeError::missing(node, "property `epsilon` is required"))?;
|
||||
|
||||
if !(0.1..=10.).contains(&damping_ratio) {
|
||||
ctx.emit_error(DecodeError::conversion(
|
||||
node,
|
||||
"damping-ratio must be between 0.1 and 10.0",
|
||||
));
|
||||
}
|
||||
if stiffness < 1 {
|
||||
ctx.emit_error(DecodeError::conversion(node, "stiffness must be >= 1"));
|
||||
}
|
||||
if !(0.00001..=0.1).contains(&epsilon) {
|
||||
ctx.emit_error(DecodeError::conversion(
|
||||
node,
|
||||
"epsilon must be between 0.00001 and 0.1",
|
||||
));
|
||||
}
|
||||
|
||||
Ok(SpringParams {
|
||||
damping_ratio,
|
||||
stiffness,
|
||||
epsilon,
|
||||
})
|
||||
}
|
||||
}
|
||||
+4
-664
@@ -12,7 +12,6 @@ use std::time::Duration;
|
||||
|
||||
use bitflags::bitflags;
|
||||
use knuffel::errors::DecodeError;
|
||||
use knuffel::Decode as _;
|
||||
use miette::{miette, Context, IntoDiagnostic};
|
||||
use niri_ipc::{
|
||||
ColumnDisplay, ConfiguredMode, LayoutSwitchTarget, PositionChange, SizeChange, Transform,
|
||||
@@ -27,10 +26,14 @@ use smithay::reexports::input;
|
||||
pub const DEFAULT_BACKGROUND_COLOR: Color = Color::from_array_unpremul([0.25, 0.25, 0.25, 1.]);
|
||||
pub const DEFAULT_BACKDROP_COLOR: Color = Color::from_array_unpremul([0.15, 0.15, 0.15, 1.]);
|
||||
|
||||
pub mod animations;
|
||||
pub mod layer_rule;
|
||||
pub mod utils;
|
||||
pub mod window_rule;
|
||||
|
||||
pub use crate::animations::{
|
||||
Animation, AnimationCurve, AnimationKind, Animations, EasingParams, SpringParams,
|
||||
};
|
||||
pub use crate::layer_rule::LayerRule;
|
||||
pub use crate::window_rule::{FloatingPosition, RelativeTo, WindowRule};
|
||||
|
||||
@@ -1089,261 +1092,6 @@ pub struct Clipboard {
|
||||
pub disable_primary: bool,
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Clone, PartialEq)]
|
||||
pub struct Animations {
|
||||
#[knuffel(child)]
|
||||
pub off: bool,
|
||||
#[knuffel(child, unwrap(argument), default = FloatOrInt(1.))]
|
||||
pub slowdown: FloatOrInt<0, { i32::MAX }>,
|
||||
#[knuffel(child, default)]
|
||||
pub workspace_switch: WorkspaceSwitchAnim,
|
||||
#[knuffel(child, default)]
|
||||
pub window_open: WindowOpenAnim,
|
||||
#[knuffel(child, default)]
|
||||
pub window_close: WindowCloseAnim,
|
||||
#[knuffel(child, default)]
|
||||
pub horizontal_view_movement: HorizontalViewMovementAnim,
|
||||
#[knuffel(child, default)]
|
||||
pub window_movement: WindowMovementAnim,
|
||||
#[knuffel(child, default)]
|
||||
pub window_resize: WindowResizeAnim,
|
||||
#[knuffel(child, default)]
|
||||
pub config_notification_open_close: ConfigNotificationOpenCloseAnim,
|
||||
#[knuffel(child, default)]
|
||||
pub exit_confirmation_open_close: ExitConfirmationOpenCloseAnim,
|
||||
#[knuffel(child, default)]
|
||||
pub screenshot_ui_open: ScreenshotUiOpenAnim,
|
||||
#[knuffel(child, default)]
|
||||
pub overview_open_close: OverviewOpenCloseAnim,
|
||||
}
|
||||
|
||||
impl Default for Animations {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
off: false,
|
||||
slowdown: FloatOrInt(1.),
|
||||
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(),
|
||||
exit_confirmation_open_close: Default::default(),
|
||||
screenshot_ui_open: Default::default(),
|
||||
overview_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, PartialEq)]
|
||||
pub struct WindowOpenAnim {
|
||||
pub anim: Animation,
|
||||
pub custom_shader: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for WindowOpenAnim {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
anim: Animation {
|
||||
off: false,
|
||||
kind: AnimationKind::Easing(EasingParams {
|
||||
duration_ms: 150,
|
||||
curve: AnimationCurve::EaseOutExpo,
|
||||
}),
|
||||
},
|
||||
custom_shader: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct WindowCloseAnim {
|
||||
pub anim: Animation,
|
||||
pub custom_shader: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for WindowCloseAnim {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
anim: Animation {
|
||||
off: false,
|
||||
kind: AnimationKind::Easing(EasingParams {
|
||||
duration_ms: 150,
|
||||
curve: AnimationCurve::EaseOutQuad,
|
||||
}),
|
||||
},
|
||||
custom_shader: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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, PartialEq)]
|
||||
pub struct WindowResizeAnim {
|
||||
pub anim: Animation,
|
||||
pub custom_shader: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for WindowResizeAnim {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
anim: Animation {
|
||||
off: false,
|
||||
kind: AnimationKind::Spring(SpringParams {
|
||||
damping_ratio: 1.,
|
||||
stiffness: 800,
|
||||
epsilon: 0.0001,
|
||||
}),
|
||||
},
|
||||
custom_shader: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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 ExitConfirmationOpenCloseAnim(pub Animation);
|
||||
|
||||
impl Default for ExitConfirmationOpenCloseAnim {
|
||||
fn default() -> Self {
|
||||
Self(Animation {
|
||||
off: false,
|
||||
kind: AnimationKind::Spring(SpringParams {
|
||||
damping_ratio: 0.6,
|
||||
stiffness: 500,
|
||||
epsilon: 0.01,
|
||||
}),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct ScreenshotUiOpenAnim(pub Animation);
|
||||
|
||||
impl Default for ScreenshotUiOpenAnim {
|
||||
fn default() -> Self {
|
||||
Self(Animation {
|
||||
off: false,
|
||||
kind: AnimationKind::Easing(EasingParams {
|
||||
duration_ms: 200,
|
||||
curve: AnimationCurve::EaseOutQuad,
|
||||
}),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct OverviewOpenCloseAnim(pub Animation);
|
||||
|
||||
impl Default for OverviewOpenCloseAnim {
|
||||
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,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum AnimationKind {
|
||||
Easing(EasingParams),
|
||||
Spring(SpringParams),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct EasingParams {
|
||||
pub duration_ms: u32,
|
||||
pub curve: AnimationCurve,
|
||||
}
|
||||
|
||||
#[derive(knuffel::DecodeScalar, Debug, Clone, Copy, PartialEq)]
|
||||
pub enum AnimationCurve {
|
||||
Linear,
|
||||
EaseOutQuad,
|
||||
EaseOutCubic,
|
||||
EaseOutExpo,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct SpringParams {
|
||||
pub damping_ratio: f64,
|
||||
pub stiffness: u32,
|
||||
pub epsilon: f64,
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, Clone, Copy, PartialEq)]
|
||||
pub struct Gestures {
|
||||
#[knuffel(child, default)]
|
||||
@@ -3061,51 +2809,6 @@ fn parse_arg_node<S: knuffel::traits::ErrorSpan, T: knuffel::traits::DecodeScala
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
impl<S> knuffel::Decode<S> for WorkspaceSwitchAnim
|
||||
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, |_, _| {
|
||||
Ok(false)
|
||||
})?))
|
||||
}
|
||||
}
|
||||
|
||||
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, |_, _| {
|
||||
Ok(false)
|
||||
})?))
|
||||
}
|
||||
}
|
||||
|
||||
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, |_, _| {
|
||||
Ok(false)
|
||||
})?))
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: knuffel::traits::ErrorSpan> knuffel::DecodeScalar<S> for WorkspaceName {
|
||||
fn type_check(
|
||||
type_name: &Option<knuffel::span::Spanned<knuffel::ast::TypeName, S>>,
|
||||
@@ -3157,369 +2860,6 @@ impl<S: knuffel::traits::ErrorSpan> knuffel::DecodeScalar<S> for WorkspaceName {
|
||||
}
|
||||
}
|
||||
|
||||
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().anim;
|
||||
let mut custom_shader = None;
|
||||
let anim = Animation::decode_node(node, ctx, default, |child, ctx| {
|
||||
if &**child.node_name == "custom-shader" {
|
||||
custom_shader = parse_arg_node("custom-shader", child, ctx)?;
|
||||
Ok(true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
})?;
|
||||
|
||||
Ok(Self {
|
||||
anim,
|
||||
custom_shader,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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().anim;
|
||||
let mut custom_shader = None;
|
||||
let anim = Animation::decode_node(node, ctx, default, |child, ctx| {
|
||||
if &**child.node_name == "custom-shader" {
|
||||
custom_shader = parse_arg_node("custom-shader", child, ctx)?;
|
||||
Ok(true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
})?;
|
||||
|
||||
Ok(Self {
|
||||
anim,
|
||||
custom_shader,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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().anim;
|
||||
let mut custom_shader = None;
|
||||
let anim = Animation::decode_node(node, ctx, default, |child, ctx| {
|
||||
if &**child.node_name == "custom-shader" {
|
||||
custom_shader = parse_arg_node("custom-shader", child, ctx)?;
|
||||
Ok(true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
})?;
|
||||
|
||||
Ok(Self {
|
||||
anim,
|
||||
custom_shader,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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, |_, _| {
|
||||
Ok(false)
|
||||
})?))
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> knuffel::Decode<S> for ExitConfirmationOpenCloseAnim
|
||||
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, |_, _| {
|
||||
Ok(false)
|
||||
})?))
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> knuffel::Decode<S> for ScreenshotUiOpenAnim
|
||||
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, |_, _| {
|
||||
Ok(false)
|
||||
})?))
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> knuffel::Decode<S> for OverviewOpenCloseAnim
|
||||
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, |_, _| {
|
||||
Ok(false)
|
||||
})?))
|
||||
}
|
||||
}
|
||||
|
||||
impl Animation {
|
||||
pub fn new_off() -> Self {
|
||||
Self {
|
||||
off: true,
|
||||
kind: AnimationKind::Easing(EasingParams {
|
||||
duration_ms: 0,
|
||||
curve: AnimationCurve::Linear,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn decode_node<S: knuffel::traits::ErrorSpan>(
|
||||
node: &knuffel::ast::SpannedNode<S>,
|
||||
ctx: &mut knuffel::decode::Context<S>,
|
||||
default: Self,
|
||||
mut process_children: impl FnMut(
|
||||
&knuffel::ast::SpannedNode<S>,
|
||||
&mut knuffel::decode::Context<S>,
|
||||
) -> Result<bool, DecodeError<S>>,
|
||||
) -> 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 = OptionalEasingParams::default();
|
||||
let mut spring_params = None;
|
||||
|
||||
for child in node.children() {
|
||||
match &**child.node_name {
|
||||
"off" => {
|
||||
knuffel::decode::check_flag_node(child, ctx);
|
||||
if off {
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
&child.node_name,
|
||||
"node",
|
||||
"duplicate node `off`, single node expected",
|
||||
));
|
||||
} else {
|
||||
off = true;
|
||||
}
|
||||
}
|
||||
"spring" => {
|
||||
if easing_params != OptionalEasingParams::default() {
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
child,
|
||||
"node",
|
||||
"cannot set both spring and easing parameters at once",
|
||||
));
|
||||
}
|
||||
if spring_params.is_some() {
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
&child.node_name,
|
||||
"node",
|
||||
"duplicate node `spring`, single node expected",
|
||||
));
|
||||
}
|
||||
|
||||
spring_params = Some(SpringParams::decode_node(child, ctx)?);
|
||||
}
|
||||
"duration-ms" => {
|
||||
if spring_params.is_some() {
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
child,
|
||||
"node",
|
||||
"cannot set both spring and easing parameters at once",
|
||||
));
|
||||
}
|
||||
if easing_params.duration_ms.is_some() {
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
&child.node_name,
|
||||
"node",
|
||||
"duplicate node `duration-ms`, single node expected",
|
||||
));
|
||||
}
|
||||
|
||||
easing_params.duration_ms = Some(parse_arg_node("duration-ms", child, ctx)?);
|
||||
}
|
||||
"curve" => {
|
||||
if spring_params.is_some() {
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
child,
|
||||
"node",
|
||||
"cannot set both spring and easing parameters at once",
|
||||
));
|
||||
}
|
||||
if easing_params.curve.is_some() {
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
&child.node_name,
|
||||
"node",
|
||||
"duplicate node `curve`, single node expected",
|
||||
));
|
||||
}
|
||||
|
||||
easing_params.curve = Some(parse_arg_node("curve", child, ctx)?);
|
||||
}
|
||||
name_str => {
|
||||
if !process_children(child, ctx)? {
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
child,
|
||||
"node",
|
||||
format!("unexpected node `{}`", name_str.escape_default()),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
// 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 })
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> knuffel::Decode<S> for SpringParams
|
||||
where
|
||||
S: knuffel::traits::ErrorSpan,
|
||||
{
|
||||
fn decode_node(
|
||||
node: &knuffel::ast::SpannedNode<S>,
|
||||
ctx: &mut knuffel::decode::Context<S>,
|
||||
) -> Result<Self, DecodeError<S>> {
|
||||
if let Some(type_name) = &node.type_name {
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
type_name,
|
||||
"type name",
|
||||
"no type name expected for this node",
|
||||
));
|
||||
}
|
||||
if let Some(val) = node.arguments.first() {
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
&val.literal,
|
||||
"argument",
|
||||
"unexpected argument",
|
||||
));
|
||||
}
|
||||
for child in node.children() {
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
child,
|
||||
"node",
|
||||
format!("unexpected node `{}`", child.node_name.escape_default()),
|
||||
));
|
||||
}
|
||||
|
||||
let mut damping_ratio = None;
|
||||
let mut stiffness = None;
|
||||
let mut epsilon = None;
|
||||
for (name, val) in &node.properties {
|
||||
match &***name {
|
||||
"damping-ratio" => {
|
||||
damping_ratio = Some(knuffel::traits::DecodeScalar::decode(val, ctx)?);
|
||||
}
|
||||
"stiffness" => {
|
||||
stiffness = Some(knuffel::traits::DecodeScalar::decode(val, ctx)?);
|
||||
}
|
||||
"epsilon" => {
|
||||
epsilon = Some(knuffel::traits::DecodeScalar::decode(val, ctx)?);
|
||||
}
|
||||
name_str => {
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
name,
|
||||
"property",
|
||||
format!("unexpected property `{}`", name_str.escape_default()),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
let damping_ratio = damping_ratio
|
||||
.ok_or_else(|| DecodeError::missing(node, "property `damping-ratio` is required"))?;
|
||||
let stiffness = stiffness
|
||||
.ok_or_else(|| DecodeError::missing(node, "property `stiffness` is required"))?;
|
||||
let epsilon =
|
||||
epsilon.ok_or_else(|| DecodeError::missing(node, "property `epsilon` is required"))?;
|
||||
|
||||
if !(0.1..=10.).contains(&damping_ratio) {
|
||||
ctx.emit_error(DecodeError::conversion(
|
||||
node,
|
||||
"damping-ratio must be between 0.1 and 10.0",
|
||||
));
|
||||
}
|
||||
if stiffness < 1 {
|
||||
ctx.emit_error(DecodeError::conversion(node, "stiffness must be >= 1"));
|
||||
}
|
||||
if !(0.00001..=0.1).contains(&epsilon) {
|
||||
ctx.emit_error(DecodeError::conversion(
|
||||
node,
|
||||
"epsilon must be between 0.00001 and 0.1",
|
||||
));
|
||||
}
|
||||
|
||||
Ok(SpringParams {
|
||||
damping_ratio,
|
||||
stiffness,
|
||||
epsilon,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> knuffel::Decode<S> for CornerRadius
|
||||
where
|
||||
S: knuffel::traits::ErrorSpan,
|
||||
|
||||
Reference in New Issue
Block a user