feat: animate compositor geometry targets

This commit is contained in:
Samuel Onoja committed 2026-09-24 11:40:41 +01:00
1 parent 7f847b2c0c
commit b111cbf531
5 files changed
+102 -15

No files matched your search

Generated
+1
View File
@@ -368,6 +368,7 @@ checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223"
name = "ferese" name = "ferese"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"ferese-animation",
"ferese-core", "ferese-core",
"ferese-layout", "ferese-layout",
"libc", "libc",
+1
View File
@@ -6,6 +6,7 @@ license.workspace = true
rust-version.workspace = true rust-version.workspace = true
[dependencies] [dependencies]
ferese-animation = { path = "../crates/ferese-animation" }
ferese-core = { path = "../crates/ferese-core" } ferese-core = { path = "../crates/ferese-core" }
ferese-layout = { path = "../crates/ferese-layout" } ferese-layout = { path = "../crates/ferese-layout" }
libc.workspace = true libc.workspace = true
+10 -2
View File
@@ -36,12 +36,20 @@ impl CompositorHandler for Ferese {
while let Some(parent) = get_parent(&root) { while let Some(parent) = get_parent(&root) {
root = parent; root = parent;
} }
if let Some(window) = self.space.elements().find(|window| { let window = {
self.space
.elements()
.find(|window| {
window window
.toplevel() .toplevel()
.is_some_and(|toplevel| toplevel.wl_surface() == &root) .is_some_and(|toplevel| toplevel.wl_surface() == &root)
}) { })
.cloned()
};
if let Some(window) = window {
window.on_commit(); window.on_commit();
self.record_client_commit(&window);
} }
} }
xdg_shell::handle_commit(&mut self.popups, &mut self.space, surface); xdg_shell::handle_commit(&mut self.popups, &mut self.space, surface);
+85 -12
View File
@@ -6,6 +6,7 @@ use std::{
time::Instant, time::Instant,
}; };
use ferese_animation::{ClientSize, SpringConfig, WindowGeometry};
use ferese_core::{WindowPlacement, WorkspaceSet}; use ferese_core::{WindowPlacement, WorkspaceSet};
use ferese_layout::{Axis, Direction, GapConfig, LayoutResult, Rect, SizeConstraints, WindowId}; use ferese_layout::{Axis, Direction, GapConfig, LayoutResult, Rect, SizeConstraints, WindowId};
@@ -47,9 +48,11 @@ pub struct Ferese {
pub space: Space<Window>, pub space: Space<Window>,
pub workspaces: WorkspaceSet, pub workspaces: WorkspaceSet,
pub window_ids: HashMap<Window, WindowId>, pub window_ids: HashMap<Window, WindowId>,
pub window_geometry: HashMap<WindowId, WindowGeometry>,
pub focused_window: Option<WindowId>, pub focused_window: Option<WindowId>,
pub intercepted_keys: HashSet<smithay::input::keyboard::Keycode>, pub intercepted_keys: HashSet<smithay::input::keyboard::Keycode>,
next_window_id: u64, next_window_id: u64,
last_animation_tick: Instant,
pub popups: PopupManager, pub popups: PopupManager,
pub seat: Seat<Self>, pub seat: Seat<Self>,
pub compositor_state: CompositorState, pub compositor_state: CompositorState,
@@ -94,17 +97,21 @@ impl Ferese {
seat.add_pointer(); seat.add_pointer();
let socket_name = Self::init_wayland_listener(display, event_loop)?; let socket_name = Self::init_wayland_listener(display, event_loop)?;
let start_time = Instant::now();
Ok(Self { Ok(Self {
start_time: Instant::now(), start_time,
socket_name, socket_name,
display_handle, display_handle,
loop_signal: event_loop.get_signal(), loop_signal: event_loop.get_signal(),
space: Space::default(), space: Space::default(),
workspaces: WorkspaceSet::default(), workspaces: WorkspaceSet::default(),
window_ids: HashMap::new(), window_ids: HashMap::new(),
window_geometry: HashMap::new(),
focused_window: None, focused_window: None,
intercepted_keys: HashSet::new(), intercepted_keys: HashSet::new(),
next_window_id: 1, next_window_id: 1,
last_animation_tick: start_time,
popups: PopupManager::default(), popups: PopupManager::default(),
seat, seat,
compositor_state, compositor_state,
@@ -228,6 +235,7 @@ impl Ferese {
}; };
self.space.unmap_elem(window); self.space.unmap_elem(window);
self.window_geometry.remove(&id);
if let Err(error) = self.workspaces.remove_window(id) { if let Err(error) = self.workspaces.remove_window(id) {
tracing::error!(%error, ?id, "failed to remove window from layout"); tracing::error!(%error, ?id, "failed to remove window from layout");
} }
@@ -298,32 +306,88 @@ impl Ferese {
} }
}; };
Some((window.clone(), rect, fullscreen == Some(*id))) Some((window.clone(), *id, rect, fullscreen == Some(*id)))
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
for (window, rect, is_fullscreen) in placements { let now = self.start_time.elapsed();
let location = (rect.x.round() as i32, rect.y.round() as i32);
let size = ( for (window, id, rect, is_fullscreen) in placements {
(rect.width.round() as i32).max(1), let committed_size = client_size(&window);
(rect.height.round() as i32).max(1), let geometry = self
); .window_geometry
.entry(id)
.or_insert_with(|| WindowGeometry::new(rect, committed_size));
let requested_size = geometry.set_logical_target(rect, now);
let visual = geometry.visual.current;
let location = (visual.x.round() as i32, visual.y.round() as i32);
self.space.map_element(window.clone(), location, false); self.space.map_element(window.clone(), location, false);
if let Some(toplevel) = window.toplevel() { if let Some(toplevel) = window.toplevel() {
toplevel.with_pending_state(|state| { let state_changed = toplevel.with_pending_state(|state| {
state.size = Some(size.into()); if let Some(size) = requested_size {
state.size = Some((size.width, size.height).into());
}
if is_fullscreen { if is_fullscreen {
state.states.set(xdg_toplevel::State::Fullscreen); state.states.set(xdg_toplevel::State::Fullscreen)
} else { } else {
state.states.unset(xdg_toplevel::State::Fullscreen); state.states.unset(xdg_toplevel::State::Fullscreen)
} }
}); });
if requested_size.is_some() || state_changed {
toplevel.send_pending_configure(); toplevel.send_pending_configure();
} }
} }
} }
}
pub fn advance_animations(&mut self, now: Instant) -> bool {
let delta = now.saturating_duration_since(self.last_animation_tick);
self.last_animation_tick = now;
let windows = self
.space
.elements()
.filter_map(|window| {
self.window_ids
.get(window)
.copied()
.map(|id| (window.clone(), id))
})
.collect::<Vec<_>>();
let mut active_animation = false;
for (window, id) in windows {
let Some(geometry) = self.window_geometry.get_mut(&id) else {
continue;
};
active_animation |= geometry.advance(delta, SpringConfig::default(), true);
let visual = geometry.visual.current;
self.space.map_element(
window,
(visual.x.round() as i32, visual.y.round() as i32),
false,
);
}
active_animation
}
pub fn record_client_commit(&mut self, window: &Window) {
let Some(id) = self.window_ids.get(window) else {
return;
};
let Some(size) = client_size(window) else {
return;
};
let Some(geometry) = self.window_geometry.get_mut(id) else {
return;
};
geometry.client.commit(size);
}
pub fn focus_direction(&mut self, direction: Direction) { pub fn focus_direction(&mut self, direction: Direction) {
let Some(current) = self.focused_window else { let Some(current) = self.focused_window else {
@@ -655,6 +719,15 @@ fn centered_transient_rect(parent: Rect) -> Rect {
) )
} }
fn client_size(window: &Window) -> Option<ClientSize> {
let size = window.geometry().size;
(size.w > 0 && size.h > 0).then_some(ClientSize {
width: size.w,
height: size.h,
})
}
#[derive(Default)] #[derive(Default)]
pub struct ClientState { pub struct ClientState {
pub compositor_state: CompositorClientState, pub compositor_state: CompositorClientState,
+5 -1
View File
@@ -1,4 +1,7 @@
use std::{error::Error, time::Duration}; use std::{
error::Error,
time::{Duration, Instant},
};
use smithay::{ use smithay::{
backend::{ backend::{
@@ -74,6 +77,7 @@ pub fn init(event_loop: &mut EventLoop<Ferese>, state: &mut Ferese) -> Result<()
} }
WinitEvent::Input(event) => state.process_input_event(event), WinitEvent::Input(event) => state.process_input_event(event),
WinitEvent::Redraw => { WinitEvent::Redraw => {
state.advance_animations(Instant::now());
let damage = Rectangle::from_size(backend.window_size()); let damage = Rectangle::from_size(backend.window_size());
let mut presentation = OutputPresentationFeedback::new(&output); let mut presentation = OutputPresentationFeedback::new(&output);
state.space.elements().for_each(|window| { state.space.elements().for_each(|window| {