Commit 618afeb78754
tarball5 files changed+131-52
@@ -26,6 +26,9 @@ pub struct Ghost {
2626
pub output: OutputId,2727
pub seed: Rect,2828
pub born: u64,29
+ /// Fades where it is for one phase instead of shrinking in the plan,30
+ /// like a dismissed scratchpad.31
+ pub fixed: Option<Rect>,2932
}3033
3134
struct Track {@@ -84,7 +87,7 @@ impl Animator {
8487
let ghosts = self8588
.ghosts8689
.iter()87
- .filter(|g| g.output == o)90
+ .filter(|g| g.output == o && g.fixed.is_none())8891
.map(|g| (g.id, g.seed));8992
let targets: Vec<(WindowId, Rect)> = ghosts.chain(targets.iter().copied()).collect();9093
let ids: Vec<WindowId> = targets.iter().map(|(id, _)| *id).collect();@@ -199,6 +202,7 @@ impl Animator {
199202
}200203
201204
pub fn spawned(&mut self, id: WindowId, now: u64) {205
+ self.ghosts.retain(|g| g.id != id);202206
if self.phase_ns() > 0 {203207
self.spawns.insert(id, now);204208
}@@ -229,6 +233,7 @@ impl Animator {
229233
self.tracks.values().any(|t| t.timeline.end_ns() > now)230234
|| self.glides.values().any(|g| g.timeline.end_ns() > now)231235
|| !self.spawns.is_empty()236
+ || self.ghosts.iter().any(|g| g.fixed.is_some())232237
}233238
234239
pub fn forget(&mut self, id: WindowId) {@@ -242,10 +247,11 @@ impl Animator {
242247
self.spawns243248
.retain(|_, start| now.saturating_sub(*start) < phase);244249
let tracks = &self.tracks;245
- self.ghosts.retain(|g| {246
- tracks250
+ self.ghosts.retain(|g| match g.fixed {251
+ Some(_) => now.saturating_sub(g.born) < phase,252
+ None => tracks247253
.get(&g.output)248
- .is_some_and(|t| t.timeline.end_ns() > now)254
+ .is_some_and(|t| t.timeline.end_ns() > now),249255
});250256
}251257
}@@ -20,7 +20,7 @@ impl State {
2020
let Some(pointer) = self.input.seat.get_pointer() else {2121
return;2222
};23
- focus_on_hover(self, pos);23
+ focus_on_hover(self, pos, self.input.warping);2424
let under = self.surface_under(pos);2525
pointer.motion(2626
self,@@ -166,7 +166,10 @@ fn window_at(state: &State, pos: Point<f64, Logical>) -> Option<WindowId> {
166166
}167167
168168
/// Layers above windows, like a bar, keep the focus where it was.169
-fn focus_on_hover(state: &mut State, pos: Point<f64, Logical>) {169
+/// Focus changes only when the pointer enters another window. Moving170
+/// inside one, or being warped, leaves focus alone, so a new window keeps171
+/// it until the pointer actually goes somewhere else.172
+fn focus_on_hover(state: &mut State, pos: Point<f64, Logical>, warped: bool) {170173
if !state.settings.config.general.focus_follows_mouse171174
|| state.services.lock.active()172175
|| state@@ -175,7 +178,11 @@ fn focus_on_hover(state: &mut State, pos: Point<f64, Logical>) {
175178
{176179
return;177180
}178
- let Some(id) = window_at(state, pos) else {181
+ let under = window_at(state, pos);182
+ if std::mem::replace(&mut state.input.hovered, under) == under || warped {183
+ return;184
+ }185
+ let Some(id) = under else {179186
return;180187
};181188
if state.wm.layout.focused() == Some(id) {@@ -202,10 +209,12 @@ impl State {
202209
let Some((_, r, _)) = focused.and_then(|id| self.wm.get(id)?.placed) else {203210
return;204211
};212
+ self.input.warping = true;205213
self.pointer_to(206214
Point::from((r.x + r.w / 2.0, r.y + r.h / 2.0)),207215
InputTime::now(),208216
);217
+ self.input.warping = false;209218
self.pointer_frame();210219
self.dirty.cursor = true;211220
}@@ -1,7 +1,7 @@
11
//! What a frame draws. The space holds final geometry for input, while the22
//! scene draws windows where the animator says they are right now.33
4
-use crate::wm::Wm;4
+use crate::wm::{LastFrame, Wm};55
use monolith_config::Decoration;66
use monolith_layout::{OutputId, Rect};77
use monolith_render_vk::{Clip, Effect, VulkanFrame, VulkanRenderer, VulkanTexture};@@ -59,9 +59,18 @@ pub fn build(
5959
let mut ghosts = Vec::new();6060
let mut tiled = Vec::new();6161
let mut above = Vec::new();62
+ let mut fading = Vec::new();6263
for g in anim.ghosts().iter().filter(|g| g.output == o) {63
- if let (Some(r), Some(frame)) = (anim.tiled(o, g.id, now), last_frames.get(&g.id)) {64
- ghosts.push((g, frame, local(r)));64
+ let Some(frame) = last_frames.get(&g.id) else {65
+ continue;66
+ };67
+ match g.fixed {68
+ Some(r) => fading.push((g, frame, local(r))),69
+ None => {70
+ if let Some(r) = anim.tiled(o, g.id, now) {71
+ ghosts.push((g, frame, local(r)));72
+ }73
+ }6574
}6675
}6776
let arr = layout.arrange(o);@@ -108,6 +117,9 @@ pub fn build(
108117
crate::layers::elements(&mut out, renderer, output, Layer::Top);109118
}110119
}120
+ for (g, frame, r) in fading.into_iter().rev() {121
+ out.extend(ghost_element(frame, r, anim.ghost_effect(g, now), deco));122
+ }111123
for (id, r, framed, gliding) in tiled.into_iter().chain(above).rev() {112124
let Some(state) = windows.get_mut(id) else {113125
continue;@@ -135,26 +147,7 @@ pub fn build(
135147
}136148
}137149
for (g, frame, r) in ghosts.into_iter().rev() {138
- let Some((at, size)) = pixels(r) else {139
- continue;140
- };141
- let src = Rectangle::new(142
- frame.geometry.loc.to_f64(),143
- (f64::from(size.0), f64::from(size.1)).into(),144
- );145
- let ghost = Draw::Effect(EffectElement::new(146
- &frame.texture,147
- frame.scale,148
- frame.transform,149
- src,150
- at,151
- size,152
- anim.ghost_effect(g, now),153
- ));154
- out.push(match corner_clip(r, deco) {155
- Some(clip) => Draw::Clipped(ClipElement::new(ghost, clip)),156
- None => ghost,157
- });150
+ out.extend(ghost_element(frame, r, anim.ghost_effect(g, now), deco));158151
}159152
if let Some(output) = output {160153
crate::layers::elements(&mut out, renderer, output, Layer::Bottom);@@ -437,6 +430,27 @@ impl RenderElement<VulkanRenderer> for EffectElement {
437430
}438431
}439432
433
+fn ghost_element(frame: &LastFrame, r: Rect, effect: Effect, deco: &Decoration) -> Option<Draw> {434
+ let (at, size) = pixels(r)?;435
+ let src = Rectangle::new(436
+ frame.geometry.loc.to_f64(),437
+ (f64::from(size.0), f64::from(size.1)).into(),438
+ );439
+ let ghost = Draw::Effect(EffectElement::new(440
+ &frame.texture,441
+ frame.scale,442
+ frame.transform,443
+ src,444
+ at,445
+ size,446
+ effect,447
+ ));448
+ Some(match corner_clip(r, deco) {449
+ Some(clip) => Draw::Clipped(ClipElement::new(ghost, clip)),450
+ None => ghost,451
+ })452
+}453
+440454
/// The rounded outline of a framed window at `r`, if it has one.441455
fn corner_clip(r: Rect, deco: &Decoration) -> Option<Clip> {442456
let radii = deco.radius.0;@@ -52,6 +52,9 @@ pub struct Input {
5252
/// The focus mouse-follows-focus last saw, so pointer-made focus5353
/// changes can mark themselves as already followed.5454
pub followed: Option<WindowId>,55
+ /// The window under the pointer when it last moved.56
+ pub hovered: Option<WindowId>,57
+ pub warping: bool,5558
pub cursor: crate::cursor::Cursor,5659
pub cursor_status: CursorImageStatus,5760
}@@ -176,6 +179,8 @@ impl State {
176179
drag: None,177180
move_inverted: false,178181
followed: None,182
+ hovered: None,183
+ warping: false,179184
cursor: crate::cursor::Cursor::load(),180185
cursor_status: CursorImageStatus::default_named(),181186
},@@ -317,6 +322,31 @@ impl State {
317322
for (id, from, to) in glides {318323
self.wm.anim.glide(id, from, to, now);319324
}325
+ let scratch = |wm: &Wm, id| wm.layout.window(id).is_some_and(|w| w.scratchpad.is_some());326
+ let summoned: Vec<WindowId> = shown327
+ .iter()328
+ .filter(|&&(id, _, kind, _)| {329
+ kind == Kind::Floating330
+ && scratch(&self.wm, id)331
+ && self332
+ .wm333
+ .get(id)334
+ .is_some_and(|s| s.placed.is_none() && !s.awaiting_buffer)335
+ })336
+ .map(|s| s.0)337
+ .collect();338
+ let dismissed: Vec<(WindowId, Rect, OutputId)> = self339
+ .wm340
+ .iter()341
+ .filter(|&(id, _)| scratch(&self.wm, id) && !shown.iter().any(|s| s.0 == id))342
+ .filter_map(|(id, s)| s.placed.map(|(_, r, o)| (id, r, o)))343
+ .collect();344
+ for id in summoned {345
+ self.wm.anim.spawned(id, now);346
+ }347
+ for (id, r, o) in dismissed {348
+ self.fade_out(id, r, o);349
+ }320350
for (_, s) in self.wm.iter_mut() {321351
s.placed = None;322352
}@@ -1,7 +1,7 @@
11
use crate::anim::Ghost;22
use crate::state::{State, now_ns};33
use crate::wm::{LastFrame, identity, root};4
-use monolith_layout::{Rect, WindowId};4
+use monolith_layout::{OutputId, Rect, WindowId};55
use monolith_render_vk::VulkanTexture;66
use smithay::backend::renderer::Renderer;77
use smithay::backend::renderer::utils::{import_surface_tree, with_renderer_surface_state};@@ -146,36 +146,56 @@ impl State {
146146
else {147147
return;148148
};149
- let Some(surface) = root(window) else {150
- return;151
- };152
- let _ = import_surface_tree(&mut self.renderer, &surface);153
- let context = self.renderer.context_id();154
- let snapshot = with_renderer_surface_state(&surface, |s| {155
- s.texture::<VulkanTexture>(context.clone())156
- .cloned()157
- .map(|t| (t, s.buffer_scale(), s.buffer_transform()))158
- })159
- .flatten();160
- if let Some((texture, scale, transform)) = snapshot {161
- self.wm.last_frames.insert(162
- id,163
- LastFrame {164
- texture,165
- scale,166
- transform,167
- geometry: window.geometry(),168
- },169
- );149
+ if let Some(frame) = self.last_frame(window) {150
+ self.wm.last_frames.insert(id, frame);170151
self.wm.anim.add_ghost(Ghost {171152
id,172153
output: o,173154
seed: seed.rect,174155
born: now_ns(),156
+ fixed: None,157
+ });158
+ }159
+ }160
+161
+ /// Fades a window that just left the screen where it was, like a162
+ /// dismissed scratchpad.163
+ pub fn fade_out(&mut self, id: WindowId, rect: Rect, output: OutputId) {164
+ let Some(window) = self.wm.window(id).cloned() else {165
+ return;166
+ };167
+ if let Some(frame) = self.last_frame(&window) {168
+ self.wm.last_frames.insert(id, frame);169
+ self.wm.anim.add_ghost(Ghost {170
+ id,171
+ output,172
+ seed: rect,173
+ born: now_ns(),174
+ fixed: Some(rect),175175
});176176
}177177
}178178
179
+ fn last_frame(&mut self, window: &Window) -> Option<LastFrame> {180
+ let surface = root(window)?;181
+ let _ = import_surface_tree(&mut self.renderer, &surface);182
+ let context = self.renderer.context_id();183
+ // The geometry lookup locks the surface's state, which the closure184
+ // below already holds.185
+ let geometry = window.geometry();186
+ with_renderer_surface_state(&surface, |s| {187
+ s.texture::<VulkanTexture>(context.clone())188
+ .cloned()189
+ .map(|texture| LastFrame {190
+ texture,191
+ scale: s.buffer_scale(),192
+ transform: s.buffer_transform(),193
+ geometry,194
+ })195
+ })196
+ .flatten()197
+ }198
+179199
/// Picks up a changed title or app id, which clients send at any time.180200
pub fn refresh_identity(&mut self, id: WindowId) {181201
let Some(w) = self.wm.get_mut(id) else {