1
0
Fork 0
forked from wry/wry

feat: add window animations

This commit is contained in:
atagen 2026-05-21 15:20:46 +10:00 committed by kossLAN
parent eece44a59c
commit 2a079ed800
No known key found for this signature in database
29 changed files with 6957 additions and 114 deletions

View file

@ -1,7 +1,11 @@
use {
crate::{
animation::{
RetainedContent, RetainedExitFrame, RetainedExitLayer, RetainedSurface,
RetainedToplevel,
},
cmm::cmm_render_intent::RenderIntent,
gfx_api::{AcquireSync, AlphaMode, GfxApiOpt, ReleaseSync, SampleRect},
gfx_api::{AcquireSync, AlphaMode, BufferResv, GfxApiOpt, ReleaseSync, SampleRect},
ifs::wl_surface::{
SurfaceBuffer, WlSurface,
x_surface::xwindow::Xwindow,
@ -14,8 +18,8 @@ use {
state::State,
theme::{Color, CornerRadius},
tree::{
ContainerNode, DisplayNode, FloatNode, OutputNode, PlaceholderNode, ToplevelData,
ToplevelNodeBase, WorkspaceNode, tab_bar::TabBar,
ContainerNode, DisplayNode, FloatNode, Node, OutputNode, PlaceholderNode, ToplevelData,
ToplevelNode, ToplevelNodeBase, WorkspaceNode, tab_bar::TabBar,
},
},
std::{ops::Deref, rc::Rc, slice},
@ -200,14 +204,22 @@ impl Renderer<'_> {
self.render_workspace(&ws, x, y);
}
}
let now = self.state.now_nsec();
let exit_frames = self.state.animations.exit_frames(now);
self.render_exit_frames(&exit_frames, RetainedExitLayer::Tiled, &opos);
macro_rules! render_stacked {
($stack:expr) => {
for stacked in $stack.iter() {
if stacked.node_visible() {
self.base.sync();
let pos = stacked.node_absolute_position();
if pos.intersects(&opos) {
let (x, y) = opos.translate(pos.x1(), pos.y1());
let visual = self.state.animations.visual_rect(
stacked.node_id(),
pos,
self.state.now_nsec(),
);
if visual.intersects(&opos) {
let (x, y) = opos.translate(visual.x1(), visual.y1());
stacked.node_render(self, x, y, None);
}
}
@ -215,6 +227,7 @@ impl Renderer<'_> {
};
}
render_stacked!(self.state.root.stacked);
self.render_exit_frames(&exit_frames, RetainedExitLayer::Floating, &opos);
// Flush RoundedFillRect ops from container/float borders so they don't
// sort after (and render on top of) layer-shell CopyTexture ops.
self.base.sync();
@ -453,6 +466,265 @@ impl Renderer<'_> {
.fill_boxes2(&rd.border_rects, &c, srgb, perceptual, x, y);
}
fn presentation_child_body(
&self,
container: &ContainerNode,
child: &Rc<dyn ToplevelNode>,
body: Rect,
) -> Rect {
let abs = body.move_(container.abs_x1.get(), container.abs_y1.get());
let visual = self
.state
.animations
.visual_rect(child.node_id(), abs, self.state.now_nsec());
visual.move_(-container.abs_x1.get(), -container.abs_y1.get())
}
fn render_child_or_snapshot(
&mut self,
child: &Rc<dyn ToplevelNode>,
x: i32,
y: i32,
bounds: Option<&Rect>,
) {
if let Some(retained) = self
.state
.animations
.retained_snapshot(child.node_id(), self.state.now_nsec())
{
self.render_retained_toplevel(&retained, x, y, bounds);
} else {
child.node_render(self, x, y, bounds);
}
}
fn render_retained_toplevel(
&mut self,
retained: &RetainedToplevel,
x: i32,
y: i32,
bounds: Option<&Rect>,
) {
let (x, y) = self
.base
.scale_point(x + retained.offset.0, y + retained.offset.1);
self.render_retained_surface_scaled(&retained.surface, x, y, None, bounds);
}
fn render_exit_frames(
&mut self,
frames: &[RetainedExitFrame],
layer: RetainedExitLayer,
output_rect: &Rect,
) {
for frame in frames {
if frame.layer != layer || !frame.rect.intersects(output_rect) {
continue;
}
self.render_exit_frame(frame, output_rect);
}
}
fn render_exit_frame(&mut self, frame: &RetainedExitFrame, output_rect: &Rect) {
let (x, y) = output_rect.translate(frame.rect.x1(), frame.rect.y1());
let inset = frame.frame_inset;
if inset > 0 {
let color = if frame.active {
self.state.theme.colors.active_border.get()
} else {
self.state.theme.colors.border.get()
};
self.render_rounded_frame(
Rect::new_sized_saturating(0, 0, frame.rect.width(), frame.rect.height()),
&color,
self.state.theme.corner_radius.get(),
inset,
x,
y,
);
}
let body = Rect::new_sized_saturating(
x + inset,
y + inset,
frame.rect.width() - 2 * inset,
frame.rect.height() - 2 * inset,
);
if body.is_empty() {
return;
}
if inset > 0 && !self.state.theme.corner_radius.get().is_zero() {
let inner_cr = self.scale_corner_radius(
self.state
.theme
.corner_radius
.get()
.expanded_by(-(inset as f32)),
);
self.corner_radius = Some(inner_cr);
}
self.render_window_body_background(body);
let bounds = self.base.scale_rect(body);
self.stretch = if frame.source_body_size != body.size() {
Some(self.base.scale_point(body.width(), body.height()))
} else {
None
};
self.render_retained_toplevel(&frame.retained, body.x1(), body.y1(), Some(&bounds));
self.stretch = None;
self.corner_radius = None;
}
fn render_window_body_background(&mut self, body: Rect) {
if body.is_empty() {
return;
}
let color = self.state.theme.colors.background.get();
let srgb_srgb = self.state.color_manager.srgb_gamma22();
let srgb = &srgb_srgb.linear;
let perceptual = RenderIntent::Perceptual;
self.base.sync();
if let Some(cr) = self.corner_radius
&& !cr.is_zero()
{
self.base
.fill_rounded_rect(body, &color, None, srgb, perceptual, cr, 0.0);
} else {
let bounds = self.base.scale_rect(body);
self.base
.fill_scaled_boxes(slice::from_ref(&bounds), &color, None, srgb, perceptual);
}
}
fn render_retained_surface_scaled(
&mut self,
retained: &RetainedSurface,
x: i32,
y: i32,
pos_rel: Option<(i32, i32)>,
bounds: Option<&Rect>,
) {
let stretch = self.stretch.take();
let corner_radius = self.corner_radius.take();
let mut size = retained.size;
if let Some((x_rel, y_rel)) = pos_rel {
let (x, y) = self.base.scale_point(x_rel, y_rel);
let (w, h) = self.base.scale_point(x_rel + size.0, y_rel + size.1);
size = (w - x, h - y);
} else {
size = self.base.scale_point(size.0, size.1);
}
let mut stretched_source = None;
if let Some(s) = stretch {
if let RetainedContent::Texture { source, .. } = &retained.content {
let mut source = *source;
if size.0 > 0 && size.1 > 0 {
let sx = s.0 as f32 / size.0 as f32;
let sy = s.1 as f32 / size.1 as f32;
source.x2 *= sx;
source.y2 *= sy;
}
stretched_source = Some(source);
}
size = s;
}
for child in &retained.below {
let (x1, y1) = self.base.scale_point(child.offset.0, child.offset.1);
self.render_retained_surface_scaled(child, x + x1, y + y1, Some(child.offset), bounds);
}
self.corner_radius = corner_radius;
self.render_retained_content(retained, stretched_source, x, y, size, bounds);
for child in &retained.above {
let (x1, y1) = self.base.scale_point(child.offset.0, child.offset.1);
self.render_retained_surface_scaled(child, x + x1, y + y1, Some(child.offset), bounds);
}
}
fn render_retained_content(
&mut self,
retained: &RetainedSurface,
stretched_source: Option<SampleRect>,
x: i32,
y: i32,
size: (i32, i32),
bounds: Option<&Rect>,
) {
let corner_radius = self.corner_radius.take();
match &retained.content {
RetainedContent::Texture {
texture,
buffer,
source,
alpha,
color_description,
render_intent,
alpha_mode,
opaque,
} => {
let source = stretched_source.unwrap_or(*source);
if let Some(cr) = corner_radius {
self.base.render_rounded_texture(
texture,
*alpha,
x,
y,
Some(source),
Some(size),
self.base.scale,
bounds,
Some(buffer.clone() as Rc<dyn BufferResv>),
AcquireSync::Unnecessary,
buffer.release_sync,
color_description,
*render_intent,
*alpha_mode,
cr,
);
} else {
self.base.render_texture(
texture,
*alpha,
x,
y,
Some(source),
Some(size),
self.base.scale,
bounds,
Some(buffer.clone() as Rc<dyn BufferResv>),
AcquireSync::Unnecessary,
buffer.release_sync,
*opaque,
color_description,
*render_intent,
*alpha_mode,
);
}
}
RetainedContent::Color {
color,
alpha,
color_description,
render_intent,
} => {
if let Some(rect) = Rect::new_sized(x, y, size.0, size.1) {
let rect = match bounds {
None => rect,
Some(bounds) => rect.intersect(*bounds),
};
if !rect.is_empty() {
self.base.sync();
self.base.fill_scaled_boxes(
&[rect],
color,
*alpha,
&color_description.linear,
*render_intent,
);
}
}
}
}
}
pub fn render_container(&mut self, container: &ContainerNode, x: i32, y: i32) {
self.render_container_decorations(container, x, y);
@ -465,6 +737,7 @@ impl Renderer<'_> {
}
}
let mb = container.mono_body.get();
let visual_mb = self.presentation_child_body(container, &child.node, mb);
if self.state.theme.sizes.gap.get() != 0 {
let bw = self.state.theme.sizes.border_width.get();
let border_color = self.state.theme.colors.border.get();
@ -476,10 +749,10 @@ impl Renderer<'_> {
};
if !child.node.node_is_container() {
let frame = Rect::new_sized_saturating(
mb.x1() - bw,
mb.y1() - bw,
mb.width() + 2 * bw,
mb.height() + 2 * bw,
visual_mb.x1() - bw,
visual_mb.y1() - bw,
visual_mb.width() + 2 * bw,
visual_mb.height() + 2 * bw,
);
self.render_rounded_frame(
frame,
@ -491,14 +764,17 @@ impl Renderer<'_> {
);
}
}
let body = mb.move_(x, y);
let body = self.base.scale_rect(body);
let content = container.mono_content.get();
self.stretch = if content.width() != mb.width() || content.height() != mb.height() {
Some(self.base.scale_point(mb.width(), mb.height()))
} else {
None
};
let body = visual_mb.move_(x, y);
let content = container
.mono_content
.get()
.at_point(visual_mb.x1(), visual_mb.y1());
self.stretch =
if content.width() != visual_mb.width() || content.height() != visual_mb.height() {
Some(self.base.scale_point(visual_mb.width(), visual_mb.height()))
} else {
None
};
if self.state.theme.sizes.gap.get() != 0 && !child.node.node_is_container() {
let cr = self.state.theme.corner_radius.get();
if !cr.is_zero() {
@ -507,9 +783,16 @@ impl Renderer<'_> {
self.corner_radius = Some(inner_cr);
}
}
child
.node
.node_render(self, x + content.x1(), y + content.y1(), Some(&body));
if !child.node.node_is_container() {
self.render_window_body_background(body);
}
let body = self.base.scale_rect(body);
self.render_child_or_snapshot(
&child.node,
x + content.x1(),
y + content.y1(),
Some(&body),
);
self.stretch = None;
self.corner_radius = None;
} else {
@ -524,10 +807,13 @@ impl Renderer<'_> {
};
let cr = self.state.theme.corner_radius.get();
for child in container.children.iter() {
let body = child.body.get();
if body.x1() >= container.width.get() || body.y1() >= container.height.get() {
let layout_body = child.body.get();
if layout_body.x1() >= container.width.get()
|| layout_body.y1() >= container.height.get()
{
break;
}
let body = self.presentation_child_body(container, &child.node, layout_body);
if gap != 0 {
let c = if child.border_color_is_focused.get() {
&focused_border_color
@ -544,7 +830,7 @@ impl Renderer<'_> {
self.render_rounded_frame(frame, c, cr, bw, x, y);
}
}
let content = child.content.get();
let content = child.content.get().at_point(body.x1(), body.y1());
self.stretch =
if content.width() != body.width() || content.height() != body.height() {
Some(self.base.scale_point(body.width(), body.height()))
@ -556,10 +842,16 @@ impl Renderer<'_> {
self.corner_radius = Some(inner_cr);
}
let body = body.move_(x, y);
if !child.node.node_is_container() {
self.render_window_body_background(body);
}
let body = self.base.scale_rect(body);
child
.node
.node_render(self, x + content.x1(), y + content.y1(), Some(&body));
self.render_child_or_snapshot(
&child.node,
x + content.x1(),
y + content.y1(),
Some(&body),
);
self.stretch = None;
self.corner_radius = None;
}
@ -793,6 +1085,10 @@ impl Renderer<'_> {
_ => return,
};
let pos = floating.position.get();
let visual =
self.state
.animations
.visual_rect(floating.node_id(), pos, self.state.now_nsec());
let theme = &self.state.theme;
let bw = theme.sizes.border_width.get();
let bc = if floating.active.get() {
@ -801,16 +1097,27 @@ impl Renderer<'_> {
theme.colors.border.get()
};
let cr = theme.corner_radius.get();
let outer = Rect::new_sized_saturating(0, 0, pos.width(), pos.height());
let outer = Rect::new_sized_saturating(0, 0, visual.width(), visual.height());
self.render_rounded_frame(outer, &bc, cr, bw, x, y);
let body =
Rect::new_sized_saturating(x + bw, y + bw, pos.width() - 2 * bw, pos.height() - 2 * bw);
let body = Rect::new_sized_saturating(
x + bw,
y + bw,
visual.width() - 2 * bw,
visual.height() - 2 * bw,
);
let scissor_body = self.base.scale_rect(body);
self.stretch = if pos.width() != visual.width() || pos.height() != visual.height() {
Some(self.base.scale_point(body.width(), body.height()))
} else {
None
};
if !cr.is_zero() {
let inner_cr = self.scale_corner_radius(cr.expanded_by(-(bw as f32)));
self.corner_radius = Some(inner_cr);
}
child.node_render(self, body.x1(), body.y1(), Some(&scissor_body));
self.render_window_body_background(body);
self.render_child_or_snapshot(&child, body.x1(), body.y1(), Some(&scissor_body));
self.stretch = None;
self.corner_radius = None;
}