1
0
Fork 0
forked from wry/wry

Animate command-driven floating changes

This commit is contained in:
atagen 2026-05-21 16:51:50 +10:00
parent aeaea3419f
commit d0cc5dc3c7
4 changed files with 60 additions and 18 deletions

View file

@ -82,8 +82,8 @@ Implementation shape:
Initial scope: Initial scope:
- Tiled reflow animation. - Tiled reflow animation.
- Floating command-driven moves are deferred until after tiled reflow, spawn-in, - Floating command-driven moves and resizes are animated. Pointer and tablet
and float/tile transitions are validated. drag/resize paths still snap directly to the live cursor position.
- 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.
@ -101,6 +101,8 @@ Tests:
- 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 - command-driven tile-to-float and float-to-tile transitions use linear motion
- command-driven floating moves and resizes animate without affecting pointer
drag/resize behavior
- pointer/header double-click unfloat bypasses the command-animation gate - pointer/header double-click unfloat bypasses the command-animation gate
## Phase 2: Retained Texture Freezing ## Phase 2: Retained Texture Freezing

View file

@ -668,7 +668,9 @@ impl ConfigProxyHandler {
fn handle_window_move(&self, window: Window, direction: Direction) -> Result<(), CphError> { fn handle_window_move(&self, window: Window, direction: Direction) -> Result<(), CphError> {
self.state.with_layout_animations(|| { self.state.with_layout_animations(|| {
let window = self.get_window(window)?; let window = self.get_window(window)?;
if let Some(c) = toplevel_parent_container(&*window) { if let Some(float) = window.tl_data().float.get() {
float.move_by_direction(direction.into());
} else if let Some(c) = toplevel_parent_container(&*window) {
c.move_child(window, direction.into()); c.move_child(window, direction.into());
} }
Ok(()) Ok(())

View file

@ -936,6 +936,9 @@ impl WlSeatGlobal {
{ {
c.move_child(tl, direction); c.move_child(tl, direction);
self.maybe_schedule_warp_mouse_to_focus(); self.maybe_schedule_warp_mouse_to_focus();
} else if let Some(float) = data.float.get() {
float.move_by_direction(direction);
self.maybe_schedule_warp_mouse_to_focus();
} }
} }

View file

@ -31,6 +31,9 @@ use {
}; };
tree_id!(FloatNodeId); tree_id!(FloatNodeId);
const COMMAND_MOVE_DELTA: i32 = 100;
pub struct FloatNode { pub struct FloatNode {
pub id: FloatNodeId, pub id: FloatNodeId,
pub state: Rc<State>, pub state: Rc<State>,
@ -371,6 +374,51 @@ impl FloatNode {
y2 += y1 - pos.y1(); y2 += y1 - pos.y1();
} }
let new_pos = Rect::new_saturating(x1, y1, x2, y2); let new_pos = Rect::new_saturating(x1, y1, x2, y2);
self.set_position(new_pos);
}
pub fn move_by_direction(self: &Rc<Self>, direction: Direction) {
let (dx, dy) = match direction {
Direction::Left => (-COMMAND_MOVE_DELTA, 0),
Direction::Down => (0, COMMAND_MOVE_DELTA),
Direction::Up => (0, -COMMAND_MOVE_DELTA),
Direction::Right => (COMMAND_MOVE_DELTA, 0),
Direction::Unspecified => return,
};
self.set_position(self.position.get().move_(dx, dy));
}
fn body_for_outer(&self, outer: Rect) -> Rect {
let bw = self.state.theme.sizes.border_width.get();
Rect::new_sized_saturating(
outer.x1() + bw,
outer.y1() + bw,
outer.width() - 2 * bw,
outer.height() - 2 * bw,
)
}
fn queue_position_animation(&self, old_pos: Rect, new_pos: Rect) {
self.state
.clone()
.queue_tiled_animation(self.id.into(), old_pos, new_pos, None);
let Some(child) = self.child.get() else {
return;
};
self.state.clone().queue_tiled_animation(
child.node_id(),
self.body_for_outer(old_pos),
self.body_for_outer(new_pos),
child.tl_animation_snapshot(),
);
}
fn set_position(self: &Rc<Self>, new_pos: Rect) {
let pos = self.position.get();
if new_pos == pos {
return;
}
self.queue_position_animation(pos, new_pos);
self.position.set(new_pos); self.position.set(new_pos);
if self.visible.get() { if self.visible.get() {
self.state.damage(pos); self.state.damage(pos);
@ -799,13 +847,7 @@ impl ContainingNode for FloatNode {
let bw = theme.sizes.border_width.get(); let bw = theme.sizes.border_width.get();
let (x, y) = (x - bw, y - bw); let (x, y) = (x - bw, y - bw);
let pos = self.position.get(); let pos = self.position.get();
if pos.position() != (x, y) { self.set_position(pos.at_point(x, y));
let new_pos = pos.at_point(x, y);
self.position.set(new_pos);
self.state.damage(pos);
self.state.damage(new_pos);
self.schedule_layout();
}
} }
fn cnode_resize_child( fn cnode_resize_child(
@ -836,14 +878,7 @@ impl ContainingNode for FloatNode {
y2 = (v + bw).max(y1 + bw + bw); y2 = (v + bw).max(y1 + bw + bw);
} }
let new_pos = Rect::new_saturating(x1, y1, x2, y2); let new_pos = Rect::new_saturating(x1, y1, x2, y2);
if new_pos != pos { self.set_position(new_pos);
self.position.set(new_pos);
if self.visible.get() {
self.state.damage(pos);
self.state.damage(new_pos);
}
self.schedule_layout();
}
} }
fn cnode_pinned(&self) -> bool { fn cnode_pinned(&self) -> bool {