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

commit4a0f1f91df6c7b784cbedf7f8fac1b0143e056cb
parent86018f10d9
authorLucas Galante <[email protected]>
date2026-08-21 11:29
MenuBar: menu buttons get the dropdown-trigger trough on horizontal bars

Mail/Message (and any bar's File/Edit-style menus) drew as bare text
with the embedded ButtonStrip's full-height square state quads. They
are dropdown triggers, so they now share the context title's chrome:
the trough_chrome closure carves each button its own band-inset trough
(side-inset 3px so adjacent troughs keep separate groove rings) and
rounds the state fill inside it — open matches the context dropdown's
accent tint rather than the strip's legacy palette, pressed keeps
BUTTON_PRESS, hover HIGHLIGHT_SECONDARY. The strip's own quads are no
longer forwarded on horizontal bars; the vertical and curved modes keep
them unchanged.

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

 src/widget/container/menu.rs | 73 ++++++++++++++++++++++++++++++++------------
 1 file changed, 53 insertions(+), 20 deletions(-)

diff --git a/src/widget/container/menu.rs b/src/widget/container/menu.rs
index 35b7fd7..0818949 100644
--- a/src/widget/container/menu.rs
+++ b/src/widget/container/menu.rs
@@ -493,38 +493,71 @@ impl Paint for MenuBar {
             ctx.quad(rect, self.bg_color());
         }
 
-        // Context-dropdown trigger: the DE-wide closed-dropdown chrome — a flush
-        // inset trough with a transparent face (Dropdown::paint_background's
-        // raised path), so a bar's folder/pane selector reads as a normal
-        // dropdown instead of bare text. The trough is carved on a band-inset
-        // rect; the open / hovered fills round to sit inside it. The vertical
-        // and curved title modes keep the plain highlight quads — their title
-        // geometry is exotic and gets no trough.
+        // Dropdown-trigger chrome shared by the context title and the menu
+        // buttons on horizontal bars: the DE-wide closed-dropdown look — a
+        // flush inset trough with a transparent face (Dropdown::
+        // paint_background's raised path) carved on a band-inset rect, with
+        // the state fill rounded to sit inside it. The vertical and curved
+        // modes keep their plain quads — their geometry is exotic and gets no
+        // trough.
+        let flat_modes = self.vertical || self.curved_circle.is_some();
+        let trough_chrome = |ctx: &mut PaintCtx, r: (f32, f32, f32, f32), fill: Option<[f32; 4]>| {
+            let trough_h = DROPDOWN_ITEM_H.min((r.3 - 6.0).max(8.0));
+            let trough = Rect { x: r.0, y: r.1 + (r.3 - trough_h) / 2.0, width: r.2, height: trough_h };
+            let radius = crate::layout::dropdown_corner_radius();
+            let depth = crate::layout::bevel_width().min(trough_h * 0.2);
+            ctx.inset_plate(trough, (radius, radius, radius, radius), [0.0; 4], depth);
+            if let Some(c) = fill {
+                ctx.rounded_rect(trough, radius, (true, true, true, true), c);
+            }
+        };
+
+        // Context-dropdown trigger (the bar's folder/pane selector).
         if !self.context_options.is_empty() {
             let tr = self.title_rect(rect);
-            if self.vertical || self.curved_circle.is_some() {
+            if flat_modes {
                 if self.context_dropdown_open {
                     ctx.quad(Rect { x: tr.0, y: tr.1, width: tr.2, height: tr.3 }, colors::highlight_primary_color());
                 } else if self.context_title_hovered {
                     ctx.quad(Rect { x: tr.0, y: tr.1, width: tr.2, height: tr.3 }, colors::HIGHLIGHT_SECONDARY);
                 }
             } else {
-                let trough_h = DROPDOWN_ITEM_H.min((tr.3 - 6.0).max(8.0));
-                let trough = Rect { x: tr.0, y: tr.1 + (tr.3 - trough_h) / 2.0, width: tr.2, height: trough_h };
-                let radius = crate::layout::dropdown_corner_radius();
-                let depth = crate::layout::bevel_width().min(trough_h * 0.2);
-                ctx.inset_plate(trough, (radius, radius, radius, radius), [0.0; 4], depth);
-                if self.context_dropdown_open {
-                    ctx.rounded_rect(trough, radius, (true, true, true, true), colors::highlight_primary_color());
+                let fill = if self.context_dropdown_open {
+                    Some(colors::highlight_primary_color())
                 } else if self.context_title_hovered {
-                    ctx.rounded_rect(trough, radius, (true, true, true, true), colors::HIGHLIGHT_SECONDARY);
-                }
+                    Some(colors::HIGHLIGHT_SECONDARY)
+                } else {
+                    None
+                };
+                trough_chrome(ctx, tr, fill);
             }
         }
 
-        // The embedded strip's geometry (it is not a tree child; its pixels are ours).
-        for (qx, qy, qw, qh, qc) in self.menus.extra_quads() {
-            ctx.quad(Rect { x: qx, y: qy, width: qw, height: qh }, qc);
+        // The embedded strip's geometry (it is not a tree child; its pixels are
+        // ours). Horizontal bars restyle the menu buttons as dropdown triggers
+        // too: each gets its own trough (side-inset so adjacent troughs keep
+        // separate groove rings), and the strip's full-height square state
+        // quads are replaced by the trigger fills — open matches the context
+        // dropdown's open tint rather than the strip's legacy palette.
+        if flat_modes {
+            for (qx, qy, qw, qh, qc) in self.menus.extra_quads() {
+                ctx.quad(Rect { x: qx, y: qy, width: qw, height: qh }, qc);
+            }
+        } else {
+            for i in 0..self.menus.buttons.len() {
+                let r = self.menus.item_rect(i);
+                let r = (r.0 + 3.0, r.1, (r.2 - 6.0).max(8.0), r.3);
+                let fill = if Some(i) == self.menus.selected {
+                    Some(colors::highlight_primary_color())
+                } else if Some(i) == self.menus.pressed_idx {
+                    Some(colors::BUTTON_PRESS)
+                } else if Some(i) == self.menus.hovered_idx {
+                    Some(colors::HIGHLIGHT_SECONDARY)
+                } else {
+                    None
+                };
+                trough_chrome(ctx, r, fill);
+            }
         }
         for (cx, cy, r, t, start, end, c) in self.menus.extra_arcs() {
             ctx.arc(cx, cy, r, t, start, end, c);