refactor: split every monolithic module into focused submodules
Each source file that had grown beyond a single concern is replaced by an identically-named directory containing focused submodules. `src/event_loop/mod.rs` (878 lines) becomes a directory with clipboard, context_menu, cursor_shape, drag, focus, handlers, invalidation, overlays_reconcile, repeat, run, surface, text_editing, and tooltip. Every widget, input handler, and theme component follows the same split. Public interfaces are unchanged — only the internal file layout moves. image bumped from 0.25.2 to 0.25.9.
This commit is contained in:
221
src/event_loop/cursor_shape.rs
Normal file
221
src/event_loop/cursor_shape.rs
Normal file
@@ -0,0 +1,221 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-only
|
||||
// Copyright (C) 2026 Liberux Labs, S. L. <info@liberux.net>
|
||||
|
||||
use smithay_client_toolkit::reexports::protocols::xdg::shell::client::xdg_toplevel::ResizeEdge;
|
||||
|
||||
use super::app_data::AppData;
|
||||
use super::surface::{ SurfaceFocus, SurfaceKind };
|
||||
use crate::app::App;
|
||||
use crate::types::{ CursorShape, Point };
|
||||
|
||||
/// Classify a pointer position against a surface of size `w × h` (in
|
||||
/// physical pixels). Returns the resize edge if the pointer is inside
|
||||
/// the `border`-thick grab band along any edge or corner.
|
||||
pub( crate ) fn resize_edge_for_pos( pos: Point, w: f32, h: f32, border: f32 ) -> Option<ResizeEdge>
|
||||
{
|
||||
let on_left = pos.x < border;
|
||||
let on_right = pos.x > w - border;
|
||||
let on_top = pos.y < border;
|
||||
let on_bottom = pos.y > h - border;
|
||||
Some( match ( on_top, on_bottom, on_left, on_right )
|
||||
{
|
||||
( true, _, true, _ ) => ResizeEdge::TopLeft,
|
||||
( true, _, _, true ) => ResizeEdge::TopRight,
|
||||
( _, true, true, _ ) => ResizeEdge::BottomLeft,
|
||||
( _, true, _, true ) => ResizeEdge::BottomRight,
|
||||
( true, _, _, _ ) => ResizeEdge::Top,
|
||||
( _, true, _, _ ) => ResizeEdge::Bottom,
|
||||
( _, _, true, _ ) => ResizeEdge::Left,
|
||||
( _, _, _, true ) => ResizeEdge::Right,
|
||||
_ => return None,
|
||||
} )
|
||||
}
|
||||
|
||||
/// Map an [`ResizeEdge`] to the [`CursorShape`](crate::types::CursorShape)
|
||||
/// to display while hovering / dragging it.
|
||||
pub( crate ) fn resize_edge_to_cursor( edge: ResizeEdge ) -> CursorShape
|
||||
{
|
||||
use CursorShape::*;
|
||||
match edge
|
||||
{
|
||||
ResizeEdge::Top => NResize,
|
||||
ResizeEdge::Bottom => SResize,
|
||||
ResizeEdge::Left => WResize,
|
||||
ResizeEdge::Right => EResize,
|
||||
ResizeEdge::TopLeft => NwResize,
|
||||
ResizeEdge::TopRight => NeResize,
|
||||
ResizeEdge::BottomLeft => SwResize,
|
||||
ResizeEdge::BottomRight => SeResize,
|
||||
_ => Default,
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: App> AppData<A>
|
||||
{
|
||||
/// Convert a public [`CursorShape`](crate::types::CursorShape) into
|
||||
/// the wire-protocol [`Shape`] enum the compositor expects.
|
||||
fn cursor_shape_to_wp( shape: CursorShape )
|
||||
-> smithay_client_toolkit::reexports::protocols::wp::cursor_shape::v1::client::wp_cursor_shape_device_v1::Shape
|
||||
{
|
||||
use CursorShape as C;
|
||||
use smithay_client_toolkit::reexports::protocols::wp::cursor_shape::v1::client::wp_cursor_shape_device_v1::Shape as S;
|
||||
match shape
|
||||
{
|
||||
C::Default => S::Default,
|
||||
C::ContextMenu => S::ContextMenu,
|
||||
C::Help => S::Help,
|
||||
C::Pointer => S::Pointer,
|
||||
C::Progress => S::Progress,
|
||||
C::Wait => S::Wait,
|
||||
C::Cell => S::Cell,
|
||||
C::Crosshair => S::Crosshair,
|
||||
C::Text => S::Text,
|
||||
C::VerticalText => S::VerticalText,
|
||||
C::Alias => S::Alias,
|
||||
C::Copy => S::Copy,
|
||||
C::Move => S::Move,
|
||||
C::NoDrop => S::NoDrop,
|
||||
C::NotAllowed => S::NotAllowed,
|
||||
C::Grab => S::Grab,
|
||||
C::Grabbing => S::Grabbing,
|
||||
C::EResize => S::EResize,
|
||||
C::NResize => S::NResize,
|
||||
C::NeResize => S::NeResize,
|
||||
C::NwResize => S::NwResize,
|
||||
C::SResize => S::SResize,
|
||||
C::SeResize => S::SeResize,
|
||||
C::SwResize => S::SwResize,
|
||||
C::WResize => S::WResize,
|
||||
C::EwResize => S::EwResize,
|
||||
C::NsResize => S::NsResize,
|
||||
C::NeswResize => S::NeswResize,
|
||||
C::NwseResize => S::NwseResize,
|
||||
C::ColResize => S::ColResize,
|
||||
C::RowResize => S::RowResize,
|
||||
C::AllScroll => S::AllScroll,
|
||||
C::ZoomIn => S::ZoomIn,
|
||||
C::ZoomOut => S::ZoomOut,
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute the cursor shape that should be active right now and
|
||||
/// push it to the compositor if it changed since the last
|
||||
/// dispatch. Precedence:
|
||||
///
|
||||
/// 1. [`crate::App::cursor_override`] — application-driven busy
|
||||
/// state, wins over everything.
|
||||
/// 2. Active slider drag — `Grabbing` while the gesture machine
|
||||
/// holds a `dragging_slider`.
|
||||
/// 3. Cursor declared by the widget under the pointer
|
||||
/// ([`crate::widget::LaidOutWidget::cursor`]).
|
||||
/// 4. `Default` — pointer is over surface chrome / empty space.
|
||||
pub( crate ) fn dispatch_cursor_shape( &mut self, focus: SurfaceFocus )
|
||||
{
|
||||
let target = {
|
||||
let app_override = self.app.cursor_override();
|
||||
let dragging = self.surface( focus ).gesture.dragging_slider.is_some();
|
||||
let hover_cursor = self.surface( focus ).hovered_idx
|
||||
.and_then( |idx| crate::tree::find_widget( &self.surface( focus ).widget_rects, idx ) )
|
||||
.map( |w| w.cursor )
|
||||
.unwrap_or( CursorShape::Default );
|
||||
let resize_cursor = self.resize_edge_under_pointer( focus ).map( resize_edge_to_cursor );
|
||||
|
||||
// Resize-edge hover wins over the widget cursor and over a
|
||||
// `Default` app override, but loses to a non-default app
|
||||
// override (so a "busy" / "wait" state still trumps chrome).
|
||||
let allow_chrome = app_override.is_none()
|
||||
|| app_override == Some( CursorShape::Default );
|
||||
if let Some( c ) = resize_cursor.filter( |_| allow_chrome )
|
||||
{
|
||||
c
|
||||
} else if let Some( o ) = app_override {
|
||||
o
|
||||
} else if dragging {
|
||||
CursorShape::Grabbing
|
||||
} else {
|
||||
hover_cursor
|
||||
}
|
||||
};
|
||||
// Skip when the compositor already shows the right shape.
|
||||
// `current_cursor_shape == None` means "we have not pushed
|
||||
// anything since the last `Enter`" — e.g. the pointer just
|
||||
// arrived from another client's surface, where the prior
|
||||
// client may have asked for a text I-beam. We MUST push
|
||||
// unconditionally in that case so the user does not see the
|
||||
// stranger cursor lingering until they hit a widget.
|
||||
if self.current_cursor_shape == Some( target ) { return; }
|
||||
|
||||
if let Some( device ) = self.cursor_shape_device.as_ref()
|
||||
{
|
||||
device.set_shape( self.last_pointer_enter_serial, Self::cursor_shape_to_wp( target ) );
|
||||
self.current_cursor_shape = Some( target );
|
||||
}
|
||||
}
|
||||
|
||||
/// Width (logical pixels) of the resize-grab band along each
|
||||
/// edge of an xdg-toplevel surface. 6 px is the GTK / KWin
|
||||
/// convention.
|
||||
pub( crate ) const RESIZE_BORDER_LOGICAL: f32 = 6.0;
|
||||
|
||||
/// Return the [`ResizeEdge`] the pointer currently sits on, or
|
||||
/// `None` if it is not in a resize-grab band. Only fires for the
|
||||
/// main surface when it is an xdg-toplevel — layer-shell surfaces
|
||||
/// and overlays cannot be resized by the user.
|
||||
pub( crate ) fn resize_edge_under_pointer( &self, focus: SurfaceFocus ) -> Option<ResizeEdge>
|
||||
{
|
||||
if !matches!( focus, SurfaceFocus::Main ) { return None; }
|
||||
if !matches!( self.main.surface, SurfaceKind::Window( _ ) ) { return None; }
|
||||
let sf = self.surface( focus ).scale_factor.max( 1 ) as f32;
|
||||
let border = Self::RESIZE_BORDER_LOGICAL * sf;
|
||||
let w = self.surface( focus ).physical_width() as f32;
|
||||
let h = self.surface( focus ).physical_height() as f32;
|
||||
resize_edge_for_pos( self.pointer_pos, w, h, border )
|
||||
}
|
||||
}
|
||||
|
||||
#[ cfg( test ) ]
|
||||
mod resize_edge_tests
|
||||
{
|
||||
use super::{ resize_edge_for_pos, ResizeEdge };
|
||||
use crate::types::Point;
|
||||
|
||||
const W: f32 = 800.0;
|
||||
const H: f32 = 600.0;
|
||||
const B: f32 = 6.0;
|
||||
|
||||
fn at( x: f32, y: f32 ) -> Option<ResizeEdge>
|
||||
{
|
||||
resize_edge_for_pos( Point { x, y }, W, H, B )
|
||||
}
|
||||
|
||||
#[ test ]
|
||||
fn middle_is_none()
|
||||
{
|
||||
assert!( at( 400.0, 300.0 ).is_none() );
|
||||
}
|
||||
|
||||
#[ test ]
|
||||
fn just_inside_border_is_none()
|
||||
{
|
||||
// 6 px border; (6, 6) is one pixel inside on both axes.
|
||||
assert!( at( 6.0, 6.0 ).is_none() );
|
||||
}
|
||||
|
||||
#[ test ]
|
||||
fn corners_take_precedence_over_single_edges()
|
||||
{
|
||||
assert_eq!( at( 1.0, 1.0 ), Some( ResizeEdge::TopLeft ) );
|
||||
assert_eq!( at( W - 1.0, 1.0 ), Some( ResizeEdge::TopRight ) );
|
||||
assert_eq!( at( 1.0, H - 1.0 ), Some( ResizeEdge::BottomLeft ) );
|
||||
assert_eq!( at( W - 1.0, H - 1.0 ), Some( ResizeEdge::BottomRight ) );
|
||||
}
|
||||
|
||||
#[ test ]
|
||||
fn straight_edges()
|
||||
{
|
||||
assert_eq!( at( 400.0, 1.0 ), Some( ResizeEdge::Top ) );
|
||||
assert_eq!( at( 400.0, H - 1.0 ), Some( ResizeEdge::Bottom ) );
|
||||
assert_eq!( at( 1.0, 300.0 ), Some( ResizeEdge::Left ) );
|
||||
assert_eq!( at( W - 1.0, 300.0 ), Some( ResizeEdge::Right ) );
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user