feat: render animated window geometry
This commit is contained in:
1 parent
b111cbf531
commit
46a7576d19
5 files changed
+189
-27
No files matched your search
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
Reference in new issue
Block a user