aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorIvan Molodetskikh <yalterz@gmail.com>2024-05-10 16:58:53 +0400
committerIvan Molodetskikh <yalterz@gmail.com>2024-05-10 20:23:08 +0400
commit9dfa121b8e31082314d1c9347a60ef2e596494cb (patch)
tree6e9b90f2f1d54213444fb89b4739e0c8948ed1e9 /src
parentc4ebb9f58e7ea1d2e688d3ee9483a7a1b3dd52b4 (diff)
downloadniri-9dfa121b8e31082314d1c9347a60ef2e596494cb.tar.gz
niri-9dfa121b8e31082314d1c9347a60ef2e596494cb.tar.bz2
niri-9dfa121b8e31082314d1c9347a60ef2e596494cb.zip
Implement interactive mouse resizing
Diffstat (limited to 'src')
-rw-r--r--src/handlers/compositor.rs18
-rw-r--r--src/handlers/xdg_shell.rs44
-rw-r--r--src/input.rs41
-rw-r--r--src/layout/mod.rs161
-rw-r--r--src/layout/monitor.rs43
-rw-r--r--src/layout/workspace.rs353
-rw-r--r--src/lib.rs1
-rw-r--r--src/niri.rs4
-rw-r--r--src/resize_grab.rs171
-rw-r--r--src/utils/mod.rs28
-rw-r--r--src/window/mapped.rs78
11 files changed, 875 insertions, 67 deletions
diff --git a/src/handlers/compositor.rs b/src/handlers/compositor.rs
index 0bdd7690..5f39d47c 100644
--- a/src/handlers/compositor.rs
+++ b/src/handlers/compositor.rs
@@ -13,6 +13,7 @@ use smithay::wayland::compositor::{
SurfaceAttributes,
};
use smithay::wayland::dmabuf::get_dmabuf;
+use smithay::wayland::shell::xdg::XdgToplevelSurfaceData;
use smithay::wayland::shm::{ShmHandler, ShmState};
use smithay::{delegate_compositor, delegate_shm};
@@ -237,8 +238,21 @@ impl CompositorHandler for State {
return;
}
+ let serial = with_states(surface, |states| {
+ let role = states
+ .data_map
+ .get::<XdgToplevelSurfaceData>()
+ .unwrap()
+ .lock()
+ .unwrap();
+ role.configure_serial
+ });
+ if serial.is_none() {
+ error!("commit on a mapped surface without a configured serial");
+ }
+
// The toplevel remains mapped.
- self.niri.layout.update_window(&window);
+ self.niri.layout.update_window(&window, serial);
// Popup placement depends on window size which might have changed.
self.update_reactive_popups(&window, &output);
@@ -256,7 +270,7 @@ impl CompositorHandler for State {
let window = mapped.window.clone();
let output = output.clone();
window.on_commit();
- self.niri.layout.update_window(&window);
+ self.niri.layout.update_window(&window, None);
self.niri.queue_redraw(&output);
return;
}
diff --git a/src/handlers/xdg_shell.rs b/src/handlers/xdg_shell.rs
index 9864ccdc..5e255d17 100644
--- a/src/handlers/xdg_shell.rs
+++ b/src/handlers/xdg_shell.rs
@@ -11,6 +11,7 @@ use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel::{se
use smithay::reexports::wayland_server::protocol::wl_output;
use smithay::reexports::wayland_server::protocol::wl_seat::WlSeat;
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
+use smithay::reexports::wayland_server::Resource;
use smithay::utils::{Logical, Rectangle, Serial};
use smithay::wayland::compositor::{
add_pre_commit_hook, send_surface_state, with_states, BufferAssignment, HookId,
@@ -31,6 +32,7 @@ use smithay::{
use crate::layout::workspace::ColumnWidth;
use crate::niri::{PopupGrabState, State};
+use crate::resize_grab::ResizeGrab;
use crate::window::{InitialConfigureState, ResolvedWindowRules, Unmapped, WindowRef};
impl XdgShellHandler for State {
@@ -60,12 +62,44 @@ impl XdgShellHandler for State {
fn resize_request(
&mut self,
- _surface: ToplevelSurface,
+ surface: ToplevelSurface,
_seat: WlSeat,
- _serial: Serial,
- _edges: ResizeEdge,
+ serial: Serial,
+ edges: ResizeEdge,
) {
- // FIXME
+ let pointer = self.niri.seat.get_pointer().unwrap();
+ if !pointer.has_grab(serial) {
+ return;
+ }
+
+ let Some(start_data) = pointer.grab_start_data() else {
+ return;
+ };
+
+ let Some((focus, _)) = &start_data.focus else {
+ return;
+ };
+
+ let wl_surface = surface.wl_surface();
+ if !focus.id().same_client_as(&wl_surface.id()) {
+ return;
+ }
+
+ let Some((mapped, _)) = self.niri.layout.find_window_and_output(wl_surface) else {
+ return;
+ };
+
+ let window = mapped.window.clone();
+ if !self
+ .niri
+ .layout
+ .interactive_resize_begin(window.clone(), edges.into())
+ {
+ return;
+ }
+
+ let grab = ResizeGrab::new(start_data, window);
+ pointer.set_grab(self, grab, serial, Focus::Clear);
}
fn reposition_request(
@@ -799,7 +833,7 @@ impl State {
drop(config);
let output = output.cloned();
let window = mapped.window.clone();
- self.niri.layout.update_window(&window);
+ self.niri.layout.update_window(&window, None);
if let Some(output) = output {
self.niri.queue_redraw(&output);
diff --git a/src/input.rs b/src/input.rs
index 6a75960a..bb970dbf 100644
--- a/src/input.rs
+++ b/src/input.rs
@@ -10,16 +10,17 @@ use niri_ipc::LayoutSwitchTarget;
use smithay::backend::input::{
AbsolutePositionEvent, Axis, AxisSource, ButtonState, Device, DeviceCapability, Event,
GestureBeginEvent, GestureEndEvent, GesturePinchUpdateEvent as _, GestureSwipeUpdateEvent as _,
- InputBackend, InputEvent, KeyState, KeyboardKeyEvent, PointerAxisEvent, PointerButtonEvent,
- PointerMotionEvent, ProximityState, TabletToolButtonEvent, TabletToolEvent,
+ InputBackend, InputEvent, KeyState, KeyboardKeyEvent, MouseButton, PointerAxisEvent,
+ PointerButtonEvent, PointerMotionEvent, ProximityState, TabletToolButtonEvent, TabletToolEvent,
TabletToolProximityEvent, TabletToolTipEvent, TabletToolTipState, TouchEvent,
};
use smithay::backend::libinput::LibinputInputBackend;
use smithay::input::keyboard::{keysyms, FilterResult, Keysym, ModifiersState};
use smithay::input::pointer::{
- AxisFrame, ButtonEvent, CursorImageStatus, GestureHoldBeginEvent, GestureHoldEndEvent,
+ AxisFrame, ButtonEvent, CursorImageStatus, Focus, GestureHoldBeginEvent, GestureHoldEndEvent,
GesturePinchBeginEvent, GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent,
- GestureSwipeEndEvent, GestureSwipeUpdateEvent, MotionEvent, RelativeMotionEvent,
+ GestureSwipeEndEvent, GestureSwipeUpdateEvent, GrabStartData as PointerGrabStartData,
+ MotionEvent, RelativeMotionEvent,
};
use smithay::input::touch::{DownEvent, MotionEvent as TouchMotionEvent, UpEvent};
use smithay::utils::{Logical, Point, SERIAL_COUNTER};
@@ -27,6 +28,7 @@ use smithay::wayland::pointer_constraints::{with_pointer_constraint, PointerCons
use smithay::wayland::tablet_manager::{TabletDescriptor, TabletSeatTrait};
use crate::niri::State;
+use crate::resize_grab::ResizeGrab;
use crate::ui::screenshot_ui::ScreenshotUi;
use crate::utils::spawning::spawn;
use crate::utils::{center, get_monotonic_time};
@@ -1073,6 +1075,37 @@ impl State {
let window = mapped.window.clone();
self.niri.layout.activate_window(&window);
+ // Check if we need to start an interactive resize.
+ if event.button() == Some(MouseButton::Right) && !pointer.is_grabbed() {
+ let mods = self.niri.seat.get_keyboard().unwrap().modifier_state();
+ let mod_down = match self.backend.mod_key() {
+ CompositorMod::Super => mods.logo,
+ CompositorMod::Alt => mods.alt,
+ };
+ if mod_down {
+ let location = pointer.current_location();
+ let (output, pos_within_output) = self.niri.output_under(location).unwrap();
+ let edges = self
+ .niri
+ .layout
+ .resize_edges_under(output, pos_within_output)
+ .unwrap();
+ if self
+ .niri
+ .layout
+ .interactive_resize_begin(window.clone(), edges)
+ {
+ let start_data = PointerGrabStartData {
+ focus: None,
+ button: event.button_code(),
+ location,
+ };
+ let grab = ResizeGrab::new(start_data, window);
+ pointer.set_grab(self, grab, serial, Focus::Clear);
+ }
+ }
+ }
+
// FIXME: granular.
self.niri.queue_redraw_all();
} else if let Some(output) = self.niri.output_under_cursor() {
diff --git a/src/layout/mod.rs b/src/layout/mod.rs
index b93dfb4d..ad241d94 100644
--- a/src/layout/mod.rs
+++ b/src/layout/mod.rs
@@ -43,7 +43,7 @@ use smithay::backend::renderer::element::Id;
use smithay::backend::renderer::gles::{GlesRenderer, GlesTexture};
use smithay::output::Output;
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
-use smithay::utils::{Logical, Point, Scale, Size, Transform};
+use smithay::utils::{Logical, Point, Scale, Serial, Size, Transform};
use self::monitor::Monitor;
pub use self::monitor::MonitorRenderElement;
@@ -52,7 +52,7 @@ use crate::niri_render_elements;
use crate::render_helpers::renderer::NiriRenderer;
use crate::render_helpers::snapshot::RenderSnapshot;
use crate::render_helpers::{BakedBuffer, RenderTarget, SplitElements};
-use crate::utils::output_size;
+use crate::utils::{output_size, ResizeEdge};
use crate::window::ResolvedWindowRules;
pub mod closing_window;
@@ -74,9 +74,16 @@ niri_render_elements! {
pub type LayoutElementRenderSnapshot =
RenderSnapshot<BakedBuffer<TextureBuffer<GlesTexture>>, BakedBuffer<SolidColorBuffer>>;
+#[derive(Debug, Clone, Copy)]
+pub struct InteractiveResizeData {
+ pub edges: ResizeEdge,
+ pub original_view_offset: i32,
+ pub original_column_width: i32,
+}
+
pub trait LayoutElement {
/// Type that can be used as a unique ID of this element.
- type Id: PartialEq;
+ type Id: PartialEq + std::fmt::Debug;
/// Unique ID of this element.
fn id(&self) -> &Self::Id;
@@ -166,6 +173,10 @@ pub trait LayoutElement {
fn animation_snapshot(&self) -> Option<&LayoutElementRenderSnapshot>;
fn take_animation_snapshot(&mut self) -> Option<LayoutElementRenderSnapshot>;
+
+ fn set_interactive_resize(&mut self, data: Option<InteractiveResizeData>);
+ fn cancel_interactive_resize(&mut self);
+ fn interactive_resize_data(&mut self, serial: Serial) -> Option<InteractiveResizeData>;
}
#[derive(Debug)]
@@ -669,13 +680,13 @@ impl<W: LayoutElement> Layout<W> {
rv
}
- pub fn update_window(&mut self, window: &W::Id) {
+ pub fn update_window(&mut self, window: &W::Id, serial: Option<Serial>) {
match &mut self.monitor_set {
MonitorSet::Normal { monitors, .. } => {
for mon in monitors {
for ws in &mut mon.workspaces {
if ws.has_window(window) {
- ws.update_window(window);
+ ws.update_window(window, serial);
return;
}
}
@@ -684,7 +695,7 @@ impl<W: LayoutElement> Layout<W> {
MonitorSet::NoOutputs { workspaces, .. } => {
for ws in workspaces {
if ws.has_window(window) {
- ws.update_window(window);
+ ws.update_window(window, serial);
return;
}
}
@@ -1225,6 +1236,19 @@ impl<W: LayoutElement> Layout<W> {
mon.window_under(pos_within_output)
}
+ pub fn resize_edges_under(
+ &self,
+ output: &Output,
+ pos_within_output: Point<f64, Logical>,
+ ) -> Option<ResizeEdge> {
+ let MonitorSet::Normal { monitors, .. } = &self.monitor_set else {
+ return None;
+ };
+
+ let mon = monitors.iter().find(|mon| &mon.output == output)?;
+ mon.resize_edges_under(pos_within_output)
+ }
+
#[cfg(test)]
fn verify_invariants(&self) {
use std::collections::HashSet;
@@ -1760,6 +1784,79 @@ impl<W: LayoutElement> Layout<W> {
None
}
+ pub fn interactive_resize_begin(&mut self, window: W::Id, edges: ResizeEdge) -> bool {
+ match &mut self.monitor_set {
+ MonitorSet::Normal { monitors, .. } => {
+ for mon in monitors {
+ for ws in &mut mon.workspaces {
+ if ws.has_window(&window) {
+ return ws.interactive_resize_begin(window, edges);
+ }
+ }
+ }
+ }
+ MonitorSet::NoOutputs { workspaces, .. } => {
+ for ws in workspaces {
+ if ws.has_window(&window) {
+ return ws.interactive_resize_begin(window, edges);
+ }
+ }
+ }
+ }
+
+ false
+ }
+
+ pub fn interactive_resize_update(
+ &mut self,
+ window: &W::Id,
+ delta: Point<f64, Logical>,
+ ) -> bool {
+ match &mut self.monitor_set {
+ MonitorSet::Normal { monitors, .. } => {
+ for mon in monitors {
+ for ws in &mut mon.workspaces {
+ if ws.has_window(window) {
+ return ws.interactive_resize_update(window, delta);
+ }
+ }
+ }
+ }
+ MonitorSet::NoOutputs { workspaces, .. } => {
+ for ws in workspaces {
+ if ws.has_window(window) {
+ return ws.interactive_resize_update(window, delta);
+ }
+ }
+ }
+ }
+
+ false
+ }
+
+ pub fn interactive_resize_end(&mut self, window: &W::Id) {
+ match &mut self.monitor_set {
+ MonitorSet::Normal { monitors, .. } => {
+ for mon in monitors {
+ for ws in &mut mon.workspaces {
+ if ws.has_window(window) {
+ ws.interactive_resize_end(Some(window));
+ return;
+ }
+ }
+ }
+ }
+ MonitorSet::NoOutputs { workspaces, .. } => {
+ for ws in workspaces {
+ if ws.has_window(window) {
+ ws.interactive_resize_end(Some(window));
+ return;
+ }
+ }
+ }
+ }
+ }
+
pub fn move_workspace_down(&mut self) {
let Some(monitor) = self.active_monitor() else {
return;
@@ -2107,6 +2204,14 @@ mod tests {
fn take_animation_snapshot(&mut self) -> Option<LayoutElementRenderSnapshot> {
None
}
+
+ fn set_interactive_resize(&mut self, _data: Option<InteractiveResizeData>) {}
+
+ fn cancel_interactive_resize(&mut self) {}
+
+ fn interactive_resize_data(&mut self, _serial: Serial) -> Option<InteractiveResizeData> {
+ None
+ }
}
fn arbitrary_bbox() -> impl Strategy<Value = Rectangle<i32, Logical>> {
@@ -2148,6 +2253,20 @@ mod tests {
prop_oneof![(-10f64..10f64), (-50000f64..50000f64),]
}
+ fn arbitrary_resize_edge() -> impl Strategy<Value = ResizeEdge> {
+ prop_oneof![
+ Just(ResizeEdge::RIGHT),
+ Just(ResizeEdge::BOTTOM),
+ Just(ResizeEdge::LEFT),
+ Just(ResizeEdge::TOP),
+ Just(ResizeEdge::BOTTOM_RIGHT),
+ Just(ResizeEdge::BOTTOM_LEFT),
+ Just(ResizeEdge::TOP_RIGHT),
+ Just(ResizeEdge::TOP_LEFT),
+ Just(ResizeEdge::empty()),
+ ]
+ }
+
#[derive(Debug, Clone, Copy, Arbitrary)]
enum Op {
AddOutput(#[proptest(strategy = "1..=5usize")] usize),
@@ -2237,6 +2356,24 @@ mod tests {
WorkspaceSwitchGestureEnd {
cancelled: bool,
},
+ InteractiveResizeBegin {
+ #[proptest(strategy = "1..=5usize")]
+ window: usize,
+ #[proptest(strategy = "arbitrary_resize_edge()")]
+ edges: ResizeEdge,
+ },
+ InteractiveResizeUpdate {
+ #[proptest(strategy = "1..=5usize")]
+ window: usize,
+ #[proptest(strategy = "-20000f64..20000f64")]
+ dx: f64,
+ #[proptest(strategy = "-20000f64..20000f64")]
+ dy: f64,
+ },
+ InteractiveResizeEnd {
+ #[proptest(strategy = "1..=5usize")]
+ window: usize,
+ },
}
impl Op {
@@ -2455,7 +2592,8 @@ mod tests {
}
if update {
- layout.update_window(&id);
+ // FIXME: serial.
+ layout.update_window(&id, None);
}
}
Op::MoveWorkspaceToOutput(id) => {
@@ -2495,6 +2633,15 @@ mod tests {
Op::WorkspaceSwitchGestureEnd { cancelled } => {
layout.workspace_switch_gesture_end(cancelled);
}
+ Op::InteractiveResizeBegin { window, edges } => {
+ layout.interactive_resize_begin(window, edges);
+ }
+ Op::InteractiveResizeUpdate { window, dx, dy } => {
+ layout.interactive_resize_update(&window, Point::from((dx, dy)));
+ }
+ Op::InteractiveResizeEnd { window } => {
+ layout.interactive_resize_end(&window);
+ }
}
}
}
diff --git a/src/layout/monitor.rs b/src/layout/monitor.rs
index 8d695139..3833a61a 100644
--- a/src/layout/monitor.rs
+++ b/src/layout/monitor.rs
@@ -19,7 +19,7 @@ use crate::render_helpers::renderer::NiriRenderer;
use crate::render_helpers::RenderTarget;
use crate::rubber_band::RubberBand;
use crate::swipe_tracker::SwipeTracker;
-use crate::utils::output_size;
+use crate::utils::{output_size, ResizeEdge};
/// Amount of touchpad movement to scroll the height of one workspace.
const WORKSPACE_GESTURE_MOVEMENT: f64 = 300.;
@@ -722,6 +722,47 @@ impl<W: LayoutElement> Monitor<W> {
}
}
+ pub fn resize_edges_under(&self, pos_within_output: Point<f64, Logical>) -> Option<ResizeEdge> {
+ match &self.workspace_switch {
+ Some(switch) => {
+ let size = output_size(&self.output);
+
+ let render_idx = switch.current_idx();
+ let before_idx = render_idx.floor();
+ let after_idx = render_idx.ceil();
+
+ let offset = ((render_idx - before_idx) * size.h as f64).round() as i32;
+
+ if after_idx < 0. || before_idx as usize >= self.workspaces.len() {
+ return None;
+ }
+
+ let after_idx = after_idx as usize;
+
+ let (idx, ws_offset) = if pos_within_output.y < (size.h - offset) as f64 {
+ if before_idx < 0. {
+ return None;
+ }
+
+ (before_idx as usize, Point::from((0, offset)))
+ } else {
+ if after_idx >= self.workspaces.len() {
+ return None;
+ }
+
+ (after_idx, Point::from((0, -size.h + offset)))
+ };
+
+ let ws = &self.workspaces[idx];
+ ws.resize_edges_under(pos_within_output + ws_offset.to_f64())
+ }
+ None => {
+ let ws = &self.workspaces[self.active_workspace_idx];
+ ws.resize_edges_under(pos_within_output)
+ }
+ }
+ }
+
pub fn render_above_top_layer(&self) -> bool {
// Render above the top layer only if the view is stationary.
if self.workspace_switch.is_some() {
diff --git a/src/layout/workspace.rs b/src/layout/workspace.rs
index a14f804e..1b6b14ff 100644
--- a/src/layout/workspace.rs
+++ b/src/layout/workspace.rs
@@ -10,19 +10,19 @@ use smithay::desktop::{layer_map_for_output, Window};
use smithay::output::Output;
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel;
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
-use smithay::utils::{Logical, Point, Rectangle, Scale, Size};
+use smithay::utils::{Logical, Point, Rectangle, Scale, Serial, Size};
use smithay::wayland::compositor::send_surface_state;
use super::closing_window::{ClosingWindow, ClosingWindowRenderElement};
use super::tile::{Tile, TileRenderElement};
-use super::{LayoutElement, Options};
+use super::{InteractiveResizeData, LayoutElement, Options};
use crate::animation::Animation;
use crate::niri_render_elements;
use crate::render_helpers::renderer::NiriRenderer;
use crate::render_helpers::RenderTarget;
use crate::swipe_tracker::SwipeTracker;
use crate::utils::id::IdCounter;
-use crate::utils::output_size;
+use crate::utils::{output_size, ResizeEdge};
use crate::window::ResolvedWindowRules;
/// Amount of touchpad movement to scroll the view for the width of one working area.
@@ -57,6 +57,9 @@ pub struct Workspace<W: LayoutElement> {
/// Index of the currently active column, if any.
pub active_column_idx: usize,
+ /// Ongoing interactive resize.
+ interactive_resize: Option<InteractiveResize<W>>,
+
/// Offset of the view computed from the active column.
///
/// Any gaps, including left padding from work area left exclusive zone, is handled
@@ -128,6 +131,13 @@ struct ViewGesture {
static_view_offset: i32,
}
+#[derive(Debug)]
+struct InteractiveResize<W: LayoutElement> {
+ window: W::Id,
+ original_window_size: Size<i32, Logical>,
+ data: InteractiveResizeData,
+}
+
/// Width of a column.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ColumnWidth {
@@ -257,6 +267,7 @@ impl<W: LayoutElement> Workspace<W> {
output: Some(output),
columns: vec![],
active_column_idx: 0,
+ interactive_resize: None,
view_offset: 0,
view_offset_adj: None,
activate_prev_column_on_removal: None,
@@ -275,6 +286,7 @@ impl<W: LayoutElement> Workspace<W> {
working_area: Rectangle::from_loc_and_size((0, 0), (1280, 720)),
columns: vec![],
active_column_idx: 0,
+ interactive_resize: None,
view_offset: 0,
view_offset_adj: None,
activate_prev_column_on_removal: None,
@@ -697,6 +709,7 @@ impl<W: LayoutElement> Workspace<W> {
// A different column was activated; reset the flag.
self.activate_prev_column_on_removal = None;
self.view_offset_before_fullscreen = None;
+ self.interactive_resize = None;
}
pub fn has_windows(&self) -> bool {
@@ -957,6 +970,13 @@ impl<W: LayoutElement> Workspace<W> {
tile.window().output_leave(output);
}
+ // Stop interactive resize.
+ if let Some(resize) = &self.interactive_resize {
+ if tile.window().id() == &resize.window {
+ self.interactive_resize = None;
+ }
+ }
+
if column.tiles.is_empty() {
if column_idx + 1 == self.active_column_idx {
// The previous column, that we were going to activate upon removal of the active
@@ -1055,6 +1075,17 @@ impl<W: LayoutElement> Workspace<W> {
}
}
+ // Stop interactive resize.
+ if let Some(resize) = &self.interactive_resize {
+ if column
+ .tiles
+ .iter()
+ .any(|tile| tile.window().id() == &resize.window)
+ {
+ self.interactive_resize = None;
+ }
+ }
+
if column_idx + 1 == self.active_column_idx {
// The previous column, that we were going to activate upon removal of the active
// column, has just been itself removed.
@@ -1109,7 +1140,7 @@ impl<W: LayoutElement> Workspace<W> {
.into_window()
}
- pub fn update_window(&mut self, window: &W::Id) {
+ pub fn update_window(&mut self, window: &W::Id, serial: Option<Serial>) {
let (col_idx, column) = self
.columns
.iter_mut()
@@ -1153,31 +1184,64 @@ impl<W: LayoutElement> Workspace<W> {
}
}
- if col_idx == self.active_column_idx
- && !matches!(self.view_offset_adj, Some(ViewOffsetAdjustment::Gesture(_)))
- {
- // We might need to move the view to ensure the resized window is still visible.
- let current_x = self.view_pos();
-
- // Upon unfullscreening, restore the view offset.
- let is_fullscreen = self.columns[col_idx].tiles[tile_idx].is_fullscreen();
- if was_fullscreen && !is_fullscreen {
- if let Some(prev_offset) = self.view_offset_before_fullscreen.take() {
- self.animate_view_offset(current_x, col_idx, prev_offset);
+ if col_idx == self.active_column_idx {
+ let col = &mut self.columns[col_idx];
+ let tile = &mut col.tiles[tile_idx];
+ let window = tile.window_mut();
+
+ let resize = serial.and_then(|serial| window.interactive_resize_data(serial));
+
+ // If this was the last resize commit, this function will now return None. This way we
+ // can animate the window into view after the last resize commit.
+ let resize_still_ongoing = serial
+ .and_then(|serial| window.interactive_resize_data(serial))
+ .is_some();
+
+ if let Some(resize) = resize {
+ // If this is an interactive resize commit of an active window, then we need to
+ // either preserve the view offset or adjust it accordingly.
+ let centered = self.options.center_focused_column == CenterFocusedColumn::Always;
+
+ let width = col.width();
+ if centered {
+ self.view_offset =
+ -(self.working_area.size.w - width) / 2 - self.working_area.loc.x;
+ } else if resize.edges.contains(ResizeEdge::LEFT) {
+ self.view_offset =
+ resize.original_view_offset + width - resize.original_column_width;
}
+
+ // We *could* compute the right offsets here to preserve the gesture but it's
+ // pretty edge-case-y so it's fine to just cancel it.
+ self.view_offset_adj = None;
}
- // Synchronize the horizontal view movement with the resize so that it looks nice. This
- // is especially important for always-centered view.
- let config = if started_resize_anim {
- self.options.animations.window_resize.anim
- } else {
- self.options.animations.horizontal_view_movement.0
- };
+ if !resize_still_ongoing
+ && !matches!(self.view_offset_adj, Some(ViewOffsetAdjustment::Gesture(_)))
+ {
+ // We might need to move the view to ensure the resized window is still visible.
+ let current_x = self.view_pos();
- // FIXME: we will want to skip the animation in some cases here to make continuously
- // resizing windows not look janky.
- self.animate_view_offset_to_column_with_config(current_x, col_idx, None, config);
+ // Upon unfullscreening, restore the view offset.
+ let is_fullscreen = self.columns[col_idx].tiles[tile_idx].is_fullscreen();
+ if was_fullscreen && !is_fullscreen {
+ if let Some(prev_offset) = self.view_offset_before_fullscreen.take() {
+ self.animate_view_offset(current_x, col_idx, prev_offset);
+ }
+ }
+
+ // Synchronize the horizontal view movement with the resize so that it looks nice.
+ // This is especially important for always-centered view.
+ let config = if started_resize_anim {
+ self.options.animations.window_resize.anim
+ } else {
+ self.options.animations.horizontal_view_movement.0
+ };
+
+ // FIXME: we will want to skip the animation in some cases here to make continuously
+ // resizing windows not look janky.
+ self.animate_view_offset_to_column_with_config(current_x, col_idx, None, config);
+ }
}
}
@@ -1332,10 +1396,11 @@ impl<W: LayoutElement> Workspace<W> {
column.verify_invariants();
}
+ let col = &self.columns[self.active_column_idx];
+
// When we have an unfullscreen view offset stored, the active column should have a
// fullscreen tile.
if self.view_offset_before_fullscreen.is_some() {
- let col = &self.columns[self.active_column_idx];
assert!(
col.is_fullscreen
|| col.tiles.iter().any(|tile| {
@@ -1344,6 +1409,16 @@ impl<W: LayoutElement> Workspace<W> {
);
}
}
+
+ if let Some(resize) = &self.interactive_resize {
+ assert!(
+ self.columns
+ .iter()
+ .flat_map(|col| &col.tiles)
+ .any(|tile| tile.window().id() == &resize.window),
+ "interactive resize window must be present on the workspace"
+ );
+ }
}
pub fn focus_left(&mut self) {
@@ -1394,7 +1469,8 @@ impl<W: LayoutElement> Workspace<W> {
let current_col_x = self.column_x(self.active_column_idx);
let next_col_x = self.column_x(self.active_column_idx + 1);
- let column = self.columns.remove(self.active_column_idx);
+ let mut column = self.columns.remove(self.active_column_idx);
+ cancel_resize_if_this_column(&mut self.interactive_resize, &mut column);
self.columns.insert(new_idx, column);
// Preserve the camera position when moving to the left.
@@ -1696,6 +1772,11 @@ impl<W: LayoutElement> Workspace<W> {
self.active_column_idx,
self.options.animations.horizontal_view_movement.0,
);
+
+ if !self.columns.is_empty() {
+ let col = &mut self.columns[self.active_column_idx];
+ cancel_resize_if_this_column(&mut self.interactive_resize, col);
+ }
}
fn view_pos(&self) -> i32 {
@@ -1818,12 +1899,48 @@ impl<W: LayoutElement> Workspace<W> {
})
}
+ pub fn resize_edges_under(&self, pos: Point<f64, Logical>) -> Option<ResizeEdge> {
+ if self.columns.is_empty() {
+ return None;
+ }
+
+ self.tiles_in_render_order().find_map(|(tile, tile_pos)| {
+ let pos_within_tile = pos - tile_pos.to_f64();
+
+ // This logic should be consistent with window_under() in when it returns Some vs.
+ // None.
+ if tile.is_in_input_region(pos_within_tile)
+ || tile.is_in_activation_region(pos_within_tile)
+ {
+ let size = tile.tile_size().to_f64();
+
+ let mut edges = ResizeEdge::empty();
+ if pos_within_tile.x < size.w / 3. {
+ edges |= ResizeEdge::LEFT;
+ } else if 2. * size.w / 3. < pos_within_tile.x {
+ edges |= ResizeEdge::RIGHT;
+ }
+ if pos_within_tile.y < size.h / 3. {
+ edges |= ResizeEdge::TOP;
+ } else if 2. * size.h / 3. < pos_within_tile.y {
+ edges |= ResizeEdge::BOTTOM;
+ }
+ return Some(edges);
+ }
+
+ None
+ })
+ }
+
pub fn toggle_width(&mut self) {
if self.columns.is_empty() {
return;
}
- self.columns[self.active_column_idx].toggle_width();
+ let col = &mut self.columns[self.active_column_idx];
+ col.toggle_width();
+
+ cancel_resize_if_this_column(&mut self.interactive_resize, col);
}
pub fn toggle_full_width(&mut self) {
@@ -1831,7 +1948,10 @@ impl<W: LayoutElement> Workspace<W> {
return;
}
- self.columns[self.active_column_idx].toggle_full_width();
+ let col = &mut self.columns[self.active_column_idx];
+ col.toggle_full_width();
+
+ cancel_resize_if_this_column(&mut self.interactive_resize, col);
}
pub fn set_column_width(&mut self, change: SizeChange) {
@@ -1839,7 +1959,10 @@ impl<W: LayoutElement> Workspace<W> {
return;
}
- self.columns[self.active_column_idx].set_column_width(change);
+ let col = &mut self.columns[self.active_column_idx];
+ col.set_column_width(change, None, true);
+
+ cancel_resize_if_this_column(&mut self.interactive_resize, col);
}
pub fn set_window_height(&mut self, change: SizeChange) {
@@ -1847,7 +1970,10 @@ impl<W: LayoutElement> Workspace<W> {
return;
}
- self.columns[self.active_column_idx].set_window_height(change);
+ let col = &mut self.columns[self.active_column_idx];
+ col.set_window_height(change, None, true);
+
+ cancel_resize_if_this_column(&mut self.interactive_resize, col);
}
pub fn set_fullscreen(&mut self, window: &W::Id, is_fullscreen: bool) {
@@ -1867,6 +1993,8 @@ impl<W: LayoutElement> Workspace<W> {
let mut col = &mut self.columns[col_idx];
+ cancel_resize_if_this_co