feat: add window animations
This commit is contained in:
parent
a29937ebe8
commit
ce14169d6b
29 changed files with 6957 additions and 114 deletions
|
|
@ -1,5 +1,12 @@
|
|||
use {
|
||||
crate::{
|
||||
animation::{
|
||||
RetainedExitLayer, RetainedToplevel,
|
||||
multiphase::{
|
||||
MultiphaseHierarchyPosition, MultiphaseHierarchyTransition,
|
||||
MultiphaseWindowHierarchy, PhaseAxis,
|
||||
},
|
||||
},
|
||||
client::{Client, ClientId},
|
||||
criteria::{
|
||||
CritDestroyListener, CritMatcherId,
|
||||
|
|
@ -117,6 +124,7 @@ impl<T: ToplevelNodeBase> ToplevelNode for T {
|
|||
let parent_was_none = data.parent.set(Some(parent.clone())).is_none();
|
||||
if parent_was_none {
|
||||
data.mapped_during_iteration.set(data.state.eng.iteration());
|
||||
data.spawn_in_pending.set(data.kind.is_app_window());
|
||||
data.property_changed(TL_CHANGED_NEW);
|
||||
}
|
||||
let was_floating = data.parent_is_float.get();
|
||||
|
|
@ -184,6 +192,57 @@ impl<T: ToplevelNodeBase> ToplevelNode for T {
|
|||
fn tl_change_extents(self: Rc<Self>, rect: &Rect) {
|
||||
let data = self.tl_data();
|
||||
let prev = data.desired_extents.replace(*rect);
|
||||
let target_hierarchy = self.tl_multiphase_hierarchy_position();
|
||||
let hierarchy = MultiphaseWindowHierarchy::new(
|
||||
data.layout_animation_position.replace(target_hierarchy),
|
||||
target_hierarchy,
|
||||
);
|
||||
let spawn_in_pending = data.spawn_in_pending.get();
|
||||
let spawn_in_eligible = spawn_in_pending
|
||||
&& !rect.is_empty()
|
||||
&& data.visible.get()
|
||||
&& !data.is_fullscreen.get()
|
||||
&& data.kind.is_app_window()
|
||||
&& !self.node_is_container();
|
||||
let parent_container = data
|
||||
.parent
|
||||
.get()
|
||||
.and_then(|parent| parent.node_into_container());
|
||||
let parent_is_mono = parent_container
|
||||
.as_ref()
|
||||
.is_some_and(|container| container.mono_child.is_some());
|
||||
let parent_mono_transition = parent_container
|
||||
.as_ref()
|
||||
.is_some_and(|container| container.mono_transition_animation_pending.get());
|
||||
let active_mono_boundary = matches!(
|
||||
hierarchy.transition,
|
||||
MultiphaseHierarchyTransition::EnteringMono
|
||||
| MultiphaseHierarchyTransition::ExitingMono
|
||||
) && parent_mono_transition
|
||||
&& (hierarchy.source.mono_active || hierarchy.target.mono_active);
|
||||
if prev != *rect
|
||||
&& !prev.is_empty()
|
||||
&& !rect.is_empty()
|
||||
&& data.visible.get()
|
||||
&& !data.parent_is_float.get()
|
||||
&& !self.node_is_container()
|
||||
&& (!parent_is_mono || active_mono_boundary)
|
||||
{
|
||||
data.state.clone().queue_tiled_animation_with_hierarchy(
|
||||
data.node_id,
|
||||
prev,
|
||||
*rect,
|
||||
hierarchy,
|
||||
);
|
||||
}
|
||||
if spawn_in_eligible {
|
||||
data.state
|
||||
.clone()
|
||||
.queue_spawn_in_animation(data.node_id, *rect);
|
||||
}
|
||||
if spawn_in_eligible {
|
||||
data.spawn_in_pending.set(false);
|
||||
}
|
||||
if prev.size() != rect.size() {
|
||||
for sc in data.jay_screencasts.lock().values() {
|
||||
sc.schedule_realloc_or_reconfigure();
|
||||
|
|
@ -275,6 +334,35 @@ pub trait ToplevelNodeBase: Node {
|
|||
true
|
||||
}
|
||||
|
||||
fn tl_multiphase_hierarchy_position(&self) -> MultiphaseHierarchyPosition {
|
||||
let data = self.tl_data();
|
||||
let Some(parent) = data.parent.get() else {
|
||||
return Default::default();
|
||||
};
|
||||
let mut position = MultiphaseHierarchyPosition {
|
||||
parent: Some(parent.node_id()),
|
||||
..Default::default()
|
||||
};
|
||||
populate_multiphase_ancestor_splits(&mut position, Some(parent.clone()));
|
||||
if let Some(container) = parent.node_into_container() {
|
||||
position.split_axis = Some(match container.split.get() {
|
||||
ContainerSplit::Horizontal => PhaseAxis::Horizontal,
|
||||
ContainerSplit::Vertical => PhaseAxis::Vertical,
|
||||
});
|
||||
if let Some(mono) = container.mono_child.get() {
|
||||
position.parent_is_mono = true;
|
||||
position.mono_active = mono.node.node_id() == data.node_id;
|
||||
}
|
||||
for (idx, child) in container.children.iter().enumerate() {
|
||||
if child.node.node_id() == data.node_id {
|
||||
position.sibling_index = Some(idx.min(u16::MAX as usize) as u16);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
position
|
||||
}
|
||||
|
||||
fn tl_set_active(&self, active: bool) {
|
||||
let _ = active;
|
||||
}
|
||||
|
|
@ -299,6 +387,11 @@ pub trait ToplevelNodeBase: Node {
|
|||
fn tl_scanout_surface(&self) -> Option<Rc<WlSurface>> {
|
||||
None
|
||||
}
|
||||
|
||||
fn tl_animation_snapshot(&self) -> Option<Rc<RetainedToplevel>> {
|
||||
None
|
||||
}
|
||||
|
||||
fn tl_restack_popups(&self) {
|
||||
// nothing
|
||||
}
|
||||
|
|
@ -339,6 +432,31 @@ pub trait ToplevelNodeBase: Node {
|
|||
}
|
||||
}
|
||||
|
||||
fn populate_multiphase_ancestor_splits(
|
||||
position: &mut MultiphaseHierarchyPosition,
|
||||
mut parent: Option<Rc<dyn ContainingNode>>,
|
||||
) {
|
||||
let mut depth = 0u16;
|
||||
while let Some(node) = parent {
|
||||
let Some(toplevel) = node.clone().node_into_toplevel() else {
|
||||
break;
|
||||
};
|
||||
depth = depth.saturating_add(1);
|
||||
if let Some(container) = node.node_into_container() {
|
||||
match container.split.get() {
|
||||
ContainerSplit::Horizontal => {
|
||||
position.nearest_horizontal_split_depth.get_or_insert(depth);
|
||||
}
|
||||
ContainerSplit::Vertical => {
|
||||
position.nearest_vertical_split_depth.get_or_insert(depth);
|
||||
}
|
||||
}
|
||||
}
|
||||
parent = toplevel.tl_data().parent.get();
|
||||
}
|
||||
position.depth = depth;
|
||||
}
|
||||
|
||||
pub struct FullscreenedData {
|
||||
pub placeholder: Rc<PlaceholderNode>,
|
||||
pub workspace: Rc<WorkspaceNode>,
|
||||
|
|
@ -377,6 +495,13 @@ impl ToplevelType {
|
|||
ToplevelType::XWindow { .. } => window::X_WINDOW,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_app_window(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
ToplevelType::XdgToplevel(_) | ToplevelType::XWindow(_)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ToplevelData {
|
||||
|
|
@ -399,8 +524,10 @@ pub struct ToplevelData {
|
|||
pub title: RefCell<String>,
|
||||
pub parent: CloneCell<Option<Rc<dyn ContainingNode>>>,
|
||||
pub mapped_during_iteration: Cell<u64>,
|
||||
pub spawn_in_pending: Cell<bool>,
|
||||
pub pos: Cell<Rect>,
|
||||
pub desired_extents: Cell<Rect>,
|
||||
pub layout_animation_position: Cell<MultiphaseHierarchyPosition>,
|
||||
pub seat_state: NodeSeatState,
|
||||
pub wants_attention: Cell<bool>,
|
||||
pub requested_attention: Cell<bool>,
|
||||
|
|
@ -462,8 +589,10 @@ impl ToplevelData {
|
|||
title: RefCell::new(title),
|
||||
parent: Default::default(),
|
||||
mapped_during_iteration: Cell::new(0),
|
||||
spawn_in_pending: Cell::new(false),
|
||||
pos: Default::default(),
|
||||
desired_extents: Default::default(),
|
||||
layout_animation_position: Default::default(),
|
||||
seat_state: Default::default(),
|
||||
wants_attention: Cell::new(false),
|
||||
requested_attention: Cell::new(false),
|
||||
|
|
@ -935,6 +1064,62 @@ impl ToplevelData {
|
|||
self.mapped_during_iteration.get() == self.state.eng.iteration()
|
||||
}
|
||||
|
||||
pub fn queue_spawn_out(&self, node: &dyn ToplevelNode, retained: Option<Rc<RetainedToplevel>>) {
|
||||
if !self.kind.is_app_window()
|
||||
|| !self.visible.get()
|
||||
|| self.is_fullscreen.get()
|
||||
|| node.node_is_container()
|
||||
{
|
||||
return;
|
||||
}
|
||||
let Some(retained) = retained else {
|
||||
return;
|
||||
};
|
||||
let bw = self.state.theme.sizes.border_width.get().max(0);
|
||||
let now = self.state.now_nsec();
|
||||
let (outer, frame_inset, layer) = if self.parent_is_float.get() {
|
||||
let Some(float) = self.float.get() else {
|
||||
return;
|
||||
};
|
||||
(
|
||||
self.state
|
||||
.animations
|
||||
.visual_rect(float.node_id(), float.position.get(), now),
|
||||
bw,
|
||||
RetainedExitLayer::Floating,
|
||||
)
|
||||
} else {
|
||||
let body =
|
||||
self.state
|
||||
.animations
|
||||
.visual_rect(self.node_id, node.node_absolute_position(), now);
|
||||
if body.is_empty() {
|
||||
return;
|
||||
}
|
||||
if self.state.theme.sizes.gap.get() != 0 {
|
||||
(
|
||||
Rect::new_sized_saturating(
|
||||
body.x1() - bw,
|
||||
body.y1() - bw,
|
||||
body.width() + 2 * bw,
|
||||
body.height() + 2 * bw,
|
||||
),
|
||||
bw,
|
||||
RetainedExitLayer::Tiled,
|
||||
)
|
||||
} else {
|
||||
(body, 0, RetainedExitLayer::Tiled)
|
||||
}
|
||||
};
|
||||
self.state.clone().queue_spawn_out_animation(
|
||||
outer,
|
||||
frame_inset,
|
||||
retained,
|
||||
self.active(),
|
||||
layer,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn set_content_type(&self, content_type: Option<ContentType>) {
|
||||
if self.content_type.replace(content_type) != content_type {
|
||||
self.property_changed(TL_CHANGED_CONTENT_TY);
|
||||
|
|
@ -1043,6 +1228,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) {
|
||||
let data = tl.tl_data();
|
||||
if data.is_fullscreen.get() {
|
||||
|
|
@ -1059,9 +1264,19 @@ pub fn toplevel_set_floating(state: &Rc<State>, tl: Rc<dyn ToplevelNode>, floati
|
|||
parent.cnode_remove_child2(&*tl, true);
|
||||
state.map_tiled(tl);
|
||||
} 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);
|
||||
parent.cnode_remove_child2(&*tl, true);
|
||||
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);
|
||||
state.queue_linear_layout_animation(node_id, old_body, new_body);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue