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},
|
pointer::{CursorImageStatus, PointerHandle},
|
||||||
},
|
},
|
||||||
reexports::wayland_server::{Resource, protocol::wl_surface::WlSurface},
|
reexports::wayland_server::{Resource, protocol::wl_surface::WlSurface},
|
||||||
|
utils::Point,
|
||||||
wayland::{
|
wayland::{
|
||||||
compositor::{TraversalAction, with_states, with_surface_tree_downward},
|
compositor::{TraversalAction, with_states, with_surface_tree_downward},
|
||||||
fractional_scale::{FractionalScaleHandler, with_fractional_scale},
|
fractional_scale::{FractionalScaleHandler, with_fractional_scale},
|
||||||
@@ -113,7 +114,15 @@ impl PointerConstraintsHandler for Ferese {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if focused_surface == *surface {
|
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, .. } => {
|
InputEvent::PointerMotion { event, .. } => {
|
||||||
let pointer = self.seat.get_pointer().expect("seat has a pointer");
|
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(
|
pointer.relative_motion(
|
||||||
self,
|
self,
|
||||||
focus,
|
focus,
|
||||||
&RelativeMotionEvent {
|
&RelativeMotionEvent {
|
||||||
delta: event.delta(),
|
delta: (delta.x / scale, delta.y / scale).into(),
|
||||||
delta_unaccel: event.delta_unaccel(),
|
delta_unaccel: (delta_unaccel.x / scale, delta_unaccel.y / scale).into(),
|
||||||
utime: (event.time_msec() as u64).saturating_mul(1_000),
|
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 pointer = self.seat.get_pointer().expect("seat has a pointer");
|
||||||
let keyboard = self.seat.get_keyboard().expect("seat has a keyboard");
|
let keyboard = self.seat.get_keyboard().expect("seat has a keyboard");
|
||||||
|
|
||||||
if let Some((window, _)) = self.space.element_under(pointer.current_location()) {
|
if let Some(window) = self.window_under_visual(pointer.current_location()) {
|
||||||
let window = window.clone();
|
|
||||||
|
|
||||||
self.focused_window = self.window_ids.get(&window).copied();
|
self.focused_window = self.window_ids.get(&window).copied();
|
||||||
if let Some(focused) = self.focused_window
|
if let Some(focused) = self.focused_window
|
||||||
&& let Err(error) = self.workspaces.focus_window(focused)
|
&& let Err(error) = self.workspaces.focus_window(focused)
|
||||||
|
|||||||
+52
-6
@@ -162,17 +162,48 @@ impl Ferese {
|
|||||||
&self,
|
&self,
|
||||||
position: Point<f64, Logical>,
|
position: Point<f64, Logical>,
|
||||||
) -> Option<(WlSurface, Point<f64, Logical>)> {
|
) -> Option<(WlSurface, Point<f64, Logical>)> {
|
||||||
self.space
|
self.space.elements().rev().find_map(|window| {
|
||||||
.element_under(position)
|
let id = self.window_ids.get(window)?;
|
||||||
.and_then(|(window, location)| {
|
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
|
window
|
||||||
.surface_under(position - location.to_f64(), WindowSurfaceType::ALL)
|
.surface_under(source_point, WindowSurfaceType::ALL)
|
||||||
.map(|(surface, surface_location)| {
|
.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) {
|
pub fn add_tiled_window(&mut self, window: Window) {
|
||||||
let id = WindowId(self.next_window_id);
|
let id = WindowId(self.next_window_id);
|
||||||
self.next_window_id += 1;
|
self.next_window_id += 1;
|
||||||
@@ -364,6 +395,15 @@ impl Ferese {
|
|||||||
};
|
};
|
||||||
|
|
||||||
active_animation |= geometry.advance(delta, SpringConfig::default(), true);
|
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;
|
let visual = geometry.visual.current;
|
||||||
self.space.map_element(
|
self.space.map_element(
|
||||||
window,
|
window,
|
||||||
@@ -386,7 +426,13 @@ impl Ferese {
|
|||||||
return;
|
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) {
|
pub fn focus_direction(&mut self, direction: Direction) {
|
||||||
|
|||||||
+72
-14
@@ -6,21 +6,35 @@ use std::{
|
|||||||
use smithay::{
|
use smithay::{
|
||||||
backend::{
|
backend::{
|
||||||
renderer::{
|
renderer::{
|
||||||
damage::OutputDamageTracker, element::surface::WaylandSurfaceRenderElement,
|
damage::OutputDamageTracker,
|
||||||
|
element::{
|
||||||
|
surface::WaylandSurfaceRenderElement,
|
||||||
|
utils::{
|
||||||
|
ConstrainAlign, ConstrainScaleBehavior, CropRenderElement,
|
||||||
|
RelocateRenderElement, RescaleRenderElement,
|
||||||
|
},
|
||||||
|
},
|
||||||
gles::GlesRenderer,
|
gles::GlesRenderer,
|
||||||
},
|
},
|
||||||
winit::{self, WinitEvent},
|
winit::{self, WinitEvent},
|
||||||
},
|
},
|
||||||
desktop::utils::OutputPresentationFeedback,
|
desktop::{
|
||||||
|
space::{ConstrainBehavior, ConstrainReference, constrain_space_element},
|
||||||
|
utils::OutputPresentationFeedback,
|
||||||
|
},
|
||||||
output::{Mode, Output, PhysicalProperties, Scale, Subpixel},
|
output::{Mode, Output, PhysicalProperties, Scale, Subpixel},
|
||||||
reexports::calloop::EventLoop,
|
reexports::calloop::EventLoop,
|
||||||
reexports::wayland_protocols::wp::presentation_time::server::wp_presentation_feedback::Kind,
|
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,
|
wayland::presentation::Refresh,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::Ferese;
|
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>> {
|
pub fn init(event_loop: &mut EventLoop<Ferese>, state: &mut Ferese) -> Result<(), Box<dyn Error>> {
|
||||||
let (mut backend, event_source) = winit::init()?;
|
let (mut backend, event_source) = winit::init()?;
|
||||||
let initial_scale = normalized_scale(backend.scale_factor());
|
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 rendered = (|| -> Result<(), Box<dyn Error>> {
|
||||||
{
|
{
|
||||||
let (renderer, mut framebuffer) = backend.bind()?;
|
let (renderer, mut framebuffer) = backend.bind()?;
|
||||||
smithay::desktop::space::render_output::<
|
let elements = animated_window_elements(state, renderer, &output);
|
||||||
_,
|
damage_tracker.render_output(
|
||||||
WaylandSurfaceRenderElement<GlesRenderer>,
|
|
||||||
_,
|
|
||||||
_,
|
|
||||||
>(
|
|
||||||
&output,
|
|
||||||
renderer,
|
renderer,
|
||||||
&mut framebuffer,
|
&mut framebuffer,
|
||||||
1.0,
|
|
||||||
0,
|
0,
|
||||||
[&state.space],
|
&elements,
|
||||||
&[],
|
|
||||||
&mut damage_tracker,
|
|
||||||
[0.035, 0.04, 0.055, 1.0],
|
[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(())
|
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 {
|
fn normalized_scale(scale: f64) -> f64 {
|
||||||
if scale.is_finite() && scale > 0.0 {
|
if scale.is_finite() && scale > 0.0 {
|
||||||
scale
|
scale
|
||||||
|
|||||||
@@ -179,6 +179,15 @@ impl ClientGeometry {
|
|||||||
self.waiting_since
|
self.waiting_since
|
||||||
.is_some_and(|started| now.saturating_sub(started) >= CLIENT_COMMIT_TIMEOUT)
|
.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)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
@@ -225,6 +234,23 @@ impl WindowGeometry {
|
|||||||
|
|
||||||
self.visual.advance(delta, config)
|
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(
|
fn advance_value(
|
||||||
@@ -347,6 +373,24 @@ mod tests {
|
|||||||
|
|
||||||
assert!(!client.timed_out(Duration::from_millis(499)));
|
assert!(!client.timed_out(Duration::from_millis(499)));
|
||||||
assert!(client.timed_out(Duration::from_millis(500)));
|
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]
|
#[test]
|
||||||
|
|||||||
Reference in new issue
Block a user