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:
@@ -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 );
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user