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:
157
src/event_loop/focus.rs
Normal file
157
src/event_loop/focus.rs
Normal file
@@ -0,0 +1,157 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-only
|
||||
// Copyright (C) 2026 Liberux Labs, S. L. <info@liberux.net>
|
||||
|
||||
use smithay_client_toolkit::reexports::client::QueueHandle;
|
||||
|
||||
use super::app_data::AppData;
|
||||
use super::surface::SurfaceFocus;
|
||||
use crate::app::{ App, OverlayId };
|
||||
use crate::tree::find_handlers;
|
||||
use crate::types::Point;
|
||||
use crate::widget::WidgetHandlers;
|
||||
|
||||
impl<A: App> AppData<A>
|
||||
{
|
||||
/// Push `on_dismiss` for every active xdg-popup overlay whose
|
||||
/// anchor widget was not hit by a press at `pos` (logical pixels
|
||||
/// in main-surface space). Used to compensate for compositors
|
||||
/// (notably Mutter) that route pointer button events to the main
|
||||
/// surface while a popup grab is technically active, instead of
|
||||
/// breaking the grab. The check skips presses on the trigger pill
|
||||
/// itself so the trigger's own `toggle` message keeps owning that
|
||||
/// transition.
|
||||
pub( crate ) fn dismiss_main_outside_popups( &mut self, pos: Point )
|
||||
{
|
||||
if self.overlays.is_empty() { return; }
|
||||
let specs = self.app.overlays();
|
||||
for spec in specs
|
||||
{
|
||||
// Only xdg-popups need the main-side fallback: they grab
|
||||
// the seat and rely on the compositor to break the grab on
|
||||
// outside input, which Mutter does not do reliably. Layer-
|
||||
// shell overlays own their own `input_region` and dispatch
|
||||
// dismissal from the overlay tap path, so firing it again
|
||||
// from a press on the main surface produces a duplicate
|
||||
// dismiss that races other intents (e.g. forge's topbar
|
||||
// taps that route a separate IPC into the same overlay).
|
||||
let Some( anchor_id ) = spec.anchor_widget_id else { continue; };
|
||||
let anchor_rect = self.main.widget_rects.iter()
|
||||
.find( | w | w.id == Some( anchor_id ) )
|
||||
.map( | w | w.rect );
|
||||
let on_anchor = anchor_rect
|
||||
.map( | r | pos.x >= r.x && pos.x < r.x + r.width
|
||||
&& pos.y >= r.y && pos.y < r.y + r.height )
|
||||
.unwrap_or( false );
|
||||
if !on_anchor
|
||||
{
|
||||
if let Some( msg ) = spec.on_dismiss
|
||||
{
|
||||
self.pending_msgs.push( msg );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Push `on_dismiss` for every active xdg-popup overlay
|
||||
/// unconditionally (used by keyboard Escape handling — there is
|
||||
/// no spatial test to do).
|
||||
pub( crate ) fn dismiss_all_popups( &mut self )
|
||||
{
|
||||
if self.overlays.is_empty() { return; }
|
||||
let specs = self.app.overlays();
|
||||
for spec in specs
|
||||
{
|
||||
if spec.anchor_widget_id.is_some()
|
||||
{
|
||||
if let Some( msg ) = spec.on_dismiss
|
||||
{
|
||||
self.pending_msgs.push( msg );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Look up the dismiss message for an overlay (tap on empty area).
|
||||
/// Returns `None` if the overlay has no `on_dismiss` set or was removed.
|
||||
pub( crate ) fn overlay_dismiss_msg( &self, id: OverlayId ) -> Option<A::Message>
|
||||
{
|
||||
self.app.overlays().into_iter()
|
||||
.find( |s| s.id == id )
|
||||
.and_then( |s| s.on_dismiss )
|
||||
}
|
||||
|
||||
pub( crate ) fn set_focus( &mut self, focus: SurfaceFocus, idx: Option<usize>, qh: &QueueHandle<Self> )
|
||||
{
|
||||
let was_text_input;
|
||||
let is_text_input;
|
||||
{
|
||||
let ss = self.surface_mut( focus );
|
||||
|
||||
is_text_input = idx
|
||||
.and_then( |i| find_handlers( &ss.widget_rects, i ) )
|
||||
.map( |h| h.is_text_input() )
|
||||
.unwrap_or( false );
|
||||
was_text_input = ss.focused_idx
|
||||
.and_then( |i| find_handlers( &ss.widget_rects, i ) )
|
||||
.map( |h| h.is_text_input() )
|
||||
.unwrap_or( false );
|
||||
|
||||
// Clear pending text value when losing focus
|
||||
if let Some( prev_idx ) = ss.focused_idx
|
||||
{
|
||||
if idx != Some( prev_idx )
|
||||
{
|
||||
ss.pending_text_values.remove( &prev_idx );
|
||||
}
|
||||
}
|
||||
|
||||
ss.focused_idx = idx;
|
||||
ss.focused_id = idx.and_then( |i|
|
||||
{
|
||||
ss.widget_rects.iter()
|
||||
.find( |w| w.flat_idx == i )
|
||||
.and_then( |w| w.id )
|
||||
} );
|
||||
ss.request_redraw();
|
||||
|
||||
// Sync cursor to end of current value when focusing a text
|
||||
// input. Default behaviour collapses the selection to the
|
||||
// cursor — focus changes always discard any prior
|
||||
// selection state. Fields built with `.select_on_focus(
|
||||
// true )` instead anchor the selection at `0` so the
|
||||
// whole value is highlighted, ready to be replaced by the
|
||||
// next keystroke (typical for numeric pickers).
|
||||
if is_text_input
|
||||
{
|
||||
if let Some( i ) = idx
|
||||
{
|
||||
let handler = find_handlers( &ss.widget_rects, i );
|
||||
let cursor = handler
|
||||
.and_then( |h| h.current_value() )
|
||||
.map( |v| v.len() )
|
||||
.unwrap_or( 0 );
|
||||
let anchor = match handler
|
||||
{
|
||||
Some( WidgetHandlers::TextEdit { select_on_focus: true, .. } ) => 0,
|
||||
_ => cursor,
|
||||
};
|
||||
ss.cursor_state.insert( i, cursor );
|
||||
ss.selection_anchor.insert( i, anchor );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if was_text_input && !is_text_input
|
||||
{
|
||||
self.deactivate_text_input();
|
||||
self.app.on_text_input_focused( false );
|
||||
self.dirty_caches();
|
||||
}
|
||||
if is_text_input && !was_text_input
|
||||
{
|
||||
self.activate_text_input( qh );
|
||||
self.app.on_text_input_focused( true );
|
||||
self.dirty_caches();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user