fix: synchronize floating window geometry
This commit is contained in:
1 parent
e1f1c1b9b6
commit
677e0ba5d1
5 files changed
+165
-5
No files matched your search
@@ -16,6 +16,7 @@ pub struct MoveSurfaceGrab {
|
||||
pub start_data: GrabStartData<Ferese>,
|
||||
pub window: Window,
|
||||
pub initial_location: Point<i32, Logical>,
|
||||
pub finished: bool,
|
||||
}
|
||||
|
||||
impl PointerGrab<Ferese> for MoveSurfaceGrab {
|
||||
@@ -28,8 +29,11 @@ impl PointerGrab<Ferese> for MoveSurfaceGrab {
|
||||
) {
|
||||
handle.motion(data, None, event);
|
||||
let location = self.initial_location.to_f64() + (event.location - self.start_data.location);
|
||||
data.space
|
||||
.map_element(self.window.clone(), location.to_i32_round(), true);
|
||||
let location = location.to_i32_round();
|
||||
let size = self.window.geometry().size;
|
||||
|
||||
data.set_floating_window_geometry(&self.window, location, size);
|
||||
data.space.map_element(self.window.clone(), location, true);
|
||||
}
|
||||
|
||||
fn relative_motion(
|
||||
@@ -50,6 +54,7 @@ impl PointerGrab<Ferese> for MoveSurfaceGrab {
|
||||
) {
|
||||
handle.button(data, event);
|
||||
if handle.current_pressed().is_empty() {
|
||||
self.finished = true;
|
||||
handle.unset_grab(self, data, event.serial, event.time, true);
|
||||
}
|
||||
}
|
||||
@@ -143,5 +148,14 @@ impl PointerGrab<Ferese> for MoveSurfaceGrab {
|
||||
&self.start_data
|
||||
}
|
||||
|
||||
fn unset(&mut self, _data: &mut Ferese) {}
|
||||
fn unset(&mut self, data: &mut Ferese) {
|
||||
if self.finished {
|
||||
return;
|
||||
}
|
||||
|
||||
let size = self.window.geometry().size;
|
||||
data.set_floating_window_geometry(&self.window, self.initial_location, size);
|
||||
data.space
|
||||
.map_element(self.window.clone(), self.initial_location, true);
|
||||
}
|
||||
}
|
||||
@@ -71,6 +71,7 @@ pub struct ResizeSurfaceGrab {
|
||||
edges: ResizeEdge,
|
||||
initial_rect: Rectangle<i32, Logical>,
|
||||
last_size: Size<i32, Logical>,
|
||||
finished: bool,
|
||||
}
|
||||
|
||||
impl ResizeSurfaceGrab {
|
||||
@@ -98,6 +99,7 @@ impl ResizeSurfaceGrab {
|
||||
edges,
|
||||
initial_rect,
|
||||
last_size: initial_rect.size,
|
||||
finished: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -123,6 +125,8 @@ impl PointerGrab<Ferese> for ResizeSurfaceGrab {
|
||||
});
|
||||
self.last_size =
|
||||
constrained_size(self.initial_rect.size, delta, self.edges, minimum, maximum);
|
||||
let rect = resized_rect(self.initial_rect, self.last_size, self.edges);
|
||||
data.set_floating_window_geometry(&self.window, rect.loc, rect.size);
|
||||
|
||||
surface.with_pending_state(|state| {
|
||||
state.states.set(xdg_toplevel::State::Resizing);
|
||||
@@ -149,6 +153,7 @@ impl PointerGrab<Ferese> for ResizeSurfaceGrab {
|
||||
) {
|
||||
handle.button(data, event);
|
||||
if handle.current_pressed().is_empty() {
|
||||
self.finished = true;
|
||||
handle.unset_grab(self, data, event.serial, event.time, true);
|
||||
let surface = self
|
||||
.window
|
||||
@@ -250,7 +255,29 @@ impl PointerGrab<Ferese> for ResizeSurfaceGrab {
|
||||
&self.start_data
|
||||
}
|
||||
|
||||
fn unset(&mut self, _data: &mut Ferese) {}
|
||||
fn unset(&mut self, data: &mut Ferese) {
|
||||
if self.finished {
|
||||
return;
|
||||
}
|
||||
|
||||
let surface = self
|
||||
.window
|
||||
.toplevel()
|
||||
.expect("managed window has a toplevel");
|
||||
surface.with_pending_state(|state| {
|
||||
state.states.unset(xdg_toplevel::State::Resizing);
|
||||
state.size = Some(self.initial_rect.size);
|
||||
});
|
||||
surface.send_pending_configure();
|
||||
ResizeState::with(surface.wl_surface(), |state| *state = ResizeState::Idle);
|
||||
data.set_floating_window_geometry(
|
||||
&self.window,
|
||||
self.initial_rect.loc,
|
||||
self.initial_rect.size,
|
||||
);
|
||||
data.space
|
||||
.map_element(self.window.clone(), self.initial_rect.loc, true);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
@@ -359,6 +386,23 @@ fn constrained_size(
|
||||
.into()
|
||||
}
|
||||
|
||||
fn resized_rect(
|
||||
initial: Rectangle<i32, Logical>,
|
||||
size: Size<i32, Logical>,
|
||||
edges: ResizeEdge,
|
||||
) -> Rectangle<i32, Logical> {
|
||||
let mut location = initial.loc;
|
||||
|
||||
if edges.left() {
|
||||
location.x += initial.size.w - size.w;
|
||||
}
|
||||
if edges.top() {
|
||||
location.y += initial.size.h - size.h;
|
||||
}
|
||||
|
||||
Rectangle::new(location, size)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -398,4 +442,17 @@ mod tests {
|
||||
);
|
||||
assert_eq!(size, (1_024, 768).into());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resized_rect_keeps_the_opposite_edge_fixed() {
|
||||
let initial = Rectangle::new((100, 80).into(), (800, 600).into());
|
||||
let resized = resized_rect(
|
||||
initial,
|
||||
(700, 550).into(),
|
||||
ResizeEdge(xdg_toplevel::ResizeEdge::TopLeft),
|
||||
);
|
||||
|
||||
assert_eq!(resized.loc, (200, 130).into());
|
||||
assert_eq!(resized.size, (700, 550).into());
|
||||
}
|
||||
}
|
||||
@@ -100,12 +100,16 @@ impl XdgShellHandler for Ferese {
|
||||
let Some(initial_location) = self.space.element_location(&window) else {
|
||||
return;
|
||||
};
|
||||
if !self.is_floating_window(&window) {
|
||||
return;
|
||||
}
|
||||
pointer.set_grab(
|
||||
self,
|
||||
MoveSurfaceGrab {
|
||||
start_data,
|
||||
window,
|
||||
initial_location,
|
||||
finished: false,
|
||||
},
|
||||
serial,
|
||||
Focus::Clear,
|
||||
@@ -143,6 +147,9 @@ impl XdgShellHandler for Ferese {
|
||||
let Some(location) = self.space.element_location(&window) else {
|
||||
return;
|
||||
};
|
||||
if !self.is_floating_window(&window) {
|
||||
return;
|
||||
}
|
||||
let rect = Rectangle::new(location, window.geometry().size);
|
||||
pointer.set_grab(
|
||||
self,
|
||||
|
||||
+29
-1
@@ -21,7 +21,7 @@ use smithay::{
|
||||
protocol::wl_surface::WlSurface,
|
||||
},
|
||||
},
|
||||
utils::{Logical, Point},
|
||||
utils::{Logical, Point, Size},
|
||||
wayland::{
|
||||
compositor::{CompositorClientState, CompositorState},
|
||||
fractional_scale::FractionalScaleManagerState,
|
||||
@@ -440,6 +440,34 @@ impl Ferese {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_floating_window(&self, window: &Window) -> bool {
|
||||
self.window_ids
|
||||
.get(window)
|
||||
.and_then(|id| self.workspaces.placement(*id))
|
||||
.is_some_and(|placement| matches!(placement, WindowPlacement::Floating { .. }))
|
||||
}
|
||||
|
||||
pub fn set_floating_window_geometry(
|
||||
&mut self,
|
||||
window: &Window,
|
||||
location: Point<i32, Logical>,
|
||||
size: Size<i32, Logical>,
|
||||
) {
|
||||
let Some(id) = self.window_ids.get(window).copied() else {
|
||||
return;
|
||||
};
|
||||
let rect = Rect::new(
|
||||
location.x as f64,
|
||||
location.y as f64,
|
||||
size.w.max(1) as f64,
|
||||
size.h.max(1) as f64,
|
||||
);
|
||||
|
||||
if let Err(error) = self.workspaces.set_floating_rect(id, rect) {
|
||||
tracing::error!(%error, ?id, "failed to update floating window geometry");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn switch_workspace(&mut self, index: u32) {
|
||||
let focus = match self.workspaces.switch_to_numeric(index) {
|
||||
Ok(focus) => focus,
|
||||
|
||||
@@ -127,6 +127,28 @@ impl WorkspaceSet {
|
||||
self.placements.get(&window).copied()
|
||||
}
|
||||
|
||||
pub fn set_floating_rect(
|
||||
&mut self,
|
||||
window: WindowId,
|
||||
rect: Rect,
|
||||
) -> Result<(), WorkspaceError> {
|
||||
let placement = self
|
||||
.placements
|
||||
.get_mut(&window)
|
||||
.ok_or(LayoutError::UnknownWindow(window))?;
|
||||
|
||||
if !matches!(placement, WindowPlacement::Floating { .. }) {
|
||||
return Err(WorkspaceError::InvalidState("window is not floating"));
|
||||
}
|
||||
|
||||
*placement = WindowPlacement::Floating {
|
||||
rect: normalized_floating_rect(rect),
|
||||
};
|
||||
|
||||
debug_assert!(self.validate().is_ok());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn focus_window(&mut self, window: WindowId) -> Result<(), WorkspaceError> {
|
||||
if self.workspace_for_window(window) != Some(self.active) {
|
||||
return Err(WorkspaceError::InvalidState(
|
||||
@@ -621,6 +643,38 @@ mod tests {
|
||||
assert!(workspaces.validate().is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn floating_geometry_updates_are_authoritative_and_validated() {
|
||||
let mut workspaces = WorkspaceSet::default();
|
||||
workspaces
|
||||
.insert_window(WindowId(1), Axis::Horizontal, 0.5)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
workspaces.set_floating_rect(WindowId(1), Rect::default()),
|
||||
Err(WorkspaceError::InvalidState("window is not floating"))
|
||||
);
|
||||
|
||||
workspaces
|
||||
.toggle_floating(
|
||||
WindowId(1),
|
||||
Rect::new(10.0, 20.0, 640.0, 480.0),
|
||||
Axis::Horizontal,
|
||||
0.5,
|
||||
)
|
||||
.unwrap();
|
||||
workspaces
|
||||
.set_floating_rect(WindowId(1), Rect::new(30.0, 40.0, f64::NAN, -2.0))
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
workspaces.placement(WindowId(1)),
|
||||
Some(WindowPlacement::Floating {
|
||||
rect: Rect::new(30.0, 40.0, 1.0, 1.0)
|
||||
})
|
||||
);
|
||||
assert!(workspaces.validate().is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fullscreen_preserves_underlying_placement() {
|
||||
let mut workspaces = WorkspaceSet::default();
|
||||
|
||||
Reference in new issue
Block a user