Add float tile transition animations
This commit is contained in:
parent
18ffaef64d
commit
aeaea3419f
5 changed files with 93 additions and 12 deletions
|
|
@ -82,8 +82,8 @@ Implementation shape:
|
||||||
Initial scope:
|
Initial scope:
|
||||||
|
|
||||||
- Tiled reflow animation.
|
- Tiled reflow animation.
|
||||||
- Floating command-driven moves, tile-to-float, and float-to-tile are deferred
|
- Floating command-driven moves are deferred until after tiled reflow, spawn-in,
|
||||||
until after tiled reflow and spawn-in are validated.
|
and float/tile transitions are validated.
|
||||||
- Cross-output and cross-scale movements snap for now.
|
- Cross-output and cross-scale movements snap for now.
|
||||||
- Linear mode may overlap windows during swaps. That is expected for the classic
|
- Linear mode may overlap windows during swaps. That is expected for the classic
|
||||||
interpolation mode; no-overlap is Phase 3.
|
interpolation mode; no-overlap is Phase 3.
|
||||||
|
|
@ -100,6 +100,8 @@ Tests:
|
||||||
- unchanged in-flight windows keep their original timeline
|
- unchanged in-flight windows keep their original timeline
|
||||||
- drag-driven floating movement bypasses animation
|
- drag-driven floating movement bypasses animation
|
||||||
- damage includes old, current, and final rects
|
- damage includes old, current, and final rects
|
||||||
|
- command-driven tile-to-float and float-to-tile transitions use linear motion
|
||||||
|
- pointer/header double-click unfloat bypasses the command-animation gate
|
||||||
|
|
||||||
## Phase 2: Retained Texture Freezing
|
## Phase 2: Retained Texture Freezing
|
||||||
|
|
||||||
|
|
@ -110,6 +112,8 @@ Initial retained-record implementation status:
|
||||||
- Tiled animation can retain GPU/dmabuf-backed XDG and Xwayland surface trees.
|
- Tiled animation can retain GPU/dmabuf-backed XDG and Xwayland surface trees.
|
||||||
- Spawn-in animation can retain GPU/dmabuf-backed XDG and Xwayland surface trees
|
- Spawn-in animation can retain GPU/dmabuf-backed XDG and Xwayland surface trees
|
||||||
for both tiled windows and floating child contents.
|
for both tiled windows and floating child contents.
|
||||||
|
- Tile-to-float and float-to-tile transitions retain GPU/dmabuf-backed child
|
||||||
|
contents while the presentation geometry changes.
|
||||||
- Retained records hold both `GfxTexture` and `SurfaceBuffer` references so the
|
- Retained records hold both `GfxTexture` and `SurfaceBuffer` references so the
|
||||||
existing buffer release/sync path remains authoritative.
|
existing buffer release/sync path remains authoritative.
|
||||||
- Single-pixel buffers can be retained as color records.
|
- Single-pixel buffers can be retained as color records.
|
||||||
|
|
|
||||||
|
|
@ -363,6 +363,7 @@ fn start_compositor2(
|
||||||
animations: Default::default(),
|
animations: Default::default(),
|
||||||
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(),
|
||||||
suppress_animations_for_next_layout: Default::default(),
|
suppress_animations_for_next_layout: Default::default(),
|
||||||
toplevels: Default::default(),
|
toplevels: Default::default(),
|
||||||
const_40hz_latch: Default::default(),
|
const_40hz_latch: Default::default(),
|
||||||
|
|
|
||||||
|
|
@ -1990,9 +1990,11 @@ impl ConfigProxyHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_set_seat_floating(&self, seat: Seat, floating: bool) -> Result<(), CphError> {
|
fn handle_set_seat_floating(&self, seat: Seat, floating: bool) -> Result<(), CphError> {
|
||||||
let seat = self.get_seat(seat)?;
|
self.state.with_linear_layout_animations(|| {
|
||||||
seat.set_floating(floating);
|
let seat = self.get_seat(seat)?;
|
||||||
Ok(())
|
seat.set_floating(floating);
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_get_window_floating(&self, window: Window) -> Result<(), CphError> {
|
fn handle_get_window_floating(&self, window: Window) -> Result<(), CphError> {
|
||||||
|
|
@ -2004,9 +2006,11 @@ impl ConfigProxyHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_set_window_floating(&self, window: Window, floating: bool) -> Result<(), CphError> {
|
fn handle_set_window_floating(&self, window: Window, floating: bool) -> Result<(), CphError> {
|
||||||
let window = self.get_window(window)?;
|
self.state.with_linear_layout_animations(|| {
|
||||||
toplevel_set_floating(&self.state, window, floating);
|
let window = self.get_window(window)?;
|
||||||
Ok(())
|
toplevel_set_floating(&self.state, window, floating);
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_add_pollable(self: &Rc<Self>, fd: i32) -> Result<(), CphError> {
|
fn handle_add_pollable(self: &Rc<Self>, fd: i32) -> Result<(), CphError> {
|
||||||
|
|
|
||||||
47
src/state.rs
47
src/state.rs
|
|
@ -270,6 +270,7 @@ pub struct State {
|
||||||
pub animations: AnimationState,
|
pub animations: AnimationState,
|
||||||
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 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>>,
|
||||||
pub const_40hz_latch: EventSource<dyn LatchListener>,
|
pub const_40hz_latch: EventSource<dyn LatchListener>,
|
||||||
|
|
@ -854,7 +855,7 @@ impl State {
|
||||||
mut height: i32,
|
mut height: i32,
|
||||||
workspace: &Rc<WorkspaceNode>,
|
workspace: &Rc<WorkspaceNode>,
|
||||||
abs_pos: Option<(i32, i32)>,
|
abs_pos: Option<(i32, i32)>,
|
||||||
) {
|
) -> Rc<FloatNode> {
|
||||||
width += 2 * self.theme.sizes.border_width.get();
|
width += 2 * self.theme.sizes.border_width.get();
|
||||||
height +=
|
height +=
|
||||||
2 * self.theme.sizes.border_width.get() + self.theme.title_plus_underline_height();
|
2 * self.theme.sizes.border_width.get() + self.theme.title_plus_underline_height();
|
||||||
|
|
@ -885,8 +886,9 @@ impl State {
|
||||||
}
|
}
|
||||||
Rect::new_sized_saturating(x1, y1, width, height)
|
Rect::new_sized_saturating(x1, y1, width, height)
|
||||||
};
|
};
|
||||||
FloatNode::new(self, workspace, position, node.clone());
|
let float = FloatNode::new(self, workspace, position, node.clone());
|
||||||
self.focus_after_map(node, self.seat_queue.last().as_deref());
|
self.focus_after_map(node, self.seat_queue.last().as_deref());
|
||||||
|
float
|
||||||
}
|
}
|
||||||
|
|
||||||
fn focus_after_map(&self, node: Rc<dyn ToplevelNode>, seat: Option<&Rc<WlSeatGlobal>>) {
|
fn focus_after_map(&self, node: Rc<dyn ToplevelNode>, seat: Option<&Rc<WlSeatGlobal>>) {
|
||||||
|
|
@ -1125,6 +1127,7 @@ impl State {
|
||||||
self.animations.clear();
|
self.animations.clear();
|
||||||
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.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();
|
||||||
|
|
@ -1478,6 +1481,31 @@ impl State {
|
||||||
old: Rect,
|
old: Rect,
|
||||||
new: Rect,
|
new: Rect,
|
||||||
retained: Option<Rc<RetainedToplevel>>,
|
retained: Option<Rc<RetainedToplevel>>,
|
||||||
|
) {
|
||||||
|
let curve = self
|
||||||
|
.layout_animation_curve_override
|
||||||
|
.get()
|
||||||
|
.unwrap_or_else(|| self.animations.curve.get());
|
||||||
|
self.queue_layout_animation(node_id, old, new, retained, curve);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn queue_linear_layout_animation(
|
||||||
|
self: &Rc<Self>,
|
||||||
|
node_id: NodeId,
|
||||||
|
old: Rect,
|
||||||
|
new: Rect,
|
||||||
|
retained: Option<Rc<RetainedToplevel>>,
|
||||||
|
) {
|
||||||
|
self.queue_layout_animation(node_id, old, new, retained, AnimationCurve::Linear);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn queue_layout_animation(
|
||||||
|
self: &Rc<Self>,
|
||||||
|
node_id: NodeId,
|
||||||
|
old: Rect,
|
||||||
|
new: Rect,
|
||||||
|
retained: Option<Rc<RetainedToplevel>>,
|
||||||
|
curve: AnimationCurve,
|
||||||
) {
|
) {
|
||||||
if !self.animations.enabled.get()
|
if !self.animations.enabled.get()
|
||||||
|| !self.layout_animations_active.get()
|
|| !self.layout_animations_active.get()
|
||||||
|
|
@ -1506,7 +1534,7 @@ impl State {
|
||||||
retained,
|
retained,
|
||||||
now,
|
now,
|
||||||
self.animations.duration_ms.get(),
|
self.animations.duration_ms.get(),
|
||||||
self.animations.curve.get(),
|
curve,
|
||||||
);
|
);
|
||||||
if started {
|
if started {
|
||||||
self.damage(expand_damage_rect(
|
self.damage(expand_damage_rect(
|
||||||
|
|
@ -1570,6 +1598,19 @@ impl State {
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn with_linear_layout_animations<T>(&self, f: impl FnOnce() -> T) -> T {
|
||||||
|
let prev_requested = self.layout_animations_requested.replace(true);
|
||||||
|
let prev_active = self.layout_animations_active.replace(true);
|
||||||
|
let prev_curve = self
|
||||||
|
.layout_animation_curve_override
|
||||||
|
.replace(Some(AnimationCurve::Linear));
|
||||||
|
let res = f();
|
||||||
|
self.layout_animations_requested.set(prev_requested);
|
||||||
|
self.layout_animations_active.set(prev_active);
|
||||||
|
self.layout_animation_curve_override.set(prev_curve);
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
fn ensure_animation_tick(self: &Rc<Self>) {
|
fn ensure_animation_tick(self: &Rc<Self>) {
|
||||||
if self.animations.tick_is_active() {
|
if self.animations.tick_is_active() {
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -1096,6 +1096,26 @@ pub fn toplevel_create_split(state: &Rc<State>, tl: Rc<dyn ToplevelNode>, axis:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn float_outer_for_body(state: &State, body: Rect) -> Rect {
|
||||||
|
let bw = state.theme.sizes.border_width.get();
|
||||||
|
Rect::new_sized_saturating(
|
||||||
|
body.x1() - bw,
|
||||||
|
body.y1() - bw,
|
||||||
|
body.width() + 2 * bw,
|
||||||
|
body.height() + 2 * bw,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn float_body_for_outer(state: &State, outer: Rect) -> Rect {
|
||||||
|
let bw = state.theme.sizes.border_width.get();
|
||||||
|
Rect::new_sized_saturating(
|
||||||
|
outer.x1() + bw,
|
||||||
|
outer.y1() + bw,
|
||||||
|
outer.width() - 2 * bw,
|
||||||
|
outer.height() - 2 * bw,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn toplevel_set_floating(state: &Rc<State>, tl: Rc<dyn ToplevelNode>, floating: bool) {
|
pub fn toplevel_set_floating(state: &Rc<State>, tl: Rc<dyn ToplevelNode>, floating: bool) {
|
||||||
let data = tl.tl_data();
|
let data = tl.tl_data();
|
||||||
if data.is_fullscreen.get() {
|
if data.is_fullscreen.get() {
|
||||||
|
|
@ -1112,9 +1132,20 @@ pub fn toplevel_set_floating(state: &Rc<State>, tl: Rc<dyn ToplevelNode>, floati
|
||||||
parent.cnode_remove_child2(&*tl, true);
|
parent.cnode_remove_child2(&*tl, true);
|
||||||
state.map_tiled(tl);
|
state.map_tiled(tl);
|
||||||
} else if let Some(ws) = data.workspace.get() {
|
} else if let Some(ws) = data.workspace.get() {
|
||||||
|
let node_id = data.node_id;
|
||||||
|
let old_body =
|
||||||
|
state
|
||||||
|
.animations
|
||||||
|
.visual_rect(node_id, tl.node_absolute_position(), state.now_nsec());
|
||||||
|
let old_outer = float_outer_for_body(state, old_body);
|
||||||
|
let retained = tl.tl_animation_snapshot();
|
||||||
parent.cnode_remove_child2(&*tl, true);
|
parent.cnode_remove_child2(&*tl, true);
|
||||||
let (width, height) = data.float_size(&ws);
|
let (width, height) = data.float_size(&ws);
|
||||||
state.map_floating(tl, width, height, &ws, None);
|
let floater = state.map_floating(tl, width, height, &ws, None);
|
||||||
|
let new_outer = floater.position.get();
|
||||||
|
let new_body = float_body_for_outer(state, new_outer);
|
||||||
|
state.queue_linear_layout_animation(floater.node_id(), old_outer, new_outer, None);
|
||||||
|
state.queue_linear_layout_animation(node_id, old_body, new_body, retained);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue