feat: add window animations
This commit is contained in:
parent
eece44a59c
commit
2a079ed800
29 changed files with 6957 additions and 114 deletions
367
src/renderer.rs
367
src/renderer.rs
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue