1- use super :: SettingsApp ;
1+ use super :: { HotkeyField , SettingsApp } ;
22use egui:: Ui ;
33
44// split a hotkey string so we can show it in the editor
@@ -36,61 +36,180 @@ fn build_hotkey(ctrl: bool, alt: bool, shift: bool, win: bool, key: &str) -> Str
3636 parts. join ( "+" )
3737}
3838
39- // renders a hotkey editor for one action
40- fn hotkey_editor ( ui : & mut Ui , label : & str , current : & str ) -> String {
41- let ( mut ctrl, mut alt, mut shift, mut win, mut key) = parse_for_edit ( current) ;
42-
43- ui. group ( |ui| {
44- ui. label ( label) ;
45- ui. horizontal ( |ui| {
46- ui. checkbox ( & mut ctrl, "Ctrl" ) ;
47- ui. checkbox ( & mut alt, "Alt" ) ;
48- ui. checkbox ( & mut shift, "Shift" ) ;
49- ui. checkbox ( & mut win, "Win" ) ;
50- } ) ;
51- ui. horizontal ( |ui| {
52- ui. label ( "Key:" ) ;
53- // Only allow a single A-Z character
54- let mut key_input = key. clone ( ) ;
55- let response = ui. add (
56- egui:: TextEdit :: singleline ( & mut key_input)
57- . desired_width ( 40.0 )
58- . char_limit ( 1 ) ,
59- ) ;
60- if response. changed ( ) {
61- let filtered: String = key_input
62- . chars ( )
63- . filter ( |c| c. is_ascii_alphabetic ( ) )
64- . map ( |c| c. to_ascii_uppercase ( ) )
65- . collect ( ) ;
66- key = filtered;
39+ // map an egui key to the token our parser understands (a-z, 0-9, f1-f12)
40+ fn egui_key_token ( key : egui:: Key ) -> Option < & ' static str > {
41+ use egui:: Key :: * ;
42+ Some ( match key {
43+ A => "a" ,
44+ B => "b" ,
45+ C => "c" ,
46+ D => "d" ,
47+ E => "e" ,
48+ F => "f" ,
49+ G => "g" ,
50+ H => "h" ,
51+ I => "i" ,
52+ J => "j" ,
53+ K => "k" ,
54+ L => "l" ,
55+ M => "m" ,
56+ N => "n" ,
57+ O => "o" ,
58+ P => "p" ,
59+ Q => "q" ,
60+ R => "r" ,
61+ S => "s" ,
62+ T => "t" ,
63+ U => "u" ,
64+ V => "v" ,
65+ W => "w" ,
66+ X => "x" ,
67+ Y => "y" ,
68+ Z => "z" ,
69+ Num0 => "0" ,
70+ Num1 => "1" ,
71+ Num2 => "2" ,
72+ Num3 => "3" ,
73+ Num4 => "4" ,
74+ Num5 => "5" ,
75+ Num6 => "6" ,
76+ Num7 => "7" ,
77+ Num8 => "8" ,
78+ Num9 => "9" ,
79+ F1 => "f1" ,
80+ F2 => "f2" ,
81+ F3 => "f3" ,
82+ F4 => "f4" ,
83+ F5 => "f5" ,
84+ F6 => "f6" ,
85+ F7 => "f7" ,
86+ F8 => "f8" ,
87+ F9 => "f9" ,
88+ F10 => "f10" ,
89+ F11 => "f11" ,
90+ F12 => "f12" ,
91+ _ => return None ,
92+ } )
93+ }
94+
95+ // look for a "modifier(s)+key" press in this frame's events
96+ fn capture_combo ( input : & egui:: InputState ) -> Option < String > {
97+ for ev in & input. events {
98+ if let egui:: Event :: Key { key, pressed : true , modifiers, .. } = ev {
99+ if let Some ( tok) = egui_key_token ( * key) {
100+ let mut parts: Vec < & str > = Vec :: new ( ) ;
101+ if modifiers. ctrl {
102+ parts. push ( "ctrl" ) ;
103+ }
104+ if modifiers. alt {
105+ parts. push ( "alt" ) ;
106+ }
107+ if modifiers. shift {
108+ parts. push ( "shift" ) ;
109+ }
110+ // require at least one modifier (a bare key is not a valid hotkey)
111+ if !parts. is_empty ( ) {
112+ parts. push ( tok) ;
113+ return Some ( parts. join ( "+" ) ) ;
114+ }
67115 }
68- let preview = build_hotkey ( ctrl , alt , shift , win , & key . to_lowercase ( ) ) ;
69- ui . label ( format ! ( "→ {}" , preview ) ) ;
70- } ) ;
71- } ) ;
116+ }
117+ }
118+ None
119+ }
72120
73- build_hotkey ( ctrl, alt, shift, win, & key. to_lowercase ( ) )
121+ fn field_label ( field : HotkeyField ) -> & ' static str {
122+ match field {
123+ HotkeyField :: Icons => "Toggle Desktop Icons" ,
124+ HotkeyField :: Taskbar => "Toggle Taskbar" ,
125+ HotkeyField :: Windows => "Toggle App Windows" ,
126+ }
74127}
75128
76129impl SettingsApp {
130+ // a hotkey editor (checkboxes + key, or a Record button) for one action
131+ fn hotkey_editor ( & mut self , ui : & mut Ui , field : HotkeyField , current : & str ) -> String {
132+ let ( mut ctrl, mut alt, mut shift, mut win, mut key) = parse_for_edit ( current) ;
133+ let recording = self . recording_hotkey == Some ( field) ;
134+
135+ // if recording this field, try to capture a combo from this frame's input
136+ let mut captured: Option < String > = None ;
137+ if recording {
138+ ui. ctx ( ) . request_repaint ( ) ;
139+ if let Some ( combo) = ui. input ( capture_combo) {
140+ captured = Some ( combo) ;
141+ self . recording_hotkey = None ;
142+ }
143+ }
144+
145+ let mut toggle_record = false ;
146+ ui. group ( |ui| {
147+ ui. horizontal ( |ui| {
148+ ui. label ( field_label ( field) ) ;
149+ let btn = if recording {
150+ "Press a combo\u{2026} (Esc to cancel)"
151+ } else {
152+ "\u{2328} Record"
153+ } ;
154+ if ui. button ( btn) . clicked ( ) {
155+ toggle_record = true ;
156+ }
157+ } ) ;
158+ ui. horizontal ( |ui| {
159+ ui. checkbox ( & mut ctrl, "Ctrl" ) ;
160+ ui. checkbox ( & mut alt, "Alt" ) ;
161+ ui. checkbox ( & mut shift, "Shift" ) ;
162+ ui. checkbox ( & mut win, "Win" ) ;
163+ } ) ;
164+ ui. horizontal ( |ui| {
165+ ui. label ( "Key:" ) ;
166+ let mut key_input = key. clone ( ) ;
167+ let response = ui. add (
168+ egui:: TextEdit :: singleline ( & mut key_input)
169+ . desired_width ( 40.0 )
170+ . char_limit ( 1 ) ,
171+ ) ;
172+ if response. changed ( ) {
173+ key = key_input
174+ . chars ( )
175+ . filter ( |c| c. is_ascii_alphabetic ( ) )
176+ . map ( |c| c. to_ascii_uppercase ( ) )
177+ . collect ( ) ;
178+ }
179+ let preview = build_hotkey ( ctrl, alt, shift, win, & key. to_lowercase ( ) ) ;
180+ ui. label ( format ! ( "\u{2192} {}" , preview) ) ;
181+ } ) ;
182+ } ) ;
183+
184+ if toggle_record {
185+ self . recording_hotkey = if recording { None } else { Some ( field) } ;
186+ }
187+
188+ if let Some ( combo) = captured {
189+ return combo;
190+ }
191+ build_hotkey ( ctrl, alt, shift, win, & key. to_lowercase ( ) )
192+ }
193+
77194 pub fn hotkeys_tab ( & mut self , ui : & mut Ui ) {
78195 ui. heading ( "Global Hotkeys" ) ;
196+ ui. label ( "Click Record and press your combo, or use the checkboxes + key." ) ;
197+ ui. weak ( "Defaults \u{2014} Icons: Ctrl+Alt+H, Taskbar: Ctrl+Alt+T, Windows: Ctrl+Alt+W" ) ;
79198 ui. add_space ( 8.0 ) ;
80199
81- let new_icons = hotkey_editor (
82- ui,
83- "Toggle Desktop Icons" ,
84- & self . config . hotkeys . icons . clone ( ) ,
85- ) ;
200+ // Esc cancels an in-progress recording
201+ if self . recording_hotkey . is_some ( ) && ui. input ( |i| i. key_pressed ( egui:: Key :: Escape ) ) {
202+ self . recording_hotkey = None ;
203+ }
204+
205+ let icons = self . config . hotkeys . icons . clone ( ) ;
206+ let new_icons = self . hotkey_editor ( ui, HotkeyField :: Icons , & icons) ;
86207 ui. add_space ( 4.0 ) ;
87- let new_taskbar = hotkey_editor ( ui, "Toggle Taskbar" , & self . config . hotkeys . taskbar . clone ( ) ) ;
208+ let taskbar = self . config . hotkeys . taskbar . clone ( ) ;
209+ let new_taskbar = self . hotkey_editor ( ui, HotkeyField :: Taskbar , & taskbar) ;
88210 ui. add_space ( 4.0 ) ;
89- let new_windows = hotkey_editor (
90- ui,
91- "Toggle App Windows" ,
92- & self . config . hotkeys . windows . clone ( ) ,
93- ) ;
211+ let windows = self . config . hotkeys . windows . clone ( ) ;
212+ let new_windows = self . hotkey_editor ( ui, HotkeyField :: Windows , & windows) ;
94213
95214 if new_icons != self . config . hotkeys . icons
96215 || new_taskbar != self . config . hotkeys . taskbar
@@ -107,7 +226,7 @@ impl SettingsApp {
107226 if h. icons == h. taskbar || h. icons == h. windows || h. taskbar == h. windows {
108227 ui. colored_label (
109228 egui:: Color32 :: YELLOW ,
110- "Warning: duplicate hotkeys — all three must be unique." ,
229+ "Warning: duplicate hotkeys \u{2014} all three must be unique." ,
111230 ) ;
112231 }
113232 }
0 commit comments