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:
41
src/input/keyboard/dispatch.rs
Normal file
41
src/input/keyboard/dispatch.rs
Normal file
@@ -0,0 +1,41 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-only
|
||||
// Copyright (C) 2026 Liberux Labs, S. L. <info@liberux.net>
|
||||
|
||||
use smithay_client_toolkit::seat::keyboard::{ KeyEvent, Keysym };
|
||||
|
||||
use crate::app::App;
|
||||
use crate::event_loop::{ AppData, SurfaceFocus };
|
||||
|
||||
impl<A: App> AppData<A>
|
||||
{
|
||||
/// Run the same dispatch logic that the Wayland press-key handler
|
||||
/// runs, but without the trait-callback signature — used both by
|
||||
/// the trait method and by the key-repeat timer.
|
||||
pub( crate ) fn dispatch_key( &mut self, focus: SurfaceFocus, event: KeyEvent )
|
||||
{
|
||||
// `set_focus` (Tab handling) needs a `QueueHandle`. Cloning is
|
||||
// cheap (an `Arc`-equivalent under the hood) and avoids
|
||||
// threading the qh through every helper.
|
||||
let qh = self.qh.clone();
|
||||
let qh = &qh;
|
||||
match event.keysym
|
||||
{
|
||||
Keysym::BackSpace => self.handle_key_backspace( focus, &event ),
|
||||
Keysym::Delete => self.handle_key_delete( focus, &event ),
|
||||
Keysym::Return | Keysym::KP_Enter => self.handle_key_return( focus, &event ),
|
||||
Keysym::Down | Keysym::Up => self.handle_key_arrow_vertical( focus, &event ),
|
||||
Keysym::Left | Keysym::Right => self.handle_key_arrow_horizontal( focus, &event ),
|
||||
Keysym::Home => self.handle_key_home( focus ),
|
||||
Keysym::End => self.handle_key_end( focus ),
|
||||
Keysym::a | Keysym::A if self.ctrl_pressed => self.handle_key_ctrl_a( focus ),
|
||||
Keysym::c | Keysym::C if self.ctrl_pressed => self.handle_key_ctrl_c( focus ),
|
||||
Keysym::x | Keysym::X if self.ctrl_pressed => self.handle_key_ctrl_x( focus ),
|
||||
Keysym::v | Keysym::V if self.ctrl_pressed => self.handle_key_ctrl_v( focus ),
|
||||
Keysym::Tab | Keysym::ISO_Left_Tab => self.handle_key_tab( focus, &event, qh ),
|
||||
Keysym::Escape => self.handle_key_escape( focus, &event, qh ),
|
||||
Keysym::space => self.handle_key_space( focus, &event ),
|
||||
_ => self.handle_key_default( focus, &event ),
|
||||
}
|
||||
self.surface_mut( focus ).request_redraw();
|
||||
}
|
||||
}
|
||||
165
src/input/keyboard/mod.rs
Normal file
165
src/input/keyboard/mod.rs
Normal file
@@ -0,0 +1,165 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-only
|
||||
// Copyright (C) 2026 Liberux Labs, S. L. <info@liberux.net>
|
||||
|
||||
//! Wayland keyboard → ltk dispatch.
|
||||
//!
|
||||
//! Translates `wl_keyboard` events into focus / text-insertion /
|
||||
//! widget-submit actions. Does not share state with the gesture state
|
||||
//! machine — keyboard tracks its own focus (`AppData::keyboard_focus`)
|
||||
//! and modifier flags (`shift_pressed`, `ctrl_pressed`).
|
||||
//!
|
||||
//! The only cross-cutting state it reads is `SurfaceState::focused_idx`
|
||||
//! (set by pointer / touch focus updates) so keyboard navigation can
|
||||
//! branch on "is a widget focused?" — Return submits / Space presses /
|
||||
//! typing inserts all funnel through that check.
|
||||
|
||||
use smithay_client_toolkit::seat::keyboard::
|
||||
{
|
||||
KeyboardHandler, KeyEvent, Keysym, Modifiers, RawModifiers, RepeatInfo,
|
||||
};
|
||||
use smithay_client_toolkit::reexports::client::
|
||||
{
|
||||
protocol::{ wl_keyboard::WlKeyboard, wl_surface::WlSurface },
|
||||
Connection, QueueHandle,
|
||||
};
|
||||
|
||||
use crate::app::App;
|
||||
use crate::event_loop::{ AppData, SurfaceFocus };
|
||||
|
||||
pub( super ) mod dispatch;
|
||||
pub( super ) mod text_keys;
|
||||
pub( super ) mod shortcuts;
|
||||
pub( super ) mod nav;
|
||||
|
||||
impl<A: App> KeyboardHandler for AppData<A>
|
||||
{
|
||||
fn enter(
|
||||
&mut self,
|
||||
_conn: &Connection,
|
||||
_qh: &QueueHandle<Self>,
|
||||
_keyboard: &WlKeyboard,
|
||||
surface: &WlSurface,
|
||||
_serial: u32,
|
||||
_raw: &[u32],
|
||||
_keysyms: &[Keysym],
|
||||
)
|
||||
{
|
||||
let focus = self.focus_for_surface( surface ).unwrap_or( SurfaceFocus::Main );
|
||||
self.keyboard_focus = focus;
|
||||
self.surface_mut( focus ).request_redraw();
|
||||
}
|
||||
|
||||
fn leave(
|
||||
&mut self,
|
||||
_conn: &Connection,
|
||||
_qh: &QueueHandle<Self>,
|
||||
_keyboard: &WlKeyboard,
|
||||
_surface: &WlSurface,
|
||||
_serial: u32,
|
||||
)
|
||||
{
|
||||
self.stop_key_repeat();
|
||||
self.keyboard_focus = SurfaceFocus::Main;
|
||||
}
|
||||
|
||||
fn press_key(
|
||||
&mut self,
|
||||
_conn: &Connection,
|
||||
_qh: &QueueHandle<Self>,
|
||||
_keyboard: &WlKeyboard,
|
||||
_serial: u32,
|
||||
event: KeyEvent,
|
||||
)
|
||||
{
|
||||
let focus = self.keyboard_focus;
|
||||
// Raw observer hook (e.g. forwarding to an embedded WPE view).
|
||||
// Fires before the focus-aware dispatch.
|
||||
self.app.on_raw_key( event.keysym, event.raw_code, true, self.ctrl_pressed, self.shift_pressed );
|
||||
// A new press always cancels any in-flight repeat — the user
|
||||
// has either released the previous key or is pressing a
|
||||
// different one. Either way, the prior timer's keysym should
|
||||
// not keep firing.
|
||||
self.stop_key_repeat();
|
||||
self.dispatch_key( focus, event.clone() );
|
||||
self.start_key_repeat( focus, event );
|
||||
}
|
||||
|
||||
fn release_key(
|
||||
&mut self,
|
||||
_conn: &Connection,
|
||||
_qh: &QueueHandle<Self>,
|
||||
_keyboard: &WlKeyboard,
|
||||
_serial: u32,
|
||||
event: KeyEvent,
|
||||
)
|
||||
{
|
||||
self.app.on_raw_key( event.keysym, event.raw_code, false, self.ctrl_pressed, self.shift_pressed );
|
||||
// Only stop if the released key matches the one currently
|
||||
// repeating; releasing a non-repeating key (e.g. a shift that
|
||||
// snuck through, or any key we never armed) leaves the timer
|
||||
// untouched.
|
||||
if let Some( ref state ) = self.key_repeat
|
||||
{
|
||||
if state.event.keysym == event.keysym
|
||||
{
|
||||
self.stop_key_repeat();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_modifiers(
|
||||
&mut self,
|
||||
_conn: &Connection,
|
||||
_qh: &QueueHandle<Self>,
|
||||
_keyboard: &WlKeyboard,
|
||||
_serial: u32,
|
||||
modifiers: Modifiers,
|
||||
_raw_modifiers: RawModifiers,
|
||||
_layout: u32,
|
||||
)
|
||||
{
|
||||
self.shift_pressed = modifiers.shift;
|
||||
self.ctrl_pressed = modifiers.ctrl;
|
||||
}
|
||||
|
||||
fn update_repeat_info(
|
||||
&mut self,
|
||||
_conn: &Connection,
|
||||
_qh: &QueueHandle<Self>,
|
||||
_keyboard: &WlKeyboard,
|
||||
info: RepeatInfo,
|
||||
)
|
||||
{
|
||||
match info
|
||||
{
|
||||
RepeatInfo::Repeat { rate, delay } =>
|
||||
{
|
||||
self.compositor_repeat_rate = rate.get();
|
||||
self.compositor_repeat_delay = delay;
|
||||
}
|
||||
RepeatInfo::Disable =>
|
||||
{
|
||||
self.compositor_repeat_rate = 0;
|
||||
self.compositor_repeat_delay = 0;
|
||||
self.stop_key_repeat();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn repeat_key(
|
||||
&mut self,
|
||||
_conn: &Connection,
|
||||
_qh: &QueueHandle<Self>,
|
||||
_keyboard: &WlKeyboard,
|
||||
_serial: u32,
|
||||
event: KeyEvent,
|
||||
)
|
||||
{
|
||||
// Compositor-driven repeat (wl_keyboard v10). When the
|
||||
// compositor takes the repeat job we just re-dispatch — and
|
||||
// suppress our internal timer to avoid double-firing.
|
||||
self.stop_key_repeat();
|
||||
let focus = self.keyboard_focus;
|
||||
self.dispatch_key( focus, event );
|
||||
}
|
||||
}
|
||||
72
src/input/keyboard/nav.rs
Normal file
72
src/input/keyboard/nav.rs
Normal file
@@ -0,0 +1,72 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-only
|
||||
// Copyright (C) 2026 Liberux Labs, S. L. <info@liberux.net>
|
||||
|
||||
use crate::app::App;
|
||||
use crate::event_loop::{ AppData, SurfaceFocus };
|
||||
|
||||
impl<A: App> AppData<A>
|
||||
{
|
||||
/// Step the topmost scroll's `hovered_idx` one item up or down with
|
||||
/// the keyboard. Returns `true` when the move was applied (a scroll
|
||||
/// with at least one navigable item was on screen and the new item
|
||||
/// is different from the current hover) so the caller can fall
|
||||
/// through to the application's own `on_key` only on a no-op.
|
||||
///
|
||||
/// The "topmost scroll" is the last entry in `scroll_rects`, which
|
||||
/// matches Stack-overlay layout order: a popup pushed after the
|
||||
/// main content sits above it. Auto-scrolls the new item into view
|
||||
/// by adjusting `scroll_offsets[scroll_idx]`.
|
||||
pub( crate ) fn move_keyboard_hover( &mut self, focus: SurfaceFocus, reverse: bool ) -> bool
|
||||
{
|
||||
// Find the topmost scroll that has a navigable item list.
|
||||
let scroll_meta = {
|
||||
let ss = self.surface( focus );
|
||||
ss.scroll_rects.iter().rev()
|
||||
.find_map( |( rect, idx )|
|
||||
{
|
||||
ss.scroll_navigable_items.get( idx )
|
||||
.filter( |list| !list.is_empty() )
|
||||
.map( |list| ( *rect, *idx, list.clone() ) )
|
||||
} )
|
||||
};
|
||||
let Some( ( scroll_rect, scroll_idx, items ) ) = scroll_meta else { return false; };
|
||||
|
||||
let current = self.surface( focus ).hovered_idx;
|
||||
let pos = current.and_then( |h| items.iter().position( |( i, _, _ )| *i == h ) );
|
||||
let next_pos = match ( reverse, pos )
|
||||
{
|
||||
( false, None ) => 0,
|
||||
( false, Some( p ) ) => ( p + 1 ).min( items.len() - 1 ),
|
||||
( true, None ) => items.len() - 1,
|
||||
( true, Some( p ) ) => p.saturating_sub( 1 ),
|
||||
};
|
||||
let ( new_idx, content_y, content_h ) = items[ next_pos ];
|
||||
|
||||
// Auto-scroll to bring the new item fully into view. Item Y is
|
||||
// in pre-offset content coordinates, so the offset that places
|
||||
// the item flush with the top of the viewport is `content_y`,
|
||||
// and flush with the bottom is `content_y + content_h - viewport_h`.
|
||||
let viewport_h = scroll_rect.height;
|
||||
let current_offset = self.surface( focus )
|
||||
.scroll_offsets.get( &scroll_idx )
|
||||
.copied().unwrap_or( 0.0 );
|
||||
let new_offset = if content_y < current_offset
|
||||
{
|
||||
content_y
|
||||
}
|
||||
else if content_y + content_h > current_offset + viewport_h
|
||||
{
|
||||
( content_y + content_h - viewport_h ).max( 0.0 )
|
||||
}
|
||||
else
|
||||
{
|
||||
current_offset
|
||||
};
|
||||
|
||||
let ss = self.surface_mut( focus );
|
||||
ss.hovered_idx = Some( new_idx );
|
||||
ss.scroll_offsets.insert( scroll_idx, new_offset );
|
||||
ss.request_redraw();
|
||||
true
|
||||
}
|
||||
}
|
||||
100
src/input/keyboard/shortcuts.rs
Normal file
100
src/input/keyboard/shortcuts.rs
Normal file
@@ -0,0 +1,100 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-only
|
||||
// Copyright (C) 2026 Liberux Labs, S. L. <info@liberux.net>
|
||||
|
||||
use smithay_client_toolkit::seat::keyboard::{ KeyEvent, Keysym };
|
||||
use smithay_client_toolkit::reexports::client::QueueHandle;
|
||||
|
||||
use crate::app::App;
|
||||
use crate::event_loop::{ AppData, SurfaceFocus };
|
||||
use crate::tree::{ find_handlers, next_focusable_index };
|
||||
|
||||
impl<A: App> AppData<A>
|
||||
{
|
||||
pub( super ) fn handle_key_ctrl_a( &mut self, focus: SurfaceFocus )
|
||||
{
|
||||
let focused = self.surface( focus ).focused_idx;
|
||||
let is_text = focused.and_then( |idx|
|
||||
find_handlers( &self.surface( focus ).widget_rects, idx )
|
||||
.map( |h| h.is_text_input() ) ).unwrap_or( false );
|
||||
if is_text { self.handle_select_all( focus ); }
|
||||
}
|
||||
|
||||
pub( super ) fn handle_key_ctrl_c( &mut self, focus: SurfaceFocus )
|
||||
{
|
||||
let focused = self.surface( focus ).focused_idx;
|
||||
let is_text = focused.and_then( |idx|
|
||||
find_handlers( &self.surface( focus ).widget_rects, idx )
|
||||
.map( |h| h.is_text_input() ) ).unwrap_or( false );
|
||||
if is_text { self.handle_copy( focus ); }
|
||||
}
|
||||
|
||||
pub( super ) fn handle_key_ctrl_x( &mut self, focus: SurfaceFocus )
|
||||
{
|
||||
let focused = self.surface( focus ).focused_idx;
|
||||
let is_text = focused.and_then( |idx|
|
||||
find_handlers( &self.surface( focus ).widget_rects, idx )
|
||||
.map( |h| h.is_text_input() ) ).unwrap_or( false );
|
||||
if is_text { self.handle_cut( focus ); }
|
||||
}
|
||||
|
||||
pub( super ) fn handle_key_ctrl_v( &mut self, focus: SurfaceFocus )
|
||||
{
|
||||
let focused = self.surface( focus ).focused_idx;
|
||||
let is_text = focused.and_then( |idx|
|
||||
find_handlers( &self.surface( focus ).widget_rects, idx )
|
||||
.map( |h| h.is_text_input() ) ).unwrap_or( false );
|
||||
if is_text { self.handle_paste( focus ); }
|
||||
}
|
||||
|
||||
pub( super ) fn handle_key_tab( &mut self, focus: SurfaceFocus, event: &KeyEvent, qh: &QueueHandle<Self> )
|
||||
{
|
||||
let reverse = event.keysym == Keysym::ISO_Left_Tab || self.shift_pressed;
|
||||
let intercepted = self.app.on_key_with_modifiers( event.keysym, self.ctrl_pressed, self.shift_pressed );
|
||||
if let Some( msg ) = intercepted
|
||||
{
|
||||
self.pending_msgs.push( msg );
|
||||
}
|
||||
else
|
||||
{
|
||||
let ss = self.surface( focus );
|
||||
let next_idx = next_focusable_index( &ss.widget_rects, ss.focused_idx, reverse );
|
||||
if let Some( next_idx ) = next_idx
|
||||
{
|
||||
self.set_focus( focus, Some( next_idx ), qh );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub( super ) fn handle_key_escape( &mut self, focus: SurfaceFocus, event: &KeyEvent, qh: &QueueHandle<Self> )
|
||||
{
|
||||
// Esc peels off transient UI one layer at a time.
|
||||
// Order: (1) open xdg-popup overlays → dismiss them;
|
||||
// (2) context menu → close it; (3) active selection in
|
||||
// a focused text input → collapse to cursor; (4) any
|
||||
// laid-out pressable carrying an `on_escape` message
|
||||
// (typically the topmost `dialog`'s cancel) → fire
|
||||
// that; (5) otherwise → drop focus and let the app see
|
||||
// Esc.
|
||||
if !self.overlays.is_empty() && self.app.overlays().iter().any( | s | s.anchor_widget_id.is_some() )
|
||||
{
|
||||
self.dismiss_all_popups();
|
||||
} else if self.surface( focus ).context_menu.is_some()
|
||||
{
|
||||
self.hide_context_menu( focus );
|
||||
} else if self.collapse_selection_if_any( focus )
|
||||
{
|
||||
// Selection collapsed — keep focus, no app msg.
|
||||
} else if let Some( msg ) = self.surface( focus ).widget_rects.iter()
|
||||
.rev()
|
||||
.find_map( |w| w.handlers.escape_msg() )
|
||||
{
|
||||
self.pending_msgs.push( msg );
|
||||
} else {
|
||||
self.set_focus( focus, None, qh );
|
||||
if let Some( msg ) = self.app.on_key_with_modifiers( event.keysym, self.ctrl_pressed, self.shift_pressed )
|
||||
{
|
||||
self.pending_msgs.push( msg );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
183
src/input/keyboard/text_keys.rs
Normal file
183
src/input/keyboard/text_keys.rs
Normal file
@@ -0,0 +1,183 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-only
|
||||
// Copyright (C) 2026 Liberux Labs, S. L. <info@liberux.net>
|
||||
|
||||
use smithay_client_toolkit::seat::keyboard::{ KeyEvent, Keysym };
|
||||
|
||||
use crate::app::App;
|
||||
use crate::event_loop::{ AppData, SurfaceFocus };
|
||||
use crate::tree::find_handlers;
|
||||
|
||||
impl<A: App> AppData<A>
|
||||
{
|
||||
pub( super ) fn handle_key_backspace( &mut self, focus: SurfaceFocus, event: &KeyEvent )
|
||||
{
|
||||
let focused = self.surface( focus ).focused_idx;
|
||||
if focused.is_some() { self.handle_backspace( focus ); }
|
||||
else if let Some( msg ) = self.app.on_key_with_modifiers( event.keysym, self.ctrl_pressed, self.shift_pressed )
|
||||
{
|
||||
self.pending_msgs.push( msg );
|
||||
}
|
||||
}
|
||||
|
||||
pub( super ) fn handle_key_delete( &mut self, focus: SurfaceFocus, event: &KeyEvent )
|
||||
{
|
||||
let focused = self.surface( focus ).focused_idx;
|
||||
// Supr / Forward-Delete: same shape as Backspace but
|
||||
// removes the char *after* the cursor. When no widget
|
||||
// is focused, the keysym bubbles to the app.
|
||||
if focused.is_some() { self.handle_delete_forward( focus ); }
|
||||
else if let Some( msg ) = self.app.on_key_with_modifiers( event.keysym, self.ctrl_pressed, self.shift_pressed )
|
||||
{
|
||||
self.pending_msgs.push( msg );
|
||||
}
|
||||
}
|
||||
|
||||
pub( super ) fn handle_key_return( &mut self, focus: SurfaceFocus, event: &KeyEvent )
|
||||
{
|
||||
let focused = self.surface( focus ).focused_idx;
|
||||
// Enter targets, in order: (a) the focused widget's submit
|
||||
// message (TextEdit), (b) its press message (Button etc),
|
||||
// (c) the press message of the keyboard-hovered list item
|
||||
// in the topmost scroll. The hovered fallback lets users
|
||||
// confirm a combo / list choice with the keyboard after
|
||||
// arrow-navigating to it without ever taking widget focus.
|
||||
//
|
||||
// Multiline text-input override: when the focused widget
|
||||
// is a `text_edit().multiline( true )`, Enter inserts a
|
||||
// literal `\n` into the buffer instead of submitting, so
|
||||
// the user can compose paragraphs.
|
||||
let is_multi = focused.and_then( |idx|
|
||||
find_handlers( &self.surface( focus ).widget_rects, idx )
|
||||
.map( |h| h.is_multiline_text_input() ) ).unwrap_or( false );
|
||||
if is_multi
|
||||
{
|
||||
self.handle_text_insert( focus, "\n" );
|
||||
} else {
|
||||
let target = focused.or( self.surface( focus ).hovered_idx );
|
||||
if let Some( idx ) = target
|
||||
{
|
||||
let msg = find_handlers( &self.surface( focus ).widget_rects, idx )
|
||||
.and_then( |h| h.submit_msg().or_else( || h.press_msg() ) );
|
||||
if let Some( m ) = msg
|
||||
{
|
||||
self.pending_msgs.push( m );
|
||||
}
|
||||
} else if let Some( msg ) = self.app.on_key_with_modifiers( event.keysym, self.ctrl_pressed, self.shift_pressed )
|
||||
{
|
||||
self.pending_msgs.push( msg );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub( super ) fn handle_key_arrow_vertical( &mut self, focus: SurfaceFocus, event: &KeyEvent )
|
||||
{
|
||||
let focused = self.surface( focus ).focused_idx;
|
||||
let is_text = focused.and_then( |idx|
|
||||
find_handlers( &self.surface( focus ).widget_rects, idx )
|
||||
.map( |h| h.is_text_input() ) ).unwrap_or( false );
|
||||
let reverse = event.keysym == Keysym::Up;
|
||||
let extend = self.shift_pressed;
|
||||
let intercepted = self.app.on_key_with_modifiers( event.keysym, self.ctrl_pressed, self.shift_pressed );
|
||||
if let Some( msg ) = intercepted
|
||||
{
|
||||
self.pending_msgs.push( msg );
|
||||
}
|
||||
else
|
||||
{
|
||||
let consumed = if is_text
|
||||
{
|
||||
if reverse { self.handle_cursor_up( focus, extend ) }
|
||||
else { self.handle_cursor_down( focus, extend ) }
|
||||
} else { false };
|
||||
if !consumed && !self.move_keyboard_hover( focus, reverse )
|
||||
{
|
||||
// already None, no extra fire
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub( super ) fn handle_key_arrow_horizontal( &mut self, focus: SurfaceFocus, event: &KeyEvent )
|
||||
{
|
||||
let focused = self.surface( focus ).focused_idx;
|
||||
let is_text = focused.and_then( |idx|
|
||||
find_handlers( &self.surface( focus ).widget_rects, idx )
|
||||
.map( |h| h.is_text_input() ) ).unwrap_or( false );
|
||||
let extend = self.shift_pressed;
|
||||
let intercepted = self.app.on_key_with_modifiers( event.keysym, self.ctrl_pressed, self.shift_pressed );
|
||||
if let Some( msg ) = intercepted
|
||||
{
|
||||
self.pending_msgs.push( msg );
|
||||
}
|
||||
else if is_text
|
||||
{
|
||||
if event.keysym == Keysym::Left
|
||||
{
|
||||
self.handle_cursor_left( focus, extend );
|
||||
} else {
|
||||
self.handle_cursor_right( focus, extend );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub( super ) fn handle_key_home( &mut self, focus: SurfaceFocus )
|
||||
{
|
||||
let focused = self.surface( focus ).focused_idx;
|
||||
let is_text = focused.and_then( |idx|
|
||||
find_handlers( &self.surface( focus ).widget_rects, idx )
|
||||
.map( |h| h.is_text_input() ) ).unwrap_or( false );
|
||||
if is_text { self.handle_cursor_home( focus, self.shift_pressed ); }
|
||||
}
|
||||
|
||||
pub( super ) fn handle_key_end( &mut self, focus: SurfaceFocus )
|
||||
{
|
||||
let focused = self.surface( focus ).focused_idx;
|
||||
let is_text = focused.and_then( |idx|
|
||||
find_handlers( &self.surface( focus ).widget_rects, idx )
|
||||
.map( |h| h.is_text_input() ) ).unwrap_or( false );
|
||||
if is_text { self.handle_cursor_end( focus, self.shift_pressed ); }
|
||||
}
|
||||
|
||||
pub( super ) fn handle_key_space( &mut self, focus: SurfaceFocus, event: &KeyEvent )
|
||||
{
|
||||
let focused = self.surface( focus ).focused_idx;
|
||||
if let Some( idx ) = focused
|
||||
{
|
||||
let press = find_handlers( &self.surface( focus ).widget_rects, idx )
|
||||
.and_then( |h| h.press_msg() );
|
||||
if let Some( msg ) = press
|
||||
{
|
||||
self.pending_msgs.push( msg );
|
||||
} else {
|
||||
// Focused widget is a TextEdit (or has no on_press) — insert space as text
|
||||
self.handle_text_insert( focus, " " );
|
||||
}
|
||||
} else if let Some( msg ) = self.app.on_key_with_modifiers( event.keysym, self.ctrl_pressed, self.shift_pressed )
|
||||
{
|
||||
self.pending_msgs.push( msg );
|
||||
}
|
||||
}
|
||||
|
||||
pub( super ) fn handle_key_default( &mut self, focus: SurfaceFocus, event: &KeyEvent )
|
||||
{
|
||||
let focused = self.surface( focus ).focused_idx;
|
||||
if self.ctrl_pressed
|
||||
{
|
||||
if let Some( msg ) = self.app.on_key_with_modifiers( event.keysym, true, self.shift_pressed )
|
||||
{
|
||||
self.pending_msgs.push( msg );
|
||||
}
|
||||
} else if focused.is_some()
|
||||
{
|
||||
if let Some( txt ) = event.utf8.clone()
|
||||
{
|
||||
if !txt.is_empty() && txt.chars().all( |c| !c.is_control() )
|
||||
{
|
||||
self.handle_text_insert( focus, &txt );
|
||||
}
|
||||
}
|
||||
} else if let Some( msg ) = self.app.on_key_with_modifiers( event.keysym, false, self.shift_pressed )
|
||||
{
|
||||
self.pending_msgs.push( msg );
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user