Commit 7373078d1149
tarball3 files changed+156-24
@@ -6,6 +6,8 @@ use monolith_config::{Animation, AnimationStyle};
66
use monolith_layout::{OutputId, Rect, WindowId};77
use monolith_motion::{CubicBezier, Edges, Position, Timeline, group_initial_slope};88
use monolith_planner::{Config as PlanConfig, Plan, Style};9
+use monolith_render_vk::VulkanTexture;10
+use smithay::utils::{Logical, Rectangle, Transform};911
use std::collections::HashMap;1012
1113
const VELOCITY_DT_NS: u64 = 1_000_000;@@ -14,6 +16,20 @@ pub struct Animator {
1416
pub settings: Animation,1517
tracks: HashMap<OutputId, Track>,1618
glides: HashMap<WindowId, Glide>,19
+ ghosts: Vec<Ghost>,20
+}21
+22
+/// A closed tiled window that stays in its output's plan, shrinking to23
+/// `seed` with the last frame its client showed.24
+pub struct Ghost {25
+ pub id: WindowId,26
+ pub output: OutputId,27
+ pub seed: Rect,28
+ pub texture: VulkanTexture,29
+ pub scale: i32,30
+ pub transform: Transform,31
+ /// Window geometry inside the buffer.32
+ pub geometry: Rectangle<i32, Logical>,1733
}1834
1935
struct Track {@@ -39,6 +55,7 @@ impl Animator {
3955
settings,4056
tracks: HashMap::new(),4157
glides: HashMap::new(),58
+ ghosts: Vec::new(),4259
}4360
}4461
@@ -67,6 +84,12 @@ impl Animator {
6784
seed: impl Fn(WindowId) -> Rect,6885
) {6986
let prev = self.tracks.get(&o);87
+ let ghosts = self88
+ .ghosts89
+ .iter()90
+ .filter(|g| g.output == o)91
+ .map(|g| (g.id, g.seed));92
+ let targets: Vec<(WindowId, Rect)> = ghosts.chain(targets.iter().copied()).collect();7093
let ids: Vec<WindowId> = targets.iter().map(|(id, _)| *id).collect();7194
let to: Vec<Rect> = targets.iter().map(|(_, r)| *r).collect();7295
if prev.is_some_and(|t| t.ids == ids && t.plan.keyframes.last() == Some(&to)) {@@ -167,6 +190,17 @@ impl Animator {
167190
}168191
}169192
193
+ /// Keeps a closing tiled window in the plan until its track ends. The194
+ /// caller retargets afterwards.195
+ pub fn add_ghost(&mut self, ghost: Ghost) {196
+ self.ghosts.retain(|g| g.id != ghost.id);197
+ self.ghosts.push(ghost);198
+ }199
+200
+ pub fn ghosts(&self) -> &[Ghost] {201
+ &self.ghosts202
+ }203
+170204
pub fn active(&self, now: u64) -> bool {171205
self.tracks.values().any(|t| t.timeline.end_ns() > now)172206
|| self.glides.values().any(|g| g.timeline.end_ns() > now)@@ -174,6 +208,12 @@ impl Animator {
174208
175209
pub fn prune(&mut self, now: u64) {176210
self.glides.retain(|_, g| g.timeline.end_ns() > now);211
+ let tracks = &self.tracks;212
+ self.ghosts.retain(|g| {213
+ tracks214
+ .get(&g.output)215
+ .is_some_and(|t| t.timeline.end_ns() > now)216
+ });177217
}178218
}179219
@@ -4,14 +4,14 @@
44
use crate::anim::Animator;55
use monolith_layout::{Layout, Rect, WindowId};66
use monolith_render_vk::{VulkanRenderer, VulkanTexture};7
-use smithay::backend::renderer::Renderer;87
use smithay::backend::renderer::element::surface::WaylandSurfaceRenderElement;98
use smithay::backend::renderer::element::texture::TextureRenderElement;109
use smithay::backend::renderer::element::utils::CropRenderElement;1110
use smithay::backend::renderer::element::{AsRenderElements, Id, Kind};1211
use smithay::backend::renderer::utils::with_renderer_surface_state;12
+use smithay::backend::renderer::{ContextId, Renderer};1313
use smithay::desktop::Window;14
-use smithay::utils::{Logical, Physical, Point, Rectangle, Scale, Size};14
+use smithay::utils::{Logical, Physical, Point, Rectangle, Scale, Size, Transform};1515
use std::collections::HashMap;1616
1717
smithay::backend::renderer::element::render_elements! {@@ -29,9 +29,15 @@ pub fn build(
2929
renderer: &mut VulkanRenderer,3030
now: u64,3131
) -> Vec<Draw> {32
+ let mut ghosts = Vec::new();3233
let mut tiled = Vec::new();3334
let mut above = Vec::new();3435
for (o, out) in layout.outputs() {36
+ for g in anim.ghosts().iter().filter(|g| g.output == o) {37
+ if let Some(r) = anim.tiled(o, g.id, now) {38
+ ghosts.push((g, r));39
+ }40
+ }3541
let arr = layout.arrange(o);3642
let fullscreen_at_rest = arr3743
.fullscreen@@ -61,6 +67,25 @@ pub fn build(
6167
window_elements(&mut out, renderer, window, r);6268
}6369
}70
+ let context = renderer.context_id();71
+ for (g, r) in ghosts.into_iter().rev() {72
+ let Some((at, size)) = pixels(r) else {73
+ continue;74
+ };75
+ let src = Rectangle::new(76
+ g.geometry.loc.to_f64(),77
+ (f64::from(size.0), f64::from(size.1)).into(),78
+ );79
+ out.push(texture(80
+ &context,81
+ &g.texture,82
+ g.scale,83
+ g.transform,84
+ src,85
+ at,86
+ size,87
+ ));88
+ }6489
out6590
}6691
@@ -68,12 +93,9 @@ pub fn build(
6893
/// smaller has its last row and column stretched over the rest, so a resize6994
/// in flight never shows a scaled or missing client.7095
fn window_elements(out: &mut Vec<Draw>, renderer: &mut VulkanRenderer, window: &Window, r: Rect) {71
- let (x, y) = (r.x.round() as i32, r.y.round() as i32);72
- let w = r.right().round() as i32 - x;73
- let h = r.bottom().round() as i32 - y;74
- if w < 1 || h < 1 {96
+ let Some(((x, y), (w, h))) = pixels(r) else {7597
return;76
- }98
+ };7799
let geo = window.geometry();78100
let loc = Point::<i32, Physical>::from((x - geo.loc.x, y - geo.loc.y));79101
let elems: Vec<WaylandSurfaceRenderElement<VulkanRenderer>> =@@ -93,7 +115,7 @@ fn window_elements(out: &mut Vec<Draw>, renderer: &mut VulkanRenderer, window: &
93115
return;94116
};95117
let context = renderer.context_id();96
- let Some((texture, scale, transform)) = with_renderer_surface_state(&root, |s| {118
+ let Some((tex, scale, transform)) = with_renderer_surface_state(&root, |s| {97119
s.texture::<VulkanTexture>(context.clone())98120
.cloned()99121
.map(|t| (t, s.buffer_scale(), s.buffer_transform()))@@ -105,22 +127,9 @@ fn window_elements(out: &mut Vec<Draw>, renderer: &mut VulkanRenderer, window: &
105127
let (cw, ch) = (w.min(gw), h.min(gh));106128
let (gx, gy) = (geo.loc.x as f64, geo.loc.y as f64);107129
let mut strip = |src: Rectangle<f64, Logical>, at: (i32, i32), size: (i32, i32)| {108
- if size.0 < 1 || size.1 < 1 {109
- return;130
+ if size.0 >= 1 && size.1 >= 1 {131
+ out.push(texture(&context, &tex, scale, transform, src, at, size));110132
}111
- out.push(Draw::Texture(TextureRenderElement::from_static_texture(112
- Id::new(),113
- context.clone(),114
- Point::<f64, Physical>::from((f64::from(at.0), f64::from(at.1))),115
- texture.clone(),116
- scale,117
- transform,118
- None,119
- Some(src),120
- Some(Size::from(size)),121
- None,122
- Kind::Unspecified,123
- )));124133
};125134
let right = gx + f64::from(gw) - 1.0;126135
let bottom = gy + f64::from(gh) - 1.0;@@ -140,3 +149,36 @@ fn window_elements(out: &mut Vec<Draw>, renderer: &mut VulkanRenderer, window: &
140149
(w - gw, h - gh),141150
);142151
}152
+153
+/// Snaps a logical rect to whole pixels by rounding its edges, so neighbours154
+/// that share an edge stay flush.155
+fn pixels(r: Rect) -> Option<((i32, i32), (i32, i32))> {156
+ let (x, y) = (r.x.round() as i32, r.y.round() as i32);157
+ let w = r.right().round() as i32 - x;158
+ let h = r.bottom().round() as i32 - y;159
+ (w >= 1 && h >= 1).then_some(((x, y), (w, h)))160
+}161
+162
+fn texture(163
+ context: &ContextId<VulkanTexture>,164
+ texture: &VulkanTexture,165
+ scale: i32,166
+ transform: Transform,167
+ src: Rectangle<f64, Logical>,168
+ at: (i32, i32),169
+ size: (i32, i32),170
+) -> Draw {171
+ Draw::Texture(TextureRenderElement::from_static_texture(172
+ Id::new(),173
+ context.clone(),174
+ Point::<f64, Physical>::from((f64::from(at.0), f64::from(at.1))),175
+ texture.clone(),176
+ scale,177
+ transform,178
+ None,179
+ Some(src),180
+ Some(Size::from(size)),181
+ None,182
+ Kind::Unspecified,183
+ ))184
+}@@ -1,11 +1,14 @@
1
-use crate::anim::Animator;1
+use crate::anim::{Animator, Ghost};22
use crate::commands::Binds;33
use crate::nested::Host;44
use monolith_config::Config;55
use monolith_layout::{Layout, OutputId, Rect, WindowId};66
use monolith_protocols::workspace::{Group, Snapshot, Workspace, WorkspaceState};77
use monolith_render_vk::VulkanRenderer;8
+use monolith_render_vk::VulkanTexture;89
use smithay::backend::renderer::ImportDma;10
+use smithay::backend::renderer::Renderer;11
+use smithay::backend::renderer::utils::{import_surface_tree, with_renderer_surface_state};912
use smithay::desktop::{PopupManager, Space, Window, WindowSurfaceType};1013
use smithay::input::keyboard::{Keycode, XkbConfig};1114
use smithay::input::{Seat, SeatState};@@ -219,12 +222,59 @@ impl State {
219222
if let Some(id) = self.window_id(surface) {220223
if let Some(window) = self.windows.remove(&id) {221224
self.space.unmap_elem(&window);225
+ self.leave_ghost(id, &window);222226
}223227
self.layout.remove_window(id);224228
self.apply_layout();225229
}226230
}227231
232
+ /// Keeps a visible tiled window's last frame shrinking to its seed while233
+ /// its neighbours close over it.234
+ fn leave_ghost(&mut self, id: WindowId, window: &Window) {235
+ let Some(o) = self.layout.window(id).and_then(|w| w.output) else {236
+ return;237
+ };238
+ let tiled = self239
+ .layout240
+ .output(o)241
+ .is_some_and(|out| out.tree.contains(id));242
+ if !tiled || !self.layout.visible(id) {243
+ return;244
+ }245
+ let Some(seed) = self246
+ .layout247
+ .arrange_hiding(o, &[id])248
+ .tiled249
+ .into_iter()250
+ .find(|p| p.window == id)251
+ else {252
+ return;253
+ };254
+ let Some(surface) = window.toplevel().map(|t| t.wl_surface().clone()) else {255
+ return;256
+ };257
+ let _ = import_surface_tree(&mut self.renderer, &surface);258
+ let context = self.renderer.context_id();259
+ let snapshot = with_renderer_surface_state(&surface, |s| {260
+ s.texture::<VulkanTexture>(context.clone())261
+ .cloned()262
+ .map(|t| (t, s.buffer_scale(), s.buffer_transform()))263
+ })264
+ .flatten();265
+ if let Some((texture, scale, transform)) = snapshot {266
+ self.anim.add_ghost(Ghost {267
+ id,268
+ output: o,269
+ seed: seed.rect,270
+ texture,271
+ scale,272
+ transform,273
+ geometry: window.geometry(),274
+ });275
+ }276
+ }277
+228278
pub fn window_id(&self, surface: &WlSurface) -> Option<WindowId> {229279
self.windows230280
.iter()