git.lucas.co / cce-files
file manager
git clone https://git.lucas.co/cce-files.git

commit0971c34e9a229092fb49a302af3833a96ab18a19
parentd2524aa225
authorLucas Galante <[email protected]>
date2026-07-31 15:05
fix: draw and route the toolkit context menu — breadcrumb menu had no text

Right-clicking the breadcrumb opened TWO menus: the toolkit's shared
context menu (which this app never drew — invisible, but its rect still
clamps display-list text beneath it in the engine) and an app-side
duplicate whose full-window-bounds labels that clamp then swallowed —
a menu box with no text.

Single-source it on the toolkit menu, the cce-graph pattern:
display_list appends extra_quads + text_labels (labels carry bounds
equal to the menu rect, the clamp's exemption), pointer moves and
clicks route to context_menu:: exclusively while open, and the
app-side breadcrumb menu duplicate is gone.

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

 src/main.rs | 69 +++++++++++++++++++++++++++++++++++++++++++------------------
 1 file changed, 49 insertions(+), 20 deletions(-)

diff --git a/src/main.rs b/src/main.rs
index 2669e9c..d641bb0 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1290,6 +1290,33 @@ impl Application for FilesystemApp {
                 *bounds,
             );
         }
+        // The toolkit's shared context menu (breadcrumb segments, config-bound
+        // controls) draws into the app's display list like every popover. Its
+        // labels carry bounds equal to the menu rect — the engine's popover
+        // occlusion clamp exempts exactly that, so they render inside the menu
+        // while page text beneath stays clamped.
+        if cce_ui::widget::context_menu::is_visible() {
+            let menu_bounds = Some([
+                cce_ui::widget::context_menu::x(),
+                cce_ui::widget::context_menu::y(),
+                cce_ui::widget::context_menu::x() + cce_ui::widget::context_menu::w(),
+                cce_ui::widget::context_menu::y() + cce_ui::widget::context_menu::h(),
+            ]);
+            for (qx, qy, qw, qh, qc) in cce_ui::widget::context_menu::extra_quads() {
+                pc.quad(Rect { x: qx, y: qy, width: qw, height: qh }, qc);
+            }
+            for label in cce_ui::widget::context_menu::text_labels() {
+                pc.text_with(
+                    label.text.clone(),
+                    label.x,
+                    label.y,
+                    label.font_size,
+                    label.color,
+                    None,
+                    menu_bounds,
+                );
+            }
+        }
         Some(pc.finish())
     }
 
@@ -1319,6 +1346,15 @@ impl Application for FilesystemApp {
             return;
         }
 
+        // The toolkit's shared context menu gets the pointer exclusively while open.
+        if cce_ui::widget::context_menu::is_visible() {
+            if cce_ui::widget::context_menu::cursor_moved(pos.x, pos.y) {
+                *needs_rebuild = true;
+                self.needs_rebuild = true;
+            }
+            return;
+        }
+
         if self.context_menu.visible {
             let cx = self.context_menu.x;
             let cy = self.context_menu.y;
@@ -1434,6 +1470,16 @@ impl Application for FilesystemApp {
             return None;
         }
 
+        // The toolkit's shared context menu (breadcrumb, config-bound controls)
+        // swallows the click — select or dismiss — before any widget routing.
+        if cce_ui::widget::context_menu::is_visible() {
+            if cce_ui::widget::context_menu::mouse_input(button, state, pos.x, pos.y, Some(&mut self.ui_context)) {
+                *needs_rebuild = true;
+                self.needs_rebuild = true;
+            }
+            return None;
+        }
+
         if let Some((_path, textbox)) = &mut self.open_with_dialog {
             let r = open_with_rects(self.width as f32, self.height as f32);
             let (dialog_x, dialog_y, dialog_w, dialog_h) = (r.x, r.y, r.w, r.h);
@@ -1525,27 +1571,10 @@ impl Application for FilesystemApp {
             if breadcrumb.hit_test(pos.x, pos.y, &self.ui_context) {
                 let ev = cce_ui::widget::Event::MouseButton { button, state, x: pos.x, y: pos.y, local_x: pos.x, local_y: pos.y };
                 let root = breadcrumb.id();
+                // The breadcrumb opens the toolkit's shared context menu itself
+                // (open_context_menu → segment header + Copy Path); the app just
+                // routes the event and redraws — no app-side menu duplicate.
                 if self.ui_context.propagate_event(&ev, root) {
-                    let idx = breadcrumb.right_clicked_seg.unwrap_or(breadcrumb.path.len());
-                    let path_str = breadcrumb.path_to_seg(idx);
-                    
-                    let header = format!("[Breadcrumb]: {}", path_str);
-                    let options = vec![
-                        (header, None),
-                        ("Copy Path".to_string(), Some(Message::CopyPath(path_str))),
-                    ];
-
-                    let (menu_w, menu_h) = context_menu_size(&options);
-
-                    self.context_menu = ContextMenu {
-                        visible: true,
-                        x: pos.x,
-                        y: pos.y,
-                        w: menu_w,
-                        h: menu_h,
-                        options,
-                        hovered: None,
-                    };
                     *needs_rebuild = true;
                     self.needs_rebuild = true;
                     return None;