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

commitf1523ab5fc76ab86203588a250f71815d546be24
parent773eb0b755
authorLucas Galante <[email protected]>
date2026-07-08 15:26
feat(widget): migrate MenuBar; delete dead Menu (Phase 5o)

MenuBar rides the container-era adapter with its legacy ButtonStrip
EMBEDDED in the model (owned by value, driven through Element calls;
events reach it via EventCtx::ui; arrange_children parents it back to the
adapter so its dummy-ctx parent-chain styling walks keep working — the new
Layout::tracked_parent hook serves Element::parent from the model's field
for exactly those walks). Dropdowns move onto new Paint popover hooks
(popover / draw_popover); z-ordering onto Layout::z_order; host-pushed
modifier state onto Input::set_modifiers; set_visible side effects onto
Input::visibility_changed; and the legacy CONDITIONAL focus (the bar holds
global focus only while a dropdown is open or a menu selected) onto
Input::is_focused + EventCtx::release_focus, with the adapter's
focus()/unfocus() now carrying the self pointer so focus events can
claim/release the global slot. PageSelector joins the capability hooks
(as_page_selector pairs — live in the designer and test-interface).
Paint::corner_style now receives the laid-out rect (MenuBar's corners are
computed against its parent backplate's edges; nine implementors updated
mechanically).

The standalone Menu widget is DELETED: zero constructors workspace-wide.

Dropped, flagged: the title_buf/curved_title_char_bufs/context_item_bufs
glyphon caches (get_text_items always returned empty — prepare_text built
buffers nothing read) and the vertical-mode dynamic rect() override
(with_vertical has no callers; the vertical geometry itself is kept).

Verification: 168 tests (menu open via strip press+release -> dropdown
popover -> item click -> menu_click roundtrip -> conditional focus
release, all through real adapter dispatch; hidden-bar gating); all four
hosts (designer, cce-graph, data-editor, test-interface) start and run;
live A/B on cce-test-interface shows the menubar strip pixel-equivalent
(zero diffs above the 8% threshold; sub-8% noise is desktop bleed through
the translucent bar) and identical interaction behavior (the File click
not opening a dropdown there is pre-existing app behavior, byte-identical
in the legacy build). Not yet live-verified: the designer's menu layer and
curved circular-pane mode (screen contended by the live session's
terminal) — worth a designer A/B when the screen frees up.

Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LkfJazPs9bRchkcozmxXCX

 src/widget/container/breadcrumb.rs  |    2 +-
 src/widget/container/menu.rs        | 1803 ++++++++++++-----------------------
 src/widget/container/mod.rs         |    2 +-
 src/widget/container/spreadsheet.rs |    2 +-
 src/widget/container/switcher.rs    |    4 +-
 src/widget/display/graph.rs         |    2 +-
 src/widget/display/progress_bar.rs  |    2 +-
 src/widget/input/button.rs          |    2 +-
 src/widget/input/checkbox.rs        |    2 +-
 src/widget/input/slider.rs          |    4 +-
 src/widget/input/spinbox.rs         |    2 +-
 src/widget/mod.rs                   |    2 +-
 src/widget/model.rs                 |  122 ++-
 13 files changed, 752 insertions(+), 1199 deletions(-)

diff --git a/src/widget/container/breadcrumb.rs b/src/widget/container/breadcrumb.rs
index c9633fe..38dd772 100644
--- a/src/widget/container/breadcrumb.rs
+++ b/src/widget/container/breadcrumb.rs
@@ -95,7 +95,7 @@ impl Paint for Breadcrumb {
         self.bg_color()
     }
 
-    fn corner_style(&self) -> Option<(f32, (bool, bool, bool, bool))> {
+    fn corner_style(&self, _rect: Rect) -> Option<(f32, (bool, bool, bool, bool))> {
         Some((crate::layout::breadcrumb_corner_radius(), (true, true, false, false)))
     }
 
diff --git a/src/widget/container/menu.rs b/src/widget/container/menu.rs
index 886ccf1..054002e 100644
--- a/src/widget/container/menu.rs
+++ b/src/widget/container/menu.rs
@@ -1,9 +1,33 @@
+//! Narrow-trait `MenuBar` (Phase 5o) — a titled bar of dropdown menus with an optional
+//! context selector on the title. The menu buttons live in an EMBEDDED legacy [`ButtonStrip`]
+//! (owned by value in the model, driven through `Element` calls — events reach it via
+//! `EventCtx::ui`, and [`Layout::arrange_children`] parents it back to the adapter so its
+//! parent-chain styling walks keep working). Dropdowns are painted through the popover hooks
+//! ([`Paint::popover`] / [`Paint::draw_popover`]) and layered by [`Layout::z_order`]. The
+//! title supports the designer's curved circular-pane mode. Focus is conditional, exactly like
+//! legacy: the bar claims the global focus only while a dropdown is open or a menu selected
+//! ([`Input::is_focused`] reports that state, ignoring the base flag).
+//!
+//! The standalone `Menu` widget that used to live here was DELETED in this migration: it had
+//! zero constructors workspace-wide (dead code).
+//!
+//! Dropped with the migration: the `title_buf`/`curved_title_char_bufs`/`context_item_bufs`
+//! glyphon caches — `get_text_items` always returned empty, so `prepare_text` built buffers
+//! nothing ever read (an abandoned optimization). Also gone: the legacy `rect()` override's
+//! vertical-mode dynamic height (`with_vertical` has no callers workspace-wide; the vertical
+//! label/title geometry is kept for the strip's rotated mode, but the widget rect is the
+//! assigned base rect).
+
 use crate::colors;
-use crate::widget::*;
-use crate::widget::display::{make_widget_text_buffer, TextLabel};
+use crate::scene::layout::Rect;
+use crate::scene::paint::PaintCtx;
+use crate::widget::display::TextLabel;
+use crate::widget::{
+    Adapted, ButtonStrip, Element, ElementState, Event, EventCtx, Input, Key, Layout,
+    MenuController, MouseButton, NamedKey, PageSelector, Paint, UiContext, DROPDOWN_ITEM_H,
+};
 
 pub struct MenuBar {
-    pub base: Widget,
     pub visible: bool,
     pub network_opacity: f32,
     pub curved_circle: Option<(f32, f32, f32)>,
@@ -20,9 +44,6 @@ pub struct MenuBar {
     pub z_level: i32,
     pub center_items: bool,
     pub title_pos: Option<(f32, f32)>,
-    pub title_buf: Option<glyphon::Buffer>,
-    pub curved_title_char_bufs: Vec<glyphon::Buffer>,
-    pub font_family: String,
     pub label: Option<String>,
     pub context_options: Vec<String>,
     pub context_selected: usize,
@@ -30,7 +51,6 @@ pub struct MenuBar {
     pub context_just_changed: bool,
     pub context_hovered_item: Option<usize>,
     pub context_title_hovered: bool,
-    pub context_item_bufs: Vec<glyphon::Buffer>,
     pub right_align_title: bool,
     pub parent: Option<*mut (dyn Element + 'static)>,
     pub page_hidden: bool,
@@ -39,30 +59,12 @@ pub struct MenuBar {
     pub on_menu_click_cb: Option<Box<dyn Fn(usize, usize) + Send + Sync>>,
     pub hovered_dropdown_item: Option<usize>,
     pub clicked_dropdown_item: Option<(usize, usize)>,
+    last_arranged: Option<Rect>,
 }
 
 impl MenuBar {
-    pub fn set_curved_circle(&mut self, circle: Option<(f32, f32, f32)>) {
-        self.curved_circle = circle;
-    }
-
-    pub fn set_network_opacity(&mut self, opacity: f32) {
-        self.network_opacity = opacity;
-    }
-
-    pub fn with_color(mut self, color: [f32; 4]) -> Self {
-        self.color = Some(color);
-        self
-    }
-
-    pub fn with_blur(mut self, blur: bool) -> Self {
-        self.blur = blur;
-        self
-    }
-
-    pub fn new(x: f32, y: f32, w: f32, h: f32) -> Self {
-        Self {
-            base: Widget::new_rect(x, y, w, h),
+    pub fn new(x: f32, y: f32, w: f32, h: f32) -> Adapted<MenuBar> {
+        let mut bar = Adapted::new(MenuBar {
             visible: true,
             network_opacity: 1.0,
             curved_circle: None,
@@ -79,9 +81,6 @@ impl MenuBar {
             z_level: 0,
             center_items: false,
             title_pos: None,
-            title_buf: None,
-            curved_title_char_bufs: Vec::new(),
-            font_family: crate::layout::menubar_font(),
             label: None,
             context_options: Vec::new(),
             context_selected: 0,
@@ -89,7 +88,7 @@ impl MenuBar {
             context_just_changed: false,
             context_hovered_item: None,
             context_title_hovered: false,
-            context_item_bufs: Vec::new(),
+            right_align_title: false,
             parent: None,
             page_hidden: false,
             layout_dirty: true,
@@ -97,48 +96,22 @@ impl MenuBar {
             on_menu_click_cb: None,
             hovered_dropdown_item: None,
             clicked_dropdown_item: None,
-            right_align_title: false,
-        }
-    }
-
-    pub fn with_right_aligned_title(mut self, right: bool) -> Self {
-        self.right_align_title = right;
-        self
-    }
-
-    pub fn on_context_change<F: Fn(usize) + Send + Sync + 'static>(mut self, cb: F) -> Self {
-        self.on_context_change_cb = Some(Box::new(cb));
-        self
+            last_arranged: None,
+        });
+        Element::set_rect(&mut bar, x, y, w, h);
+        bar
     }
 
-    pub fn on_menu_click<F: Fn(usize, usize) + Send + Sync + 'static>(mut self, cb: F) -> Self {
-        self.on_menu_click_cb = Some(Box::new(cb));
-        self
-    }
-
-    pub fn with_label(mut self, label: &str) -> Self {
-        self.label = Some(label.to_string());
-        self.base.label = Some(label.to_string());
-        self
-    }
-
-    pub fn set_label(&mut self, label: &str) {
-        self.label = Some(label.to_string());
-        self.base.label = Some(label.to_string());
-        self.layout_dirty = true;
+    pub fn set_curved_circle(&mut self, circle: Option<(f32, f32, f32)>) {
+        self.curved_circle = circle;
     }
 
-    pub fn with_context_options(mut self, options: Vec<String>, selected: usize) -> Self {
-        self.context_options = options;
-        self.context_selected = selected;
-        self
+    pub fn set_network_opacity(&mut self, opacity: f32) {
+        self.network_opacity = opacity;
     }
 
     pub fn set_context_selected(&mut self, selected: usize) {
-        if self.context_selected != selected {
-            self.context_selected = selected;
-            self.context_item_bufs.clear();
-        }
+        self.context_selected = selected;
     }
 
     pub fn take_context_change(&mut self) -> Option<usize> {
@@ -150,7 +123,15 @@ impl MenuBar {
         }
     }
 
-    pub fn title_rect(&self) -> (f32, f32, f32, f32) {
+    fn display_title(&self) -> String {
+        let mut display_title = self.title.clone();
+        if !self.context_options.is_empty() {
+            display_title.push_str(if self.vertical { "▼" } else { " ▼" });
+        }
+        display_title
+    }
+
+    pub fn title_rect(&self, rect: Rect) -> (f32, f32, f32, f32) {
         if self.title.is_empty() {
             return (0.0, 0.0, 0.0, 0.0);
         }
@@ -165,12 +146,12 @@ impl MenuBar {
             display_title.push_str(" ▼");
         }
 
-        if let Some((_ccx, _ccy, _ccr)) = self.curved_circle {
+        if self.curved_circle.is_some() {
             if let Some((tx, ty)) = self.title_pos {
                 let title_w = display_title.len() as f32 * char_w + 24.0;
-                (tx, ty, title_w, self.base.h)
+                (tx, ty, title_w, rect.height)
             } else {
-                (self.base.x, self.base.y, display_title.len() as f32 * char_w + 24.0, self.base.h)
+                (rect.x, rect.y, display_title.len() as f32 * char_w + 24.0, rect.height)
             }
         } else if self.vertical {
             let mut cy = 16.0;
@@ -180,12 +161,8 @@ impl MenuBar {
                 cy += label_h + 20.0;
             }
             let line_height = font_size * 1.2;
-            let mut display_title_vertical = self.title.clone();
-            if !self.context_options.is_empty() {
-                display_title_vertical.push_str("▼");
-            }
-            let title_h = display_title_vertical.chars().count() as f32 * line_height;
-            (self.base.x, self.base.y + cy, self.base.w, title_h)
+            let title_h = self.display_title().chars().count() as f32 * line_height;
+            (rect.x, rect.y + cy, rect.width, title_h)
         } else {
             let mut start_x = 8.0;
             if self.center_items {
@@ -194,21 +171,21 @@ impl MenuBar {
                 for btn_label in &self.menus.buttons {
                     total_width += btn_label.len() as f32 * char_w + 2.0 * padding_x;
                 }
-                if self.base.w > total_width {
-                    start_x = (self.base.w - total_width) / 2.0;
+                if rect.width > total_width {
+                    start_x = (rect.width - total_width) / 2.0;
                 }
             }
             let title_w = display_title.len() as f32 * char_w + 24.0;
             let tx = if self.right_align_title {
-                self.base.x + self.base.w - title_w - 20.0
+                rect.x + rect.width - title_w - 20.0
             } else {
-                self.base.x + start_x
+                rect.x + start_x
             };
-            (tx, self.base.y, title_w, self.base.h)
+            (tx, rect.y, title_w, rect.height)
         }
     }
 
-    pub fn context_popover_rect(&self) -> Option<(f32, f32, f32, f32)> {
+    pub fn context_popover_rect(&self, rect: Rect) -> Option<(f32, f32, f32, f32)> {
         if self.context_options.is_empty() || !self.context_dropdown_open {
             return None;
         }
@@ -221,71 +198,12 @@ impl MenuBar {
         let dw = (max_len as f32 * char_w + 40.0).max(140.0);
         let dh = self.context_options.len() as f32 * DROPDOWN_ITEM_H;
 
-        let tr = self.title_rect();
-        let dx = if self.vertical {
-            tr.0 + tr.2
-        } else {
-            tr.0
-        };
-        let dy = if self.vertical {
-            tr.1
-        } else {
-            tr.1 + tr.3
-        };
+        let tr = self.title_rect(rect);
+        let dx = if self.vertical { tr.0 + tr.2 } else { tr.0 };
+        let dy = if self.vertical { tr.1 } else { tr.1 + tr.3 };
         Some((dx, dy, dw, dh))
     }
 
-    pub fn with_center_items(mut self, center: bool) -> Self {
-        self.center_items = center;
-        self
-    }
-
-    pub fn with_title(mut self, title: &str) -> Self {
-        self.title = title.to_string();
-        self
-    }
-
-    pub fn with_item(mut self, label: &str, items: &[&str]) -> Self {
-        self.menu_items.push(label.to_string());
-        self.vertical_items.push(label.to_string());
-        self.menu_dropdowns.push(items.iter().map(|s| s.to_string()).collect());
-        self.menu_dropdown_checked.push(vec![None; items.len()]);
-        self.menus.add_button(label);
-        self
-    }
-
-    pub fn with_item_vh(mut self, horizontal_label: &str, vertical_label: &str, items: &[&str]) -> Self {
-        self.menu_items.push(horizontal_label.to_string());
-        self.vertical_items.push(vertical_label.to_string());
-        self.menu_dropdowns.push(items.iter().map(|s| s.to_string()).collect());
-        self.menu_dropdown_checked.push(vec![None; items.len()]);
-        let label = if self.vertical { vertical_label } else { horizontal_label };
-        self.menus.add_button(label);
-        self
-    }
-
-    fn update_menu_labels(&mut self) {
-        let src = if self.vertical {
-            &self.vertical_items
-        } else {
-            &self.menu_items
-        };
-        self.menus.buttons = src.clone();
-        self.menus.generate_rotated_labels();
-    }
-
-    pub fn with_vertical(mut self, vertical: bool) -> Self {
-        self.vertical = vertical;
-        self.menus.vertical = vertical;
-        self.update_menu_labels();
-        self
-    }
-
-    pub fn with_z_index(mut self, z: i32) -> Self {
-        self.z_level = z;
-        self
-    }
-
     pub fn menu_dropdown_rect(&self) -> Option<(f32, f32, f32, f32)> {
         let menu_idx = self.menus.selected?;
         let items = self.menu_dropdowns.get(menu_idx)?;
@@ -300,17 +218,9 @@ impl MenuBar {
         let font_size = font_size_opt.unwrap_or(12.0);
         let char_w = 7.5 * (font_size / 12.0);
         let dw = (max_len as f32 * char_w + 40.0).max(120.0);
-        
-        let dx = if self.vertical {
-            hr.0 + hr.2
-        } else {
-            hr.0
-        };
-        let dy = if self.vertical {
-            hr.1
-        } else {
-            hr.1 + hr.3
-        };
+
+        let dx = if self.vertical { hr.0 + hr.2 } else { hr.0 };
+        let dy = if self.vertical { hr.1 } else { hr.1 + hr.3 };
         Some((dx, dy, dw, dh))
     }
 
@@ -331,91 +241,55 @@ impl MenuBar {
         }
         self.blur
     }
-}
-
-impl Element for MenuBar {
-    crate::impl_widget_base!(MenuBar);
-
-    fn set_modifiers(&mut self, ctrl: bool, shift: bool, alt: bool) {
-        self.menus.set_modifiers(ctrl, shift, alt);
-    }
-
-    fn layout_ignore(&self) -> bool {
-        true
-    }
-
-    fn blocks_backplate_drag(&self) -> bool {
-        false
-    }
-
-    fn parent(&self, _ctx: &UiContext) -> Option<*mut (dyn Element + 'static)> {
-        self.parent
-    }
 
-    fn set_parent(&mut self, parent: Option<*mut (dyn Element + 'static)>, _ctx: &mut UiContext) {
-        self.parent = parent;
+    fn update_menu_labels(&mut self) {
+        let src = if self.vertical { &self.vertical_items } else { &self.menu_items };
+        self.menus.buttons = src.clone();
+        self.menus.generate_rotated_labels();
     }
 
-    fn label(&self) -> Option<String> {
-        self.label.clone()
+    fn bg_color(&self) -> [f32; 4] {
+        if let Some(p_ptr) = self.parent {
+            if unsafe { (*p_ptr).is_backplate() } {
+                return crate::colors::backplate_menubar_color();
+            }
+        }
+        self.color.unwrap_or_else(|| colors::sidebar_bg_color())
     }
 
-    fn set_text(&mut self, text: &str) {
-        self.label = Some(text.to_string());
-        self.base.label = Some(text.to_string());
-        self.layout_dirty = true;
-    }
+    fn corners_against_parent(&self, rect: Rect) -> (bool, bool, bool, bool) {
+        if let Some(p_ptr) = self.parent {
+            let is_bp = unsafe { (*p_ptr).is_backplate() };
+            if is_bp {
+                let (px, py, pw, ph) = unsafe { (*p_ptr).rect() };
+                let (x, y, w, h) = (rect.x, rect.y, rect.width, rect.height);
+                let is_at_top = (y - py).abs() < 0.1;
+                let is_at_bottom = (y + h - (py + ph)).abs() < 0.1;
 
-    fn rect(&self) -> (f32, f32, f32, f32) {
-        if !self.visible {
-            return (0.0, 0.0, 0.0, 0.0);
-        }
-        if self.vertical {
-            let mut h = 16.0;
-            if let Some(ref label) = self.label {
-                let font_size = 12.0;
-                let line_height = font_size * 1.2;
-                let label_h = label.chars().count() as f32 * line_height;
-                h += label_h + 20.0;
-            }
-            if !self.title.is_empty() {
-                let font_size = 12.0;
-                let line_height = font_size * 1.2;
-                let mut display_title = self.title.clone();
-                if !self.context_options.is_empty() {
-                    display_title.push_str("▼");
+                if is_at_top && is_at_bottom {
+                    let is_at_left = (x - px).abs() < 0.1;
+                    let is_at_right = (x + w - (px + pw)).abs() < 0.1;
+                    return (is_at_left, is_at_right, is_at_right, is_at_left);
+                } else if is_at_top {
+                    return (true, true, false, false);
+                } else if is_at_bottom {
+                    return (false, false, true, true);
                 }
-                let title_h = display_title.chars().count() as f32 * line_height;
-                h += title_h + 36.0;
             }
-            let (_, _, _, menus_h) = self.menus.rect();
-            (self.base.x, self.base.y, self.base.w, h + menus_h)
-        } else {
-            (self.base.x, self.base.y, self.base.w, self.base.h)
         }
+        (false, false, false, false)
     }
 
-    fn set_rect(&mut self, x: f32, y: f32, w: f32, h: f32) {
-        if self.base.x == x && self.base.y == y && self.base.w == w && self.base.h == h && !self.layout_dirty {
+    /// Position the embedded strip inside `rect` — the legacy `set_rect` body, minus the
+    /// parent clamping (that lives in [`Layout::adjust_rect`]) and the base assignment (the
+    /// adapter's). Early-outs when the rect and content are unchanged, like legacy.
+    fn layout_strip(&mut self, rect: Rect) {
+        if self.last_arranged == Some(rect) && !self.layout_dirty {
             return;
         }
         self.layout_dirty = false;
-        let (clamped_x, clamped_y, clamped_w, clamped_h) = if let Some(parent_ptr) = self.parent {
-            let (px, py, pw, ph) = unsafe { (*parent_ptr).rect() };
-            let cx = x.clamp(px, px + pw);
-            let cy = y.clamp(py, py + ph);
-            let cw = w.min(px + pw - cx);
-            let ch = h.min(py + ph - cy);
-            (cx, cy, cw, ch)
-        } else {
-            (x, y, w, h)
-        };
-
-        self.base.x = clamped_x;
-        self.base.y = clamped_y;
-        self.base.w = clamped_w;
-        self.base.h = clamped_h;
-        let parent_ptr = self as *mut MenuBar as *mut (dyn Element + 'static);
+        self.last_arranged = Some(rect);
+        let (x, y, w, h) = (rect.x, rect.y, rect.width, rect.height);
 
         let font_setting = crate::layout::menubar_font();
         let font_info = crate::layout::parse_font_string(&font_setting);
@@ -433,19 +307,13 @@ impl Element for MenuBar {
             if !self.title.is_empty() {
                 let font_size = 12.0;
                 let line_height = font_size * 1.2;
-                let mut display_title = self.title.clone();
-                if !self.context_options.is_empty() {
-                    display_title.push_str("▼");
-                }
-                let title_h = display_title.chars().count() as f32 * line_height;
+                let title_h = self.display_title().chars().count() as f32 * line_height;
                 cy += title_h + 36.0;
             }
-            let menus_y = (clamped_y + cy).clamp(clamped_y, clamped_y + clamped_h);
-            let menus_h = (clamped_h - cy).min(clamped_y + clamped_h - menus_y).max(0.0);
+            let menus_y = (y + cy).clamp(y, y + h);
+            let menus_h = (h - cy).min(y + h - menus_y).max(0.0);
             self.menus.vertical = true;
-            self.menus.set_rect(clamped_x, menus_y, clamped_w, menus_h);
-            let mut dummy = crate::context::UiContext::new();
-            self.menus.set_parent(Some(parent_ptr), &mut dummy);
+            self.menus.set_rect(x, menus_y, w, menus_h);
         } else {
             let padding = crate::layout::button_padding();
             let spacing = crate::layout::button_strip_spacing();
@@ -468,8 +336,8 @@ impl Element for MenuBar {
                     }
                 }
                 total_width += btn_strip_w;
-                if self.base.w > total_width {
-                    cx = (self.base.w - total_width) / 2.0;
+                if w > total_width {
+                    cx = (w - total_width) / 2.0;
                 }
             }
             if !self.title.is_empty() && !self.right_align_title {
@@ -487,477 +355,209 @@ impl Element for MenuBar {
                     btn_strip_w += spacing;
                 }
             }
-            let menus_x = (clamped_x + cx).clamp(clamped_x, clamped_x + clamped_w);
-            let menus_w = btn_strip_w.min(clamped_x + clamped_w - menus_x);
+            let menus_x = (x + cx).clamp(x, x + w);
+            let menus_w = btn_strip_w.min(x + w - menus_x);
             self.menus.vertical = false;
-            self.menus.set_rect(menus_x, clamped_y, menus_w, clamped_h);
-            let mut dummy = crate::context::UiContext::new();
-            self.menus.set_parent(Some(parent_ptr), &mut dummy);
+            self.menus.set_rect(menus_x, y, menus_w, h);
         }
     }
 
-    fn color(&self) -> [f32; 4] {
-        if let Some(p_ptr) = self.parent {
-            if unsafe { (*p_ptr).is_backplate() } {
-                return crate::colors::backplate_menubar_color();
-            }
-        }
-        self.color.unwrap_or_else(|| colors::sidebar_bg_color())
+    /// Close every open dropdown and drop internal focus state — the state half of the legacy
+    /// `unfocus` (the global-focus release is the caller's, via `EventCtx::release_focus`).
+    fn close_all(&mut self) {
+        self.focused = false;
+        self.context_dropdown_open = false;
+        self.context_hovered_item = None;
+        self.hovered_dropdown_item = None;
+        self.menus.set_selected(None);
     }
 
-
-
-    fn rounded_corners(&self) -> (bool, bool, bool, bool) {
-        if let Some(p_ptr) = self.parent {
-            let is_bp = unsafe { (*p_ptr).is_backplate() };
-            if is_bp {
-                let (px, py, pw, ph) = unsafe { (*p_ptr).rect() };
-                let (x, y, w, h) = self.rect();
-                let is_at_top = (y - py).abs() < 0.1;
-                let is_at_bottom = (y + h - (py + ph)).abs() < 0.1;
-                
-                if is_at_top && is_at_bottom {
-                    let is_at_left = (x - px).abs() < 0.1;
-                    let is_at_right = (x + w - (px + pw)).abs() < 0.1;
-                    return (is_at_left, is_at_right, is_at_right, is_at_left);
-                } else if is_at_top {
-                    return (true, true, false, false);
-                } else if is_at_bottom {
-                    return (false, false, true, true);
-                }
-            }
+    /// The conditional focus claim of the legacy `focus()`: hold the global focus only while
+    /// something is open.
+    fn sync_focus(&mut self, ectx: &mut EventCtx) {
+        if self.context_dropdown_open || self.menus.selected.is_some() {
+            self.focused = true;
+            ectx.request_focus();
+        } else {
+            self.focused = false;
+            ectx.release_focus();
         }
-        (false, false, false, false)
     }
+}
 
-    fn corner_radius(&self) -> f32 {
-        if let Some(p_ptr) = self.parent {
-            unsafe { (*p_ptr).corner_radius() }
-        } else {
-            0.0
-        }
+impl Adapted<MenuBar> {
+    pub fn with_color(mut self, color: [f32; 4]) -> Self {
+        self.color = Some(color);
+        self
     }
 
+    pub fn with_blur(mut self, blur: bool) -> Self {
+        self.blur = blur;
+        self
+    }
 
-    fn set_hovered(&mut self, v: bool) {
-        self.base.hovered = v;
+    pub fn with_right_aligned_title(mut self, right: bool) -> Self {
+        self.right_align_title = right;
+        self
     }
 
-    fn hovered(&self) -> bool {
-        self.base.hovered
+    pub fn on_context_change<F: Fn(usize) + Send + Sync + 'static>(mut self, cb: F) -> Self {
+        self.on_context_change_cb = Some(Box::new(cb));
+        self
     }
 
-    fn hit_test(&self, px: f32, py: f32, ctx: &UiContext) -> bool {
-        if !self.visible {
-            return false;
-        }
-        if ctx.is_coordinate_covered(self as *const Self as *const () as usize, px, py) {
-            return false;
-        }
-        if let Some((dx, dy, dw, dh)) = self.context_popover_rect() {
-            if px >= dx && px < dx + dw && py >= dy && py < dy + dh {
-                return true;
-            }
-        }
-        if let Some((dx, dy, dw, dh)) = self.menu_dropdown_rect() {
-            if px >= dx && px < dx + dw && py >= dy && py < dy + dh {
-                return true;
-            }
-        }
-        let (rx, ry, rw, rh) = self.rect();
-        if px >= rx && px <= rx + rw && py >= ry && py <= ry + rh {
-            return true;
-        }
-        if self.menus.hit_test(px, py, ctx) {
-            return true;
-        }
-        false
+    pub fn on_menu_click<F: Fn(usize, usize) + Send + Sync + 'static>(mut self, cb: F) -> Self {
+        self.on_menu_click_cb = Some(Box::new(cb));
+        self
     }
 
-    fn on_cursor_moved(&mut self, px: f32, py: f32, ctx: &mut UiContext) -> bool {
-        if !self.visible {
-            return false;
-        }
-        let (rx, ry, rw, rh) = self.rect();
-        self.set_rect(rx, ry, rw, rh);
-
-        let mut changed = false;
-
-        let old_title_hovered = self.context_title_hovered;
-        self.context_title_hovered = false;
-        if !self.context_options.is_empty() {
-            let tr = self.title_rect();
-            if px >= tr.0 && px <= tr.0 + tr.2 && py >= tr.1 && py <= tr.1 + tr.3 {
-                self.context_title_hovered = true;
-            }
-        }
-        if old_title_hovered != self.context_title_hovered {
-            changed = true;
-        }
-
-        let old_hovered_item = self.context_hovered_item;
-        self.context_hovered_item = None;
-        if let Some((dx, dy, dw, dh)) = self.context_popover_rect() {
-            if px >= dx && px < dx + dw && py >= dy && py < dy + dh {
-                let di = ((py - dy) / DROPDOWN_ITEM_H) as usize;
-                if di < self.context_options.len() {
-                    self.context_hovered_item = Some(di);
-                }
-            }
-        }
-        if old_hovered_item != self.context_hovered_item {
-            changed = true;
-        }
-
-        let old_hovered_dropdown = self.hovered_dropdown_item;
-        self.hovered_dropdown_item = None;
-        if let Some((dx, dy, dw, dh)) = self.menu_dropdown_rect() {
-            if px >= dx && px < dx + dw && py >= dy && py < dy + dh {
-                let di = ((py - dy) / DROPDOWN_ITEM_H) as usize;
-                if let Some(menu_idx) = self.menus.selected {
-                    if let Some(items) = self.menu_dropdowns.get(menu_idx) {
-                        if di < items.len() {
-                            self.hovered_dropdown_item = Some(di);
-                        }
-                    }
-                }
-            }
-        }
-        if old_hovered_dropdown != self.hovered_dropdown_item {
-            changed = true;
-        }
-
-        if self.menus.cursor_moved(px, py, ctx) {
-            changed = true;
-        }
-        changed
+    pub fn with_context_options(mut self, options: Vec<String>, selected: usize) -> Self {
+        self.context_options = options;
+        self.context_selected = selected;
+        self
     }
 
-    fn mouse_input(&mut self, button: MouseButton, state: ElementState, px: f32, py: f32, ctx: &mut UiContext) -> bool {
-        if !self.visible {
-            return false;
-        }
-        if button != MouseButton::Left {
-            return false;
-        }
-
-        let (rx, ry, rw, rh) = self.rect();
-        self.set_rect(rx, ry, rw, rh);
-
-        let mut changed = false;
-
-        if let Some((dx, dy, dw, dh)) = self.menu_dropdown_rect() {
-            if px >= dx && px < dx + dw && py >= dy && py < dy + dh {
-                if state == ElementState::Pressed {
-                    let di = ((py - dy) / DROPDOWN_ITEM_H) as usize;
-                    if let Some(menu_idx) = self.menus.selected {
-                        if let Some(items) = self.menu_dropdowns.get(menu_idx) {
-                            if di < items.len() {
-                                self.clicked_dropdown_item = Some((menu_idx, di));
-                                self.menus.set_selected(None);
-                                self.hovered_dropdown_item = None;
-                                self.unfocus();
-                                return true;
-                            }
-                        }
-                    }
-                }
-                return true;
-            }
-        }
-
-        if let Some((dx, dy, dw, dh)) = self.context_popover_rect() {
-            if px >= dx && px < dx + dw && py >= dy && py < dy + dh {
-                if state == ElementState::Pressed {
-                    let di = ((py - dy) / DROPDOWN_ITEM_H) as usize;
-                    if di < self.context_options.len() {
-                        self.context_selected = di;
-                        self.context_just_changed = true;
-                        self.context_dropdown_open = false;
-                        self.unfocus();
-                        if let Some(ref cb) = self.on_context_change_cb {
-                            cb(di);
-                        }
-                        return true;
-                    }
-                }
-            }
-        }
-
-        if !self.context_options.is_empty() {
-            let tr = self.title_rect();
-            if px >= tr.0 && px <= tr.0 + tr.2 && py >= tr.1 && py <= tr.1 + tr.3 {
-                if state == ElementState::Pressed {
-                    if self.context_dropdown_open {
-                        self.context_dropdown_open = false;
-                        self.unfocus();
-                    } else {
-                        self.menus.unfocus();
-                        self.context_dropdown_open = true;
-                        self.focus();
-                    }
-                }
-                return true;
-            }
-        }
+    pub fn with_center_items(mut self, center: bool) -> Self {
+        self.center_items = center;
+        self
+    }
 
-        if self.context_dropdown_open && state == ElementState::Pressed {
-            self.context_dropdown_open = false;
-            self.unfocus();
-            changed = true;
-        }
+    pub fn with_title(mut self, title: &str) -> Self {
+        self.title = title.to_string();
+        self
+    }
 
-        let old_menu_selected = self.menus.selected;
-        if self.menus.mouse_input(button, state, px, py, ctx) {
-            changed = true;
-            if self.menus.selected.is_some() && old_menu_selected != self.menus.selected {
-                self.focus();
-            }
-        }
-        changed
+    pub fn with_item(mut self, label: &str, items: &[&str]) -> Self {
+        self.menu_items.push(label.to_string());
+        self.vertical_items.push(label.to_string());
+        self.menu_dropdowns.push(items.iter().map(|s| s.to_string()).collect());
+        self.menu_dropdown_checked.push(vec![None; items.len()]);
+        self.menus.add_button(label);
+        self
     }
 
-    fn focus(&mut self) {
-        if self.context_dropdown_open || self.menus.selected.is_some() {
-            self.focused = true;
-            focus::set_focused(self);
-        } else {
-            self.focused = false;
-            focus::clear_if_matches(self);
-        }
+    pub fn with_item_vh(mut self, horizontal_label: &str, vertical_label: &str, items: &[&str]) -> Self {
+        self.menu_items.push(horizontal_label.to_string());
+        self.vertical_items.push(vertical_label.to_string());
+        self.menu_dropdowns.push(items.iter().map(|s| s.to_string()).collect());
+        self.menu_dropdown_checked.push(vec![None; items.len()]);
+        let label = if self.vertical { vertical_label } else { horizontal_label };
+        self.menus.add_button(label);
+        self
     }
 
-    fn unfocus(&mut self) {
-        self.focused = false;
-        self.context_dropdown_open = false;
-        self.context_hovered_item = None;
-        self.hovered_dropdown_item = None;
-        focus::clear_if_matches(self);
-        self.menus.set_selected(None);
+    pub fn with_vertical(mut self, vertical: bool) -> Self {
+        self.vertical = vertical;
+        self.menus.vertical = vertical;
+        self.update_menu_labels();
+        self
     }
 
-    fn focused(&self, _ctx: &UiContext) -> bool {
-        self.focused || self.context_dropdown_open || self.menus.selected.is_some()
+    pub fn with_z_index(mut self, z: i32) -> Self {
+        self.z_level = z;
+        self
     }
+}
 
-    fn popover_rect(&self) -> Option<(f32, f32, f32, f32)> {
-        self.context_popover_rect().or_else(|| self.menu_dropdown_rect())
+impl Layout for MenuBar {
+    fn layout_ignore(&self) -> bool {
+        true
     }
 
-    fn render_popover(&self, pc: &mut dyn crate::layout::RenderTarget) {
-        if self.context_dropdown_open {
-            if let Some((dx, dy, dw, dh)) = self.context_popover_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);
-                }
-                
-                let label_color = self.text_color();
-                let srgb = crate::colors::to_srgb(label_color);
-                let color_f32 = [srgb[0], srgb[1], srgb[2], 1.0];
-                let bounds = Some([dx, dy, dx + dw, dy + dh]);
-                
-                let font = self.widget_font();
-                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 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);
-                    } else {
-                        pc.text_with_bounds(&text, dx + 8.0, iy, 12.0, color_f32, 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);
-            }
+    fn z_order(&self) -> i32 {
+        self.z_level
+    }
 
-            let label_color = self.text_color();
-            let srgb = crate::colors::to_srgb(label_color);
-            let color_f32 = [srgb[0], srgb[1], srgb[2], 1.0];
-            let bounds = Some([dx, dy, dx + dw, dy + dh]);
+    fn tracked_parent(&self) -> Option<Option<*mut (dyn Element + 'static)>> {
+        Some(self.parent)
+    }
 
-            let font = self.widget_font();
-            if let Some(menu_idx) = self.menus.selected {
-                if let Some(items) = self.menu_dropdowns.get(menu_idx) {
-                    for (i, option) in items.iter().enumerate() {
-                        let checked = self.menu_dropdown_checked.get(menu_idx)
-                            .and_then(|menu| menu.get(i))
-                            .and_then(|&v| v);
-                        let prefix = match checked {
-                            Some(true) => "✓ ",
-                            Some(false) => "  ",
-                            None => "",
-                        };
-                        let text = format!("{}{}", prefix, option);
-                        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);
-                        } else {
-                            pc.text_with_bounds(&text, dx + 8.0, iy, 12.0, color_f32, bounds);
-                        }
-                    }
-                }
-            }
-        }
+    fn parent_changed(&mut self, parent: Option<*mut (dyn Element + 'static)>) {
+        self.parent = parent;
     }
 
-    fn keyboard_input(&mut self, event: &KeyEvent, ctx: &mut UiContext) -> bool {
-        if event.state != ElementState::Pressed { return false; }
-        if self.context_dropdown_open {
-            match event.logical_key {
-                Key::Named(NamedKey::ArrowDown) => {
-                    let current = self.context_hovered_item.unwrap_or(self.context_selected);
-                    if current + 1 < self.context_options.len() {
-                        self.context_hovered_item = Some(current + 1);
-                    } else {
-                        self.context_hovered_item = Some(0);
-                    }
-                    return true;
-                }
-                Key::Named(NamedKey::ArrowUp) => {
-                    let current = self.context_hovered_item.unwrap_or(self.context_selected);
-                    if current > 0 {
-                        self.context_hovered_item = Some(current - 1);
-                    } else {
-                        self.context_hovered_item = Some(self.context_options.len() - 1);
-                    }
-                    return true;
-                }
-                Key::Named(NamedKey::Enter) | Key::Named(NamedKey::Space) => {
-                    if let Some(idx) = self.context_hovered_item {
-                        self.context_selected = idx;
-                        self.context_just_changed = true;
-                    }
-                    self.context_dropdown_open = false;
-                    self.unfocus();
-                    return true;
-                }
-                Key::Named(NamedKey::Escape) => {
-                    self.context_dropdown_open = false;
-                    self.unfocus();
-                    return true;
-                }
-                _ => {}
-            }
-        }
-        self.menus.keyboard_input(event, ctx)
+    /// Legacy `set_rect` clamped into the parent rect (no zero floor, unlike Switcher).
+    fn adjust_rect(&self, requested: Rect) -> Rect {
+        let Some(parent_ptr) = self.parent else {
+            return requested;
+        };
+        let (px, py, pw, ph) = unsafe { (*parent_ptr).rect() };
+        let cx = requested.x.clamp(px, px + pw);
+        let cy = requested.y.clamp(py, py + ph);
+        let cw = requested.width.min(px + pw - cx);
+        let ch = requested.height.min(py + ph - cy);
+        Rect { x: cx, y: cy, width: cw, height: ch }
     }
 
-    fn set_selected(&mut self, selected: bool) {
-        self.focused = selected;
-        if !selected {
-            self.menus.set_selected(None);
-        }
+    fn arrange_children(&mut self, rect: Rect, host: *mut (dyn Element + 'static)) {
+        self.layout_strip(rect);
+        // The strip walks its parent chain for backplate-aware styling; through the adapter
+        // the chain is host -> tracked parent (a dummy-ctx-safe walk, as legacy relied on).
+        let mut dummy = crate::context::UiContext::new();
+        self.menus.set_parent(Some(host), &mut dummy);
     }
+}
 
-    fn as_page_selector(&self) -> Option<&dyn PageSelector> { Some(self) }
-    fn as_page_selector_mut(&mut self) -> Option<&mut dyn PageSelector> { Some(self) }
-    fn as_menu_controller(&self) -> Option<&dyn MenuController> { Some(self) }
-    fn as_menu_controller_mut(&mut self) -> Option<&mut dyn MenuController> { Some(self) }
+impl Paint for MenuBar {
+    fn color(&self) -> [f32; 4] {
+        self.bg_color()
+    }
 
-    fn all_quads(&self, ctx: &UiContext) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
-        if !self.visible {
-            return Vec::new();
-        }
-        let mut quads = self.extra_quads();
-        if let Some(hq) = self.menus.highlight_quad(ctx) {
-            if hq.4 != colors::HIGHLIGHT_SECONDARY {
-                quads.push(hq);
-            }
-        }
-        quads
+    fn corner_style(&self, rect: Rect) -> Option<(f32, (bool, bool, bool, bool))> {
+        let radius = match self.parent {
+            Some(p_ptr) => unsafe { (*p_ptr).corner_radius() },
+            None => 0.0,
+        };
+        Some((radius, self.corners_against_parent(rect)))
     }
 
-    fn extra_arcs(&self) -> Vec<(f32, f32, f32, f32, f32, f32, [f32; 4])> {
-        if !self.visible {
-            return Vec::new();
-        }
-        self.menus.extra_arcs()
+    fn widget_font(&self) -> Option<String> {
+        Some(crate::layout::menubar_font())
     }
 
-    fn extra_circles(&self) -> Vec<(f32, f32, f32, [f32; 4])> {
-        if !self.visible {
-            return Vec::new();
-        }
-        self.menus.extra_circles()
+    fn sync_label(&mut self, label: &str) {
+        self.label = Some(label.to_string());
+        self.layout_dirty = true;
     }
 
-    fn prepare_text(&mut self, fs: &mut glyphon::FontSystem) {
-        if !self.visible {
-            return;
-        }
-        let current_font = crate::layout::menubar_font();
-        if self.font_family != current_font {
-            self.font_family = current_font;
-            self.title_buf = None;
-            self.curved_title_char_bufs.clear();
+    fn paint(&self, rect: Rect, ctx: &mut PaintCtx) {
+        // Background: plain when cornerless (the legacy extra_quads bg), rounded against the
+        // parent's corners otherwise (the legacy Element-default all_rounded_quads bg).
+        let corners = self.corners_against_parent(rect);
+        let bg = self.bg_color();
+        if corners == (false, false, false, false) {
+            ctx.quad(rect, bg);
+        } else if bg[3].abs() > 0.001 {
+            let radius = match self.parent {
+                Some(p_ptr) => unsafe { (*p_ptr).corner_radius() },
+                None => 0.0,
+            };
+            ctx.rounded_rect(rect, radius, corners, bg);
         }
-        let (font_fam, font_size_opt) = crate::layout::parse_font_string(&self.font_family);
-        let font_size = font_size_opt.unwrap_or(12.0);
 
-        if !self.title.is_empty() {
-            let mut display_title = self.title.clone();
-            if !self.context_options.is_empty() {
-                display_title.push_str(" ▼");
-            }
-            if let Some((_ccx, _ccy, _ccr)) = self.curved_circle {
-                if self.curved_title_char_bufs.len() != display_title.chars().count() {
-                    let font_fam_clone = font_fam.clone();
-                    self.curved_title_char_bufs = display_title.chars()
-                        .map(|c| make_widget_text_buffer(fs, &c.to_string(), font_size, &font_fam_clone))
-                        .collect();
-                }
-                self.title_buf = None;
-            } else if self.vertical {
-                self.title_buf = None;
-                self.curved_title_char_bufs.clear();
-            } else {
-                if self.title_buf.is_none() {
-                    self.title_buf = Some(make_widget_text_buffer(fs, &display_title, font_size, &font_fam));
-                }
-                self.curved_title_char_bufs.clear();
+        // Title highlight while the context dropdown is open / hovered.
+        if !self.context_options.is_empty() {
+            let tr = self.title_rect(rect);
+            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 {
-            self.title_buf = None;
-            self.curved_title_char_bufs.clear();
         }
 
-        if self.context_dropdown_open {
-            if self.context_item_bufs.len() != self.context_options.len() {
-                let font_fam_clone = font_fam.clone();
-                self.context_item_bufs = self.context_options.iter().enumerate().map(|(i, option)| {
-                    let is_selected = self.context_selected == i;
-                    let prefix = if is_selected { "✓ " } else { "  " };
-                    let text = format!("{}{}", prefix, option);
-                    make_widget_text_buffer(fs, &text, font_size, &font_fam_clone)
-                }).collect();
-            }
-        } else {
-            self.context_item_bufs.clear();
+        // 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);
         }
-
-        self.menus.prepare_text(fs);
-    }
-
-    fn get_text_items(&self) -> Vec<(&glyphon::Buffer, f32, f32, glyphon::Color)> {
-        Vec::new()
-    }
-
-    fn text_labels(&self) -> Vec<TextLabel> {
-        if !self.visible {
-            return Vec::new();
+        for (cx, cy, r, t, start, end, c) in self.menus.extra_arcs() {
+            ctx.arc(cx, cy, r, t, start, end, c);
         }
+        for (cx, cy, r, c) in self.menus.extra_circles() {
+            ctx.circle(cx, cy, r, c);
+        }
+
+        // Text: the sidebar label (vertical), the title (curved / vertical / horizontal), and
+        // the strip's button labels — the legacy `text_labels` body.
         let label_color = self.text_color();
         let srgb = crate::colors::to_srgb(label_color);
         let text_color = [
@@ -966,25 +566,18 @@ impl Element for MenuBar {
             (srgb[2] * 255.0) as u8,
         ];
         let padding_x = crate::layout::paginator_tab_padding_x();
-        let mut labels = Vec::new();
 
         if let Some(ref label) = self.label {
             if self.vertical {
                 let font_size = 12.0;
                 let line_height = font_size * 1.2;
-                let start_y = self.base.y + 16.0;
+                let start_y = rect.y + 16.0;
                 for (i, c) in label.chars().enumerate() {
                     let char_str = c.to_string();
                     let char_w = crate::widget::display::measure_text(&char_str, font_size);
-                    let x_pos = self.base.x + (self.base.w - char_w) / 2.0;
+                    let x_pos = rect.x + (rect.width - char_w) / 2.0;
                     let y_pos = start_y + i as f32 * line_height;
-                    labels.push(TextLabel {
-                        text: char_str,
-                        x: x_pos,
-                        y: y_pos,
-                        font_size,
-                        color: [0x83, 0x83, 0x8a],
-                    });
+                    ctx.text(char_str, x_pos, y_pos, font_size, [0x83, 0x83, 0x8a]);
                 }
             }
         }
@@ -1000,7 +593,7 @@ impl Element for MenuBar {
         }
 
         if let Some((ccx, ccy, ccr)) = self.curved_circle {
-            let r_mid = ccr - self.base.h / 2.0;
+            let r_mid = ccr - rect.height / 2.0;
             let mut total_width = 8.0;
             if !self.title.is_empty() {
                 total_width += display_title.len() as f32 * char_w + 24.0;
@@ -1015,17 +608,19 @@ impl Element for MenuBar {
             if !self.title.is_empty() {
                 let title_w = display_title.len() as f32 * char_w + 24.0;
                 let dtheta_title = title_w / r_mid;
-                labels.extend(TextLabel::curved_layout(
+                for l in TextLabel::curved_layout(
                     &display_title,
                     ccx, ccy, r_mid,
                     current_angle, current_angle + dtheta_title,
                     font_size,
                     text_color,
-                ));
+                ) {
+                    ctx.text(l.text, l.x, l.y, l.font_size, l.color);
+                }
             }
         } else if self.vertical {
             if !self.title.is_empty() {
-                let mut start_y = self.base.y + 16.0;
+                let mut start_y = rect.y + 16.0;
                 if let Some(ref label) = self.label {
                     let font_size = 12.0;
                     let line_height = font_size * 1.2;
@@ -1034,587 +629,390 @@ impl Element for MenuBar {
                 }
                 let line_height = font_size * 1.2;
                 let char_w = crate::widget::display::measure_text("o", font_size);
-                let x_pos = self.base.x + (self.base.w - char_w) / 2.0;
-                let mut display_title_vertical = self.title.clone();
-                if !self.context_options.is_empty() {
-                    display_title_vertical.push_str("▼");
-                }
-                for (i, c) in display_title_vertical.chars().enumerate() {
+                let x_pos = rect.x + (rect.width - char_w) / 2.0;
+                for (i, c) in self.display_title().chars().enumerate() {
                     let char_str = c.to_string();
                     let y_pos = start_y + i as f32 * line_height;
-                    labels.push(TextLabel {
-                        text: char_str,
-                        x: x_pos,
-                        y: y_pos,
-                        font_size,
-                        color: text_color,
-                    });
+                    ctx.text(char_str, x_pos, y_pos, font_size, text_color);
                 }
             }
-        } else {
+        } else if !self.title.is_empty() {
             let mut start_x = 8.0;
             if self.center_items {
                 let mut total_width = 8.0;
-                if !self.title.is_empty() && !self.right_align_title {
+                if !self.right_align_title {
                     total_width += display_title.len() as f32 * char_w + 24.0;
                 }
                 for btn_label in &self.menus.buttons {
                     total_width += btn_label.len() as f32 * char_w + 2.0 * padding_x;
                 }
-                if self.base.w > total_width {
-                    start_x = (self.base.w - total_width) / 2.0;
+                if rect.width > total_width {
+                    start_x = (rect.width - total_width) / 2.0;
                 }
             }
-            if !self.title.is_empty() {
-                let text_y = crate::layout::align_text_y(self.base.y, self.base.h, font_size, 0.0);
-                let x_pos = if self.right_align_title {
-                    let title_w = crate::widget::display::measure_text_width(&display_title, &font_fam, font_size) + 24.0;
-                    self.base.x + self.base.w - title_w - 20.0
-                } else {
-                    self.base.x + start_x
-                };
-                labels.push(TextLabel {
-                    text: display_title,
-                    x: x_pos,
-                    y: text_y,
-                    font_size,
-                    color: text_color,
-                });
-            }
+            let text_y = crate::layout::align_text_y(rect.y, rect.height, font_size, 0.0);
+            let x_pos = if self.right_align_title {
+                let title_w = crate::widget::display::measure_text_width(&display_title, &font_fam, font_size) + 24.0;
+                rect.x + rect.width - title_w - 20.0
+            } else {
+                rect.x + start_x
+            };
+            ctx.text(display_title, x_pos, text_y, font_size, text_color);
         }
 
-        labels.extend(self.menus.text_labels());
-        labels
-    }
-
-    fn set_visible(&mut self, visible: bool) {
-        if self.visible != visible {
-            self.visible = visible;
-            self.menus.set_visible(visible);
-            self.layout_dirty = true;
+        for l in self.menus.text_labels() {
+            ctx.text(l.text, l.x, l.y, l.font_size, l.color);
         }
     }
 
-    fn visible(&self) -> bool {
-        self.visible
-    }
-
-    fn children(&self, _ctx: &UiContext) -> Vec<*mut (dyn Element + 'static)> {
-        let ptr: *const dyn Element = &self.menus as &dyn Element;
-        vec![ptr as *mut (dyn Element + 'static)]
+    fn popover(&self, rect: Rect) -> Option<(f32, f32, f32, f32)> {
+        self.context_popover_rect(rect).or_else(|| self.menu_dropdown_rect())
     }
 
-    fn z_index(&self) -> i32 {
-        self.z_level
-    }
-
-
-
-    fn extra_quads(&self) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
-        if !self.visible {
-            return Vec::new();
-        }
-        let mut quads = Vec::new();
+    fn draw_popover(&self, rect: Rect, pc: &mut dyn crate::layout::RenderTarget) {
+        let label_color = self.text_color();
+        let srgb = crate::colors::to_srgb(label_color);
+        let color_f32 = [srgb[0], srgb[1], srgb[2], 1.0];
+        let font = Paint::widget_font(self);
 
-        // Draw main background
-        let (r1, r2, r3, r4) = self.rounded_corners();
-        if !r1 && !r2 && !r3 && !r4 {
-            quads.push((self.base.x, self.base.y, self.base.w, self.base.h, self.color()));
-        }
+        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);
+                }
 
-        // 1. Highlight the title on hover or open
-        if !self.context_options.is_empty() {
-            let tr = self.title_rect();
-            if self.context_dropdown_open {
-                quads.push((tr.0, tr.1, tr.2, tr.3, colors::highlight_primary_color()));
-            } else if self.context_title_hovered {
-                quads.push((tr.0, tr.1, tr.2, tr.3, colors::HIGHLIGHT_SECONDARY));
+                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 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);
+                    } else {
+                        pc.text_with_bounds(&text, dx + 8.0, iy, 12.0, color_f32, 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);
             }
-        }
-
-        quads.extend(self.menus.extra_quads());
-        quads
-    }
-
-    fn widget_font(&self) -> Option<String> {
-        Some(crate::layout::menubar_font())
-    }
-
-}
-impl Drop for MenuBar {
-    fn drop(&mut self) {
-        clear_widget_references(self);
-    }
-}
-
-#[derive(Debug, Clone)]
-pub struct Menu {
-    pub base: Widget,
-    pub title: String,
-    pub vertical_title: String,
-    pub items: Vec<String>,
-    pub item_checked: Vec<Option<bool>>,
-    pub open: bool,
-    pub vertical: bool,
-    hovered_item: Option<usize>,
-    clicked_item: Option<usize>,
-    was_open: Option<usize>,
-    pub parent: Option<*mut (dyn Element + 'static)>,
-    pub children: Vec<*mut (dyn Element + 'static)>,
-    pub curved_arc: Option<(f32, f32, f32, f32, f32, f32)>,
-    pub title_buf: Option<glyphon::Buffer>,
-    pub item_bufs: Vec<glyphon::Buffer>,
-    pub check_buf: Option<glyphon::Buffer>,
-    pub curved_char_bufs: Vec<glyphon::Buffer>,
-    pub font_family: String,
-}
-
-impl Menu {
-    pub fn new(title: &str, vertical_title: &str, items: &[String]) -> Self {
-        Self {
-            base: Widget::new(),
-            title: title.to_string(),
-            vertical_title: vertical_title.to_string(),
-            items: items.to_vec(),
-            item_checked: vec![None; items.len()],
-            open: false,
-            vertical: false,
-            hovered_item: None,
-            clicked_item: None,
-            was_open: None,
-            parent: None,
-            children: Vec::new(),
-            curved_arc: None,
-            title_buf: None,
-            item_bufs: Vec::new(),
-            check_buf: None,
-            curved_char_bufs: Vec::new(),
-            font_family: String::new(),
-        }
-    }
-
-    pub fn active_title(&self) -> &str {
-        if self.vertical {
-            &self.vertical_title
-        } else {
-            &self.title
-        }
-    }
-
-    fn dropdown_rect(&self) -> (f32, f32, f32, f32) {
-        let dh = self.items.len() as f32 * DROPDOWN_ITEM_H;
-        let mut max_len = 0;
-        for item in &self.items {
-            max_len = max_len.max(item.len());
-        }
-        let font_setting = crate::layout::menubar_font();
-        let (_, font_size_opt) = crate::layout::parse_font_string(&font_setting);
-        let font_size = font_size_opt.unwrap_or(12.0);
-        let char_w = 7.5 * (font_size / 12.0);
-
-        let dw = (max_len as f32 * char_w + 40.0).max(120.0);
-        let dx = if self.vertical {
-            self.base.x + self.base.w
-        } else {
-            self.base.x
-        };
-        let dy = if self.vertical {
-            self.base.y
-        } else {
-            self.base.y + self.base.h
-        };
-        (dx, dy, dw, dh)
-    }
 
-    pub fn text_color(&self) -> [f32; 4] {
-        if let Some(p_ptr) = self.parent {
-            let mut curr = p_ptr;
-            loop {
-                if unsafe { (*curr).is_backplate() } {
-                    return crate::colors::backplate_menubar_text_color();
-                }
-                let dummy = crate::context::UiContext::new();
-                if let Some(next_p) = unsafe { (*curr).parent(&dummy) } {
-                    curr = next_p;
-                } else {
-                    break;
+            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) {
+                    for (i, option) in items.iter().enumerate() {
+                        let checked = self.menu_dropdown_checked.get(menu_idx)
+                            .and_then(|menu| menu.get(i))
+                            .and_then(|&v| v);
+                        let prefix = match checked {
+                            Some(true) => "✓ ",
+                            Some(false) => "  ",
+                            None => "",
+                        };
+                        let text = format!("{}{}", prefix, option);
+                        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);
+                        } else {
+                            pc.text_with_bounds(&text, dx + 8.0, iy, 12.0, color_f32, bounds);
+                        }
+                    }
                 }
             }
         }
-        crate::colors::menubar_tab_label_color()
     }
 }
-impl Element for Menu {
-    crate::impl_widget_base!(Menu);
 
-    fn label(&self) -> Option<String> {
-        Some(self.active_title().to_string())
-    }
-
-    fn color(&self) -> [f32; 4] {
-        [0.0, 0.0, 0.0, 0.0]
+impl Input for MenuBar {
+    fn blocks_backplate_drag(&self) -> bool {
+        false
     }
 
-    fn is_active(&self) -> bool {
-        self.base.focused || self.open
+    /// Legacy `mouse_input` saw every press: any press closes an open context dropdown, even
+    /// outside the bar.
+    fn gates_presses(&self) -> bool {
+        false
     }
 
-    fn hit_test(&self, px: f32, py: f32, ctx: &UiContext) -> bool {
-        if ctx.is_coordinate_covered(self as *const Self as *const () as usize, px, py) {
-            return false;
-        }
-        if let Some((cx, cy, r, thickness, start_angle, end_angle)) = self.curved_arc {
-            let dx = px - cx;
-            let dy = py - cy;
-            let dist = (dx * dx + dy * dy).sqrt();
-            if dist >= r - thickness && dist <= r {
-                let angle = dy.atan2(dx);
-                let mut norm_angle = angle;
-                if norm_angle < 0.0 {
-                    norm_angle += 2.0 * std::f32::consts::PI;
-                }
-                if norm_angle >= start_angle && norm_angle <= end_angle {
-                    return true;
-                }
-            }
-            if self.open {
-                let (dx, dy, dw, dh) = self.dropdown_rect();
-                if dh > 0.0 && px >= dx && px < dx + dw && py >= dy && py < dy + dh {
-                    return true;
-                }
-            }
+    fn hit(&self, rect: Rect, px: f32, py: f32) -> bool {
+        if !self.visible {
             return false;
         }
-        let (rx, ry, rw, rh) = self.rect();
-        if px >= rx && px <= rx + rw && py >= ry && py <= ry + rh {
-            return true;
-        }
-        if self.open {
-            let (dx, dy, dw, dh) = self.dropdown_rect();
-            if dh > 0.0 && px >= dx && px < dx + dw && py >= dy && py < dy + dh {
+        if let Some((dx, dy, dw, dh)) = self.context_popover_rect(rect) {
+            if px >= dx && px < dx + dw && py >= dy && py < dy + dh {
                 return true;
             }
         }
-        false
-    }
-
-    fn on_cursor_moved(&mut self, px: f32, py: f32, ctx: &mut UiContext) -> bool {
-        self.was_open = None;
-        let was_hovering = self.base.hovered;
-        self.base.hovered = self.hit_test(px, py, ctx);
-        let old_item = self.hovered_item;
-        self.hovered_item = None;
-
-        if self.open {
-            let (dx, dy, dw, dh) = self.dropdown_rect();
+        if let Some((dx, dy, dw, dh)) = self.menu_dropdown_rect() {
             if px >= dx && px < dx + dw && py >= dy && py < dy + dh {
-                let di = ((py - dy) / DROPDOWN_ITEM_H) as usize;
-                if di < self.items.len() {
-                    self.hovered_item = Some(di);
-                }
+                return true;
             }
         }
-
-        was_hovering != self.base.hovered || old_item != self.hovered_item
-    }
-
-    fn mouse_input(&mut self, button: MouseButton, state: ElementState, px: f32, py: f32, ctx: &mut UiContext) -> bool {
-        if button != MouseButton::Left || state != ElementState::Pressed {
-            return false;
-        }
-        if !self.hit_test(px, py, ctx) {
-            return false;
-        }
-
-        // Check dropdown click if open
-        if self.open {
-            let (dx, dy, dw, dh) = self.dropdown_rect();
-            if dh > 0.0 && px >= dx && px < dx + dw && py >= dy && py < dy + dh {
-                let di = ((py - dy) / DROPDOWN_ITEM_H) as usize;
-                if di < self.items.len() {
-                    self.clicked_item = Some(di);
-                    self.open = false;
-                    return true;
-                }
-            }
+        if px >= rect.x && px <= rect.x + rect.width && py >= rect.y && py <= rect.y + rect.height {
+            return true;
         }
+        // The strip may extend past the assigned rect (clamped layouts).
+        let (sx, sy, sw, sh) = self.menus.rect();
+        px >= sx && px <= sx + sw && py >= sy && py <= sy + sh
+    }
 
-        // Since we passed hit_test and didn't click dropdown, it's a click on the header title
-        if self.was_open == Some(0) || self.open {
-            self.open = false;
-            self.was_open = None;
-        } else {
-            self.open = true;
-            self.was_open = None;
-            focus::set_focused(self);
-        }
-        true
+    fn set_modifiers(&mut self, ctrl: bool, shift: bool, alt: bool) {
+        self.menus.set_modifiers(ctrl, shift, alt);
     }
 
-    fn focus(&mut self) {
-        self.open = true;
-        self.base.focused = true;
-        focus::set_focused(self);
+    fn visibility_changed(&mut self, visible: bool) {
+        self.visible = visible;
+        self.menus.set_visible(visible);
+        self.layout_dirty = true;
     }
 
-    fn unfocus(&mut self) {
-        if self.open {
-            self.was_open = Some(0);
-        }
-        self.open = false;
-        self.base.focused = false;
-        focus::clear_if_matches(self);
-        self.hovered_item = None;
+    fn is_focused(&self, _base_focused: bool) -> bool {
+        self.focused || self.context_dropdown_open || self.menus.selected.is_some()
     }
 
     fn set_selected(&mut self, selected: bool) {
-        self.base.focused = selected;
+        self.focused = selected;
         if !selected {
-            self.open = false;
-            self.was_open = None;
-            self.hovered_item = None;
-            focus::clear_if_matches(self);
+            self.menus.set_selected(None);
         }
     }
 
-    fn as_menu_controller(&self) -> Option<&dyn MenuController> { Some(self) }
-    fn as_menu_controller_mut(&mut self) -> Option<&mut dyn MenuController> { Some(self) }
-
-    fn highlight_quad(&self, _ctx: &UiContext) -> Option<(f32, f32, f32, f32, [f32; 4])>{
-        None
-    }
-
-    fn extra_quads(&self) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
-        let mut quads = Vec::new();
-        if self.curved_arc.is_none() {
-            if self.base.focused || self.open {
-                quads.push((self.base.x, self.base.y, self.base.w, self.base.h, colors::highlight_primary_color()));
-            } else if self.base.hovered {
-                quads.push((self.base.x, self.base.y, self.base.w, self.base.h, colors::HIGHLIGHT_SECONDARY));
-            }
-        }
-        if self.open {
-            let (dx, dy, dw, dh) = self.dropdown_rect();
-            if dh > 0.0 {
-                quads.push((dx, dy, dw, dh, colors::popover_bg_color()));
-                if let Some(di) = self.hovered_item {
-                    quads.push((dx, dy + di as f32 * DROPDOWN_ITEM_H, dw, DROPDOWN_ITEM_H, colors::PANEL_MENU_HOVER));
+    fn on_event(&mut self, event: &Event, ectx: &mut EventCtx) -> bool {
+        match event {
+            Event::PointerMove { x: px, y: py, .. } => {
+                if !self.visible {
+                    return false;
                 }
-            }
-        }
-        quads
-    }
+                let (px, py) = (*px, *py);
+                let rect = ectx.rect;
+                self.layout_strip(rect);
 
-    fn extra_arcs(&self) -> Vec<(f32, f32, f32, f32, f32, f32, [f32; 4])> {
-        let mut arcs = Vec::new();
-        if let Some((cx, cy, r, thickness, start_angle, end_angle)) = self.curved_arc {
-            if self.base.hovered && !self.open {
-                arcs.push((cx, cy, r, thickness, start_angle, end_angle, colors::PANEL_MENU_HOVER));
-            }
-        }
-        arcs
-    }
+                let mut changed = false;
 
-    fn text_labels(&self) -> Vec<TextLabel> {
-        let label_color = self.text_color();
-        let srgb = crate::colors::to_srgb(label_color);
-        let text_color = [
-            (srgb[0] * 255.0) as u8,
-            (srgb[1] * 255.0) as u8,
-            (srgb[2] * 255.0) as u8,
-        ];
-        let mut labels = Vec::new();
-        if let Some((cx, cy, r, thickness, start_angle, end_angle)) = self.curved_arc {
-            let r_mid = r - thickness / 2.0;
-            labels.extend(TextLabel::curved_layout(
-                &self.active_title(),
-                cx, cy, r_mid,
-                start_angle, end_angle,
-                12.0,
-                text_color,
-            ));
-        } else if self.vertical {
-            let font_size = 12.0;
-            let line_height = font_size * 1.2;
-            let title = self.active_title();
-            let label_len = title.chars().count() as f32;
-            let total_h = label_len * line_height;
-            let start_y = self.base.y + (self.base.h - total_h) / 2.0;
-            let char_w = crate::widget::display::measure_text("o", font_size);
-            let x_pos = self.base.x + (self.base.w - char_w) / 2.0;
-            for (i, c) in title.chars().enumerate() {
-                let char_str = c.to_string();
-                let y_pos = start_y + i as f32 * line_height;
-                labels.push(TextLabel {
-                    text: char_str,
-                    x: x_pos,
-                    y: y_pos,
-                    font_size,
-                    color: text_color,
-                });
-            }
-        } else {
-            labels.push(TextLabel {
-                text: self.active_title().to_string(),
-                x: self.base.x + crate::layout::paginator_tab_padding_x(),
-                y: self.base.y + 7.0,
-                font_size: 12.0,
-                color: text_color,
-            });
-        }
-        if self.open {
-            let (dx, dy, _, _) = self.dropdown_rect();
-            for (i, item) in self.items.iter().enumerate() {
-                let checked = self.item_checked.get(i).and_then(|&v| v);
-                let prefix = match checked {
-                    Some(true) => "\u{2713} ",
-                    Some(false) => "  ",
-                    None => "",
-                };
-                labels.push(TextLabel {
-                    text: format!("{}{}", prefix, item),
-                    x: dx + 8.0,
-                    y: dy + i as f32 * DROPDOWN_ITEM_H + 5.0,
-                    font_size: 12.0,
-                    color: text_color,
-                });
-            }
-        }
-        labels
-    }
+                let old_title_hovered = self.context_title_hovered;
+                self.context_title_hovered = false;
+                if !self.context_options.is_empty() {
+                    let tr = self.title_rect(rect);
+                    if px >= tr.0 && px <= tr.0 + tr.2 && py >= tr.1 && py <= tr.1 + tr.3 {
+                        self.context_title_hovered = true;
+                    }
+                }
+                if old_title_hovered != self.context_title_hovered {
+                    changed = true;
+                }
 
-    fn set_visible(&mut self, visible: bool) {
-        self.base.hovered = false;
-        if !visible {
-            self.open = false;
-            self.was_open = None;
-            self.hovered_item = None;
-            focus::clear_if_matches(self);
-        }
-    }
+                let old_hovered_item = self.context_hovered_item;
+                self.context_hovered_item = None;
+                if let Some((dx, dy, dw, dh)) = self.context_popover_rect(rect) {
+                    if px >= dx && px < dx + dw && py >= dy && py < dy + dh {
+                        let di = ((py - dy) / DROPDOWN_ITEM_H) as usize;
+                        if di < self.context_options.len() {
+                            self.context_hovered_item = Some(di);
+                        }
+                    }
+                }
+                if old_hovered_item != self.context_hovered_item {
+                    changed = true;
+                }
 
-    fn parent(&self, _ctx: &UiContext) -> Option<*mut (dyn Element + 'static)> {
-        self.parent
-    }
+                let old_hovered_dropdown = self.hovered_dropdown_item;
+                self.hovered_dropdown_item = None;
+                if let Some((dx, dy, dw, dh)) = self.menu_dropdown_rect() {
+                    if px >= dx && px < dx + dw && py >= dy && py < dy + dh {
+                        let di = ((py - dy) / DROPDOWN_ITEM_H) as usize;
+                        if let Some(menu_idx) = self.menus.selected {
+                            if let Some(items) = self.menu_dropdowns.get(menu_idx) {
+                                if di < items.len() {
+                                    self.hovered_dropdown_item = Some(di);
+                                }
+                            }
+                        }
+                    }
+                }
+                if old_hovered_dropdown != self.hovered_dropdown_item {
+                    changed = true;
+                }
 
-    fn set_parent(&mut self, parent: Option<*mut (dyn Element + 'static)>, _ctx: &mut UiContext) {
-        self.parent = parent;
-    }
+                if let Some(ui) = ectx.ui.as_deref_mut() {
+                    if self.menus.cursor_moved(px, py, ui) {
+                        changed = true;
+                    }
+                }
+                changed
+            }
+            Event::MouseButton { button, state, x: px, y: py, .. } => {
+                if !self.visible {
+                    return false;
+                }
+                if *button != MouseButton::Left {
+                    return false;
+                }
+                let (px, py, state) = (*px, *py, *state);
+                let rect = ectx.rect;
+                self.layout_strip(rect);
+
+                let mut changed = false;
+
+                if let Some((dx, dy, dw, dh)) = self.menu_dropdown_rect() {
+                    if px >= dx && px < dx + dw && py >= dy && py < dy + dh {
+                        if state == ElementState::Pressed {
+                            let di = ((py - dy) / DROPDOWN_ITEM_H) as usize;
+                            if let Some(menu_idx) = self.menus.selected {
+                                if let Some(items) = self.menu_dropdowns.get(menu_idx) {
+                                    if di < items.len() {
+                                        self.clicked_dropdown_item = Some((menu_idx, di));
+                                        self.close_all();
+                                        ectx.release_focus();
+                                        return true;
+                                    }
+                                }
+                            }
+                        }
+                        return true;
+                    }
+                }
 
-    fn z_index(&self) -> i32 {
-        100
-    }
+                if let Some((dx, dy, dw, dh)) = self.context_popover_rect(rect) {
+                    if px >= dx && px < dx + dw && py >= dy && py < dy + dh {
+                        if state == ElementState::Pressed {
+                            let di = ((py - dy) / DROPDOWN_ITEM_H) as usize;
+                            if di < self.context_options.len() {
+                                self.context_selected = di;
+                                self.context_just_changed = true;
+                                self.close_all();
+                                ectx.release_focus();
+                                if let Some(ref cb) = self.on_context_change_cb {
+                                    cb(di);
+                                }
+                                return true;
+                            }
+                        }
+                    }
+                }
 
-    fn focused(&self, _ctx: &UiContext) -> bool {
-        self.base.focused
-    }
+                if !self.context_options.is_empty() {
+                    let tr = self.title_rect(rect);
+                    if px >= tr.0 && px <= tr.0 + tr.2 && py >= tr.1 && py <= tr.1 + tr.3 {
+                        if state == ElementState::Pressed {
+                            if self.context_dropdown_open {
+                                self.close_all();
+                                ectx.release_focus();
+                            } else {
+                                self.menus.unfocus();
+                                self.context_dropdown_open = true;
+                                self.sync_focus(ectx);
+                            }
+                        }
+                        return true;
+                    }
+                }
 
-    fn prepare_text(&mut self, fs: &mut glyphon::FontSystem) {
-        let font_setting = crate::layout::menubar_font();
-        if self.font_family != font_setting {
-            self.font_family = font_setting.clone();
-            self.title_buf = None;
-            self.curved_char_bufs.clear();
-            self.item_bufs.clear();
-        }
-        let title_text = self.active_title();
-        let (font_fam, font_size_opt) = crate::layout::parse_font_string(&font_setting);
-        let font_size = font_size_opt.unwrap_or(12.0);
+                if self.context_dropdown_open && state == ElementState::Pressed {
+                    self.close_all();
+                    ectx.release_focus();
+                    changed = true;
+                }
 
-        if let Some((_cx, _cy, _r, _thickness, _start_angle, _end_angle)) = self.curved_arc {
-            if self.curved_char_bufs.len() != title_text.chars().count() {
-                let font_fam_clone = font_fam.clone();
-                self.curved_char_bufs = title_text.chars()
-                    .map(|c| make_widget_text_buffer(fs, &c.to_string(), font_size, &font_fam_clone))
-                    .collect();
+                let old_menu_selected = self.menus.selected;
+                if let Some(ui) = ectx.ui.as_deref_mut() {
+                    if self.menus.mouse_input(MouseButton::Left, state, px, py, ui) {
+                        changed = true;
+                        if self.menus.selected.is_some() && old_menu_selected != self.menus.selected {
+                            self.sync_focus(ectx);
+                        }
+                    }
+                }
+                changed
             }
-            self.title_buf = None;
-        } else if self.vertical {
-            self.title_buf = None;
-            self.curved_char_bufs.clear();
-        } else {
-            if self.title_buf.is_none() {
-                self.title_buf = Some(make_widget_text_buffer(fs, title_text, font_size, &font_fam));
+            Event::KeyInput(key_event) => {
+                if key_event.state != ElementState::Pressed {
+                    return false;
+                }
+                if self.context_dropdown_open {
+                    match key_event.logical_key {
+                        Key::Named(NamedKey::ArrowDown) => {
+                            let current = self.context_hovered_item.unwrap_or(self.context_selected);
+                            if current + 1 < self.context_options.len() {
+                                self.context_hovered_item = Some(current + 1);
+                            } else {
+                                self.context_hovered_item = Some(0);
+                            }
+                            return true;
+                        }
+                        Key::Named(NamedKey::ArrowUp) => {
+                            let current = self.context_hovered_item.unwrap_or(self.context_selected);
+                            if current > 0 {
+                                self.context_hovered_item = Some(current - 1);
+                            } else {
+                                self.context_hovered_item = Some(self.context_options.len() - 1);
+                            }
+                            return true;
+                        }
+                        Key::Named(NamedKey::Enter) | Key::Named(NamedKey::Space) => {
+                            if let Some(idx) = self.context_hovered_item {
+                                self.context_selected = idx;
+                                self.context_just_changed = true;
+                            }
+                            self.close_all();
+                            ectx.release_focus();
+                            return true;
+                        }
+                        Key::Named(NamedKey::Escape) => {
+                            self.close_all();
+                            ectx.release_focus();
+                            return true;
+                        }
+                        _ => {}
+                    }
+                }
+                match ectx.ui.as_deref_mut() {
+                    Some(ui) => self.menus.keyboard_input(key_event, ui),
+                    None => false,
+                }
             }
-            self.curved_char_bufs.clear();
-        }
-
-        if self.open {
-            if self.item_bufs.len() != self.items.len() {
-                let font_fam_clone = font_fam.clone();
-                self.item_bufs = self.items.iter().enumerate().map(|(i, item)| {
-                    let checked = self.item_checked.get(i).and_then(|&v| v);
-                    let prefix = match checked {
-                        Some(true) => "\u{2713} ",
-                        Some(false) => "  ",
-                        None => "",
-                    };
-                    let text = format!("{}{}", prefix, item);
-                    make_widget_text_buffer(fs, &text, font_size, &font_fam_clone)
-                }).collect();
+            // Hosts call `focus()`/`unfocus()` directly; legacy semantics: focus is claimed
+            // conditionally (only while something is open), unfocus closes everything.
+            Event::FocusIn => {
+                self.sync_focus(ectx);
+                true
             }
-        } else {
-            self.item_bufs.clear();
+            Event::FocusOut => {
+                self.close_all();
+                ectx.release_focus();
+                true
+            }
+            _ => false,
         }
     }
 
-    fn get_text_items(&self) -> Vec<(&glyphon::Buffer, f32, f32, glyphon::Color)> {
-        Vec::new()
+    fn menu_controller(&self) -> Option<&dyn MenuController> {
+        Some(self)
     }
-
-    fn widget_font(&self) -> Option<String> {
-        Some(crate::layout::menubar_font())
+    fn menu_controller_mut(&mut self) -> Option<&mut dyn MenuController> {
+        Some(self)
     }
-}
-
-unsafe impl Send for Menu {}
-unsafe impl Sync for Menu {}
-
-impl Drop for Menu {
-    fn drop(&mut self) {
-        clear_widget_references(self);
-    }
-}
-
-impl MenuController for Menu {
-    fn menu_click(&mut self) -> Option<(usize, usize)> {
-        self.clicked_item.take().map(|i| (0, i))
+    fn page_selector(&self) -> Option<&dyn PageSelector> {
+        Some(self)
     }
-    fn trigger_menu_click(&mut self, _menu_idx: usize, item_idx: usize) {
-        if item_idx < self.items.len() {
-            self.clicked_item = Some(item_idx);
-        }
+    fn page_selector_mut(&mut self) -> Option<&mut dyn PageSelector> {
+        Some(self)
     }
-    fn set_item_checked(&mut self, _menu_idx: usize, item_idx: usize, checked: bool) {
-        if item_idx < self.item_checked.len() {
-            self.item_checked[item_idx] = Some(checked);
-            self.item_bufs.clear();
-        }
-    }
-    fn set_menu_items(&mut self, _menu_idx: usize, items: &[String]) {
-        self.items = items.to_vec();
-        self.item_checked = vec![None; items.len()];
-        self.item_bufs.clear();
-    }
-    fn is_menu_bar(&self) -> bool { false }
-    fn is_menu_open(&self) -> bool { self.open }
-    fn menu_items(&self) -> Vec<String> { self.items.clone() }
-    fn menu_item_checked(&self) -> Vec<Option<bool>> { self.item_checked.clone() }
-    fn is_vertical(&self) -> bool { self.vertical }
-    fn menu_names(&self) -> Vec<String> { vec![self.active_title().to_string()] }
-    fn menu_items_list(&self) -> Vec<Vec<String>> { vec![self.items.clone()] }
-    fn menu_checked_list(&self) -> Vec<Vec<Option<bool>>> { vec![self.item_checked.clone()] }
-    fn take_context_change(&mut self) -> Option<usize> { None }
-    fn set_context_selected(&mut self, _selected: usize) {}
-    fn set_center_items(&mut self, _center: bool) {}
-    fn get_menu_items_at(&self, _px: f32, _py: f32) -> Option<(usize, String, Vec<String>, f32, f32, f32, f32)> { None }
 }
 
-
 impl MenuController for MenuBar {
     fn menu_click(&mut self) -> Option<(usize, usize)> {
         let _ = self.menus.take_click();
-        
+
         if let Some((menu_idx, item_idx)) = self.clicked_dropdown_item.take() {
             if let Some(ref cb) = self.on_menu_click_cb {
                 cb(menu_idx, item_idx);
@@ -1769,7 +1167,6 @@ impl PageSelector for MenuBar {
 
     fn set_sidebar_label(&mut self, label: Option<String>) {
         self.label = label.clone();
-        self.base.label = label.clone();
         if self.vertical {
             self.title = label.unwrap_or_default();
             self.label = None;
@@ -1779,3 +1176,67 @@ impl PageSelector for MenuBar {
     fn add_widget_to_page(&mut self, _page_idx: usize, _widget: *mut (dyn Element + 'static), _ctx: &mut UiContext) {}
     fn clear_page_widgets(&mut self, _page_idx: usize, _ctx: &mut UiContext) {}
 }
+
+unsafe impl Send for MenuBar {}
+unsafe impl Sync for MenuBar {}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::context::UiContext;
+
+    fn bar() -> Adapted<MenuBar> {
+        MenuBar::new(0.0, 0.0, 400.0, 24.0)
+            .with_title("Test")
+            .with_item("File", &["New", "Save"])
+            .with_item("Edit", &["Undo"])
+    }
+
+    #[test]
+    fn menu_open_click_and_controller_roundtrip() {
+        let mut ctx = UiContext::new();
+        let mut mb = bar();
+        let (id, ptr) = (mb.id(), mb.as_ptr_mut());
+        ctx.register_widget(id, ptr);
+        Element::set_rect(&mut mb, 0.0, 0.0, 400.0, 24.0);
+
+        // Click the "File" strip button (the strip commits selection on release): the dropdown
+        // opens, the bar reports focused (conditional focus), and a popover rect exists.
+        let (bx, by, bw, bh) = mb.menus.item_rect(0);
+        assert!(bw > 0.0, "strip laid out");
+        assert!(mb.mouse_input(MouseButton::Left, ElementState::Pressed, bx + bw / 2.0, by + bh / 2.0, &mut ctx));
+        assert!(mb.mouse_input(MouseButton::Left, ElementState::Released, bx + bw / 2.0, by + bh / 2.0, &mut ctx));
+        let elem: &dyn Element = &mb;
+        assert!(elem.as_menu_controller().unwrap().is_menu_open(), "dropdown open");
+        assert!(Element::focused(&mb, &ctx), "bar holds focus while open");
+        let (dx, dy, _, _) = Element::popover_rect(&mb).expect("dropdown popover");
+
+        // Click the second item ("Save"): menu_click reports (0, 1) and everything closes.
+        assert!(mb.mouse_input(MouseButton::Left, ElementState::Pressed, dx + 10.0, dy + DROPDOWN_ITEM_H * 1.5, &mut ctx));
+        {
+            let elem: &mut dyn Element = &mut mb;
+            assert_eq!(elem.as_menu_controller_mut().unwrap().menu_click(), Some((0, 1)));
+            assert!(!elem.as_menu_controller().unwrap().is_menu_open());
+        }
+        assert!(!Element::focused(&mb, &ctx), "focus released after the click");
+
+        // The PageSelector capability rides the same hooks.
+        let elem: &dyn Element = &mb;
+        assert!(elem.as_page_selector().unwrap().sidebar_w() > 0.0);
+    }
+
+    #[test]
+    fn hidden_menubar_reports_no_menu_and_rejects_hits() {
+        let mut ctx = UiContext::new();
+        let mut mb = bar();
+        let (id, ptr) = (mb.id(), mb.as_ptr_mut());
+        ctx.register_widget(id, ptr);
+        Element::set_rect(&mut mb, 0.0, 0.0, 400.0, 24.0);
+
+        Element::set_visible(&mut mb, false);
+        let elem: &dyn Element = &mb;
+        assert!(!elem.as_menu_controller().unwrap().is_menu_bar(), "hidden bar is not a menu bar");
+        assert!(!Element::hit_test(&mb, 10.0, 10.0, &ctx));
+        assert!(elem.as_menu_controller().unwrap().get_menu_items_at(10.0, 10.0).is_none());
+    }
+}
diff --git a/src/widget/container/mod.rs b/src/widget/container/mod.rs
index d8e1b0e..43a385b 100644
--- a/src/widget/container/mod.rs
+++ b/src/widget/container/mod.rs
@@ -29,7 +29,7 @@ pub use section_container::SectionContainer;
 pub use header::Header;
 pub use content_bg::ContentBg;
 pub use parameters_bg::ParametersBg;
-pub use menu::{Menu, MenuBar};
+pub use menu::MenuBar;
 pub use breadcrumb::Breadcrumb;
 pub use spreadsheet::Spreadsheet;
 pub use scroll_box::ScrollBox;
diff --git a/src/widget/container/spreadsheet.rs b/src/widget/container/spreadsheet.rs
index 60b65b8..c1cb14d 100644
--- a/src/widget/container/spreadsheet.rs
+++ b/src/widget/container/spreadsheet.rs
@@ -108,7 +108,7 @@ impl Paint for Spreadsheet {
         colors::PARAM_BG
     }
 
-    fn corner_style(&self) -> Option<(f32, (bool, bool, bool, bool))> {
+    fn corner_style(&self, _rect: Rect) -> Option<(f32, (bool, bool, bool, bool))> {
         // Legacy: rounded_corners override (all corners) with the Element-default 12.0 radius.
         Some((12.0, (true, true, true, true)))
     }
diff --git a/src/widget/container/switcher.rs b/src/widget/container/switcher.rs
index 9759fa6..b1e44b0 100644
--- a/src/widget/container/switcher.rs
+++ b/src/widget/container/switcher.rs
@@ -89,7 +89,7 @@ impl Layout for Switcher {
     }
 
     /// The active child fills the switcher's rect.
-    fn arrange_children(&mut self, rect: Rect) {
+    fn arrange_children(&mut self, rect: Rect, _host: *mut (dyn Element + 'static)) {
         if let Some(child) = self.active_child() {
             unsafe {
                 (*child).set_rect(rect.x, rect.y, rect.width, rect.height);
@@ -120,7 +120,7 @@ impl Paint for Switcher {
         }
     }
 
-    fn corner_style(&self) -> Option<(f32, (bool, bool, bool, bool))> {
+    fn corner_style(&self, _rect: Rect) -> Option<(f32, (bool, bool, bool, bool))> {
         // Legacy kept the Element-default 12.0 radius and proxied the corner flags.
         let corners = match self.active_child() {
             Some(child) => unsafe { (*child).rounded_corners() },
diff --git a/src/widget/display/graph.rs b/src/widget/display/graph.rs
index 7e33a66..4b86ffd 100644
--- a/src/widget/display/graph.rs
+++ b/src/widget/display/graph.rs
@@ -588,7 +588,7 @@ impl Paint for Graph {
         self.bg_color()
     }
 
-    fn corner_style(&self) -> Option<(f32, (bool, bool, bool, bool))> {
+    fn corner_style(&self, _rect: Rect) -> Option<(f32, (bool, bool, bool, bool))> {
         Some((WIDGET_RADIUS, WIDGET_CORNERS))
     }
 
diff --git a/src/widget/display/progress_bar.rs b/src/widget/display/progress_bar.rs
index 0907fdf..c3c10aa 100644
--- a/src/widget/display/progress_bar.rs
+++ b/src/widget/display/progress_bar.rs
@@ -30,7 +30,7 @@ impl Paint for ProgressBar {
         colors::progress_bg()
     }
 
-    fn corner_style(&self) -> Option<(f32, (bool, bool, bool, bool))> {
+    fn corner_style(&self, _rect: Rect) -> Option<(f32, (bool, bool, bool, bool))> {
         Some((crate::layout::slider_corner_radius(), (true, true, true, true)))
     }
 
diff --git a/src/widget/input/button.rs b/src/widget/input/button.rs
index e96a23c..7bb92c0 100644
--- a/src/widget/input/button.rs
+++ b/src/widget/input/button.rs
@@ -239,7 +239,7 @@ impl Paint for Button {
         }
     }
 
-    fn corner_style(&self) -> Option<(f32, (bool, bool, bool, bool))> {
+    fn corner_style(&self, _rect: Rect) -> Option<(f32, (bool, bool, bool, bool))> {
         let r = crate::layout::button_corner_radius();
         if r > 0.0 {
             Some((r, (true, true, true, true)))
diff --git a/src/widget/input/checkbox.rs b/src/widget/input/checkbox.rs
index 421983d..52ac0f2 100644
--- a/src/widget/input/checkbox.rs
+++ b/src/widget/input/checkbox.rs
@@ -271,7 +271,7 @@ impl Paint for Toggle {
         colors::toggle_bg_color()
     }
 
-    fn corner_style(&self) -> Option<(f32, (bool, bool, bool, bool))> {
+    fn corner_style(&self, _rect: Rect) -> Option<(f32, (bool, bool, bool, bool))> {
         let r = crate::layout::toggle_corner_radius();
         if r > 0.0 {
             Some((r, (true, true, true, true)))
diff --git a/src/widget/input/slider.rs b/src/widget/input/slider.rs
index 63b9205..c379c26 100644
--- a/src/widget/input/slider.rs
+++ b/src/widget/input/slider.rs
@@ -197,7 +197,7 @@ impl Paint for Slider {
         [0.0, 0.0, 0.0, 0.0]
     }
 
-    fn corner_style(&self) -> Option<(f32, (bool, bool, bool, bool))> {
+    fn corner_style(&self, _rect: Rect) -> Option<(f32, (bool, bool, bool, bool))> {
         let r = crate::layout::slider_corner_radius();
         if r > 0.0 {
             Some((r, (true, true, true, true)))
@@ -496,7 +496,7 @@ impl Paint for RangeSlider {
         [0.0, 0.0, 0.0, 0.0]
     }
 
-    fn corner_style(&self) -> Option<(f32, (bool, bool, bool, bool))> {
+    fn corner_style(&self, _rect: Rect) -> Option<(f32, (bool, bool, bool, bool))> {
         let r = crate::layout::rangeslider_corner_radius();
         if r > 0.0 {
             Some((r, (true, true, true, true)))
diff --git a/src/widget/input/spinbox.rs b/src/widget/input/spinbox.rs
index 6fdfaeb..7777327 100644
--- a/src/widget/input/spinbox.rs
+++ b/src/widget/input/spinbox.rs
@@ -177,7 +177,7 @@ impl Paint for Spinbox {
         [0.0, 0.0, 0.0, 0.0]
     }
 
-    fn corner_style(&self) -> Option<(f32, (bool, bool, bool, bool))> {
+    fn corner_style(&self, _rect: Rect) -> Option<(f32, (bool, bool, bool, bool))> {
         let r = crate::layout::spinbox_corner_radius();
         if r > 0.0 {
             Some((r, (true, true, true, true)))
diff --git a/src/widget/mod.rs b/src/widget/mod.rs
index 6de7ce2..eddfa22 100644
--- a/src/widget/mod.rs
+++ b/src/widget/mod.rs
@@ -834,7 +834,7 @@ pub use self::container::{
     Container, ContainerLayout, OverlayLayout, ManualLayout, VerticalLayout, GridLayout, AdaptiveGridLayout,
     ColumnsLayout, MosaicLayout, ReverseMosaicLayout,
     SectionContainer, Header, ContentBg, ParametersBg, List, ListColumn, ListRow, ColumnWidth,
-    ScrollBox, Menu, MenuBar, Spreadsheet, Breadcrumb, Plate,
+    ScrollBox, MenuBar, Spreadsheet, Breadcrumb, Plate,
     Switcher, Layer, Page, Backplate, Paginator, ScrollBar, TreeList, TreeElement, ControlPanel,
     SplitBox, SplitDirection
 };
diff --git a/src/widget/model.rs b/src/widget/model.rs
index 0bbb22a..3d199a0 100644
--- a/src/widget/model.rs
+++ b/src/widget/model.rs
@@ -29,8 +29,9 @@
 use crate::scene::layout::{Rect, Size, Style};
 use crate::scene::paint::{PaintCtx, Prim};
 use crate::widget::{
-    Element, Event, GeomController, GraphController, MenuController, ParamController,
-    PathController, SpreadsheetController, TextLabel, UiContext, Widget, WidgetId,
+    Element, Event, GeomController, GraphController, MenuController, PageSelector,
+    ParamController, PathController, SpreadsheetController, TextLabel, UiContext, Widget,
+    WidgetId,
 };
 
 /// Layout inputs for the scene layout engine — the RFC's `Widget` concern, named `Layout` here to
@@ -122,8 +123,10 @@ pub trait Layout {
     }
 
     /// Position children after a `set_rect` (no ctx available — use the owned pointers).
-    /// Called only while the widget is visible, matching the legacy overrides.
-    fn arrange_children(&mut self, _rect: Rect) {}
+    /// Called only while the widget is visible, matching the legacy overrides. `host` is the
+    /// adapter's `*mut dyn Element` — widgets that embed a legacy child (MenuBar's
+    /// ButtonStrip) parent it back to the host so legacy parent-chain styling walks work.
+    fn arrange_children(&mut self, _rect: Rect, _host: *mut (dyn Element + 'static)) {}
 
     /// Recursive child layout for the `Element::layout` pass (this one has ctx). Called after
     /// the adapter has measured and placed the container itself, only while visible.
@@ -134,6 +137,20 @@ pub trait Layout {
     fn child_visible(&self, _child: *mut (dyn Element + 'static)) -> bool {
         true
     }
+
+    /// Legacy `Element::z_index` (host render ordering; MenuBar's dropdowns layer at 100+).
+    fn z_order(&self) -> i32 {
+        0
+    }
+
+    /// `Some(parent)` when the model tracks its parent pointer itself (via
+    /// [`parent_changed`](Layout::parent_changed)) — the adapter then serves `Element::parent`
+    /// from it instead of the tree. Legacy widgets with parent-dependent styling walk the
+    /// chain with a DUMMY ctx (MenuBar/ButtonStrip backplate checks), which a tree lookup
+    /// cannot answer. `None` (default): use the tree.
+    fn tracked_parent(&self) -> Option<Option<*mut (dyn Element + 'static)>> {
+        None
+    }
 }
 
 /// The paint concern — a widget's fill color, its own (non-recursive) geometry emission, and
@@ -161,15 +178,26 @@ pub trait Paint {
         false
     }
 
-    /// Corner rounding `(radius, per-corner flags)` of the widget's background. **Transitional:**
-    /// this exists only for legacy render paths that draw widget backgrounds themselves from
-    /// style properties (`widget_vertices` / `push_widget_vertices` readers of
+    /// Corner rounding `(radius, per-corner flags)` of the widget's background, given its
+    /// laid-out rect (MenuBar's corners depend on where it sits against its parent's edges).
+    /// **Transitional:** this exists only for legacy render paths that draw widget backgrounds
+    /// themselves from style properties (`widget_vertices` / `push_widget_vertices` readers of
     /// `Element::corner_radius` + `rounded_corners`) — the widget's real geometry is whatever
     /// [`paint`](Paint::paint) emits. Dies with those paths. Default: sharp corners.
-    fn corner_style(&self) -> Option<(f32, (bool, bool, bool, bool))> {
+    fn corner_style(&self, _rect: Rect) -> Option<(f32, (bool, bool, bool, bool))> {
         None
     }
 
+    /// This widget's OWN popover (dropdown) rect, if one is open — hosts float it above
+    /// z-ordered siblings (`register_popover` + `render_popovers`). Containers combine this
+    /// with their children's popovers in the adapter. Default: none.
+    fn popover(&self, _rect: Rect) -> Option<(f32, f32, f32, f32)> {
+        None
+    }
+
+    /// Draw this widget's own popover (legacy `Element::render_popover`).
+    fn draw_popover(&self, _rect: Rect, _pc: &mut dyn crate::layout::RenderTarget) {}
+
     /// Solid border `(color, thickness)` of the widget's background quad. **Transitional**, like
     /// [`corner_style`](Paint::corner_style): `render_widget` gives a widget's background quad a
     /// border+inset treatment when this is `Some` — `Toggle`'s square mode depends on it.
@@ -242,6 +270,14 @@ impl EventCtx<'_> {
         }
     }
 
+    /// Drop this widget's claim on the global focus if it holds it (legacy
+    /// `focus::clear_if_matches(self)` — MenuBar releases focus when its dropdowns close).
+    pub fn release_focus(&mut self) {
+        if let Some(ptr) = self.self_ptr {
+            unsafe { crate::widget::focus::clear_if_matches(&mut *ptr) };
+        }
+    }
+
     /// Open the shared context menu on this widget (legacy `ctx.handle_right_click(self, …)`),
     /// for widgets that must do work *before* the menu opens — Breadcrumb records which segment
     /// was right-clicked first, so the menu header can show that segment's path.
@@ -432,6 +468,28 @@ pub trait Input {
     /// Copy this widget's path/content to the clipboard — the context menu's "Copy Path" action
     /// calls `Element::copy_path` on its target (Breadcrumb is the only implementor).
     fn copy_path(&self) {}
+
+    /// Keyboard modifier state pushed in by hosts before dispatch (legacy
+    /// `Element::set_modifiers`).
+    fn set_modifiers(&mut self, _ctrl: bool, _shift: bool, _alt: bool) {}
+
+    /// The widget's visibility flag changed through `Element::set_visible` (the adapter owns
+    /// the flag) — legacy hideable widgets used the setter for side effects (MenuBar closes
+    /// its dropdowns and invalidates layout).
+    fn visibility_changed(&mut self, _visible: bool) {}
+
+    /// The widget's `Element::focused` answer, given the base flag — MenuBar reports focused
+    /// while any of its dropdowns is open, beyond the flag itself. Default: the flag.
+    fn is_focused(&self, base_focused: bool) -> bool {
+        base_focused
+    }
+
+    fn page_selector(&self) -> Option<&dyn PageSelector> {
+        None
+    }
+    fn page_selector_mut(&mut self) -> Option<&mut dyn PageSelector> {
+        None
+    }
 }
 
 /// Wraps a narrow-trait widget `W` so it lives in the legacy `*mut dyn Element` tree. Carries the
@@ -681,7 +739,10 @@ impl<W: Layout + Paint + Input + 'static> Element for Adapted<W> {
     }
 
     fn set_visible(&mut self, visible: bool) {
-        self.visible = visible;
+        if self.visible != visible {
+            self.visible = visible;
+            Input::visibility_changed(&mut self.inner, visible);
+        }
     }
     fn visible(&self) -> bool {
         self.visible
@@ -754,6 +815,32 @@ impl<W: Layout + Paint + Input + 'static> Element for Adapted<W> {
         false
     }
 
+    fn z_index(&self) -> i32 {
+        Layout::z_order(&self.inner)
+    }
+
+    fn parent(&self, ctx: &UiContext) -> Option<*mut (dyn Element + 'static)> {
+        if let Some(tracked) = Layout::tracked_parent(&self.inner) {
+            return tracked;
+        }
+        ctx.tree.parent_ptr(self.base.id())
+    }
+
+    fn set_modifiers(&mut self, ctrl: bool, shift: bool, alt: bool) {
+        Input::set_modifiers(&mut self.inner, ctrl, shift, alt)
+    }
+
+    fn focused(&self, _ctx: &UiContext) -> bool {
+        Input::is_focused(&self.inner, self.base.focused)
+    }
+
+    fn as_page_selector(&self) -> Option<&dyn PageSelector> {
+        Input::page_selector(&self.inner)
+    }
+    fn as_page_selector_mut(&mut self) -> Option<&mut dyn PageSelector> {
+        Input::page_selector_mut(&mut self.inner)
+    }
+
     fn layout(&mut self, origin: crate::widget::Point, constraints: crate::widget::LayoutConstraints, ctx: &mut UiContext) {
         // The Element default (measure + set_rect), plus recursive child layout for visible
         // containers — the ctx-carrying half of the arrangement the model can't do in
@@ -788,13 +875,15 @@ impl<W: Layout + Paint + Input + 'static> Element for Adapted<W> {
         if !self.visible() {
             return None;
         }
-        self.visible_children().into_iter().find_map(|c| unsafe { &*c }.popover_rect())
+        Paint::popover(&self.inner, self.content_rect())
+            .or_else(|| self.visible_children().into_iter().find_map(|c| unsafe { &*c }.popover_rect()))
     }
 
     fn render_popover(&self, pc: &mut dyn crate::layout::RenderTarget) {
         if !self.visible() {
             return;
         }
+        Paint::draw_popover(&self.inner, self.content_rect(), pc);
         for child in self.visible_children() {
             unsafe { &*child }.render_popover(pc);
         }
@@ -835,7 +924,8 @@ impl<W: Layout + Paint + Input + 'static> Element for Adapted<W> {
         // overrides); hidden containers skip it, like the legacy impls.
         if self.visible {
             let content = self.content_rect();
-            Layout::arrange_children(&mut self.inner, content);
+            let host = self.as_ptr_mut();
+            Layout::arrange_children(&mut self.inner, content, host);
         }
     }
 
@@ -872,10 +962,10 @@ impl<W: Layout + Paint + Input + 'static> Element for Adapted<W> {
     }
     fn corner_radius(&self) -> f32 {
         // 12.0 mirrors the `Element` default for widgets without a corner style.
-        Paint::corner_style(&self.inner).map_or(12.0, |(r, _)| r)
+        Paint::corner_style(&self.inner, self.content_rect()).map_or(12.0, |(r, _)| r)
     }
     fn rounded_corners(&self) -> (bool, bool, bool, bool) {
-        Paint::corner_style(&self.inner).map_or((false, false, false, false), |(_, c)| c)
+        Paint::corner_style(&self.inner, self.content_rect()).map_or((false, false, false, false), |(_, c)| c)
     }
     fn solid_border(&self) -> Option<([f32; 4], f32)> {
         Paint::solid_border(&self.inner)
@@ -1208,12 +1298,14 @@ impl<W: Layout + Paint + Input + 'static> Element for Adapted<W> {
     /// tell the widget via the same `FocusIn`/`FocusOut` events the router would send.
     fn focus(&mut self) {
         self.base.focused = true;
-        let mut ectx = EventCtx { rect: self.content_rect(), id: self.base.id(), ui: None, self_ptr: None };
+        let self_ptr = self.as_ptr_mut();
+        let mut ectx = EventCtx { rect: self.content_rect(), id: self.base.id(), ui: None, self_ptr: Some(self_ptr) };
         Input::on_event(&mut self.inner, &Event::FocusIn, &mut ectx);
     }
     fn unfocus(&mut self) {
         self.base.focused = false;
-        let mut ectx = EventCtx { rect: self.content_rect(), id: self.base.id(), ui: None, self_ptr: None };
+        let self_ptr = self.as_ptr_mut();
+        let mut ectx = EventCtx { rect: self.content_rect(), id: self.base.id(), ui: None, self_ptr: Some(self_ptr) };
         Input::on_event(&mut self.inner, &Event::FocusOut, &mut ectx);
     }