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

commit0b178118b9e5a55cddbd2df78ba29dcf7a29c495
parent5df2fe8d5f
authorLucas Galante <[email protected]>
date2026-07-09 15:48
feat: root Backplate DISSOLVED (Phase 6r)

Per-child render_widget through the same immediate-mode path the root
recursion used, replicating the legacy aggregate's GLOBAL tuple order:
every plain child quad, then the dissolved plate's translucent wash,
then every rounded child quad (partitioned by radius — the muted look
depends on the wash sitting over the plain content). Top-level widgets
register parentless; window dragging falls back to drag_allowed_at.
A/B: AE=0; view-dropdown popover with occlusion and breadcrumb context
menu re-verified live.

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

 src/main.rs | 94 +++++++++++++++++++++++++++++++++++++++++++++++--------------
 1 file changed, 73 insertions(+), 21 deletions(-)

diff --git a/src/main.rs b/src/main.rs
index c3b20ec..9378d0b 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -324,7 +324,6 @@ struct FilesystemApp {
     fs_service: services::fs::FsService,
     context_menu: ContextMenu,
     open_with_dialog: Option<(std::path::PathBuf, cce_ui::widget::Adapted<cce_ui::widget::TextBox>)>,
-    root_window: cce_ui::widget::Backplate,
     browse_splitter: cce_ui::widget::SplitBox,
     network_splitter: cce_ui::widget::SplitBox,
     browse_container: BrowseContainer,
@@ -403,11 +402,9 @@ impl FilesystemApp {
         cce_ui::widget::hover_animation::reset_frame_registration();
         cce_ui::widget::hover_animation::set_cursor_pos(self.cursor_x, self.cursor_y);
 
-        // Update root window size, background color, opacity
-        self.root_window.set_rect(0.0, 0.0, self.width as f32, self.height as f32);
-
-        // Rebuild Element Focus Hierarchy
-        self.root_window.clear_children(&mut self.ui_context);
+        // Root Backplate DISSOLVED: top-level widgets register parentless below; the
+        // window plate tuple is emitted in the legacy aggregate order (after the plain
+        // child quads).
 
         // Clear all widgets' hierarchy links
         self.paginator.clear_children(&mut self.ui_context); self.paginator.set_parent(None, &mut self.ui_context);
@@ -443,10 +440,17 @@ impl FilesystemApp {
             self.height as f32 - 32.0
         };
 
-        if has_sidebar {
-            link_parent_child(&mut self.root_window, &mut self.paginator, &mut self.ui_context);
+        {
+            let self_ptr = self as *mut Self;
+            unsafe {
+                if has_sidebar {
+                    self.ui_context.register_widget((*self_ptr).paginator.base().unwrap().id(), (*self_ptr).paginator.as_ptr_mut());
+                    (*self_ptr).paginator.set_parent(None, &mut self.ui_context);
+                }
+                self.ui_context.register_widget((*self_ptr).view_dropdown.base().unwrap().id(), (*self_ptr).view_dropdown.as_ptr_mut());
+                (*self_ptr).view_dropdown.set_parent(None, &mut self.ui_context);
+            }
         }
-        link_parent_child(&mut self.root_window, &mut self.view_dropdown, &mut self.ui_context);
 
         match self.current_page {
             Page::Browse => {
@@ -454,7 +458,13 @@ impl FilesystemApp {
                     self.browse_splitter.add_child_with_proportion(&mut self.browse_container, 0.49, 100.0);
                     self.browse_splitter.add_child_with_proportion(&mut self.preview, 0.51, 100.0);
                 }
-                link_parent_child(&mut self.root_window, &mut self.browse_splitter, &mut self.ui_context);
+                {
+                    let self_ptr = self as *mut Self;
+                    unsafe {
+                        self.ui_context.register_widget((*self_ptr).browse_splitter.base().unwrap().id(), (*self_ptr).browse_splitter.as_ptr_mut());
+                        (*self_ptr).browse_splitter.set_parent(None, &mut self.ui_context);
+                    }
+                }
                 self.browse_splitter.set_rect(browse_x, content_y, usable_w, content_h);
             }
             Page::Network => {
@@ -462,13 +472,20 @@ impl FilesystemApp {
                     self.network_splitter.add_child_with_proportion(&mut self.network_container, 0.49, 100.0);
                     self.network_splitter.add_child_with_proportion(&mut self.preview, 0.51, 100.0);
                 }
-                link_parent_child(&mut self.root_window, &mut self.network_splitter, &mut self.ui_context);
+                {
+                    let self_ptr = self as *mut Self;
+                    unsafe {
+                        self.ui_context.register_widget((*self_ptr).network_splitter.base().unwrap().id(), (*self_ptr).network_splitter.as_ptr_mut());
+                        (*self_ptr).network_splitter.set_parent(None, &mut self.ui_context);
+                    }
+                }
                 self.network_splitter.set_rect(browse_x, content_y, usable_w, content_h);
             }
         }
 
         if let Some((_, textbox)) = &mut self.open_with_dialog {
-            link_parent_child(&mut self.root_window, textbox, &mut self.ui_context);
+            self.ui_context.register_widget(textbox.base().unwrap().id(), textbox.as_ptr_mut());
+            textbox.set_parent(None, &mut self.ui_context);
         }
 
         // Layout widgets recursively inside the parent space
@@ -479,9 +496,48 @@ impl FilesystemApp {
             cce_ui::layout::render_widget(&mut dummy_pc, &mut self.paginator, 0.0, 0.0, sidebar_w, self.height as f32, &mut self.ui_context);
         }
 
-        // Render root window recursively
+        // Each top-level widget rendered through the same immediate-mode path the root
+        // recursion used, replicating the legacy TUPLE ORDER: plain child quads first,
+        // then the dissolved root Backplate's plate, then the rounded children (the
+        // aggregate emitted all plain quads before the rounded root bg).
         let mut window_pc = pages::PageContent::new();
-        cce_ui::layout::render_widget(&mut window_pc, &mut self.root_window, 0.0, 0.0, self.width as f32, self.height as f32, &mut self.ui_context);
+        {
+            let self_ptr = self as *mut Self;
+            unsafe {
+                let mut plain_pc = pages::PageContent::new();
+                let (x, y, w, h) = (*self_ptr).view_dropdown.rect();
+                cce_ui::layout::render_widget(&mut plain_pc, &mut (*self_ptr).view_dropdown, x, y, w, h, &mut self.ui_context);
+                match self.current_page {
+                    Page::Browse => {
+                        let (x, y, w, h) = (*self_ptr).browse_splitter.rect();
+                        cce_ui::layout::render_widget(&mut plain_pc, &mut (*self_ptr).browse_splitter, x, y, w, h, &mut self.ui_context);
+                    }
+                    Page::Network => {
+                        let (x, y, w, h) = (*self_ptr).network_splitter.rect();
+                        cce_ui::layout::render_widget(&mut plain_pc, &mut (*self_ptr).network_splitter, x, y, w, h, &mut self.ui_context);
+                    }
+                }
+                if let Some((_, textbox)) = &mut (*self_ptr).open_with_dialog {
+                    let (x, y, w, h) = textbox.rect();
+                    cce_ui::layout::render_widget(&mut plain_pc, textbox, x, y, w, h, &mut self.ui_context);
+                }
+
+                // Replicate the legacy aggregate's GLOBAL order: every plain child quad,
+                // then the dissolved root plate (its translucent wash sat over the plain
+                // content), then every rounded child quad.
+                let (plain, rounded): (Vec<_>, Vec<_>) = plain_pc.rects.into_iter().partition(|r| r.5 <= 0.1);
+                window_pc.rects.extend(plain);
+                let mut c = cce_ui::color::page_low_color();
+                if c[3] > 0.001 {
+                    c[3] = cce_ui::color::active_backplate_opacity();
+                }
+                let radius = cce_ui::color::backplate_corner_radius();
+                window_pc.rects.push((c, 0.0, 0.0, self.width as f32, self.height as f32, radius.max(0.0), (radius > 0.1, radius > 0.1, radius > 0.1, radius > 0.1)));
+                window_pc.rects.extend(rounded);
+                window_pc.texts.extend(plain_pc.texts);
+                window_pc.buttons.extend(plain_pc.buttons);
+            }
+        }
 
         // 3. Draw Page custom/static content (drawn to pc)
         let mut pc = pages::PageContent::new();
@@ -815,8 +871,9 @@ impl Application for FilesystemApp {
                 return false;
             }
         }
-        // 5. Fallback to ui_context's check for registered widgets
-        self.ui_context.is_movable_backplate_at(px, py)
+        // 5. Root Backplate dissolved: the surface itself is the movable plate; drag
+        // anywhere a drag-blocking widget isn't.
+        self.ui_context.drag_allowed_at(px, py)
     }
 
     fn new(_qh: &QueueHandle<cce_ui::engine::EngineState<Self>>, sender: calloop::channel::Sender<Self::Message>) -> Self {
@@ -841,10 +898,6 @@ impl Application for FilesystemApp {
         let fs_service = services::fs::FsService::new(sender.clone());
         let initial_w = if select_mode { 900 } else { 1200 };
         let initial_h = if select_mode { 500 } else { 720 };
-        let root_window = cce_ui::widget::Backplate::new(0.0, 0.0, initial_w as f32, initial_h as f32)
-            .with_background(cce_ui::color::page_low_color())
-            .with_radius(cce_ui::color::backplate_corner_radius());
-
         let mut app = Self {
             current_page: Page::Browse,
             browse,
@@ -879,7 +932,6 @@ impl Application for FilesystemApp {
                 hovered: None,
             },
             open_with_dialog: None,
-            root_window,
             browse_splitter: cce_ui::widget::SplitBox::new(cce_ui::widget::SplitDirection::Horizontal, 12.0),
             network_splitter: cce_ui::widget::SplitBox::new(cce_ui::widget::SplitDirection::Horizontal, 12.0),
             browse_container: BrowseContainer {