// SPDX-License-Identifier: LGPL-2.1-only // Copyright (C) 2026 Liberux Labs, S. L. use smithay_client_toolkit::seat::keyboard::{ KeyEvent, Keysym }; use crate::app::App; use crate::event_loop::{ AppData, SurfaceFocus }; use crate::tree::find_handlers; impl AppData { 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 ); } } }