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:
2026-05-15 23:46:56 +02:00
parent 3d237039c6
commit 4aa3480b64
155 changed files with 13832 additions and 13035 deletions
+257
View File
@@ -0,0 +1,257 @@
// SPDX-License-Identifier: LGPL-2.1-only
// Copyright (C) 2026 Liberux Labs, S. L. <info@liberux.net>
use smithay_client_toolkit::seat::pointer::{ PointerEvent, PointerEventKind };
use smithay_client_toolkit::reexports::client::
{
protocol::wl_pointer::WlPointer,
Connection, QueueHandle,
};
use crate::app::App;
use crate::event_loop::{ AppData, SurfaceFocus };
use crate::tree::{ find_handlers, find_widget_at };
use crate::widget::WidgetHandlers;
impl<A: App> AppData<A>
{
pub( super ) fn on_pointer_right_click(
&mut self,
_conn: &Connection,
qh: &QueueHandle<Self>,
_pointer: &WlPointer,
event: &PointerEvent,
)
{
let focus = self.focus_for_surface( &event.surface )
.unwrap_or( SurfaceFocus::Main );
// Right-click: the desktop equivalent of a touch
// long-press. Three cases on the press target:
//
// * Widget with `on_long_press` (Button or
// Pressable that opted in) — fire the message.
// The drag-arm slot (`on_drag_start`) is NOT
// consumed: right-click never enters drag mode,
// so an icon's context menu opens but no drag
// is armed.
// * TextEdit with no user `on_long_press` — open
// the built-in Copy / Cut / Paste menu near the
// click. Selection is preserved (we deliberately
// skip `handle_text_pointer_down`) so the common
// "select text → right-click → Copy" flow works.
// * Anywhere else — dismiss any already-open menu.
let pos = self.surface( focus ).to_physical( event.position.0, event.position.1 );
self.pointer_pos = pos;
self.app.on_pointer_move( pos.x, pos.y );
let hit_idx = find_widget_at( &self.surface( focus ).widget_rects, pos );
let lp_msg = hit_idx.and_then( |idx|
find_handlers( &self.surface( focus ).widget_rects, idx )
.and_then( |h| h.long_press_msg() ) );
if let Some( msg ) = lp_msg
{
self.pending_msgs.push( msg );
self.surface_mut( focus ).request_redraw();
} else {
let is_text = hit_idx.and_then( |idx|
find_handlers( &self.surface( focus ).widget_rects, idx )
.map( |h| h.is_text_input() ) ).unwrap_or( false );
if is_text
{
let idx = hit_idx.unwrap();
if self.surface( focus ).focused_idx != Some( idx )
{
self.set_focus( focus, hit_idx, qh );
}
self.show_context_menu( focus, idx, pos );
} else {
self.hide_context_menu( focus );
}
}
}
pub( super ) fn on_pointer_left_press(
&mut self,
_conn: &Connection,
qh: &QueueHandle<Self>,
_pointer: &WlPointer,
event: &PointerEvent,
)
{
let focus = self.focus_for_surface( &event.surface )
.unwrap_or( SurfaceFocus::Main );
let serial = if let PointerEventKind::Press { serial, .. } = event.kind
{
serial
} else {
return;
};
self.last_pointer_serial = serial;
self.last_input_serial = serial;
self.cancel_tooltip();
let pos = self.surface( focus ).to_physical( event.position.0, event.position.1 );
self.pointer_pos = pos;
self.app.on_pointer_move( pos.x, pos.y );
if matches!( focus, SurfaceFocus::Main ) && !self.overlays.is_empty()
{
self.dismiss_main_outside_popups( pos );
}
// Built-in context menu intercepts the press
// before the regular gesture machine. Either an
// item activates (Copy / Cut / Paste) or the
// click is outside the menu and dismisses it —
// in both cases we consume the event.
if self.surface( focus ).context_menu.is_some()
{
if self.handle_context_menu_press( focus, pos )
{
return;
}
}
// Client-side title bar interaction — pointer-only
// (touch never hits a titlebar; layer-shell surfaces
// have titlebar_height == 0).
let sf = self.surface( focus ).scale_factor.max( 1 ) as f32;
let tb_h = self.surface( focus ).titlebar_height * sf;
if tb_h > 0.0 && pos.y < tb_h
{
let close_rect = self.surface( focus ).titlebar_close_rect;
if close_rect.contains( pos )
{
if self.app.on_close_requested()
{
self.exit_requested = true;
}
return;
}
}
// Resize-edge interception. Wins over the titlebar
// drag-move (top-left / top-right corners overlap
// the titlebar) and over the gesture machine.
if let Some( edge ) = self.resize_edge_under_pointer( focus )
{
if let crate::event_loop::SurfaceKind::Window( ref window ) = self.main.surface
{
let seats: Vec<_> = self.seat_state.seats().collect();
if let Some( seat ) = seats.into_iter().next()
{
window.resize( &seat, serial, edge );
}
}
return;
}
if tb_h > 0.0 && pos.y < tb_h
{
// Drag to move the window.
if matches!( focus, SurfaceFocus::Main )
{
if let crate::event_loop::SurfaceKind::Window( ref window ) = self.main.surface
{
let seats: Vec<_> = self.seat_state.seats().collect();
if let Some( seat ) = seats.into_iter().next()
{
window.move_( &seat, serial );
}
}
}
return;
}
// Past every chrome interception — surface the
// press to the app so embeddings (e.g. an
// embedded WPE WebView) see real button-down
// events that were not consumed by the window
// frame.
self.app.on_pointer_button( pos.x, pos.y, true );
let outcome =
{
let ss = self.surface_mut( focus );
let result = ss.gesture.on_press( pos, &ss.widget_rects, &ss.scroll_rects );
// Mark this gesture as mouse-driven so the
// gesture machine's 6 px stray-cancel skips
// the drag-start / long-press slots — mouse
// motion is intentional and should never
// drop the candidate before the pointer-side
// promotion at 24 px gets to fire.
ss.gesture.mouse_press = true;
ss.needs_redraw = true;
result
};
self.set_focus( focus, outcome.hit_idx, qh );
if let Some( msg ) = outcome.initial_slider_msg
{
self.pending_msgs.push( msg );
}
// Press-and-hold repeat: when the hit is a button
// that opted into `.repeating( true )`, fire the
// `on_press` message immediately and arm the
// repeat timer. The release handler in
// `gesture.rs` knows to suppress the regular tap-
// on-release fire for repeating buttons so a
// quick click still counts as exactly one press.
// The timer re-reads `on_press` from the live
// widget tree on every tick — see
// `start_button_repeat` for why.
if let Some( idx ) = outcome.hit_idx
{
let immediate = {
let handlers = find_handlers( &self.surface( focus ).widget_rects, idx );
if matches!( handlers, Some( WidgetHandlers::Button { repeating: true, .. } ) )
{
handlers.and_then( |h| h.press_msg() )
} else { None }
};
if let Some( msg ) = immediate
{
self.pending_msgs.push( msg );
self.start_button_repeat( focus, idx );
}
}
// Click-to-position the text cursor when the press
// landed on a TextEdit. `set_focus` above moves the
// cursor to the end of the value; this overrides
// it with the byte offset under the pointer and
// collapses the selection there so a subsequent
// drag widens the selection from the click point.
//
// Double-click on a TextEdit selects the word
// under the cursor instead of just positioning.
if let Some( idx ) = outcome.hit_idx
{
let is_text = find_handlers( &self.surface( focus ).widget_rects, idx )
.map( |h| h.is_text_input() ).unwrap_or( false );
if is_text
{
// Eye icon hit on a password field
// short-circuits the text-edit
// dispatch — fire the toggle msg and
// skip cursor placement.
if self.handle_password_toggle_press( focus, idx, pos )
{
let _ = self.note_press_for_double_click( pos );
}
else
{
let is_double = self.note_press_for_double_click( pos );
if is_double
{
self.handle_text_select_word( focus, idx, pos );
} else {
self.handle_text_pointer_down( focus, idx, pos );
}
}
} else {
let _ = self.note_press_for_double_click( pos );
}
} else {
let _ = self.note_press_for_double_click( pos );
}
// Slider press → drag may have started; refresh
// the cursor shape so it switches to `Grabbing`.
self.dispatch_cursor_shape( focus );
}
}