@@ -19,6 +19,7 @@ const BASE_ANGULAR_SPEED: f64 = 0.8; // rad/s
1919const FAST_MULTIPLIER : f64 = 2.0 ; // Shift modifier
2020
2121/// Overlay styling
22+ const OVERLAY_MARGIN : f32 = 12.0 ;
2223const OVERLAY_PADDING : f32 = 10.0 ;
2324const OVERLAY_ROUNDING : f32 = 8.0 ;
2425const OVERLAY_BG : egui:: Color32 = egui:: Color32 :: from_rgba_premultiplied ( 20 , 20 , 30 , 220 ) ;
@@ -65,13 +66,11 @@ impl KeyState {
6566}
6667
6768/// Handles keyboard input and publishes Twist via LCM.
68- /// Must be activated by clicking the overlay before keys are captured.
6969pub struct KeyboardHandler {
7070 publisher : LcmPublisher ,
7171 state : KeyState ,
7272 was_active : bool ,
7373 estop_flash : bool , // true briefly after space pressed
74- engaged : bool , // true when user has clicked the overlay to activate
7574}
7675
7776impl KeyboardHandler {
@@ -83,30 +82,29 @@ impl KeyboardHandler {
8382 state : KeyState :: new ( ) ,
8483 was_active : false ,
8584 estop_flash : false ,
86- engaged : false ,
8785 } )
8886 }
8987
9088 /// Process keyboard input from egui and publish Twist if keys are held.
9189 /// Called once per frame from DimosApp.ui().
92- /// Only captures keys when the overlay has been clicked (engaged).
9390 ///
9491 /// Returns true if any movement key is active (for UI overlay).
9592 pub fn process ( & mut self , ctx : & egui:: Context ) -> bool {
9693 self . estop_flash = false ;
9794
98- // If not engaged, don't capture any keys
99- if !self . engaged {
95+ // Check if any text widget has focus - if so, skip keyboard capture
96+ let text_has_focus = ctx. memory ( |m| m. focused ( ) . is_some ( ) ) ;
97+ if text_has_focus {
10098 if self . was_active {
10199 if let Err ( e) = self . publish_stop ( ) {
102- re_log:: warn!( "Failed to send stop on disengage : {e:?}" ) ;
100+ re_log:: warn!( "Failed to send stop command on focus change : {e:?}" ) ;
103101 }
104102 self . was_active = false ;
105103 }
106104 return false ;
107105 }
108106
109- // Update key state from egui input (engaged flag is the only gate)
107+ // Update key state from egui input
110108 self . update_key_state ( ctx) ;
111109
112110 // Check for emergency stop (Space key pressed - one-shot action)
@@ -136,85 +134,33 @@ impl KeyboardHandler {
136134 self . state . any_active ( )
137135 }
138136
139- /// Draw keyboard overlay HUD at bottom-right of the 3D viewport area.
140- /// Clickable: clicking the overlay toggles engaged state.
141- pub fn draw_overlay ( & mut self , ctx : & egui:: Context ) {
142- let screen_rect = ctx. content_rect ( ) ;
143- // Default position: bottom-left, just above the timeline bar
144- let overlay_height = 160.0 ;
145- let left_margin = 12.0 ;
146- let bottom_timeline_offset = 120.0 ;
147- let default_pos = egui:: pos2 (
148- screen_rect. min . x + left_margin,
149- screen_rect. max . y - overlay_height - bottom_timeline_offset,
150- ) ;
151-
152- let area_response = egui:: Area :: new ( "keyboard_hud" . into ( ) )
153- . pivot ( egui:: Align2 :: LEFT_BOTTOM )
154- . default_pos ( default_pos)
155- . movable ( true )
137+ /// Draw keyboard overlay HUD. Always shown (dim when idle, bright when active).
138+ pub fn draw_overlay ( & self , ctx : & egui:: Context ) {
139+ egui:: Area :: new ( "keyboard_hud" . into ( ) )
140+ . fixed_pos ( egui:: pos2 ( OVERLAY_MARGIN , OVERLAY_MARGIN ) )
156141 . order ( egui:: Order :: Foreground )
157- . interactable ( true )
142+ . interactable ( false )
158143 . show ( ctx, |ui| {
159- let border_color = if self . engaged {
160- egui:: Color32 :: from_rgb ( 60 , 180 , 75 ) // green border when active
161- } else {
162- egui:: Color32 :: from_rgb ( 80 , 80 , 100 ) // dim border when inactive
163- } ;
164-
165- let response = egui:: Frame :: new ( )
144+ egui:: Frame :: new ( )
166145 . fill ( OVERLAY_BG )
167146 . corner_radius ( egui:: CornerRadius :: same ( OVERLAY_ROUNDING as u8 ) )
168147 . inner_margin ( egui:: Margin :: same ( OVERLAY_PADDING as i8 ) )
169- . stroke ( egui:: Stroke :: new ( 2.0 , border_color) )
170148 . show ( ui, |ui| {
171149 self . draw_hud_content ( ui) ;
172150 } ) ;
173-
174- // Make the frame rect clickable (Frame doesn't have click sense by default)
175- let click_response = ui. interact (
176- response. response . rect ,
177- ui. id ( ) . with ( "wasd_click" ) ,
178- egui:: Sense :: click ( ) ,
179- ) ;
180-
181- // Force arrow cursor over the entire overlay (overrides label I-beam)
182- if click_response. hovered ( ) {
183- ctx. set_cursor_icon ( egui:: CursorIcon :: Default ) ;
184- }
185-
186- // Toggle engaged state on click
187- if click_response. clicked ( ) {
188- self . engaged = !self . engaged ;
189- if !self . engaged {
190- // Send stop when disengaging
191- if let Err ( e) = self . publish_stop ( ) {
192- re_log:: warn!( "Failed to send stop on disengage: {e:?}" ) ;
193- }
194- self . state . reset ( ) ;
195- self . was_active = false ;
196- }
197- }
198- } )
199- . response ;
200-
201- // Disengage when clicking anywhere outside the overlay
202- if self . engaged
203- && !ctx. rect_contains_pointer ( area_response. layer_id , area_response. interact_rect )
204- && ctx. input ( |i| i. pointer . primary_clicked ( ) )
205- {
206- self . engaged = false ;
207- if let Err ( e) = self . publish_stop ( ) {
208- re_log:: warn!( "Failed to send stop on outside click: {e:?}" ) ;
209- }
210- self . state . reset ( ) ;
211- self . was_active = false ;
212- }
151+ } ) ;
213152 }
214153
215154 fn draw_hud_content ( & self , ui : & mut egui:: Ui ) {
155+ let active = self . state . any_active ( ) || self . estop_flash ;
156+
216157 // Title
217- ui. label ( egui:: RichText :: new ( "Keyboard Teleop" ) . color ( LABEL_COLOR ) . size ( 13.0 ) ) ;
158+ let title_color = if active {
159+ egui:: Color32 :: WHITE
160+ } else {
161+ egui:: Color32 :: from_rgb ( 120 , 120 , 140 )
162+ } ;
163+ ui. label ( egui:: RichText :: new ( "🎮 Keyboard Teleop" ) . color ( title_color) . size ( 13.0 ) ) ;
218164 ui. add_space ( 4.0 ) ;
219165
220166 // Key grid: [Q] [W] [E]
@@ -406,7 +352,6 @@ mod tests {
406352 state,
407353 was_active : false ,
408354 estop_flash : false ,
409- engaged : true ,
410355 } ;
411356 let ( lin_x, lin_y, _, _, _, ang_z) = handler. compute_twist ( ) ;
412357 assert_eq ! ( lin_x, BASE_LINEAR_SPEED ) ;
@@ -423,7 +368,6 @@ mod tests {
423368 state,
424369 was_active : false ,
425370 estop_flash : false ,
426- engaged : true ,
427371 } ;
428372 let ( lin_x, lin_y, _, _, _, ang_z) = handler. compute_twist ( ) ;
429373 assert_eq ! ( lin_x, 0.0 ) ;
@@ -437,7 +381,6 @@ mod tests {
437381 state,
438382 was_active : false ,
439383 estop_flash : false ,
440- engaged : true ,
441384 } ;
442385 let ( lin_x, lin_y, _, _, _, ang_z) = handler. compute_twist ( ) ;
443386 assert_eq ! ( lin_x, 0.0 ) ;
@@ -454,7 +397,6 @@ mod tests {
454397 state,
455398 was_active : false ,
456399 estop_flash : false ,
457- engaged : true ,
458400 } ;
459401 let ( lin_x, lin_y, _, _, _, ang_z) = handler. compute_twist ( ) ;
460402 assert_eq ! ( lin_x, 0.0 ) ;
@@ -468,7 +410,6 @@ mod tests {
468410 state,
469411 was_active : false ,
470412 estop_flash : false ,
471- engaged : true ,
472413 } ;
473414 let ( lin_x, lin_y, _, _, _, ang_z) = handler. compute_twist ( ) ;
474415 assert_eq ! ( lin_x, 0.0 ) ;
@@ -486,7 +427,6 @@ mod tests {
486427 state,
487428 was_active : false ,
488429 estop_flash : false ,
489- engaged : true ,
490430 } ;
491431 let ( lin_x, lin_y, _, _, _, ang_z) = handler. compute_twist ( ) ;
492432 assert_eq ! ( lin_x, BASE_LINEAR_SPEED * FAST_MULTIPLIER ) ;
@@ -504,7 +444,6 @@ mod tests {
504444 state,
505445 was_active : false ,
506446 estop_flash : false ,
507- engaged : true ,
508447 } ;
509448 let ( lin_x, lin_y, _, _, _, ang_z) = handler. compute_twist ( ) ;
510449 assert_eq ! ( lin_x, BASE_LINEAR_SPEED ) ;
@@ -532,7 +471,6 @@ mod tests {
532471 assert ! ( handler. is_ok( ) ) ;
533472 let handler = handler. unwrap ( ) ;
534473 assert ! ( !handler. was_active) ;
535- assert ! ( !handler. engaged) ;
536474 assert ! ( !handler. state. any_active( ) ) ;
537475 }
538476
@@ -546,7 +484,6 @@ mod tests {
546484 state,
547485 was_active : false ,
548486 estop_flash : false ,
549- engaged : true ,
550487 } ;
551488 let ( lin_x, lin_y, _, _, _, ang_z) = handler. compute_twist ( ) ;
552489 assert_eq ! ( lin_x, 0.0 ) ;
@@ -561,7 +498,6 @@ mod tests {
561498 state : KeyState :: new ( ) ,
562499 was_active : false ,
563500 estop_flash : false ,
564- engaged : true ,
565501 } ;
566502 let ( lin_x, lin_y, lin_z, ang_x, ang_y, ang_z) = handler. compute_twist ( ) ;
567503 assert_eq ! ( lin_x, 0.0 ) ;
0 commit comments