Add retained spawn-out animations
This commit is contained in:
parent
d0cc5dc3c7
commit
fa5c28ca3d
9 changed files with 331 additions and 8 deletions
|
|
@ -14,8 +14,9 @@ be handled deliberately.
|
||||||
in-flight windows keep their existing timelines.
|
in-flight windows keep their existing timelines.
|
||||||
- Spawn-in uses scale and position for newly mapped tiled and floating app
|
- Spawn-in uses scale and position for newly mapped tiled and floating app
|
||||||
windows. Layer-shell, overlay, override-redirect, and fullscreen surfaces do
|
windows. Layer-shell, overlay, override-redirect, and fullscreen surfaces do
|
||||||
not use this path. Spawn-out requires retained visual content after the live
|
not use this path. Spawn-out uses retained visual content after the live node
|
||||||
node is gone and remains deferred.
|
is gone, when a stable retained surface tree can be captured before unmap or
|
||||||
|
destroy.
|
||||||
- Command-driven tile-to-float and float-to-tile transitions may animate.
|
- Command-driven tile-to-float and float-to-tile transitions may animate.
|
||||||
Protocol drag/drop paths do not.
|
Protocol drag/drop paths do not.
|
||||||
- The no-overlap multiphase system is a separate phase after the linear path is
|
- The no-overlap multiphase system is a separate phase after the linear path is
|
||||||
|
|
@ -90,7 +91,8 @@ Initial scope:
|
||||||
- Live client buffers are rendered in Phase 1. Retained content freezing is
|
- Live client buffers are rendered in Phase 1. Retained content freezing is
|
||||||
deferred, but animated windows must still be clipped to their presentation
|
deferred, but animated windows must still be clipped to their presentation
|
||||||
bounds and must preserve the existing stretch behavior for undersized contents.
|
bounds and must preserve the existing stretch behavior for undersized contents.
|
||||||
- No spawn-out.
|
- Spawn-out is retained-content-only. If the surface cannot be retained safely
|
||||||
|
the window snaps out instead of animating an empty frame.
|
||||||
- No multiphase no-overlap planner.
|
- No multiphase no-overlap planner.
|
||||||
|
|
||||||
Tests:
|
Tests:
|
||||||
|
|
@ -116,6 +118,9 @@ Initial retained-record implementation status:
|
||||||
for both tiled windows and floating child contents.
|
for both tiled windows and floating child contents.
|
||||||
- Tile-to-float and float-to-tile transitions retain GPU/dmabuf-backed child
|
- Tile-to-float and float-to-tile transitions retain GPU/dmabuf-backed child
|
||||||
contents while the presentation geometry changes.
|
contents while the presentation geometry changes.
|
||||||
|
- Spawn-out captures retained app-window contents before XDG/Xwayland unmap or
|
||||||
|
destroy, then renders a detached shrinking presentation record until the
|
||||||
|
animation completes.
|
||||||
- Retained records hold both `GfxTexture` and `SurfaceBuffer` references so the
|
- Retained records hold both `GfxTexture` and `SurfaceBuffer` references so the
|
||||||
existing buffer release/sync path remains authoritative.
|
existing buffer release/sync path remains authoritative.
|
||||||
- Single-pixel buffers can be retained as color records.
|
- Single-pixel buffers can be retained as color records.
|
||||||
|
|
|
||||||
117
src/animation.rs
117
src/animation.rs
|
|
@ -57,6 +57,7 @@ pub struct AnimationState {
|
||||||
pub duration_ms: Cell<u32>,
|
pub duration_ms: Cell<u32>,
|
||||||
pub curve: Cell<AnimationCurve>,
|
pub curve: Cell<AnimationCurve>,
|
||||||
windows: RefCell<AHashMap<NodeId, WindowAnimation>>,
|
windows: RefCell<AHashMap<NodeId, WindowAnimation>>,
|
||||||
|
exits: RefCell<Vec<ExitAnimation>>,
|
||||||
tick: CloneCell<Option<Rc<AnimationTick>>>,
|
tick: CloneCell<Option<Rc<AnimationTick>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -92,6 +93,21 @@ pub enum RetainedContent {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub enum RetainedExitLayer {
|
||||||
|
Tiled,
|
||||||
|
Floating,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct RetainedExitFrame {
|
||||||
|
pub rect: Rect,
|
||||||
|
pub retained: Rc<RetainedToplevel>,
|
||||||
|
pub frame_inset: i32,
|
||||||
|
pub source_body_size: (i32, i32),
|
||||||
|
pub active: bool,
|
||||||
|
pub layer: RetainedExitLayer,
|
||||||
|
}
|
||||||
|
|
||||||
impl RetainedToplevel {
|
impl RetainedToplevel {
|
||||||
pub fn capture_surface(surface: &WlSurface, offset: (i32, i32)) -> Option<Rc<Self>> {
|
pub fn capture_surface(surface: &WlSurface, offset: (i32, i32)) -> Option<Rc<Self>> {
|
||||||
Some(Rc::new(Self {
|
Some(Rc::new(Self {
|
||||||
|
|
@ -173,6 +189,7 @@ impl Default for AnimationState {
|
||||||
duration_ms: Cell::new(DEFAULT_DURATION_MS),
|
duration_ms: Cell::new(DEFAULT_DURATION_MS),
|
||||||
curve: Cell::new(AnimationCurve::EaseOut),
|
curve: Cell::new(AnimationCurve::EaseOut),
|
||||||
windows: Default::default(),
|
windows: Default::default(),
|
||||||
|
exits: Default::default(),
|
||||||
tick: Default::default(),
|
tick: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -181,6 +198,7 @@ impl Default for AnimationState {
|
||||||
impl AnimationState {
|
impl AnimationState {
|
||||||
pub fn clear(&self) {
|
pub fn clear(&self) {
|
||||||
self.windows.borrow_mut().clear();
|
self.windows.borrow_mut().clear();
|
||||||
|
self.exits.borrow_mut().clear();
|
||||||
if let Some(tick) = self.tick.take() {
|
if let Some(tick) = self.tick.take() {
|
||||||
tick.detach();
|
tick.detach();
|
||||||
}
|
}
|
||||||
|
|
@ -246,6 +264,42 @@ impl AnimationState {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_spawn_out(
|
||||||
|
&self,
|
||||||
|
from: Rect,
|
||||||
|
frame_inset: i32,
|
||||||
|
retained: Rc<RetainedToplevel>,
|
||||||
|
active: bool,
|
||||||
|
layer: RetainedExitLayer,
|
||||||
|
now_nsec: u64,
|
||||||
|
duration_ms: u32,
|
||||||
|
) -> bool {
|
||||||
|
if from.is_empty() || duration_ms == 0 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let to = spawn_in_start_rect(from);
|
||||||
|
if to == from || to.is_empty() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let source_body_size = body_size_for_frame(from, frame_inset);
|
||||||
|
if source_body_size.0 <= 0 || source_body_size.1 <= 0 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
self.exits.borrow_mut().push(ExitAnimation {
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
start_nsec: now_nsec,
|
||||||
|
duration_nsec: duration_ms as u64 * 1_000_000,
|
||||||
|
last_damage: from,
|
||||||
|
retained,
|
||||||
|
frame_inset,
|
||||||
|
source_body_size,
|
||||||
|
active,
|
||||||
|
layer,
|
||||||
|
});
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
pub fn visual_rect(&self, node_id: NodeId, layout: Rect, now_nsec: u64) -> Rect {
|
pub fn visual_rect(&self, node_id: NodeId, layout: Rect, now_nsec: u64) -> Rect {
|
||||||
let windows = self.windows.borrow();
|
let windows = self.windows.borrow();
|
||||||
match windows.get(&node_id) {
|
match windows.get(&node_id) {
|
||||||
|
|
@ -266,6 +320,22 @@ impl AnimationState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn exit_frames(&self, now_nsec: u64) -> Vec<RetainedExitFrame> {
|
||||||
|
self.exits
|
||||||
|
.borrow()
|
||||||
|
.iter()
|
||||||
|
.filter(|exit| !exit.done(now_nsec))
|
||||||
|
.map(|exit| RetainedExitFrame {
|
||||||
|
rect: exit.rect_at(now_nsec),
|
||||||
|
retained: exit.retained.clone(),
|
||||||
|
frame_inset: exit.frame_inset,
|
||||||
|
source_body_size: exit.source_body_size,
|
||||||
|
active: exit.active,
|
||||||
|
layer: exit.layer,
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
fn damage_active(&self, state: &State, now_nsec: u64) -> bool {
|
fn damage_active(&self, state: &State, now_nsec: u64) -> bool {
|
||||||
let mut damages = vec![];
|
let mut damages = vec![];
|
||||||
let mut any_active = false;
|
let mut any_active = false;
|
||||||
|
|
@ -283,6 +353,18 @@ impl AnimationState {
|
||||||
any_active |= active;
|
any_active |= active;
|
||||||
active
|
active
|
||||||
});
|
});
|
||||||
|
self.exits.borrow_mut().retain_mut(|exit| {
|
||||||
|
let current = exit.rect_at(now_nsec);
|
||||||
|
let damage = exit.last_damage.union(current).union(exit.to);
|
||||||
|
damages.push(expand_damage_rect(
|
||||||
|
damage,
|
||||||
|
state.theme.sizes.border_width.get().max(0),
|
||||||
|
));
|
||||||
|
exit.last_damage = current;
|
||||||
|
let active = !exit.done(now_nsec);
|
||||||
|
any_active |= active;
|
||||||
|
active
|
||||||
|
});
|
||||||
}
|
}
|
||||||
for damage in damages {
|
for damage in damages {
|
||||||
state.damage(damage);
|
state.damage(damage);
|
||||||
|
|
@ -329,6 +411,34 @@ impl WindowAnimation {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct ExitAnimation {
|
||||||
|
from: Rect,
|
||||||
|
to: Rect,
|
||||||
|
start_nsec: u64,
|
||||||
|
duration_nsec: u64,
|
||||||
|
last_damage: Rect,
|
||||||
|
retained: Rc<RetainedToplevel>,
|
||||||
|
frame_inset: i32,
|
||||||
|
source_body_size: (i32, i32),
|
||||||
|
active: bool,
|
||||||
|
layer: RetainedExitLayer,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExitAnimation {
|
||||||
|
fn done(&self, now_nsec: u64) -> bool {
|
||||||
|
now_nsec.saturating_sub(self.start_nsec) >= self.duration_nsec
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rect_at(&self, now_nsec: u64) -> Rect {
|
||||||
|
if self.duration_nsec == 0 {
|
||||||
|
return self.to;
|
||||||
|
}
|
||||||
|
let elapsed = now_nsec.saturating_sub(self.start_nsec);
|
||||||
|
let t = (elapsed as f64 / self.duration_nsec as f64).clamp(0.0, 1.0);
|
||||||
|
lerp_rect(self.from, self.to, t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct AnimationTick {
|
pub struct AnimationTick {
|
||||||
state: Weak<State>,
|
state: Weak<State>,
|
||||||
slf: Weak<dyn LatchListener>,
|
slf: Weak<dyn LatchListener>,
|
||||||
|
|
@ -389,6 +499,13 @@ pub(crate) fn spawn_in_start_rect(target: Rect) -> Rect {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn body_size_for_frame(rect: Rect, frame_inset: i32) -> (i32, i32) {
|
||||||
|
(
|
||||||
|
rect.width().saturating_sub(2 * frame_inset),
|
||||||
|
rect.height().saturating_sub(2 * frame_inset),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fn lerp_rect(from: Rect, to: Rect, t: f64) -> Rect {
|
fn lerp_rect(from: Rect, to: Rect, t: f64) -> Rect {
|
||||||
fn lerp(from: i32, to: i32, t: f64) -> i32 {
|
fn lerp(from: i32, to: i32, t: f64) -> i32 {
|
||||||
(from as f64 + (to as f64 - from as f64) * t).round() as i32
|
(from as f64 + (to as f64 - from as f64) * t).round() as i32
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
ifs::wl_surface::{
|
ifs::wl_surface::{
|
||||||
SurfaceExt, WlSurface, WlSurfaceError,
|
PendingState, SurfaceExt, WlSurface, WlSurfaceError,
|
||||||
x_surface::{xwayland_surface_v1::XwaylandSurfaceV1, xwindow::Xwindow},
|
x_surface::{xwayland_surface_v1::XwaylandSurfaceV1, xwindow::Xwindow},
|
||||||
},
|
},
|
||||||
leaks::Tracker,
|
leaks::Tracker,
|
||||||
|
|
@ -30,6 +30,22 @@ impl SurfaceExt for XSurface {
|
||||||
win.node_layer()
|
win.node_layer()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn before_apply_commit(
|
||||||
|
self: Rc<Self>,
|
||||||
|
pending: &mut PendingState,
|
||||||
|
) -> Result<(), WlSurfaceError> {
|
||||||
|
if pending
|
||||||
|
.buffer
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|buffer| buffer.is_none())
|
||||||
|
&& self.surface.buffer.is_some()
|
||||||
|
&& let Some(xwindow) = self.xwindow.get()
|
||||||
|
{
|
||||||
|
xwindow.queue_spawn_out();
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn after_apply_commit(self: Rc<Self>) {
|
fn after_apply_commit(self: Rc<Self>) {
|
||||||
if let Some(xwindow) = self.xwindow.get() {
|
if let Some(xwindow) = self.xwindow.get() {
|
||||||
xwindow.map_status_changed();
|
xwindow.map_status_changed();
|
||||||
|
|
@ -45,6 +61,7 @@ impl SurfaceExt for XSurface {
|
||||||
}
|
}
|
||||||
self.surface.unset_ext();
|
self.surface.unset_ext();
|
||||||
if let Some(xwindow) = self.xwindow.take() {
|
if let Some(xwindow) = self.xwindow.take() {
|
||||||
|
xwindow.queue_spawn_out();
|
||||||
xwindow.tl_destroy();
|
xwindow.tl_destroy();
|
||||||
xwindow.data.window.set(None);
|
xwindow.data.window.set(None);
|
||||||
xwindow.data.surface_id.set(None);
|
xwindow.data.surface_id.set(None);
|
||||||
|
|
|
||||||
|
|
@ -253,6 +253,11 @@ impl Xwindow {
|
||||||
self.x.surface.buffer.is_some() && self.data.info.mapped.get()
|
self.x.surface.buffer.is_some() && self.data.info.mapped.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn queue_spawn_out(&self) {
|
||||||
|
self.toplevel_data
|
||||||
|
.queue_spawn_out(self, self.tl_animation_snapshot());
|
||||||
|
}
|
||||||
|
|
||||||
fn map_change(&self) -> Change {
|
fn map_change(&self) -> Change {
|
||||||
match (self.may_be_mapped(), self.is_mapped()) {
|
match (self.may_be_mapped(), self.is_mapped()) {
|
||||||
(true, false) => Change::Map,
|
(true, false) => Change::Map,
|
||||||
|
|
@ -275,6 +280,7 @@ impl Xwindow {
|
||||||
match map_change {
|
match map_change {
|
||||||
Change::None => return,
|
Change::None => return,
|
||||||
Change::Unmap => {
|
Change::Unmap => {
|
||||||
|
self.queue_spawn_out();
|
||||||
self.data
|
self.data
|
||||||
.info
|
.info
|
||||||
.pending_extents
|
.pending_extents
|
||||||
|
|
|
||||||
|
|
@ -226,6 +226,10 @@ pub trait XdgSurfaceExt: Debug {
|
||||||
// nothing
|
// nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn prepare_unmap(&self) {
|
||||||
|
// nothing
|
||||||
|
}
|
||||||
|
|
||||||
fn extents_changed(&self) {
|
fn extents_changed(&self) {
|
||||||
// nothing
|
// nothing
|
||||||
}
|
}
|
||||||
|
|
@ -664,6 +668,15 @@ impl SurfaceExt for XdgSurface {
|
||||||
if let Some(serial) = pending.serial.take() {
|
if let Some(serial) = pending.serial.take() {
|
||||||
self.applied_serial.set(serial);
|
self.applied_serial.set(serial);
|
||||||
}
|
}
|
||||||
|
if pending
|
||||||
|
.buffer
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|buffer| buffer.is_none())
|
||||||
|
&& self.surface.buffer.is_some()
|
||||||
|
&& let Some(ext) = self.ext.get()
|
||||||
|
{
|
||||||
|
ext.prepare_unmap();
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -260,6 +260,7 @@ impl XdgToplevelRequestHandler for XdgToplevel {
|
||||||
type Error = XdgToplevelError;
|
type Error = XdgToplevelError;
|
||||||
|
|
||||||
fn destroy(&self, _req: Destroy, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
fn destroy(&self, _req: Destroy, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||||
|
self.queue_spawn_out();
|
||||||
self.tl_destroy();
|
self.tl_destroy();
|
||||||
self.xdg.unset_ext();
|
self.xdg.unset_ext();
|
||||||
{
|
{
|
||||||
|
|
@ -399,6 +400,11 @@ impl XdgToplevelRequestHandler for XdgToplevel {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl XdgToplevel {
|
impl XdgToplevel {
|
||||||
|
fn queue_spawn_out(&self) {
|
||||||
|
self.toplevel_data
|
||||||
|
.queue_spawn_out(self, self.tl_animation_snapshot());
|
||||||
|
}
|
||||||
|
|
||||||
fn map(
|
fn map(
|
||||||
self: &Rc<Self>,
|
self: &Rc<Self>,
|
||||||
parent: Option<&XdgToplevel>,
|
parent: Option<&XdgToplevel>,
|
||||||
|
|
@ -824,6 +830,10 @@ impl XdgSurfaceExt for XdgToplevel {
|
||||||
self.after_commit(None);
|
self.after_commit(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn prepare_unmap(&self) {
|
||||||
|
self.queue_spawn_out();
|
||||||
|
}
|
||||||
|
|
||||||
fn extents_changed(&self) {
|
fn extents_changed(&self) {
|
||||||
self.toplevel_data.pos.set(self.xdg.extents.get());
|
self.toplevel_data.pos.set(self.xdg.extents.get());
|
||||||
self.tl_extents_changed();
|
self.tl_extents_changed();
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
animation::{RetainedContent, RetainedSurface, RetainedToplevel},
|
animation::{
|
||||||
|
RetainedContent, RetainedExitFrame, RetainedExitLayer, RetainedSurface,
|
||||||
|
RetainedToplevel,
|
||||||
|
},
|
||||||
cmm::cmm_render_intent::RenderIntent,
|
cmm::cmm_render_intent::RenderIntent,
|
||||||
gfx_api::{AcquireSync, AlphaMode, BufferResv, GfxApiOpt, ReleaseSync, SampleRect},
|
gfx_api::{AcquireSync, AlphaMode, BufferResv, GfxApiOpt, ReleaseSync, SampleRect},
|
||||||
ifs::wl_surface::{
|
ifs::wl_surface::{
|
||||||
|
|
@ -201,6 +204,9 @@ impl Renderer<'_> {
|
||||||
self.render_workspace(&ws, x, y);
|
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 {
|
macro_rules! render_stacked {
|
||||||
($stack:expr) => {
|
($stack:expr) => {
|
||||||
for stacked in $stack.iter() {
|
for stacked in $stack.iter() {
|
||||||
|
|
@ -221,6 +227,7 @@ impl Renderer<'_> {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
render_stacked!(self.state.root.stacked);
|
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
|
// Flush RoundedFillRect ops from container/float borders so they don't
|
||||||
// sort after (and render on top of) layer-shell CopyTexture ops.
|
// sort after (and render on top of) layer-shell CopyTexture ops.
|
||||||
self.base.sync();
|
self.base.sync();
|
||||||
|
|
@ -504,6 +511,68 @@ impl Renderer<'_> {
|
||||||
self.render_retained_surface_scaled(&retained.surface, x, y, None, bounds);
|
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;
|
||||||
|
}
|
||||||
|
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
|
||||||
|
};
|
||||||
|
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_retained_toplevel(&frame.retained, body.x1(), body.y1(), Some(&bounds));
|
||||||
|
self.stretch = None;
|
||||||
|
self.corner_radius = None;
|
||||||
|
}
|
||||||
|
|
||||||
fn render_retained_surface_scaled(
|
fn render_retained_surface_scaled(
|
||||||
&mut self,
|
&mut self,
|
||||||
retained: &RetainedSurface,
|
retained: &RetainedSurface,
|
||||||
|
|
|
||||||
34
src/state.rs
34
src/state.rs
|
|
@ -3,8 +3,8 @@ use {
|
||||||
acceptor::Acceptor,
|
acceptor::Acceptor,
|
||||||
allocator::BufferObject,
|
allocator::BufferObject,
|
||||||
animation::{
|
animation::{
|
||||||
AnimationCurve, AnimationState, AnimationTick, RetainedToplevel, expand_damage_rect,
|
AnimationCurve, AnimationState, AnimationTick, RetainedExitLayer, RetainedToplevel,
|
||||||
spawn_in_start_rect,
|
expand_damage_rect, spawn_in_start_rect,
|
||||||
},
|
},
|
||||||
async_engine::{AsyncEngine, SpawnedFuture},
|
async_engine::{AsyncEngine, SpawnedFuture},
|
||||||
backend::{
|
backend::{
|
||||||
|
|
@ -1572,6 +1572,36 @@ impl State {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn queue_spawn_out_animation(
|
||||||
|
self: &Rc<Self>,
|
||||||
|
from: Rect,
|
||||||
|
frame_inset: i32,
|
||||||
|
retained: Rc<RetainedToplevel>,
|
||||||
|
active: bool,
|
||||||
|
layer: RetainedExitLayer,
|
||||||
|
) {
|
||||||
|
if !self.animations.enabled.get() || from.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let now = self.now_nsec();
|
||||||
|
let started = self.animations.set_spawn_out(
|
||||||
|
from,
|
||||||
|
frame_inset,
|
||||||
|
retained,
|
||||||
|
active,
|
||||||
|
layer,
|
||||||
|
now,
|
||||||
|
self.animations.duration_ms.get(),
|
||||||
|
);
|
||||||
|
if started {
|
||||||
|
self.damage(expand_damage_rect(
|
||||||
|
from,
|
||||||
|
self.theme.sizes.border_width.get().max(0),
|
||||||
|
));
|
||||||
|
self.ensure_animation_tick();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_animations_enabled(&self, enabled: bool) {
|
pub fn set_animations_enabled(&self, enabled: bool) {
|
||||||
if self.animations.enabled.replace(enabled) && !enabled {
|
if self.animations.enabled.replace(enabled) && !enabled {
|
||||||
self.animations.clear();
|
self.animations.clear();
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
animation::RetainedToplevel,
|
animation::{RetainedExitLayer, RetainedToplevel},
|
||||||
client::{Client, ClientId},
|
client::{Client, ClientId},
|
||||||
criteria::{
|
criteria::{
|
||||||
CritDestroyListener, CritMatcherId,
|
CritDestroyListener, CritMatcherId,
|
||||||
|
|
@ -988,6 +988,62 @@ impl ToplevelData {
|
||||||
self.mapped_during_iteration.get() == self.state.eng.iteration()
|
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>) {
|
pub fn set_content_type(&self, content_type: Option<ContentType>) {
|
||||||
if self.content_type.replace(content_type) != content_type {
|
if self.content_type.replace(content_type) != content_type {
|
||||||
self.property_changed(TL_CHANGED_CONTENT_TY);
|
self.property_changed(TL_CHANGED_CONTENT_TY);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue