1
0
Fork 0
forked from wry/wry

Repair animation integration paths

This commit is contained in:
atagen 2026-05-22 09:16:51 +10:00
parent 31c289f628
commit 0fefe814c3
9 changed files with 229 additions and 35 deletions

View file

@ -195,13 +195,14 @@ impl RetainedToplevel {
impl RetainedSurface { impl RetainedSurface {
fn capture(surface: &WlSurface, offset: (i32, i32)) -> Option<Self> { fn capture(surface: &WlSurface, offset: (i32, i32)) -> Option<Self> {
let buffer = surface.buffer.get()?; let buffer = surface.buffer.get()?;
buffer.buffer.buf.update_texture_or_log(surface, true);
let size = surface.buffer_abs_pos.get().size(); let size = surface.buffer_abs_pos.get().size();
let source = *surface.buffer_points_norm.borrow(); let source = *surface.buffer_points_norm.borrow();
let color_description = surface.color_description(); let color_description = surface.color_description();
let render_intent = surface.render_intent(); let render_intent = surface.render_intent();
let alpha_mode = surface.alpha_mode(); let alpha_mode = surface.alpha_mode();
let alpha = surface.alpha(); let alpha = surface.alpha();
let content = match buffer.buffer.buf.get_stable_texture() { let content = match buffer.buffer.buf.get_texture(surface) {
Some(texture) => RetainedContent::Texture { Some(texture) => RetainedContent::Texture {
opaque: surface.opaque(), opaque: surface.opaque(),
texture, texture,
@ -237,14 +238,18 @@ impl RetainedSurface {
continue; continue;
} }
let pos = child.sub_surface.position.get(); let pos = child.sub_surface.position.get();
below.push(Self::capture(&child.sub_surface.surface, pos)?); if let Some(surface) = Self::capture(&child.sub_surface.surface, pos) {
below.push(surface);
}
} }
for child in children.above.iter() { for child in children.above.iter() {
if child.pending.get() { if child.pending.get() {
continue; continue;
} }
let pos = child.sub_surface.position.get(); let pos = child.sub_surface.position.get();
above.push(Self::capture(&child.sub_surface.surface, pos)?); if let Some(surface) = Self::capture(&child.sub_surface.surface, pos) {
above.push(surface);
}
} }
} }
Some(Self { Some(Self {

View file

@ -152,6 +152,7 @@ pub enum PlanStrategy {
SingleAction, SingleAction,
MixedSinglePhase, MixedSinglePhase,
HierarchyOrderedScales, HierarchyOrderedScales,
OrientationChange { from_axis: PhaseAxis },
SwapLanes { axis: PhaseAxis }, SwapLanes { axis: PhaseAxis },
SpaceThenOrthogonalGrowth { axis: PhaseAxis }, SpaceThenOrthogonalGrowth { axis: PhaseAxis },
ReversedForwardPlan { original: Box<PlanStrategy> }, ReversedForwardPlan { original: Box<PlanStrategy> },
@ -501,6 +502,22 @@ fn plan_forward(
} }
} }
} }
for axis in [PhaseAxis::Horizontal, PhaseAxis::Vertical] {
match plan_orientation_change(request, axis) {
Ok(plan) => return Ok(plan),
Err(error) => {
record_rejection(
&mut attempted,
direction,
PlanStrategy::OrientationChange { from_axis: axis },
error,
);
if error != MultiphasePlanFailure::NoPattern {
rejection.get_or_insert(error);
}
}
}
}
for axis in [PhaseAxis::Horizontal, PhaseAxis::Vertical] { for axis in [PhaseAxis::Horizontal, PhaseAxis::Vertical] {
match plan_axis_crossing_lanes(request, axis) { match plan_axis_crossing_lanes(request, axis) {
Ok(plan) => return Ok(plan), Ok(plan) => return Ok(plan),
@ -920,6 +937,94 @@ fn plan_space_then_orthogonal_growth(
) )
} }
fn plan_orientation_change(
request: &MultiphaseRequest,
from_axis: PhaseAxis,
) -> Result<MultiphasePlanned, MultiphasePlanFailure> {
if request.windows.len() < 2 {
return Err(MultiphasePlanFailure::NoPattern);
}
let to_axis = from_axis.other();
let min_lane_size = sane_min_size(main_size(request.bounds, to_axis));
let target_start = request
.windows
.first()
.map(|window| main_start(window.to, from_axis))
.ok_or(MultiphasePlanFailure::NoPattern)?;
let target_end = request
.windows
.first()
.map(|window| main_end(window.to, from_axis))
.ok_or(MultiphasePlanFailure::NoPattern)?;
let source_start = request
.windows
.first()
.map(|window| main_start(window.from, to_axis))
.ok_or(MultiphasePlanFailure::NoPattern)?;
let source_end = request
.windows
.first()
.map(|window| main_end(window.from, to_axis))
.ok_or(MultiphasePlanFailure::NoPattern)?;
if request.windows.iter().any(|window| {
main_start(window.from, to_axis) != source_start
|| main_end(window.from, to_axis) != source_end
|| main_start(window.to, from_axis) != target_start
|| main_end(window.to, from_axis) != target_end
|| main_size(window.to, to_axis) < min_lane_size
}) {
return Err(MultiphasePlanFailure::NoPattern);
}
let mut phase1 = vec![];
let mut phase2 = vec![];
let mut phase3 = vec![];
for window in &request.windows {
let lane = with_main_interval(
window.from,
to_axis,
main_start(window.to, to_axis),
main_end(window.to, to_axis),
);
let moved = with_main_interval(
lane,
from_axis,
main_start(window.to, from_axis),
main_start(window.to, from_axis) + main_size(lane, from_axis),
);
push_step(&mut phase1, window.node_id, window.from, lane);
push_step(&mut phase2, window.node_id, lane, moved);
push_step(&mut phase3, window.node_id, moved, window.to);
}
if phase1.is_empty() || phase3.is_empty() {
return Err(MultiphasePlanFailure::NoPattern);
}
build_validated_plan(
request,
PlanStrategy::OrientationChange { from_axis },
[
phase_draft(
PhaseKind::Scale,
to_axis,
phase1,
PhaseReason::ShrinkIntoLanes { lane_axis: to_axis },
),
phase_draft(
PhaseKind::Move,
from_axis,
phase2,
PhaseReason::MoveThroughFreedSpace,
),
phase_draft(
PhaseKind::Scale,
from_axis,
phase3,
PhaseReason::GrowOutOfLanes,
),
],
)
}
struct MultiphasePhaseDraft { struct MultiphasePhaseDraft {
action: MultiphasePhaseActionDraft, action: MultiphasePhaseActionDraft,
steps: Vec<MultiphaseStep>, steps: Vec<MultiphaseStep>,
@ -1665,6 +1770,20 @@ mod tests {
strategy: PlanStrategy::HierarchyOrderedScales, strategy: PlanStrategy::HierarchyOrderedScales,
reason: MultiphasePlanFailure::NoPattern, reason: MultiphasePlanFailure::NoPattern,
}, },
RejectedStrategy {
direction,
strategy: PlanStrategy::OrientationChange {
from_axis: PhaseAxis::Horizontal,
},
reason: MultiphasePlanFailure::NoPattern,
},
RejectedStrategy {
direction,
strategy: PlanStrategy::OrientationChange {
from_axis: PhaseAxis::Vertical,
},
reason: MultiphasePlanFailure::NoPattern,
},
RejectedStrategy { RejectedStrategy {
direction, direction,
strategy: PlanStrategy::SwapLanes { strategy: PlanStrategy::SwapLanes {
@ -2024,6 +2143,46 @@ mod tests {
assert!(validate_plan_continuous(&req, plan)); assert!(validate_plan_continuous(&req, plan));
} }
#[test]
fn orientation_change_shrinks_moves_then_grows() {
let req = request(vec![
window(1, rect(0, 0, 200, 400), rect(0, 0, 400, 200)),
window(2, rect(200, 0, 400, 400), rect(0, 200, 400, 400)),
]);
let planned = plan_no_overlap_explained(&req).unwrap();
let plan = &planned.plan;
assert_eq!(
planned.explanation.strategy,
PlanStrategy::OrientationChange {
from_axis: PhaseAxis::Horizontal,
}
);
assert_eq!(
actions(plan),
vec![
PhaseAction {
kind: PhaseKind::Scale,
axis: PhaseAxis::Vertical,
},
PhaseAction {
kind: PhaseKind::Move,
axis: PhaseAxis::Horizontal,
},
PhaseAction {
kind: PhaseKind::Scale,
axis: PhaseAxis::Horizontal,
},
]
);
assert_eq!(step_to(plan, 0, id(1)), rect(0, 0, 200, 200));
assert_eq!(step_to(plan, 0, id(2)), rect(200, 200, 400, 400));
assert_eq!(step_to(plan, 1, id(2)), rect(0, 200, 200, 400));
assert_eq!(step_to(plan, 2, id(1)), rect(0, 0, 400, 200));
assert_eq!(step_to(plan, 2, id(2)), rect(0, 200, 400, 400));
assert!(validate_plan_continuous(&req, plan));
}
#[test] #[test]
fn two_axis_redistribution_without_hierarchy_still_falls_back() { fn two_axis_redistribution_without_hierarchy_still_falls_back() {
let req = request(vec![ let req = request(vec![

View file

@ -310,19 +310,6 @@ impl WlBuffer {
} }
} }
pub fn get_stable_texture(&self) -> Option<Rc<dyn GfxTexture>> {
match &*self.storage.borrow() {
None => None,
Some(s) => match s {
WlBufferStorage::Shm {
dmabuf_buffer_params,
..
} => dmabuf_buffer_params.tex.clone(),
WlBufferStorage::Dmabuf { tex, .. } => tex.clone(),
},
}
}
pub fn update_texture_or_log(&self, surface: &WlSurface, sync_shm: bool) { pub fn update_texture_or_log(&self, surface: &WlSurface, sync_shm: bool) {
if let Err(e) = self.update_texture(surface, sync_shm) { if let Err(e) = self.update_texture(surface, sync_shm) {
log::warn!("Could not update texture: {}", ErrorFmt(e)); log::warn!("Could not update texture: {}", ErrorFmt(e));

View file

@ -628,6 +628,11 @@ fn schedule_async_upload(
{ {
back_tex_opt = None; back_tex_opt = None;
} }
if let Some(back_tex) = &back_tex_opt
&& Rc::strong_count(back_tex) > 1
{
back_tex_opt = None;
}
let damage_full = || { let damage_full = || {
back.damage.clear(); back.damage.clear();
back.damage.damage(slice::from_ref(&buf.rect)); back.damage.damage(slice::from_ref(&buf.rect));

View file

@ -836,7 +836,14 @@ impl State {
pub fn map_tiled(self: &Rc<Self>, node: Rc<dyn ToplevelNode>) { pub fn map_tiled(self: &Rc<Self>, node: Rc<dyn ToplevelNode>) {
let seat = self.seat_queue.last(); let seat = self.seat_queue.last();
self.do_map_tiled(seat.as_deref(), node.clone()); let animate_new_app_map = node.tl_data().parent.is_none()
&& node.tl_data().kind.is_app_window()
&& !node.tl_data().visible.get();
if animate_new_app_map {
self.with_layout_animations(|| self.do_map_tiled(seat.as_deref(), node.clone()));
} else {
self.do_map_tiled(seat.as_deref(), node.clone());
}
self.focus_after_map(node, seat.as_deref()); self.focus_after_map(node, seat.as_deref());
} }

View file

@ -1766,17 +1766,38 @@ enum SeatOpKind {
pub async fn container_layout(state: Rc<State>) { pub async fn container_layout(state: Rc<State>) {
loop { loop {
let container = state.pending_container_layout.pop().await; let first = state.pending_container_layout.pop().await;
if container.layout_scheduled.get() { let mut containers = vec![first];
while let Some(container) = state.pending_container_layout.try_pop() {
containers.push(container);
}
let mut animated = vec![];
let mut immediate = vec![];
for container in containers {
if !container.layout_scheduled.get() {
continue;
}
let animate = container.animate_next_layout.replace(false) let animate = container.animate_next_layout.replace(false)
&& !state.suppress_animations_for_next_layout.get(); && !state.suppress_animations_for_next_layout.get();
let prev_active = state.layout_animations_active.replace(animate);
if animate { if animate {
state.begin_layout_animation_batch(); animated.push(container);
} else {
immediate.push(container);
} }
container.perform_layout(); }
if animate { if !animated.is_empty() {
state.finish_layout_animation_batch(); let prev_active = state.layout_animations_active.replace(true);
state.begin_layout_animation_batch();
for container in animated {
container.perform_layout();
}
state.finish_layout_animation_batch();
state.layout_animations_active.set(prev_active);
}
if !immediate.is_empty() {
let prev_active = state.layout_animations_active.replace(false);
for container in immediate {
container.perform_layout();
} }
state.layout_animations_active.set(prev_active); state.layout_animations_active.set(prev_active);
} }

View file

@ -2,7 +2,10 @@ use {
crate::{ crate::{
animation::{ animation::{
RetainedExitLayer, RetainedToplevel, RetainedExitLayer, RetainedToplevel,
multiphase::{MultiphaseHierarchyPosition, MultiphaseWindowHierarchy, PhaseAxis}, multiphase::{
MultiphaseHierarchyPosition, MultiphaseHierarchyTransition,
MultiphaseWindowHierarchy, PhaseAxis,
},
}, },
client::{Client, ClientId}, client::{Client, ClientId},
criteria::{ criteria::{
@ -195,18 +198,30 @@ impl<T: ToplevelNodeBase> ToplevelNode for T {
target_hierarchy, target_hierarchy,
); );
let spawn_in_pending = data.spawn_in_pending.get(); 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_is_mono = data let parent_is_mono = data
.parent .parent
.get() .get()
.and_then(|parent| parent.node_into_container()) .and_then(|parent| parent.node_into_container())
.is_some_and(|container| container.mono_child.is_some()); .is_some_and(|container| container.mono_child.is_some());
let active_mono_boundary = matches!(
hierarchy.transition,
MultiphaseHierarchyTransition::EnteringMono
| MultiphaseHierarchyTransition::ExitingMono
) && (hierarchy.source.mono_active
|| hierarchy.target.mono_active);
if prev != *rect if prev != *rect
&& !prev.is_empty() && !prev.is_empty()
&& !rect.is_empty() && !rect.is_empty()
&& data.visible.get() && data.visible.get()
&& !data.parent_is_float.get() && !data.parent_is_float.get()
&& !self.node_is_container() && !self.node_is_container()
&& !parent_is_mono && (!parent_is_mono || active_mono_boundary)
{ {
data.state.clone().queue_tiled_animation_with_hierarchy( data.state.clone().queue_tiled_animation_with_hierarchy(
data.node_id, data.node_id,
@ -216,20 +231,14 @@ impl<T: ToplevelNodeBase> ToplevelNode for T {
hierarchy, hierarchy,
); );
} }
if spawn_in_pending if spawn_in_eligible {
&& !rect.is_empty()
&& data.visible.get()
&& !data.is_fullscreen.get()
&& data.kind.is_app_window()
&& !self.node_is_container()
{
data.state.clone().queue_spawn_in_animation( data.state.clone().queue_spawn_in_animation(
data.node_id, data.node_id,
*rect, *rect,
self.tl_animation_snapshot(), self.tl_animation_snapshot(),
); );
} }
if spawn_in_pending && !rect.is_empty() { if spawn_in_eligible {
data.spawn_in_pending.set(false); data.spawn_in_pending.set(false);
} }
if prev.size() != rect.size() { if prev.size() != rect.size() {

View file

@ -197,10 +197,10 @@ impl WorkspaceNode {
} }
self.pull_child_properties(&**container); self.pull_child_properties(&**container);
let pos = self.position.get(); let pos = self.position.get();
container.clone().tl_change_extents(&pos);
container.tl_set_parent(self.clone()); container.tl_set_parent(self.clone());
container.tl_set_visible(self.container_visible()); container.tl_set_visible(self.container_visible());
self.container.set(Some(container.clone())); self.container.set(Some(container.clone()));
container.clone().tl_change_extents(&pos);
self.state.damage(self.position.get()); self.state.damage(self.position.get());
} }

View file

@ -2034,6 +2034,7 @@ impl Wm {
self.windows_by_surface_serial.remove(&serial); self.windows_by_surface_serial.remove(&serial);
} }
if let Some(window) = data.window.take() { if let Some(window) = data.window.take() {
window.queue_spawn_out();
window.destroy(); window.destroy();
} }
if let Some(parent) = data.parent.take() { if let Some(parent) = data.parent.take() {