ltk/input/keyboard/
shortcuts.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
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 );
			}
		}
	}
}