From fcb79fa05cf43cdaee43e9303fcea1c5e9c0378d Mon Sep 17 00:00:00 2001
From: Simon Gellis <simongellis@gmail.com>
Date: Thu, 13 Feb 2025 23:06:27 -0500
Subject: [PATCH] Fix keyboard navigation for numberedit

---
 src/window/utils.rs | 53 ++++++++++++++++++++++++++++++++++++++-------
 1 file changed, 45 insertions(+), 8 deletions(-)

diff --git a/src/window/utils.rs b/src/window/utils.rs
index df2f748..7465794 100644
--- a/src/window/utils.rs
+++ b/src/window/utils.rs
@@ -1,8 +1,9 @@
 use std::ops::{Bound, RangeBounds};
 
 use egui::{
-    ecolor::HexColor, Align, Color32, CursorIcon, Frame, Layout, Margin, Rect, Response, RichText,
-    Rounding, Sense, Shape, Stroke, TextEdit, Ui, UiBuilder, Vec2, Widget, WidgetText,
+    ecolor::HexColor, Align, Color32, CursorIcon, Event, Frame, Key, Layout, Margin, Rect,
+    Response, RichText, Rounding, Sense, Shape, Stroke, TextEdit, Ui, UiBuilder, Vec2, Widget,
+    WidgetText,
 };
 
 pub trait UiExt {
@@ -117,10 +118,13 @@ impl<'a> NumberEdit<'a> {
 
 impl Widget for NumberEdit<'_> {
     fn ui(self, ui: &mut Ui) -> Response {
-        let (last_value, mut str) = ui.memory(|m| {
-            m.data
+        let (last_value, mut str, focus) = ui.memory(|m| {
+            let (lv, s) = m
+                .data
                 .get_temp(ui.id())
-                .unwrap_or((*self.value, self.value.to_string()))
+                .unwrap_or((*self.value, self.value.to_string()));
+            let focus = m.has_focus(ui.id());
+            (lv, s, focus)
         });
         let mut stale = false;
         if *self.value != last_value {
@@ -128,8 +132,34 @@ impl Widget for NumberEdit<'_> {
             stale = true;
         }
         let valid = str.parse().is_ok_and(|v: usize| v == *self.value);
+        let mut up_pressed = false;
+        let mut down_pressed = false;
+        if focus {
+            ui.input_mut(|i| {
+                i.events.retain(|e| match e {
+                    Event::Key {
+                        key: Key::ArrowUp,
+                        pressed: true,
+                        ..
+                    } => {
+                        up_pressed = true;
+                        false
+                    }
+                    Event::Key {
+                        key: Key::ArrowDown,
+                        pressed: true,
+                        ..
+                    } => {
+                        down_pressed = true;
+                        false
+                    }
+                    _ => true,
+                })
+            });
+        }
         let text = TextEdit::singleline(&mut str)
             .horizontal_align(Align::Max)
+            .id(ui.id())
             .margin(Margin {
                 left: 4.0,
                 right: 20.0,
@@ -167,14 +197,14 @@ impl Widget for NumberEdit<'_> {
             min: (arrow_left, arrow_top).into(),
             max: (arrow_right, arrow_middle).into(),
         };
-        if draw_arrow(ui, top_arrow_rect, true).clicked_or_dragged() {
+        if draw_arrow(ui, top_arrow_rect, true).clicked_or_dragged() || up_pressed {
             delta = 1;
         }
         let bottom_arrow_rect = Rect {
             min: (arrow_left, arrow_middle).into(),
             max: (arrow_right, arrow_bottom).into(),
         };
-        if draw_arrow(ui, bottom_arrow_rect, false).clicked_or_dragged() {
+        if draw_arrow(ui, bottom_arrow_rect, false).clicked_or_dragged() || down_pressed {
             delta = -1;
         }
 
@@ -201,7 +231,14 @@ impl Widget for NumberEdit<'_> {
 
 fn draw_arrow(ui: &mut Ui, rect: Rect, up: bool) -> Response {
     let arrow_res = ui
-        .allocate_rect(rect, Sense::click_and_drag())
+        .allocate_rect(
+            rect,
+            Sense {
+                click: true,
+                drag: true,
+                focusable: false,
+            },
+        )
         .on_hover_cursor(CursorIcon::Default);
     let visuals = ui.style().visuals.widgets.style(&arrow_res);
     let painter = ui.painter_at(arrow_res.rect);