git.lucas.co / cce-ui
GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git

commit5253e1896f027cfb6b3a3b6396a5618ff6d86747
parent76ef7f71c7
authorLucas Galante <[email protected]>
date2026-07-27 15:52
feat: MenuBar dropdowns paint in the Dropdown widget's popover idiom

Menu and context dropdowns now share the normal dropdown look — layered
soft shadows, surface border/bg, primary-accent hover with white text,
blue selected/checked items — instead of the flat panel with the bar's
label color. Hit-testing (DROPDOWN_ITEM_H) is untouched.

Co-Authored-By: Claude Fable 5 <[email protected]>

 src/widget/container/menu.rs | 58 ++++++++++++++++++++++++++------------------
 1 file changed, 35 insertions(+), 23 deletions(-)

diff --git a/src/widget/container/menu.rs b/src/widget/container/menu.rs
index 14e1412..15c4390 100644
--- a/src/widget/container/menu.rs
+++ b/src/widget/container/menu.rs
@@ -633,38 +633,49 @@ impl Paint for MenuBar {
         let color_f32 = [srgb[0], srgb[1], srgb[2], 1.0];
         let font = Paint::widget_font(self);
 
+        // Both dropdown flavors paint in the Dropdown widget's popover idiom
+        // (layered soft shadows, surface border/bg, accent hover, blue
+        // selected) so menubar menus read as the DE's normal dropdowns.
+        let draw_panel = |pc: &mut dyn crate::layout::RenderTarget, dx: f32, dy: f32, dw: f32, dh: f32, hovered: Option<usize>| {
+            let theme = colors::active_theme();
+            pc.rect([0.02, 0.02, 0.05, 0.15], dx + 1.0, dy + 1.0, dw, dh);
+            pc.rect([0.02, 0.02, 0.05, 0.08], dx + 3.0, dy + 3.0, dw, dh);
+            pc.rect([0.02, 0.02, 0.05, 0.04], dx + 5.0, dy + 5.0, dw, dh);
+            pc.rect(theme.surface_border, dx, dy, dw, dh);
+            pc.rect(theme.surface_bg, dx + 1.0, dy + 1.0, dw - 2.0, dh - 2.0);
+            if let Some(di) = hovered {
+                let iy = dy + di as f32 * DROPDOWN_ITEM_H;
+                pc.rect(theme.primary_accent, dx + 2.0, iy + 2.0, dw - 4.0, DROPDOWN_ITEM_H - 4.0);
+            }
+        };
+        let item_color = |hovered: bool, selected: bool| -> [f32; 4] {
+            let c: [u8; 3] = if hovered {
+                [0xff, 0xff, 0xff]
+            } else if selected {
+                [0x3a, 0x9a, 0xff]
+            } else {
+                [0xcc, 0xcc, 0xd4]
+            };
+            [c[0] as f32 / 255.0, c[1] as f32 / 255.0, c[2] as f32 / 255.0, 1.0]
+        };
+        let _ = color_f32;
+
         if self.context_dropdown_open {
             if let Some((dx, dy, dw, dh)) = self.context_popover_rect(rect) {
-                let theme = colors::active_theme();
-                pc.rect(theme.surface_border, dx, dy, dw, dh);
-                pc.rect(theme.surface_bg, dx + 1.0, dy + 1.0, dw - 2.0, dh - 2.0);
-                if let Some(di) = self.context_hovered_item {
-                    let iy = dy + di as f32 * DROPDOWN_ITEM_H;
-                    pc.rect(colors::PANEL_MENU_HOVER, dx + 2.0, iy + 2.0, dw - 4.0, DROPDOWN_ITEM_H - 4.0);
-                }
-
+                draw_panel(pc, dx, dy, dw, dh, self.context_hovered_item);
                 let bounds = Some([dx, dy, dx + dw, dy + dh]);
                 for (i, option) in self.context_options.iter().enumerate() {
-                    let is_selected = self.context_selected == i;
-                    let prefix = if is_selected { "✓ " } else { "  " };
-                    let text = format!("{}{}", prefix, option);
+                    let color = item_color(self.context_hovered_item == Some(i), self.context_selected == i);
                     let iy = crate::layout::align_text_y(dy + i as f32 * DROPDOWN_ITEM_H, DROPDOWN_ITEM_H, 12.0, 0.0);
                     if let Some(ref f) = font {
-                        pc.text_with_font_and_bounds(&text, dx + 8.0, iy, 12.0, color_f32, f, bounds);
+                        pc.text_with_font_and_bounds(option, dx + 8.0, iy, 12.0, color, f, bounds);
                     } else {
-                        pc.text_with_bounds(&text, dx + 8.0, iy, 12.0, color_f32, bounds);
+                        pc.text_with_bounds(option, dx + 8.0, iy, 12.0, color, bounds);
                     }
                 }
             }
         } else if let Some((dx, dy, dw, dh)) = self.menu_dropdown_rect() {
-            let theme = colors::active_theme();
-            pc.rect(theme.surface_border, dx, dy, dw, dh);
-            pc.rect(theme.surface_bg, dx + 1.0, dy + 1.0, dw - 2.0, dh - 2.0);
-            if let Some(di) = self.hovered_dropdown_item {
-                let iy = dy + di as f32 * DROPDOWN_ITEM_H;
-                pc.rect(colors::PANEL_MENU_HOVER, dx + 2.0, iy + 2.0, dw - 4.0, DROPDOWN_ITEM_H - 4.0);
-            }
-
+            draw_panel(pc, dx, dy, dw, dh, self.hovered_dropdown_item);
             let bounds = Some([dx, dy, dx + dw, dy + dh]);
             if let Some(menu_idx) = self.menus.selected {
                 if let Some(items) = self.menu_dropdowns.get(menu_idx) {
@@ -678,11 +689,12 @@ impl Paint for MenuBar {
                             None => "",
                         };
                         let text = format!("{}{}", prefix, option);
+                        let color = item_color(self.hovered_dropdown_item == Some(i), checked == Some(true));
                         let iy = crate::layout::align_text_y(dy + i as f32 * DROPDOWN_ITEM_H, DROPDOWN_ITEM_H, 12.0, 0.0);
                         if let Some(ref f) = font {
-                            pc.text_with_font_and_bounds(&text, dx + 8.0, iy, 12.0, color_f32, f, bounds);
+                            pc.text_with_font_and_bounds(&text, dx + 8.0, iy, 12.0, color, f, bounds);
                         } else {
-                            pc.text_with_bounds(&text, dx + 8.0, iy, 12.0, color_f32, bounds);
+                            pc.text_with_bounds(&text, dx + 8.0, iy, 12.0, color, bounds);
                         }
                     }
                 }