From 908af3fed72131d21d7c7ffbc503c9b1e8a65144 Mon Sep 17 00:00:00 2001
From: Héctor Ramón Jiménez <hector@hecrj.dev>
Date: Wed, 23 Oct 2024 20:04:13 +0200
Subject: Implement `reactive-rendering` for `menu`

---
 widget/src/overlay/menu.rs | 43 +++++++++++++++++++++++++++++++++++++------
 1 file changed, 37 insertions(+), 6 deletions(-)

diff --git a/widget/src/overlay/menu.rs b/widget/src/overlay/menu.rs
index b641e8f5..e79bd3da 100644
--- a/widget/src/overlay/menu.rs
+++ b/widget/src/overlay/menu.rs
@@ -8,7 +8,8 @@ use crate::core::overlay;
 use crate::core::renderer;
 use crate::core::text::{self, Text};
 use crate::core::touch;
-use crate::core::widget::Tree;
+use crate::core::widget::tree::{self, Tree};
+use crate::core::window;
 use crate::core::{
     Background, Clipboard, Color, Length, Padding, Pixels, Point, Rectangle,
     Size, Theme, Vector,
@@ -334,6 +335,10 @@ where
     class: &'a <Theme as Catalog>::Class<'b>,
 }
 
+struct ListState {
+    is_hovered: Option<bool>,
+}
+
 impl<'a, 'b, T, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
     for List<'a, 'b, T, Message, Theme, Renderer>
 where
@@ -341,6 +346,14 @@ where
     Theme: Catalog,
     Renderer: text::Renderer,
 {
+    fn tag(&self) -> tree::Tag {
+        tree::Tag::of::<Option<bool>>()
+    }
+
+    fn state(&self) -> tree::State {
+        tree::State::new(ListState { is_hovered: None })
+    }
+
     fn size(&self) -> Size<Length> {
         Size {
             width: Length::Fill,
@@ -376,7 +389,7 @@ where
 
     fn on_event(
         &mut self,
-        _state: &mut Tree,
+        tree: &mut Tree,
         event: Event,
         layout: Layout<'_>,
         cursor: mouse::Cursor,
@@ -411,14 +424,20 @@ where
                     let new_hovered_option =
                         (cursor_position.y / option_height) as usize;
 
-                    if let Some(on_option_hovered) = self.on_option_hovered {
-                        if *self.hovered_option != Some(new_hovered_option) {
-                            if let Some(option) =
-                                self.options.get(new_hovered_option)
+                    if *self.hovered_option != Some(new_hovered_option) {
+                        if let Some(option) =
+                            self.options.get(new_hovered_option)
+                        {
+                            if let Some(on_option_hovered) =
+                                self.on_option_hovered
                             {
                                 shell
                                     .publish(on_option_hovered(option.clone()));
                             }
+
+                            shell.request_redraw(
+                                window::RedrawRequest::NextFrame,
+                            );
                         }
                     }
 
@@ -451,6 +470,18 @@ where
             _ => {}
         }
 
+        let state = tree.state.downcast_mut::<ListState>();
+
+        if state.is_hovered.is_some_and(|is_hovered| {
+            is_hovered != cursor.is_over(layout.bounds())
+        }) {
+            shell.request_redraw(window::RedrawRequest::NextFrame);
+        }
+
+        if let Event::Window(window::Event::RedrawRequested(_now)) = event {
+            state.is_hovered = Some(cursor.is_over(layout.bounds()));
+        }
+
         event::Status::Ignored
     }
 
-- 
cgit