Commit 2af1d8c55876
tarball11 files changed+502-26
@@ -398,6 +398,8 @@ dependencies = [
398398
"monolith-config",399399
"monolith-ipc",400400
"monolith-layout",401
+ "monolith-motion",402
+ "monolith-planner",401403
"monolith-protocols",402404
"monolith-render-vk",403405
"pound",@@ -10,6 +10,13 @@ startup = []
1010
mod = "super"1111
gaps = { inner = 8, outer = 8 }1212
13
+[animation]14
+enabled = true15
+# orthogonal moves each window along one axis per phase, direct allows both.16
+style = "orthogonal"17
+phase_ms = 16018
+curve = [0.45, 0.0, 0.2, 1.0]19
+1320
[keyboard]1421
layout = ""1522
repeat_delay = 200@@ -12,6 +12,7 @@ pub const DEFAULT: &str = include_str!("../default.toml");
1212
#[derive(Debug, Clone)]1313
pub struct Config {1414
pub general: General,15
+ pub animation: Animation,1516
pub keyboard: Keyboard,1617
pub binds: Vec<Bind>,1718
pub rules: Vec<Rule>,@@ -54,6 +55,35 @@ impl Default for Gaps {
5455
5556
#[derive(Deserialize, Debug, Clone, PartialEq)]5657
#[serde(default, deny_unknown_fields)]58
+pub struct Animation {59
+ pub enabled: bool,60
+ pub style: AnimationStyle,61
+ /// Every phase of a planned transition lasts this long.62
+ pub phase_ms: u64,63
+ /// A cubic bezier, as in CSS.64
+ pub curve: [f64; 4],65
+}66
+67
+impl Default for Animation {68
+ fn default() -> Self {69
+ Self {70
+ enabled: true,71
+ style: AnimationStyle::Orthogonal,72
+ phase_ms: 160,73
+ curve: [0.45, 0.0, 0.2, 1.0],74
+ }75
+ }76
+}77
+78
+#[derive(Deserialize, Debug, Clone, Copy, PartialEq, Eq)]79
+#[serde(rename_all = "lowercase")]80
+pub enum AnimationStyle {81
+ Orthogonal,82
+ Direct,83
+}84
+85
+#[derive(Deserialize, Debug, Clone, PartialEq)]86
+#[serde(default, deny_unknown_fields)]5787
pub struct Keyboard {5888
pub rules: String,5989
pub model: String,@@ -166,6 +196,8 @@ struct Raw {
166196
#[serde(default)]167197
keyboard: Keyboard,168198
#[serde(default)]199
+ animation: Animation,200
+ #[serde(default)]169201
binds: BTreeMap<String, Words>,170202
#[serde(default)]171203
rules: Vec<RawRule>,@@ -243,6 +275,7 @@ impl Config {
243275
.collect::<Result<_, Error>>()?;244276
Ok(Self {245277
general: raw.general,278
+ animation: raw.animation,246279
keyboard: raw.keyboard,247280
binds,248281
rules,@@ -783,14 +783,21 @@ impl Layout {
783783
/// Target geometry for everything on `o`, in output-local logical784784
/// coordinates offset by the output's area.785785
pub fn arrange(&self, o: OutputId) -> Arrangement {786
+ self.arrange_hiding(o, &[])787
+ }788
+789
+ /// Like `arrange` with `hide` treated as hidden, which places them on the790
+ /// seeds they would grow from.791
+ pub fn arrange_hiding(&self, o: OutputId, hide: &[WindowId]) -> Arrangement {786792
let Some(out) = self.outputs.get(o) else {787793
return Arrangement::default();788794
};789795
let area = inset(out.area, self.gaps.outer);790
- let vis = self.vis_on(o);796
+ let shown = self.vis_on(o);797
+ let vis = &|w: WindowId| shown(w) && !hide.contains(&w);791798
let tiled = out792799
.tree793
- .arrange(area, self.gaps.inner, &vis)800
+ .arrange(area, self.gaps.inner, vis)794801
.into_iter()795802
.map(|(window, rect)| Placed {796803
window,@@ -16,6 +16,8 @@ calloop-wayland-source = "0.4"
1616
monolith-config.workspace = true1717
monolith-ipc.workspace = true1818
monolith-layout.workspace = true19
+monolith-motion.workspace = true20
+monolith-planner.workspace = true1921
monolith-protocols.workspace = true2022
monolith-render-vk.workspace = true2123
pound = "0.5"@@ -0,0 +1,213 @@
1
+//! Layout transitions. Each output has one track, a plan over every tiled2
+//! window it holds, with hidden windows as zero-size seeds. Switches between3
+//! tiled, floating and fullscreen glide directly above the tiled layer.4
+5
+use monolith_config::{Animation, AnimationStyle};6
+use monolith_layout::{OutputId, Rect, WindowId};7
+use monolith_motion::{CubicBezier, Edges, Position, Timeline, group_initial_slope};8
+use monolith_planner::{Config as PlanConfig, Plan, Style};9
+use std::collections::HashMap;10
+11
+const VELOCITY_DT_NS: u64 = 1_000_000;12
+13
+pub struct Animator {14
+ pub settings: Animation,15
+ tracks: HashMap<OutputId, Track>,16
+ glides: HashMap<WindowId, Glide>,17
+}18
+19
+struct Track {20
+ ids: Vec<WindowId>,21
+ plan: Plan,22
+ timeline: Timeline,23
+ base: CubicBezier,24
+ /// Each window's curve in the first phase. A replan can start a group25
+ /// with the speed it already had, later phases always use `base`.26
+ first: Vec<CubicBezier>,27
+}28
+29
+struct Glide {30
+ from: Rect,31
+ to: Rect,32
+ timeline: Timeline,33
+ curve: CubicBezier,34
+}35
+36
+impl Animator {37
+ pub fn new(settings: Animation) -> Self {38
+ Self {39
+ settings,40
+ tracks: HashMap::new(),41
+ glides: HashMap::new(),42
+ }43
+ }44
+45
+ fn base(&self) -> CubicBezier {46
+ let [x1, y1, x2, y2] = self.settings.curve;47
+ let c = CubicBezier::new(x1, y1, x2, y2);48
+ if c.is_valid() { c } else { CubicBezier::EASE }49
+ }50
+51
+ fn phase_ns(&self) -> u64 {52
+ if self.settings.enabled {53
+ self.settings.phase_ms * 1_000_00054
+ } else {55
+ 056
+ }57
+ }58
+59
+ /// Plans `o`'s tiled windows from where they are drawn now to `targets`.60
+ /// Windows the track has never seen start from `seed`.61
+ pub fn retarget(62
+ &mut self,63
+ o: OutputId,64
+ now: u64,65
+ targets: &[(WindowId, Rect)],66
+ gap: f64,67
+ seed: impl Fn(WindowId) -> Rect,68
+ ) {69
+ let prev = self.tracks.get(&o);70
+ let ids: Vec<WindowId> = targets.iter().map(|(id, _)| *id).collect();71
+ let to: Vec<Rect> = targets.iter().map(|(_, r)| *r).collect();72
+ if prev.is_some_and(|t| t.ids == ids && t.plan.keyframes.last() == Some(&to)) {73
+ return;74
+ }75
+ let from: Vec<Rect> = ids76
+ .iter()77
+ .map(|&id| {78
+ prev.and_then(|t| t.sample(id, now))79
+ .unwrap_or_else(|| seed(id))80
+ })81
+ .collect();82
+ let cfg = PlanConfig {83
+ style: match self.settings.style {84
+ AnimationStyle::Orthogonal => Style::Orthogonal,85
+ AnimationStyle::Direct => Style::Direct,86
+ },87
+ gap,88
+ ..PlanConfig::default()89
+ };90
+ let plan = if self.phase_ns() == 0 {91
+ monolith_planner::plan(&to, &to, &cfg)92
+ } else {93
+ monolith_planner::plan(&from, &to, &cfg)94
+ };95
+ let base = self.base();96
+ let phase_secs = self.phase_ns() as f64 / 1e9;97
+ let mut first = vec![base; ids.len()];98
+ if plan.phases() > 0 {99
+ for g in &plan.groups {100
+ let movers = g.members.iter().map(|&i| {101
+ let v = prev.map_or([0.0; 4], |t| t.velocity(ids[i], now));102
+ let d = sub(edges(&plan.keyframes[1][i]), edges(&plan.keyframes[0][i]));103
+ (v, d)104
+ });105
+ if let Some(slope) = group_initial_slope(movers, phase_secs) {106
+ let curve = base.with_initial_slope(slope);107
+ for &i in &g.members {108
+ first[i] = curve;109
+ }110
+ }111
+ }112
+ }113
+ let timeline = Timeline {114
+ start_ns: now,115
+ phase_ns: self.phase_ns(),116
+ phases: plan.phases(),117
+ };118
+ self.tracks.insert(119
+ o,120
+ Track {121
+ ids,122
+ plan,123
+ timeline,124
+ base,125
+ first,126
+ },127
+ );128
+ }129
+130
+ /// Where a tiled window on `o` is drawn at `now`.131
+ pub fn tiled(&self, o: OutputId, id: WindowId, now: u64) -> Option<Rect> {132
+ self.tracks.get(&o)?.sample(id, now)133
+ }134
+135
+ /// Glides a window that changed between tiled, floating and fullscreen.136
+ pub fn glide(&mut self, id: WindowId, from: Rect, to: Rect, now: u64) {137
+ let phases = if self.phase_ns() == 0 { 0 } else { 2 };138
+ self.glides.insert(139
+ id,140
+ Glide {141
+ from,142
+ to,143
+ timeline: Timeline {144
+ start_ns: now,145
+ phase_ns: self.phase_ns(),146
+ phases,147
+ },148
+ curve: self.base(),149
+ },150
+ );151
+ }152
+153
+ pub fn stop_glide(&mut self, id: WindowId) {154
+ self.glides.remove(&id);155
+ }156
+157
+ pub fn gliding(&self, id: WindowId, now: u64) -> Option<Rect> {158
+ let g = self.glides.get(&id)?;159
+ match g.timeline.locate(now) {160
+ Position::Finished => None,161
+ Position::NotStarted => Some(g.from),162
+ Position::Phase { phase, t } => {163
+ let total = g.timeline.phases as f64;164
+ let s = g.curve.sample((phase as f64 + t) / total);165
+ Some(g.from.lerp(&g.to, s))166
+ }167
+ }168
+ }169
+170
+ pub fn active(&self, now: u64) -> bool {171
+ self.tracks.values().any(|t| t.timeline.end_ns() > now)172
+ || self.glides.values().any(|g| g.timeline.end_ns() > now)173
+ }174
+175
+ pub fn prune(&mut self, now: u64) {176
+ self.glides.retain(|_, g| g.timeline.end_ns() > now);177
+ }178
+}179
+180
+impl Track {181
+ fn sample(&self, id: WindowId, now: u64) -> Option<Rect> {182
+ let i = self.ids.iter().position(|&x| x == id)?;183
+ let keys = &self.plan.keyframes;184
+ Some(match self.timeline.locate(now) {185
+ Position::NotStarted => keys.first()?[i],186
+ Position::Finished => keys.last()?[i],187
+ Position::Phase { phase, t } => {188
+ let curve = if phase == 0 { self.first[i] } else { self.base };189
+ keys[phase][i].lerp(&keys[phase + 1][i], curve.sample(t))190
+ }191
+ })192
+ }193
+194
+ /// Edge velocities in logical pixels per second.195
+ fn velocity(&self, id: WindowId, now: u64) -> Edges {196
+ let (Some(a), Some(b)) = (197
+ self.sample(id, now.saturating_sub(VELOCITY_DT_NS)),198
+ self.sample(id, now),199
+ ) else {200
+ return [0.0; 4];201
+ };202
+ let d = sub(edges(&b), edges(&a));203
+ d.map(|x| x * 1e9 / VELOCITY_DT_NS as f64)204
+ }205
+}206
+207
+fn edges(r: &Rect) -> Edges {208
+ [r.x, r.right(), r.y, r.bottom()]209
+}210
+211
+fn sub(a: Edges, b: Edges) -> Edges {212
+ [a[0] - b[0], a[1] - b[1], a[2] - b[2], a[3] - b[3]]213
+}@@ -181,6 +181,7 @@ impl State {
181181
let keyboard_changed = config.keyboard != self.config.keyboard;182182
self.binds = Binds::new(&config);183183
self.layout.gaps = crate::state::gaps(&config);184
+ self.anim.settings = config.animation.clone();184185
self.config = config;185186
if keyboard_changed {186187
self.reconfigure_keyboard();@@ -1,9 +1,11 @@
1
+mod anim;12
mod commands;23
mod handlers;34
mod input;45
mod ipc;56
mod launch;67
mod nested;8
+mod scene;79
mod state;810
911
use monolith_ipc::{Command, Query, Request};@@ -11,8 +11,6 @@ use smithay::backend::allocator::dmabuf::Dmabuf;
1111
use smithay::backend::allocator::{Format, Fourcc, Modifier, Slot, Swapchain};1212
use smithay::backend::renderer::Bind;1313
use smithay::backend::renderer::damage::OutputDamageTracker;14
-use smithay::backend::renderer::element::surface::WaylandSurfaceRenderElement;15
-use smithay::desktop::space::render_output;1614
use smithay::output::{Mode, Output, PhysicalProperties, Subpixel};1715
use smithay::reexports::calloop::EventLoop;1816
use smithay::utils::{Logical, Point, Size, Transform};@@ -148,6 +146,9 @@ pub fn render(state: &mut State) {
148146
space,149147
needs_redraw,150148
start,149
+ layout,150
+ windows,151
+ anim,151152
..152153
} = state;153154
let Some(host) = host.as_mut() else { return };@@ -181,6 +182,7 @@ pub fn render(state: &mut State) {
181182
return;182183
}183184
};185
+ let now = crate::state::now_ns();184186
let mut dmabuf = (*slot).clone();185187
let damage = {186188
let mut fb = match renderer.bind(&mut dmabuf) {@@ -190,18 +192,11 @@ pub fn render(state: &mut State) {
190192
return;191193
}192194
};193
- let elements: &[WaylandSurfaceRenderElement<monolith_render_vk::VulkanRenderer>] = &[];194
- match render_output(195
- &host.output,196
- renderer,197
- &mut fb,198
- 1.0,199
- slot.age() as usize,200
- [&*space],201
- elements,202
- &mut host.damage,203
- CLEAR,204
- ) {195
+ let elements = crate::scene::build(layout, windows, anim, renderer, now);196
+ match host197
+ .damage198
+ .render_output(renderer, &mut fb, slot.age() as usize, &elements, CLEAR)199
+ {205200
Ok(result) => {206201
if let Err(e) = result.sync.wait() {207202
tracing::warn!("waiting for the frame failed: {e:?}");@@ -214,14 +209,23 @@ pub fn render(state: &mut State) {
214209
}215210
}216211
};217
- *needs_redraw = false;212
+ *needs_redraw = anim.active(now);213
+ anim.prune(now);218214
let elapsed = start.elapsed();219215
for window in space.elements() {220216
window.send_frame(&host.output, elapsed, Some(Duration::ZERO), |_, _| {221217
Some(host.output.clone())222218
});223219
}224
- let Some(damage) = damage else { return };220
+ let Some(damage) = damage else {221
+ if *needs_redraw {222
+ host.surface.frame(&host.qh, ());223
+ host.surface.commit();224
+ host.frame_pending = true;225
+ let _ = host.conn.flush();226
+ }227
+ return;228
+ };225229
let buffer = match slot.userdata().get::<HostBuffer>() {226230
Some(b) => b.0.clone(),227231
None => {@@ -0,0 +1,142 @@
1
+//! What a frame draws. The space holds final geometry for input, while the2
+//! scene draws windows where the animator says they are right now.3
+4
+use crate::anim::Animator;5
+use monolith_layout::{Layout, Rect, WindowId};6
+use monolith_render_vk::{VulkanRenderer, VulkanTexture};7
+use smithay::backend::renderer::Renderer;8
+use smithay::backend::renderer::element::surface::WaylandSurfaceRenderElement;9
+use smithay::backend::renderer::element::texture::TextureRenderElement;10
+use smithay::backend::renderer::element::utils::CropRenderElement;11
+use smithay::backend::renderer::element::{AsRenderElements, Id, Kind};12
+use smithay::backend::renderer::utils::with_renderer_surface_state;13
+use smithay::desktop::Window;14
+use smithay::utils::{Logical, Physical, Point, Rectangle, Scale, Size};15
+use std::collections::HashMap;16
+17
+smithay::backend::renderer::element::render_elements! {18
+ pub Draw<=VulkanRenderer>;19
+ Surface=WaylandSurfaceRenderElement<VulkanRenderer>,20
+ Cropped=CropRenderElement<WaylandSurfaceRenderElement<VulkanRenderer>>,21
+ Texture=TextureRenderElement<VulkanTexture>,22
+}23
+24
+/// Elements for every output, front to back.25
+pub fn build(26
+ layout: &Layout,27
+ windows: &HashMap<WindowId, Window>,28
+ anim: &Animator,29
+ renderer: &mut VulkanRenderer,30
+ now: u64,31
+) -> Vec<Draw> {32
+ let mut tiled = Vec::new();33
+ let mut above = Vec::new();34
+ for (o, out) in layout.outputs() {35
+ let arr = layout.arrange(o);36
+ let fullscreen_at_rest = arr37
+ .fullscreen38
+ .is_some_and(|w| anim.gliding(w, now).is_none());39
+ for p in &arr.tiled {40
+ if Some(p.window) == arr.fullscreen || fullscreen_at_rest {41
+ continue;42
+ }43
+ match anim.gliding(p.window, now) {44
+ Some(r) => above.push((p.window, r)),45
+ None => {46
+ let r = anim.tiled(o, p.window, now).unwrap_or(p.rect);47
+ tiled.push((p.window, r));48
+ }49
+ }50
+ }51
+ for &(w, r) in &arr.floating {52
+ above.push((w, anim.gliding(w, now).unwrap_or(r)));53
+ }54
+ if let Some(w) = arr.fullscreen {55
+ above.push((w, anim.gliding(w, now).unwrap_or(out.area)));56
+ }57
+ }58
+ let mut out = Vec::new();59
+ for (id, r) in tiled.into_iter().chain(above).rev() {60
+ if let Some(window) = windows.get(&id) {61
+ window_elements(&mut out, renderer, window, r);62
+ }63
+ }64
+ out65
+}66
+67
+/// Draws `window` filling `r`. A buffer bigger than `r` is cropped, and one68
+/// smaller has its last row and column stretched over the rest, so a resize69
+/// in flight never shows a scaled or missing client.70
+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 {75
+ return;76
+ }77
+ let geo = window.geometry();78
+ let loc = Point::<i32, Physical>::from((x - geo.loc.x, y - geo.loc.y));79
+ let elems: Vec<WaylandSurfaceRenderElement<VulkanRenderer>> =80
+ window.render_elements(renderer, loc, Scale::from(1.0), 1.0);81
+ if (w, h) == (geo.size.w, geo.size.h) {82
+ out.extend(elems.into_iter().map(Draw::Surface));83
+ return;84
+ }85
+ let crop = Rectangle::new((x, y).into(), (w, h).into());86
+ out.extend(87
+ elems88
+ .into_iter()89
+ .filter_map(|e| CropRenderElement::from_element(e, 1.0, crop))90
+ .map(Draw::Cropped),91
+ );92
+ let Some(root) = window.toplevel().map(|t| t.wl_surface().clone()) else {93
+ return;94
+ };95
+ let context = renderer.context_id();96
+ let Some((texture, scale, transform)) = with_renderer_surface_state(&root, |s| {97
+ s.texture::<VulkanTexture>(context.clone())98
+ .cloned()99
+ .map(|t| (t, s.buffer_scale(), s.buffer_transform()))100
+ })101
+ .flatten() else {102
+ return;103
+ };104
+ let (gw, gh) = (geo.size.w, geo.size.h);105
+ let (cw, ch) = (w.min(gw), h.min(gh));106
+ let (gx, gy) = (geo.loc.x as f64, geo.loc.y as f64);107
+ let mut strip = |src: Rectangle<f64, Logical>, at: (i32, i32), size: (i32, i32)| {108
+ if size.0 < 1 || size.1 < 1 {109
+ return;110
+ }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
+ )));124
+ };125
+ let right = gx + f64::from(gw) - 1.0;126
+ let bottom = gy + f64::from(gh) - 1.0;127
+ strip(128
+ Rectangle::new((right, gy).into(), (1.0, f64::from(ch)).into()),129
+ (x + gw, y),130
+ (w - gw, ch),131
+ );132
+ strip(133
+ Rectangle::new((gx, bottom).into(), (f64::from(cw), 1.0).into()),134
+ (x, y + gh),135
+ (cw, h - gh),136
+ );137
+ strip(138
+ Rectangle::new((right, bottom).into(), (1.0, 1.0).into()),139
+ (x + gw, y + gh),140
+ (w - gw, h - gh),141
+ );142
+}@@ -1,3 +1,4 @@
1
+use crate::anim::Animator;12
use crate::commands::Binds;23
use crate::nested::Host;34
use monolith_config::Config;@@ -14,7 +15,7 @@ use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel::Sta
1415
use smithay::reexports::wayland_server::backend::{ClientData, ClientId, DisconnectReason};1516
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;1617
use smithay::reexports::wayland_server::{Display, DisplayHandle, Resource};17
-use smithay::utils::{Logical, Point, SERIAL_COUNTER, Size};18
+use smithay::utils::{Clock, Logical, Monotonic, Point, SERIAL_COUNTER, Size};1819
use smithay::wayland::compositor::{CompositorClientState, CompositorState, with_states};1920
use smithay::wayland::dmabuf::{DmabufGlobal, DmabufState};2021
use smithay::wayland::output::OutputManagerState;@@ -27,7 +28,7 @@ use std::collections::HashMap;
2728
use std::ffi::OsString;2829
use std::path::PathBuf;2930
use std::sync::Arc;30
-use std::time::Instant;31
+use std::time::{Duration, Instant};3132
3233
pub struct State {3334
pub start: Instant,@@ -54,6 +55,10 @@ pub struct State {
5455
pub ipc: crate::ipc::Server,5556
pub launches: crate::launch::Launches,5657
pub workspaces: WorkspaceState,58
+ pub anim: Animator,59
+ /// How each shown window was placed by the last layout pass, to spot60
+ /// switches between tiled, floating and fullscreen.61
+ pub last_kind: HashMap<WindowId, (Kind, Rect, OutputId)>,5762
5863
pub compositor_state: CompositorState,5964
pub xdg_shell_state: XdgShellState,@@ -111,6 +116,7 @@ impl State {
111116
let mut layout = Layout::default();112117
layout.gaps = gaps(&config);113118
let binds = Binds::new(&config);119
+ let animation = config.animation.clone();114120
Ok(Self {115121
start: Instant::now(),116122
socket_name,@@ -132,6 +138,8 @@ impl State {
132138
ipc: crate::ipc::Server::default(),133139
launches: Default::default(),134140
workspaces: WorkspaceState::new::<State>(&dh),141
+ anim: Animator::new(animation),142
+ last_kind: HashMap::new(),135143
compositor_state: CompositorState::new::<State>(&dh),136144
xdg_shell_state: XdgShellState::new::<State>(&dh),137145
_xdg_decoration_state: XdgDecorationState::new::<State>(&dh),@@ -226,27 +234,78 @@ impl State {
226234
227235
/// Pushes the layout's target geometry into the space and to clients.228236
pub fn apply_layout(&mut self) {237
+ let now = now_ns();229238
let mut shown = Vec::new();239
+ let mut tracks = Vec::new();230240
let outputs: Vec<OutputId> = self.layout.outputs().map(|(o, _)| o).collect();231241
for o in outputs {232242
let arr = self.layout.arrange(o);233243
let area = self.layout.output(o).map(|o| o.area).unwrap_or_default();234244
for p in &arr.tiled {235245
if p.visible && arr.fullscreen != Some(p.window) {236
- shown.push((p.window, p.rect, Kind::Tiled));246
+ shown.push((p.window, p.rect, Kind::Tiled, o));237247
}238248
}239249
for &(w, rect) in &arr.floating {240250
if arr.fullscreen != Some(w) {241
- shown.push((w, rect, Kind::Floating));251
+ shown.push((w, rect, Kind::Floating, o));242252
}243253
}244254
if let Some(w) = arr.fullscreen {245
- shown.push((w, area, Kind::Fullscreen));255
+ shown.push((w, area, Kind::Fullscreen, o));246256
}257
+ let targets: Vec<(WindowId, Rect)> =258
+ arr.tiled.iter().map(|p| (p.window, p.rect)).collect();259
+ tracks.push((o, targets));247260
}261
+262
+ let mut glides = Vec::new();263
+ for &(id, rect, kind, _) in &shown {264
+ match self.last_kind.get(&id) {265
+ Some(&(was, from, o)) if was != kind => {266
+ let from = self267
+ .anim268
+ .gliding(id, now)269
+ .or_else(|| {270
+ (was == Kind::Tiled)271
+ .then(|| self.anim.tiled(o, id, now))272
+ .flatten()273
+ })274
+ .unwrap_or(from);275
+ glides.push((id, from, rect));276
+ }277
+ Some(&(Kind::Floating, from, _)) if from != rect => self.anim.stop_glide(id),278
+ _ => {}279
+ }280
+ }281
+ let gap = self.layout.gaps.inner;282
+ for (o, targets) in tracks {283
+ let new: Vec<WindowId> = targets284
+ .iter()285
+ .map(|t| t.0)286
+ .filter(|&id| self.anim.tiled(o, id, now).is_none())287
+ .collect();288
+ let seeds: HashMap<WindowId, Rect> = if new.is_empty() {289
+ HashMap::new()290
+ } else {291
+ self.layout292
+ .arrange_hiding(o, &new)293
+ .tiled294
+ .into_iter()295
+ .map(|p| (p.window, p.rect))296
+ .collect()297
+ };298
+ self.anim.retarget(o, now, &targets, gap, |id| {299
+ seeds.get(&id).copied().unwrap_or_default()300
+ });301
+ }302
+ for (id, from, to) in glides {303
+ self.anim.glide(id, from, to, now);304
+ }305
+ self.last_kind = shown.iter().map(|&(id, r, k, o)| (id, (k, r, o))).collect();306
+248307
let mut mapped = Vec::new();249
- for (id, rect, kind) in shown {308
+ for (id, rect, kind, _) in shown {250309
if let Some(window) = self.windows.get(&id)251310
&& configure(window, rect, kind)252311
{@@ -350,8 +409,8 @@ impl State {
350409
}351410
}352411
353
-#[derive(Clone, Copy, PartialEq, Eq)]354
-enum Kind {412
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]413
+pub enum Kind {355414
Tiled,356415
Floating,357416
Fullscreen,@@ -395,6 +454,10 @@ fn configure(window: &Window, rect: Rect, kind: Kind) -> bool {
395454
true396455
}397456
457
+pub fn now_ns() -> u64 {458
+ Duration::from(Clock::<Monotonic>::new().now()).as_nanos() as u64459
+}460
+398461
pub fn identity(t: &smithay::wayland::shell::xdg::ToplevelSurface) -> (String, String) {399462
with_states(t.wl_surface(), |s| {400463
let data = s