Add animation style toggle
This commit is contained in:
parent
02222d5189
commit
e7f9a5cb09
15 changed files with 238 additions and 20 deletions
|
|
@ -6,11 +6,11 @@ be handled deliberately.
|
||||||
|
|
||||||
## Accepted Decisions
|
## Accepted Decisions
|
||||||
|
|
||||||
- The first landed slice is linear interpolation only, disabled by default.
|
- The first landed slice is plain interpolation only, disabled by default.
|
||||||
- Animation is presentation-only. Logical layout, input hit testing, focus, and
|
- Animation is presentation-only. Logical layout, input hit testing, focus, and
|
||||||
Wayland configure state use final geometry immediately.
|
Wayland configure state use final geometry immediately.
|
||||||
- Pointer drag and resize initiated by the mouse or tablet do not animate.
|
- Pointer drag and resize initiated by the mouse or tablet do not animate.
|
||||||
- Linear animations restart only for windows whose destination changes. Other
|
- Plain animations restart only for windows whose destination changes. Other
|
||||||
in-flight windows keep their existing timelines.
|
in-flight windows keep their existing timelines.
|
||||||
- Spawn-in uses scale and position for newly mapped tiled and floating app
|
- Spawn-in uses scale and position for newly mapped tiled and floating app
|
||||||
windows. Layer-shell, overlay, override-redirect, and fullscreen surfaces do
|
windows. Layer-shell, overlay, override-redirect, and fullscreen surfaces do
|
||||||
|
|
@ -19,7 +19,7 @@ be handled deliberately.
|
||||||
destroy.
|
destroy.
|
||||||
- Command-driven tile-to-float and float-to-tile transitions may animate.
|
- Command-driven tile-to-float and float-to-tile transitions may animate.
|
||||||
Protocol drag/drop paths do not.
|
Protocol drag/drop paths do not.
|
||||||
- The no-overlap multiphase system is a separate phase after the linear path is
|
- The no-overlap multiphase system is a separate phase after the plain path is
|
||||||
working and testable.
|
working and testable.
|
||||||
- Content freezing will use retained per-surface texture references, not a full
|
- Content freezing will use retained per-surface texture references, not a full
|
||||||
offscreen snapshot as the default design.
|
offscreen snapshot as the default design.
|
||||||
|
|
@ -34,7 +34,7 @@ be handled deliberately.
|
||||||
below roughly one quarter of the relevant full size. The implementation may
|
below roughly one quarter of the relevant full size. The implementation may
|
||||||
enforce a conservative sanity minimum, and pathological cases may fall back.
|
enforce a conservative sanity minimum, and pathological cases may fall back.
|
||||||
- If the no-overlap planner cannot produce a legal sequence, only the affected
|
- If the no-overlap planner cannot produce a legal sequence, only the affected
|
||||||
group should fall back to linear animation. This is expected to be rare for
|
group should fall back to plain animation. This is expected to be rare for
|
||||||
valid tiling layouts.
|
valid tiling layouts.
|
||||||
- When entering mono mode, the active child should animate to the mono geometry.
|
- When entering mono mode, the active child should animate to the mono geometry.
|
||||||
Inactive siblings may snap invisible. Floats may overlap normally and do not
|
Inactive siblings may snap invisible. Floats may overlap normally and do not
|
||||||
|
|
@ -285,6 +285,7 @@ Phase 1 should expose a disabled-by-default setting for:
|
||||||
|
|
||||||
- enabled/disabled
|
- enabled/disabled
|
||||||
- duration
|
- duration
|
||||||
|
- style: `plain` or `multiphase`
|
||||||
- curve preset or cubic bezier
|
- curve preset or cubic bezier
|
||||||
|
|
||||||
Initial TOML shape:
|
Initial TOML shape:
|
||||||
|
|
@ -293,6 +294,7 @@ Initial TOML shape:
|
||||||
[animations]
|
[animations]
|
||||||
enabled = false
|
enabled = false
|
||||||
duration-ms = 160
|
duration-ms = 160
|
||||||
|
style = "multiphase"
|
||||||
curve = "ease-out"
|
curve = "ease-out"
|
||||||
# or:
|
# or:
|
||||||
curve = [0.25, 0.1, 0.25, 1.0]
|
curve = [0.25, 0.1, 0.25, 1.0]
|
||||||
|
|
|
||||||
|
|
@ -24,9 +24,24 @@ Relevant internal config hooks:
|
||||||
|
|
||||||
- `SetAnimationsEnabled`
|
- `SetAnimationsEnabled`
|
||||||
- `SetAnimationDurationMs`
|
- `SetAnimationDurationMs`
|
||||||
|
- `SetAnimationStyle`
|
||||||
- `SetAnimationCurve`
|
- `SetAnimationCurve`
|
||||||
- `SetAnimationCubicBezier`
|
- `SetAnimationCubicBezier`
|
||||||
|
|
||||||
|
TOML example:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[animations]
|
||||||
|
enabled = true
|
||||||
|
duration-ms = 600
|
||||||
|
style = "multiphase"
|
||||||
|
curve = "ease-out"
|
||||||
|
```
|
||||||
|
|
||||||
|
Set `style = "plain"` to force ordinary one-step movement interpolation while
|
||||||
|
keeping the configured curve. `curve = "linear"` only changes easing; it does
|
||||||
|
not select the plain animation style.
|
||||||
|
|
||||||
Current curve IDs in code:
|
Current curve IDs in code:
|
||||||
|
|
||||||
- `0`: linear
|
- `0`: linear
|
||||||
|
|
@ -37,19 +52,20 @@ Current curve IDs in code:
|
||||||
|
|
||||||
## Enabling Multiphase Tests
|
## Enabling Multiphase Tests
|
||||||
|
|
||||||
There is currently no separate user-facing multiphase toggle. To exercise the
|
To exercise the multiphase planner:
|
||||||
multiphase planner:
|
|
||||||
|
|
||||||
1. Enable animations with `SetAnimationsEnabled`.
|
1. Enable animations with `SetAnimationsEnabled`.
|
||||||
2. Set a slow duration with `SetAnimationDurationMs`, around `400-700ms`.
|
2. Set a slow duration with `SetAnimationDurationMs`, around `400-700ms`.
|
||||||
3. Use tiled layout commands that are wired through `State::with_layout_animations`.
|
3. Select `style = "multiphase"` in TOML, or call `SetAnimationStyle` with
|
||||||
4. Use layouts where at least two tiled windows change geometry in the same
|
`AnimationStyle::MULTIPHASE`.
|
||||||
|
4. Use tiled layout commands that are wired through `State::with_layout_animations`.
|
||||||
|
5. Use layouts where at least two tiled windows change geometry in the same
|
||||||
container layout batch.
|
container layout batch.
|
||||||
|
|
||||||
The compositor then attempts multiphase planning automatically when the batched
|
The compositor then attempts multiphase planning automatically when the batched
|
||||||
layout pass completes. If the planner proves a legal no-overlap sequence, that
|
layout pass completes. If the planner proves a legal no-overlap sequence, that
|
||||||
group uses phased animation. If it cannot prove one, only that motion group falls
|
group uses phased animation. If it cannot prove one, only that motion group falls
|
||||||
back to ordinary linear animation.
|
back to ordinary plain animation.
|
||||||
|
|
||||||
Good command families for multiphase testing:
|
Good command families for multiphase testing:
|
||||||
|
|
||||||
|
|
@ -64,7 +80,7 @@ Good command families for multiphase testing:
|
||||||
|
|
||||||
These paths should not be used as evidence of multiphase behavior:
|
These paths should not be used as evidence of multiphase behavior:
|
||||||
|
|
||||||
- tile-to-float and float-to-tile, which deliberately use linear animation
|
- tile-to-float and float-to-tile, which deliberately use plain animation
|
||||||
- command-driven floating move/resize, which may animate but can overlap
|
- command-driven floating move/resize, which may animate but can overlap
|
||||||
- pointer or tablet drag/resize, which should not animate
|
- pointer or tablet drag/resize, which should not animate
|
||||||
- spawn-in and spawn-out, which are single-phase and use the configured curve
|
- spawn-in and spawn-out, which are single-phase and use the configured curve
|
||||||
|
|
@ -73,7 +89,7 @@ These paths should not be used as evidence of multiphase behavior:
|
||||||
|
|
||||||
Useful debug signal:
|
Useful debug signal:
|
||||||
|
|
||||||
- `falling back to linear layout animation for group ...` means the group entered
|
- `falling back to plain layout animation for group ...` means the group entered
|
||||||
the multiphase gate but the planner rejected it. That is acceptable for
|
the multiphase gate but the planner rejected it. That is acceptable for
|
||||||
unsupported patterns, but unexpected for the supported swap/extraction cases
|
unsupported patterns, but unexpected for the supported swap/extraction cases
|
||||||
below.
|
below.
|
||||||
|
|
|
||||||
|
|
@ -1035,6 +1035,10 @@ impl ConfigClient {
|
||||||
self.send(&ClientMessage::SetAnimationCurve { curve });
|
self.send(&ClientMessage::SetAnimationCurve { curve });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_animation_style(&self, style: u32) {
|
||||||
|
self.send(&ClientMessage::SetAnimationStyle { style });
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_animation_cubic_bezier(&self, x1: f32, y1: f32, x2: f32, y2: f32) {
|
pub fn set_animation_cubic_bezier(&self, x1: f32, y1: f32, x2: f32, y2: f32) {
|
||||||
self.send(&ClientMessage::SetAnimationCubicBezier { x1, y1, x2, y2 });
|
self.send(&ClientMessage::SetAnimationCubicBezier { x1, y1, x2, y2 });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -554,6 +554,9 @@ pub enum ClientMessage<'a> {
|
||||||
SetAnimationCurve {
|
SetAnimationCurve {
|
||||||
curve: u32,
|
curve: u32,
|
||||||
},
|
},
|
||||||
|
SetAnimationStyle {
|
||||||
|
style: u32,
|
||||||
|
},
|
||||||
SetAnimationCubicBezier {
|
SetAnimationCubicBezier {
|
||||||
x1: f32,
|
x1: f32,
|
||||||
y1: f32,
|
y1: f32,
|
||||||
|
|
|
||||||
|
|
@ -115,6 +115,15 @@ impl AnimationCurve {
|
||||||
pub const EASE_IN_OUT: Self = Self(4);
|
pub const EASE_IN_OUT: Self = Self(4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The presentation style used for tiled window movement animations.
|
||||||
|
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct AnimationStyle(pub u32);
|
||||||
|
|
||||||
|
impl AnimationStyle {
|
||||||
|
pub const PLAIN: Self = Self(0);
|
||||||
|
pub const MULTIPHASE: Self = Self(1);
|
||||||
|
}
|
||||||
|
|
||||||
/// Exits the compositor.
|
/// Exits the compositor.
|
||||||
pub fn quit() {
|
pub fn quit() {
|
||||||
get!().quit()
|
get!().quit()
|
||||||
|
|
@ -320,6 +329,13 @@ pub fn set_animation_curve(curve: AnimationCurve) {
|
||||||
get!().set_animation_curve(curve.0);
|
get!().set_animation_curve(curve.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the presentation style used for tiled window movement animations.
|
||||||
|
///
|
||||||
|
/// The default is [`AnimationStyle::MULTIPHASE`].
|
||||||
|
pub fn set_animation_style(style: AnimationStyle) {
|
||||||
|
get!().set_animation_style(style.0);
|
||||||
|
}
|
||||||
|
|
||||||
/// Sets a custom cubic-bezier curve used by tiled window animations.
|
/// Sets a custom cubic-bezier curve used by tiled window animations.
|
||||||
///
|
///
|
||||||
/// `x1` and `x2` must be between `0.0` and `1.0`. The curve starts at `(0, 0)`
|
/// `x1` and `x2` must be between `0.0` and `1.0`. The curve starts at `(0, 0)`
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,22 @@ pub enum AnimationCurve {
|
||||||
Piecewise(PiecewiseCurve),
|
Piecewise(PiecewiseCurve),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub enum AnimationStyle {
|
||||||
|
Plain,
|
||||||
|
Multiphase,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AnimationStyle {
|
||||||
|
pub fn from_config(value: u32) -> Option<Self> {
|
||||||
|
match value {
|
||||||
|
0 => Some(Self::Plain),
|
||||||
|
1 => Some(Self::Multiphase),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl AnimationCurve {
|
impl AnimationCurve {
|
||||||
pub fn from_config(value: u32) -> Self {
|
pub fn from_config(value: u32) -> Self {
|
||||||
match value {
|
match value {
|
||||||
|
|
@ -129,6 +145,7 @@ pub struct AnimationState {
|
||||||
pub enabled: Cell<bool>,
|
pub enabled: Cell<bool>,
|
||||||
pub duration_ms: Cell<u32>,
|
pub duration_ms: Cell<u32>,
|
||||||
pub curve: Cell<AnimationCurve>,
|
pub curve: Cell<AnimationCurve>,
|
||||||
|
pub style: Cell<AnimationStyle>,
|
||||||
windows: RefCell<AHashMap<NodeId, WindowAnimation>>,
|
windows: RefCell<AHashMap<NodeId, WindowAnimation>>,
|
||||||
phased: RefCell<AHashMap<NodeId, PhasedWindowAnimation>>,
|
phased: RefCell<AHashMap<NodeId, PhasedWindowAnimation>>,
|
||||||
exits: RefCell<Vec<ExitAnimation>>,
|
exits: RefCell<Vec<ExitAnimation>>,
|
||||||
|
|
@ -267,6 +284,7 @@ impl Default for AnimationState {
|
||||||
enabled: Cell::new(false),
|
enabled: Cell::new(false),
|
||||||
duration_ms: Cell::new(DEFAULT_DURATION_MS),
|
duration_ms: Cell::new(DEFAULT_DURATION_MS),
|
||||||
curve: Cell::new(AnimationCurve::from_config(3)),
|
curve: Cell::new(AnimationCurve::from_config(3)),
|
||||||
|
style: Cell::new(AnimationStyle::Multiphase),
|
||||||
windows: Default::default(),
|
windows: Default::default(),
|
||||||
phased: Default::default(),
|
phased: Default::default(),
|
||||||
exits: Default::default(),
|
exits: Default::default(),
|
||||||
|
|
|
||||||
|
|
@ -364,6 +364,7 @@ fn start_compositor2(
|
||||||
layout_animations_requested: Default::default(),
|
layout_animations_requested: Default::default(),
|
||||||
layout_animations_active: Default::default(),
|
layout_animations_active: Default::default(),
|
||||||
layout_animation_curve_override: Default::default(),
|
layout_animation_curve_override: Default::default(),
|
||||||
|
layout_animation_style_override: Default::default(),
|
||||||
layout_animation_batch: Default::default(),
|
layout_animation_batch: Default::default(),
|
||||||
suppress_animations_for_next_layout: Default::default(),
|
suppress_animations_for_next_layout: Default::default(),
|
||||||
toplevels: Default::default(),
|
toplevels: Default::default(),
|
||||||
|
|
|
||||||
|
|
@ -1005,6 +1005,12 @@ impl ConfigProxyHandler {
|
||||||
self.state.set_animation_curve(curve);
|
self.state.set_animation_curve(curve);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_set_animation_style(&self, style: u32) {
|
||||||
|
if !self.state.set_animation_style(style) {
|
||||||
|
log::warn!("Ignoring invalid animation style");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn handle_set_animation_cubic_bezier(&self, x1: f32, y1: f32, x2: f32, y2: f32) {
|
fn handle_set_animation_cubic_bezier(&self, x1: f32, y1: f32, x2: f32, y2: f32) {
|
||||||
if !self.state.set_animation_cubic_bezier(x1, y1, x2, y2) {
|
if !self.state.set_animation_cubic_bezier(x1, y1, x2, y2) {
|
||||||
log::warn!("Ignoring invalid animation cubic-bezier curve");
|
log::warn!("Ignoring invalid animation cubic-bezier curve");
|
||||||
|
|
@ -3249,6 +3255,7 @@ impl ConfigProxyHandler {
|
||||||
self.handle_set_animation_duration_ms(duration_ms)
|
self.handle_set_animation_duration_ms(duration_ms)
|
||||||
}
|
}
|
||||||
ClientMessage::SetAnimationCurve { curve } => self.handle_set_animation_curve(curve),
|
ClientMessage::SetAnimationCurve { curve } => self.handle_set_animation_curve(curve),
|
||||||
|
ClientMessage::SetAnimationStyle { style } => self.handle_set_animation_style(style),
|
||||||
ClientMessage::SetAnimationCubicBezier { x1, y1, x2, y2 } => {
|
ClientMessage::SetAnimationCubicBezier { x1, y1, x2, y2 } => {
|
||||||
self.handle_set_animation_cubic_bezier(x1, y1, x2, y2)
|
self.handle_set_animation_cubic_bezier(x1, y1, x2, y2)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
70
src/state.rs
70
src/state.rs
|
|
@ -3,7 +3,8 @@ use {
|
||||||
acceptor::Acceptor,
|
acceptor::Acceptor,
|
||||||
allocator::BufferObject,
|
allocator::BufferObject,
|
||||||
animation::{
|
animation::{
|
||||||
AnimationCurve, AnimationState, AnimationTick, RetainedExitLayer, RetainedToplevel,
|
AnimationCurve, AnimationState, AnimationStyle, AnimationTick, RetainedExitLayer,
|
||||||
|
RetainedToplevel,
|
||||||
expand_damage_rect,
|
expand_damage_rect,
|
||||||
multiphase::{
|
multiphase::{
|
||||||
MultiphaseRequest, MultiphaseWindow, MultiphaseWindowHierarchy,
|
MultiphaseRequest, MultiphaseWindow, MultiphaseWindowHierarchy,
|
||||||
|
|
@ -168,6 +169,7 @@ pub(crate) struct LayoutAnimationCandidate {
|
||||||
old: Rect,
|
old: Rect,
|
||||||
new: Rect,
|
new: Rect,
|
||||||
curve: AnimationCurve,
|
curve: AnimationCurve,
|
||||||
|
style: AnimationStyle,
|
||||||
hierarchy: MultiphaseWindowHierarchy,
|
hierarchy: MultiphaseWindowHierarchy,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -182,6 +184,7 @@ fn coalesce_layout_animation_candidates(
|
||||||
{
|
{
|
||||||
existing.new = candidate.new;
|
existing.new = candidate.new;
|
||||||
existing.curve = candidate.curve;
|
existing.curve = candidate.curve;
|
||||||
|
existing.style = candidate.style;
|
||||||
existing.hierarchy = MultiphaseWindowHierarchy::new(
|
existing.hierarchy = MultiphaseWindowHierarchy::new(
|
||||||
existing.hierarchy.source,
|
existing.hierarchy.source,
|
||||||
candidate.hierarchy.target,
|
candidate.hierarchy.target,
|
||||||
|
|
@ -193,6 +196,15 @@ fn coalesce_layout_animation_candidates(
|
||||||
merged
|
merged
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn layout_animation_group_uses_plain(
|
||||||
|
candidates: &[LayoutAnimationCandidate],
|
||||||
|
group: &[usize],
|
||||||
|
) -> bool {
|
||||||
|
group
|
||||||
|
.iter()
|
||||||
|
.any(|&idx| candidates[idx].style == AnimationStyle::Plain)
|
||||||
|
}
|
||||||
|
|
||||||
pub struct State {
|
pub struct State {
|
||||||
pub pid: c::pid_t,
|
pub pid: c::pid_t,
|
||||||
pub kb_ctx: KbvmContext,
|
pub kb_ctx: KbvmContext,
|
||||||
|
|
@ -307,6 +319,7 @@ pub struct State {
|
||||||
pub layout_animations_requested: Cell<bool>,
|
pub layout_animations_requested: Cell<bool>,
|
||||||
pub layout_animations_active: Cell<bool>,
|
pub layout_animations_active: Cell<bool>,
|
||||||
pub layout_animation_curve_override: Cell<Option<AnimationCurve>>,
|
pub layout_animation_curve_override: Cell<Option<AnimationCurve>>,
|
||||||
|
pub layout_animation_style_override: Cell<Option<AnimationStyle>>,
|
||||||
pub(crate) layout_animation_batch: RefCell<Option<Vec<LayoutAnimationCandidate>>>,
|
pub(crate) layout_animation_batch: RefCell<Option<Vec<LayoutAnimationCandidate>>>,
|
||||||
pub suppress_animations_for_next_layout: Cell<bool>,
|
pub suppress_animations_for_next_layout: Cell<bool>,
|
||||||
pub toplevels: CopyHashMap<ToplevelIdentifier, Weak<dyn ToplevelNode>>,
|
pub toplevels: CopyHashMap<ToplevelIdentifier, Weak<dyn ToplevelNode>>,
|
||||||
|
|
@ -1172,6 +1185,7 @@ impl State {
|
||||||
self.layout_animations_requested.set(false);
|
self.layout_animations_requested.set(false);
|
||||||
self.layout_animations_active.set(false);
|
self.layout_animations_active.set(false);
|
||||||
self.layout_animation_curve_override.set(None);
|
self.layout_animation_curve_override.set(None);
|
||||||
|
self.layout_animation_style_override.set(None);
|
||||||
self.suppress_animations_for_next_layout.set(false);
|
self.suppress_animations_for_next_layout.set(false);
|
||||||
self.render_ctx_watchers.clear();
|
self.render_ctx_watchers.clear();
|
||||||
self.workspace_watchers.clear();
|
self.workspace_watchers.clear();
|
||||||
|
|
@ -1599,6 +1613,10 @@ impl State {
|
||||||
old,
|
old,
|
||||||
new,
|
new,
|
||||||
curve,
|
curve,
|
||||||
|
style: self
|
||||||
|
.layout_animation_style_override
|
||||||
|
.get()
|
||||||
|
.unwrap_or_else(|| self.animations.style.get()),
|
||||||
hierarchy,
|
hierarchy,
|
||||||
};
|
};
|
||||||
if let Some(batch) = self.layout_animation_batch.borrow_mut().as_mut() {
|
if let Some(batch) = self.layout_animation_batch.borrow_mut().as_mut() {
|
||||||
|
|
@ -1659,6 +1677,12 @@ impl State {
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
for group in partition_motion_groups(&windows, self.layout_animation_clearance()) {
|
for group in partition_motion_groups(&windows, self.layout_animation_clearance()) {
|
||||||
|
if layout_animation_group_uses_plain(&candidates, &group) {
|
||||||
|
for idx in group {
|
||||||
|
self.start_layout_animation_candidate(candidates[idx].clone(), now);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if self.start_multiphase_layout_animation(&candidates, &windows, &group, now) {
|
if self.start_multiphase_layout_animation(&candidates, &windows, &group, now) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -1701,7 +1725,7 @@ impl State {
|
||||||
Ok(plan) => plan,
|
Ok(plan) => plan,
|
||||||
Err(diagnostic) => {
|
Err(diagnostic) => {
|
||||||
log::debug!(
|
log::debug!(
|
||||||
"falling back to linear layout animation for group {:?}: {:?}",
|
"falling back to plain layout animation for group {:?}: {:?}",
|
||||||
group,
|
group,
|
||||||
diagnostic
|
diagnostic
|
||||||
);
|
);
|
||||||
|
|
@ -1881,6 +1905,14 @@ impl State {
|
||||||
.set(AnimationCurve::from_config(curve));
|
.set(AnimationCurve::from_config(curve));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_animation_style(&self, style: u32) -> bool {
|
||||||
|
let Some(style) = AnimationStyle::from_config(style) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
self.animations.style.set(style);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_animation_cubic_bezier(&self, x1: f32, y1: f32, x2: f32, y2: f32) -> bool {
|
pub fn set_animation_cubic_bezier(&self, x1: f32, y1: f32, x2: f32, y2: f32) -> bool {
|
||||||
let Some(curve) = AnimationCurve::from_cubic_bezier(x1, y1, x2, y2) else {
|
let Some(curve) = AnimationCurve::from_cubic_bezier(x1, y1, x2, y2) else {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -1904,10 +1936,14 @@ impl State {
|
||||||
let prev_curve = self
|
let prev_curve = self
|
||||||
.layout_animation_curve_override
|
.layout_animation_curve_override
|
||||||
.replace(Some(AnimationCurve::Linear));
|
.replace(Some(AnimationCurve::Linear));
|
||||||
|
let prev_style = self
|
||||||
|
.layout_animation_style_override
|
||||||
|
.replace(Some(AnimationStyle::Plain));
|
||||||
let res = f();
|
let res = f();
|
||||||
self.layout_animations_requested.set(prev_requested);
|
self.layout_animations_requested.set(prev_requested);
|
||||||
self.layout_animations_active.set(prev_active);
|
self.layout_animations_active.set(prev_active);
|
||||||
self.layout_animation_curve_override.set(prev_curve);
|
self.layout_animation_curve_override.set(prev_curve);
|
||||||
|
self.layout_animation_style_override.set(prev_style);
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2475,6 +2511,28 @@ mod tests {
|
||||||
MultiphaseWindowHierarchy::new(source, target)
|
MultiphaseWindowHierarchy::new(source, target)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn candidate(node_id: u32, style: AnimationStyle) -> LayoutAnimationCandidate {
|
||||||
|
LayoutAnimationCandidate {
|
||||||
|
node_id: NodeId(node_id),
|
||||||
|
old: rect(0, 0, 100, 100),
|
||||||
|
new: rect(100, 0, 200, 100),
|
||||||
|
curve: AnimationCurve::Linear,
|
||||||
|
style,
|
||||||
|
hierarchy: MultiphaseWindowHierarchy::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn plain_style_candidate_forces_group_plain() {
|
||||||
|
let candidates = vec![
|
||||||
|
candidate(1, AnimationStyle::Multiphase),
|
||||||
|
candidate(2, AnimationStyle::Plain),
|
||||||
|
];
|
||||||
|
|
||||||
|
assert!(!layout_animation_group_uses_plain(&candidates, &[0]));
|
||||||
|
assert!(layout_animation_group_uses_plain(&candidates, &[0, 1]));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn layout_animation_candidates_coalesce_duplicate_nodes() {
|
fn layout_animation_candidates_coalesce_duplicate_nodes() {
|
||||||
let source = MultiphaseHierarchyPosition {
|
let source = MultiphaseHierarchyPosition {
|
||||||
|
|
@ -2514,6 +2572,7 @@ mod tests {
|
||||||
old: rect(0, 0, 100, 100),
|
old: rect(0, 0, 100, 100),
|
||||||
new: rect(0, 0, 80, 100),
|
new: rect(0, 0, 80, 100),
|
||||||
curve: AnimationCurve::Linear,
|
curve: AnimationCurve::Linear,
|
||||||
|
style: AnimationStyle::Multiphase,
|
||||||
hierarchy: hierarchy(source, intermediate),
|
hierarchy: hierarchy(source, intermediate),
|
||||||
},
|
},
|
||||||
LayoutAnimationCandidate {
|
LayoutAnimationCandidate {
|
||||||
|
|
@ -2521,6 +2580,7 @@ mod tests {
|
||||||
old: rect(100, 0, 200, 100),
|
old: rect(100, 0, 200, 100),
|
||||||
new: rect(120, 0, 220, 100),
|
new: rect(120, 0, 220, 100),
|
||||||
curve: AnimationCurve::Linear,
|
curve: AnimationCurve::Linear,
|
||||||
|
style: AnimationStyle::Multiphase,
|
||||||
hierarchy: hierarchy(second_source, second_target),
|
hierarchy: hierarchy(second_source, second_target),
|
||||||
},
|
},
|
||||||
LayoutAnimationCandidate {
|
LayoutAnimationCandidate {
|
||||||
|
|
@ -2528,6 +2588,7 @@ mod tests {
|
||||||
old: rect(0, 0, 80, 100),
|
old: rect(0, 0, 80, 100),
|
||||||
new: rect(0, 0, 60, 100),
|
new: rect(0, 0, 60, 100),
|
||||||
curve: AnimationCurve::from_config(4),
|
curve: AnimationCurve::from_config(4),
|
||||||
|
style: AnimationStyle::Plain,
|
||||||
hierarchy: hierarchy(intermediate, target),
|
hierarchy: hierarchy(intermediate, target),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
@ -2539,6 +2600,7 @@ mod tests {
|
||||||
assert_eq!(merged[0].old, rect(0, 0, 100, 100));
|
assert_eq!(merged[0].old, rect(0, 0, 100, 100));
|
||||||
assert_eq!(merged[0].new, rect(0, 0, 60, 100));
|
assert_eq!(merged[0].new, rect(0, 0, 60, 100));
|
||||||
assert_eq!(merged[0].curve, AnimationCurve::from_config(4));
|
assert_eq!(merged[0].curve, AnimationCurve::from_config(4));
|
||||||
|
assert_eq!(merged[0].style, AnimationStyle::Plain);
|
||||||
assert_eq!(merged[0].hierarchy, hierarchy(source, target));
|
assert_eq!(merged[0].hierarchy, hierarchy(source, target));
|
||||||
assert_eq!(merged[1].node_id, NodeId(2));
|
assert_eq!(merged[1].node_id, NodeId(2));
|
||||||
assert_eq!(merged[1].old, rect(100, 0, 200, 100));
|
assert_eq!(merged[1].old, rect(100, 0, 200, 100));
|
||||||
|
|
@ -2555,6 +2617,7 @@ mod tests {
|
||||||
old: rect(0, 0, 100, 100),
|
old: rect(0, 0, 100, 100),
|
||||||
new: rect(0, 0, 80, 100),
|
new: rect(0, 0, 80, 100),
|
||||||
curve: AnimationCurve::Linear,
|
curve: AnimationCurve::Linear,
|
||||||
|
style: AnimationStyle::Multiphase,
|
||||||
hierarchy,
|
hierarchy,
|
||||||
},
|
},
|
||||||
LayoutAnimationCandidate {
|
LayoutAnimationCandidate {
|
||||||
|
|
@ -2562,6 +2625,7 @@ mod tests {
|
||||||
old: rect(0, 0, 80, 100),
|
old: rect(0, 0, 80, 100),
|
||||||
new: rect(0, 0, 100, 100),
|
new: rect(0, 0, 100, 100),
|
||||||
curve: AnimationCurve::Linear,
|
curve: AnimationCurve::Linear,
|
||||||
|
style: AnimationStyle::Plain,
|
||||||
hierarchy,
|
hierarchy,
|
||||||
},
|
},
|
||||||
LayoutAnimationCandidate {
|
LayoutAnimationCandidate {
|
||||||
|
|
@ -2569,6 +2633,7 @@ mod tests {
|
||||||
old: rect(100, 0, 200, 100),
|
old: rect(100, 0, 200, 100),
|
||||||
new: rect(120, 0, 220, 100),
|
new: rect(120, 0, 220, 100),
|
||||||
curve: AnimationCurve::Linear,
|
curve: AnimationCurve::Linear,
|
||||||
|
style: AnimationStyle::Multiphase,
|
||||||
hierarchy,
|
hierarchy,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
@ -2579,6 +2644,7 @@ mod tests {
|
||||||
assert_eq!(merged[0].node_id, NodeId(1));
|
assert_eq!(merged[0].node_id, NodeId(1));
|
||||||
assert_eq!(merged[0].old, rect(0, 0, 100, 100));
|
assert_eq!(merged[0].old, rect(0, 0, 100, 100));
|
||||||
assert_eq!(merged[0].new, rect(0, 0, 100, 100));
|
assert_eq!(merged[0].new, rect(0, 0, 100, 100));
|
||||||
|
assert_eq!(merged[0].style, AnimationStyle::Plain);
|
||||||
assert_eq!(merged[1].node_id, NodeId(2));
|
assert_eq!(merged[1].node_id, NodeId(2));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -270,6 +270,7 @@ pub struct UiDrag {
|
||||||
pub struct Animations {
|
pub struct Animations {
|
||||||
pub enabled: Option<bool>,
|
pub enabled: Option<bool>,
|
||||||
pub duration_ms: Option<u32>,
|
pub duration_ms: Option<u32>,
|
||||||
|
pub style: Option<String>,
|
||||||
pub curve: Option<AnimationCurveConfig>,
|
pub curve: Option<AnimationCurveConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -678,3 +679,13 @@ fn custom_animation_curve_parses() {
|
||||||
Some(AnimationCurveConfig::CubicBezier([0.25, 0.1, 0.25, 1.0]))
|
Some(AnimationCurveConfig::CubicBezier([0.25, 0.1, 0.25, 1.0]))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn animation_style_parses() {
|
||||||
|
let input = b"
|
||||||
|
[animations]
|
||||||
|
style = \"plain\"
|
||||||
|
";
|
||||||
|
let config = parse_config(input, &Default::default(), |_| ()).unwrap();
|
||||||
|
assert_eq!(config.animations.style.as_deref(), Some("plain"));
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ use {
|
||||||
config::{
|
config::{
|
||||||
AnimationCurveConfig, Animations,
|
AnimationCurveConfig, Animations,
|
||||||
context::Context,
|
context::Context,
|
||||||
extractor::{Extractor, ExtractorError, bol, n32, opt, recover, val},
|
extractor::{Extractor, ExtractorError, bol, n32, opt, recover, str, val},
|
||||||
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||||
},
|
},
|
||||||
toml::{
|
toml::{
|
||||||
|
|
@ -44,9 +44,10 @@ impl Parser for AnimationsParser<'_> {
|
||||||
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
||||||
) -> ParseResult<Self> {
|
) -> ParseResult<Self> {
|
||||||
let mut ext = Extractor::new(self.0, span, table);
|
let mut ext = Extractor::new(self.0, span, table);
|
||||||
let (enabled, duration_ms, curve) = ext.extract((
|
let (enabled, duration_ms, style, curve) = ext.extract((
|
||||||
recover(opt(bol("enabled"))),
|
recover(opt(bol("enabled"))),
|
||||||
recover(opt(n32("duration-ms"))),
|
recover(opt(n32("duration-ms"))),
|
||||||
|
recover(opt(str("style"))),
|
||||||
opt(val("curve")),
|
opt(val("curve")),
|
||||||
))?;
|
))?;
|
||||||
let curve = match curve {
|
let curve = match curve {
|
||||||
|
|
@ -56,6 +57,7 @@ impl Parser for AnimationsParser<'_> {
|
||||||
Ok(Animations {
|
Ok(Animations {
|
||||||
enabled: enabled.despan(),
|
enabled: enabled.despan(),
|
||||||
duration_ms: duration_ms.despan(),
|
duration_ms: duration_ms.despan(),
|
||||||
|
style: style.despan().map(|style| style.to_string()),
|
||||||
curve,
|
curve,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ use {
|
||||||
ahash::{AHashMap, AHashSet},
|
ahash::{AHashMap, AHashSet},
|
||||||
error_reporter::Report,
|
error_reporter::Report,
|
||||||
jay_config::{
|
jay_config::{
|
||||||
AnimationCurve, Axis,
|
AnimationCurve, AnimationStyle, Axis,
|
||||||
client::Client,
|
client::Client,
|
||||||
config, config_dir,
|
config, config_dir,
|
||||||
exec::{Command, set_env, unset_env},
|
exec::{Command, set_env, unset_env},
|
||||||
|
|
@ -38,8 +38,9 @@ use {
|
||||||
keyboard::Keymap,
|
keyboard::Keymap,
|
||||||
logging::{clean_logs_older_than, set_log_level},
|
logging::{clean_logs_older_than, set_log_level},
|
||||||
on_devices_enumerated, on_idle, on_unload, quit, reload, set_animation_cubic_bezier,
|
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_animation_curve, set_animation_duration_ms, set_animation_style,
|
||||||
set_color_management_enabled, set_corner_radius, set_default_workspace_capture,
|
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_explicit_sync_enabled, set_float_above_fullscreen, set_floating_titles, set_idle,
|
||||||
set_idle_grace_period, set_middle_click_paste_enabled, set_show_bar,
|
set_idle_grace_period, set_middle_click_paste_enabled, set_show_bar,
|
||||||
set_show_float_pin_icon, set_show_titles, set_tab_title_align, set_ui_drag_enabled,
|
set_show_float_pin_icon, set_show_titles, set_tab_title_align, set_ui_drag_enabled,
|
||||||
|
|
@ -1652,6 +1653,11 @@ fn load_config(initial_load: bool, auto_reload: bool, persistent: &Rc<Persistent
|
||||||
}
|
}
|
||||||
set_animations_enabled(config.animations.enabled.unwrap_or(false));
|
set_animations_enabled(config.animations.enabled.unwrap_or(false));
|
||||||
set_animation_duration_ms(config.animations.duration_ms.unwrap_or(160));
|
set_animation_duration_ms(config.animations.duration_ms.unwrap_or(160));
|
||||||
|
match config.animations.style.as_deref().unwrap_or("multiphase") {
|
||||||
|
"plain" => set_animation_style(AnimationStyle::PLAIN),
|
||||||
|
"multiphase" => set_animation_style(AnimationStyle::MULTIPHASE),
|
||||||
|
style_name => log::warn!("Unknown animation style: {style_name}"),
|
||||||
|
}
|
||||||
match config
|
match config
|
||||||
.animations
|
.animations
|
||||||
.curve
|
.curve
|
||||||
|
|
|
||||||
|
|
@ -665,8 +665,16 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"AnimationStyle": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Describes a tiled window movement animation style.\n",
|
||||||
|
"enum": [
|
||||||
|
"plain",
|
||||||
|
"multiphase"
|
||||||
|
]
|
||||||
|
},
|
||||||
"Animations": {
|
"Animations": {
|
||||||
"description": "Describes window animation settings.\n\n- Example:\n\n ```toml\n [animations]\n enabled = true\n duration-ms = 160\n curve = [0.25, 0.1, 0.25, 1.0]\n ```\n",
|
"description": "Describes window animation settings.\n\n- Example:\n\n ```toml\n [animations]\n enabled = true\n duration-ms = 160\n style = \"multiphase\"\n curve = [0.25, 0.1, 0.25, 1.0]\n ```\n",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"enabled": {
|
"enabled": {
|
||||||
|
|
@ -677,6 +685,10 @@
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"description": "Sets the animation duration in milliseconds.\n\nThe default is `160`.\n"
|
"description": "Sets the animation duration in milliseconds.\n\nThe default is `160`.\n"
|
||||||
},
|
},
|
||||||
|
"style": {
|
||||||
|
"description": "Sets the animation style used for tiled window movement animations.\n\nThe default is `multiphase`.\n",
|
||||||
|
"$ref": "#/$defs/AnimationStyle"
|
||||||
|
},
|
||||||
"curve": {
|
"curve": {
|
||||||
"description": "Sets the animation curve.\n\nThe default is `ease-out`.\n",
|
"description": "Sets the animation curve.\n\nThe default is `ease-out`.\n",
|
||||||
"$ref": "#/$defs/AnimationCurve"
|
"$ref": "#/$defs/AnimationCurve"
|
||||||
|
|
@ -1129,7 +1141,7 @@
|
||||||
"$ref": "#/$defs/UiDrag"
|
"$ref": "#/$defs/UiDrag"
|
||||||
},
|
},
|
||||||
"animations": {
|
"animations": {
|
||||||
"description": "Configures window animations.\n\nAnimations are disabled by default.\n\n- Example:\n\n ```toml\n [animations]\n enabled = true\n duration-ms = 160\n curve = \"ease-out\"\n ```\n",
|
"description": "Configures window animations.\n\nAnimations are disabled by default.\n\n- Example:\n\n ```toml\n [animations]\n enabled = true\n duration-ms = 160\n style = \"multiphase\"\n curve = \"ease-out\"\n ```\n",
|
||||||
"$ref": "#/$defs/Animations"
|
"$ref": "#/$defs/Animations"
|
||||||
},
|
},
|
||||||
"xwayland": {
|
"xwayland": {
|
||||||
|
|
|
||||||
|
|
@ -987,6 +987,26 @@ be between `0` and `1`.
|
||||||
Each element of this array should be a number.
|
Each element of this array should be a number.
|
||||||
|
|
||||||
|
|
||||||
|
<a name="types-AnimationStyle"></a>
|
||||||
|
### `AnimationStyle`
|
||||||
|
|
||||||
|
Describes a tiled window movement animation style.
|
||||||
|
|
||||||
|
Values of this type should be strings.
|
||||||
|
|
||||||
|
The string should have one of the following values:
|
||||||
|
|
||||||
|
- `plain`:
|
||||||
|
|
||||||
|
Uses a single interpolated movement from each window's current visual
|
||||||
|
rectangle to its destination rectangle.
|
||||||
|
|
||||||
|
- `multiphase`:
|
||||||
|
|
||||||
|
Uses the no-overlap multiphase planner for tiled window movement when a
|
||||||
|
supported plan exists.
|
||||||
|
|
||||||
|
|
||||||
<a name="types-Animations"></a>
|
<a name="types-Animations"></a>
|
||||||
### `Animations`
|
### `Animations`
|
||||||
|
|
||||||
|
|
@ -998,6 +1018,7 @@ Describes window animation settings.
|
||||||
[animations]
|
[animations]
|
||||||
enabled = true
|
enabled = true
|
||||||
duration-ms = 160
|
duration-ms = 160
|
||||||
|
style = "multiphase"
|
||||||
curve = [0.25, 0.1, 0.25, 1.0]
|
curve = [0.25, 0.1, 0.25, 1.0]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -1023,6 +1044,14 @@ The table has the following fields:
|
||||||
|
|
||||||
The numbers should be integers.
|
The numbers should be integers.
|
||||||
|
|
||||||
|
- `style` (optional):
|
||||||
|
|
||||||
|
Sets the animation style used for tiled window movement animations.
|
||||||
|
|
||||||
|
The default is `multiphase`.
|
||||||
|
|
||||||
|
The value of this field should be a [AnimationStyle](#types-AnimationStyle).
|
||||||
|
|
||||||
- `curve` (optional):
|
- `curve` (optional):
|
||||||
|
|
||||||
Sets the animation curve.
|
Sets the animation curve.
|
||||||
|
|
@ -2271,6 +2300,7 @@ The table has the following fields:
|
||||||
[animations]
|
[animations]
|
||||||
enabled = true
|
enabled = true
|
||||||
duration-ms = 160
|
duration-ms = 160
|
||||||
|
style = "multiphase"
|
||||||
curve = "ease-out"
|
curve = "ease-out"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2956,6 +2956,7 @@ Config:
|
||||||
[animations]
|
[animations]
|
||||||
enabled = true
|
enabled = true
|
||||||
duration-ms = 160
|
duration-ms = 160
|
||||||
|
style = "multiphase"
|
||||||
curve = "ease-out"
|
curve = "ease-out"
|
||||||
```
|
```
|
||||||
xwayland:
|
xwayland:
|
||||||
|
|
@ -3682,6 +3683,7 @@ Animations:
|
||||||
[animations]
|
[animations]
|
||||||
enabled = true
|
enabled = true
|
||||||
duration-ms = 160
|
duration-ms = 160
|
||||||
|
style = "multiphase"
|
||||||
curve = [0.25, 0.1, 0.25, 1.0]
|
curve = [0.25, 0.1, 0.25, 1.0]
|
||||||
```
|
```
|
||||||
fields:
|
fields:
|
||||||
|
|
@ -3700,6 +3702,13 @@ Animations:
|
||||||
Sets the animation duration in milliseconds.
|
Sets the animation duration in milliseconds.
|
||||||
|
|
||||||
The default is `160`.
|
The default is `160`.
|
||||||
|
style:
|
||||||
|
ref: AnimationStyle
|
||||||
|
required: false
|
||||||
|
description: |
|
||||||
|
Sets the animation style used for tiled window movement animations.
|
||||||
|
|
||||||
|
The default is `multiphase`.
|
||||||
curve:
|
curve:
|
||||||
ref: AnimationCurve
|
ref: AnimationCurve
|
||||||
required: false
|
required: false
|
||||||
|
|
@ -3709,6 +3718,21 @@ Animations:
|
||||||
The default is `ease-out`.
|
The default is `ease-out`.
|
||||||
|
|
||||||
|
|
||||||
|
AnimationStyle:
|
||||||
|
kind: string
|
||||||
|
description: |
|
||||||
|
Describes a tiled window movement animation style.
|
||||||
|
values:
|
||||||
|
- value: plain
|
||||||
|
description: |
|
||||||
|
Uses a single interpolated movement from each window's current visual
|
||||||
|
rectangle to its destination rectangle.
|
||||||
|
- value: multiphase
|
||||||
|
description: |
|
||||||
|
Uses the no-overlap multiphase planner for tiled window movement when a
|
||||||
|
supported plan exists.
|
||||||
|
|
||||||
|
|
||||||
AnimationCurve:
|
AnimationCurve:
|
||||||
kind: variable
|
kind: variable
|
||||||
description: |
|
description: |
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue