feat: render animated window geometry

This commit is contained in:
Samuel Onoja committed 2026-09-24 16:06:34 +01:00
1 parent b111cbf531
commit 46a7576d19
5 files changed
+189 -27

No files matched your search

+10 -1
View File
@@ -8,6 +8,7 @@ use smithay::{
pointer::{CursorImageStatus, PointerHandle},
},
reexports::wayland_server::{Resource, protocol::wl_surface::WlSurface},
utils::Point,
wayland::{
compositor::{TraversalAction, with_states, with_surface_tree_downward},
fractional_scale::{FractionalScaleHandler, with_fractional_scale},
@@ -113,7 +114,15 @@ impl PointerConstraintsHandler for Ferese {
return;
};
if focused_surface == *surface {
pointer.set_location(origin + location);
let current = pointer.current_location();
let scale = self
.window_under_visual(current)
.and_then(|window| self.visual_scale_for_window(&window))
.unwrap_or(1.0);
let current_surface_location = current - origin;
let offset = location - current_surface_location;
pointer.set_location(current + Point::from((offset.x * scale, offset.y * scale)));
}
}
}
+11 -6
View File
@@ -135,14 +135,21 @@ impl Ferese {
}
InputEvent::PointerMotion { event, .. } => {
let pointer = self.seat.get_pointer().expect("seat has a pointer");
let focus = self.surface_under(pointer.current_location());
let position = pointer.current_location();
let focus = self.surface_under(position);
let scale = self
.window_under_visual(position)
.and_then(|window| self.visual_scale_for_window(&window))
.unwrap_or(1.0);
let delta = event.delta();
let delta_unaccel = event.delta_unaccel();
pointer.relative_motion(
self,
focus,
&RelativeMotionEvent {
delta: event.delta(),
delta_unaccel: event.delta_unaccel(),
delta: (delta.x / scale, delta.y / scale).into(),
delta_unaccel: (delta_unaccel.x / scale, delta_unaccel.y / scale).into(),
utime: (event.time_msec() as u64).saturating_mul(1_000),
},
);
@@ -212,9 +219,7 @@ impl Ferese {
let pointer = self.seat.get_pointer().expect("seat has a pointer");
let keyboard = self.seat.get_keyboard().expect("seat has a keyboard");
if let Some((window, _)) = self.space.element_under(pointer.current_location()) {
let window = window.clone();
if let Some(window) = self.window_under_visual(pointer.current_location()) {
self.focused_window = self.window_ids.get(&window).copied();
if let Some(focused) = self.focused_window
&& let Err(error) = self.workspaces.focus_window(focused)
+52 -6
View File
@@ -162,17 +162,48 @@ impl Ferese {
&self,
position: Point<f64, Logical>,
) -> Option<(WlSurface, Point<f64, Logical>)> {
self.space
.element_under(position)
.and_then(|(window, location)| {
self.space.elements().rev().find_map(|window| {
let id = self.window_ids.get(window)?;
let geometry = self.window_geometry.get(id)?;
let visual = geometry.visual.current;
let inside_visual = position.x >= visual.x
&& position.y >= visual.y
&& position.x < visual.x + visual.width
&& position.y < visual.y + visual.height;
if !inside_visual {
return None;
}
let (source_x, source_y) = geometry.inverse_visual_point(position.x, position.y)?;
let source_point = Point::from((source_x, source_y)) + window.geometry().loc.to_f64();
window
.surface_under(position - location.to_f64(), WindowSurfaceType::ALL)
.surface_under(source_point, WindowSurfaceType::ALL)
.map(|(surface, surface_location)| {
(surface, (surface_location + location).to_f64())
let surface_point = source_point - surface_location.to_f64();
(surface, position - surface_point)
})
})
}
pub fn window_under_visual(&self, position: Point<f64, Logical>) -> Option<Window> {
self.space.elements().rev().find_map(|window| {
let id = self.window_ids.get(window)?;
let visual = self.window_geometry.get(id)?.visual.current;
(position.x >= visual.x
&& position.y >= visual.y
&& position.x < visual.x + visual.width
&& position.y < visual.y + visual.height)
.then(|| window.clone())
})
}
pub fn visual_scale_for_window(&self, window: &Window) -> Option<f64> {
let id = self.window_ids.get(window)?;
self.window_geometry.get(id)?.visual_scale()
}
pub fn add_tiled_window(&mut self, window: Window) {
let id = WindowId(self.next_window_id);
self.next_window_id += 1;
@@ -364,6 +395,15 @@ impl Ferese {
};
active_animation |= geometry.advance(delta, SpringConfig::default(), true);
if let Some(size) = geometry.client.expire_wait(self.start_time.elapsed()) {
tracing::warn!(
?id,
configured_width = size.width,
configured_height = size.height,
committed = ?geometry.client.committed_size,
"client did not commit the final configured size within 500 ms"
);
}
let visual = geometry.visual.current;
self.space.map_element(
window,
@@ -386,7 +426,13 @@ impl Ferese {
return;
};
geometry.client.commit(size);
let matches_target = geometry.client.commit(size);
tracing::debug!(
?id,
?size,
matches_target,
"recorded client geometry commit"
);
}
pub fn focus_direction(&mut self, direction: Direction) {
+72 -14
View File
@@ -6,21 +6,35 @@ use std::{
use smithay::{
backend::{
renderer::{
damage::OutputDamageTracker, element::surface::WaylandSurfaceRenderElement,
damage::OutputDamageTracker,
element::{
surface::WaylandSurfaceRenderElement,
utils::{
ConstrainAlign, ConstrainScaleBehavior, CropRenderElement,
RelocateRenderElement, RescaleRenderElement,
},
},
gles::GlesRenderer,
},
winit::{self, WinitEvent},
},
desktop::utils::OutputPresentationFeedback,
desktop::{
space::{ConstrainBehavior, ConstrainReference, constrain_space_element},
utils::OutputPresentationFeedback,
},
output::{Mode, Output, PhysicalProperties, Scale, Subpixel},
reexports::calloop::EventLoop,
reexports::wayland_protocols::wp::presentation_time::server::wp_presentation_feedback::Kind,
utils::{Clock, Monotonic, Rectangle, Transform},
utils::{Clock, Logical, Monotonic, Rectangle, Transform},
wayland::presentation::Refresh,
};
use crate::Ferese;
type AnimatedWindowRenderElement = CropRenderElement<
RelocateRenderElement<RescaleRenderElement<WaylandSurfaceRenderElement<GlesRenderer>>>,
>;
pub fn init(event_loop: &mut EventLoop<Ferese>, state: &mut Ferese) -> Result<(), Box<dyn Error>> {
let (mut backend, event_source) = winit::init()?;
let initial_scale = normalized_scale(backend.scale_factor());
@@ -90,20 +104,12 @@ pub fn init(event_loop: &mut EventLoop<Ferese>, state: &mut Ferese) -> Result<()
let rendered = (|| -> Result<(), Box<dyn Error>> {
{
let (renderer, mut framebuffer) = backend.bind()?;
smithay::desktop::space::render_output::<
_,
WaylandSurfaceRenderElement<GlesRenderer>,
_,
_,
>(
&output,
let elements = animated_window_elements(state, renderer, &output);
damage_tracker.render_output(
renderer,
&mut framebuffer,
1.0,
0,
[&state.space],
&[],
&mut damage_tracker,
&elements,
[0.035, 0.04, 0.055, 1.0],
)?;
}
@@ -143,6 +149,58 @@ pub fn init(event_loop: &mut EventLoop<Ferese>, state: &mut Ferese) -> Result<()
Ok(())
}
fn animated_window_elements(
state: &Ferese,
renderer: &mut GlesRenderer,
output: &Output,
) -> Vec<AnimatedWindowRenderElement> {
let Some(output_geometry) = state.space.output_geometry(output) else {
return Vec::new();
};
let scale = output.current_scale().fractional_scale();
state
.space
.elements()
.rev()
.filter_map(|window| {
let id = state.window_ids.get(window)?;
let visual = state.window_geometry.get(id)?.visual.current;
let constrain = Rectangle::<i32, Logical>::new(
(
(visual.x - f64::from(output_geometry.loc.x)).round() as i32,
(visual.y - f64::from(output_geometry.loc.y)).round() as i32,
)
.into(),
(
visual.width.round().max(1.0) as i32,
visual.height.round().max(1.0) as i32,
)
.into(),
);
Some(constrain_space_element::<
GlesRenderer,
_,
AnimatedWindowRenderElement,
>(
renderer,
window,
constrain.loc,
1.0,
scale,
constrain,
ConstrainBehavior {
reference: ConstrainReference::Geometry,
behavior: ConstrainScaleBehavior::Zoom,
align: ConstrainAlign::TOP | ConstrainAlign::LEFT,
},
))
})
.flatten()
.collect()
}
fn normalized_scale(scale: f64) -> f64 {
if scale.is_finite() && scale > 0.0 {
scale
+44
View File
@@ -179,6 +179,15 @@ impl ClientGeometry {
self.waiting_since
.is_some_and(|started| now.saturating_sub(started) >= CLIENT_COMMIT_TIMEOUT)
}
pub fn expire_wait(&mut self, now: Duration) -> Option<ClientSize> {
if !self.timed_out(now) {
return None;
}
self.waiting_since = None;
self.last_configured_size
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
@@ -225,6 +234,23 @@ impl WindowGeometry {
self.visual.advance(delta, config)
}
pub fn inverse_visual_point(&self, x: f64, y: f64) -> Option<(f64, f64)> {
let visual = self.visual.current;
let scale = self.visual_scale()?;
Some(((x - visual.x) / scale, (y - visual.y) / scale))
}
pub fn visual_scale(&self) -> Option<f64> {
let source = self.client.committed_size?;
let visual = self.visual.current;
if source.width <= 0 || source.height <= 0 || visual.width <= 0.0 || visual.height <= 0.0 {
return None;
}
Some((visual.width / f64::from(source.width)).max(visual.height / f64::from(source.height)))
}
}
fn advance_value(
@@ -347,6 +373,24 @@ mod tests {
assert!(!client.timed_out(Duration::from_millis(499)));
assert!(client.timed_out(Duration::from_millis(500)));
assert_eq!(
client.expire_wait(Duration::from_millis(500)),
Some(ClientSize::from_rect(rect))
);
assert!(!client.timed_out(Duration::from_secs(1)));
}
#[test]
fn inverse_visual_point_matches_uniform_cover_transform() {
let initial = Rect::new(100.0, 50.0, 400.0, 300.0);
let mut geometry = WindowGeometry::new(initial, Some(ClientSize::from_rect(initial)));
geometry.visual.current = Rect::new(200.0, 100.0, 600.0, 600.0);
// Cover scales the 4:3 source by 2x for a square allocation, then clips it.
assert_eq!(
geometry.inverse_visual_point(600.0, 500.0),
Some((200.0, 200.0))
);
}
#[test]