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

commite70916e5ca6e463b81e7f915352c6a7a5af5228a
parent0b178118b9
authorLucas Galante <[email protected]>
date2026-07-10 10:26
feat: SplitBox + pane containers DISSOLVED (Phase 6y) — app-owned SplitPane

The two SplitBoxes and the BrowseContainer/NetworkContainer shims reduce to an
app-owned SplitPane (frac + divider drag/hover + divider quad, the SplitBox
two-child horizontal math verbatim). The pages always laid out and rendered the
left pane's content from the pane rect — the containers' copies just coincided
underneath (the Phase 0 double-paint) — so the window assembly now emits only
the divider quad and the preview pane (render_widget at the right pane rect,
text clamped to the pane like the legacy SplitBox bounds clamp).

Fixes on the way: the left pane's under-copy is gone (every translucent quad was
double-composited), including the NetworkContainer's full-width breadcrumb-copy
strip that visibly leaked behind the graph page's top bar — the class the
Phase 0 stopgap patched for Browse only.

A/B: browse page pixels structurally identical (zero >8%-amplitude diffs);
network page's only >8% diff is the removed leak strip. Live-verified: row
select + double-click navigation, view-dropdown page switch, divider hover tint
(hover-off vs hover-on crop diff = the 2px tint), preview populate.
Held divider drag not headlessly drivable — user spot-check pending.
VERIFY NOTE: wlrctl warp lands as Enter without Motion — nudge (move 2 2)
after warping or hover state never updates.

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

 src/main.rs | 365 ++++++++++++++++++++++++------------------------------------
 1 file changed, 143 insertions(+), 222 deletions(-)

diff --git a/src/main.rs b/src/main.rs
index 9378d0b..906f1c7 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -136,161 +136,111 @@ struct ContextMenu {
     hovered: Option<usize>,
 }
 
-struct BrowseContainer {
-    pub base: cce_ui::widget::Widget,
-    pub parent: Option<*mut (dyn cce_ui::widget::Element + 'static)>,
-    pub breadcrumb: *mut cce_ui::widget::Adapted<cce_ui::widget::Breadcrumb>,
-    pub list_box: *mut cce_ui::widget::List,
-    pub save_name_box: *mut cce_ui::widget::Adapted<cce_ui::widget::TextBox>,
-    pub select_mode: bool,
+/// App-owned two-pane horizontal split, replacing the dissolved `SplitBox` +
+/// `BrowseContainer`/`NetworkContainer` shims (Phase 6y). Those existed to (a) position
+/// pane content — but the pages already lay out and render everything from the pane rect,
+/// the container copies just coincided (the Phase 0 double-paint) — and (b) own the
+/// divider: its quad, hover tint, and proportion drag. This is (b), app-side, with the
+/// `SplitBox` two-child horizontal math verbatim.
+struct SplitPane {
+    x: f32,
+    y: f32,
+    w: f32,
+    h: f32,
+    /// Left pane's share of the space (proportions summed to 1.0 in the legacy SplitBox).
+    frac: f32,
+    min_left: f32,
+    min_right: f32,
+    gap: f32,
+    dragging: bool,
+    hovered: bool,
 }
 
-impl cce_ui::widget::Element for BrowseContainer {
-    cce_ui::impl_widget_base!(BrowseContainer);
-
-    fn color(&self) -> [f32; 4] {
-        [0.0, 0.0, 0.0, 0.0]
+impl SplitPane {
+    fn new(frac: f32, min_left: f32, min_right: f32, gap: f32) -> Self {
+        Self { x: 0.0, y: 0.0, w: 0.0, h: 0.0, frac, min_left, min_right, gap, dragging: false, hovered: false }
     }
 
-    fn children(&self, _ctx: &cce_ui::widget::UiContext) -> Vec<*mut (dyn cce_ui::widget::Element + 'static)> {
-        let mut list = vec![self.breadcrumb as *mut (dyn cce_ui::widget::Element + 'static), self.list_box as *mut (dyn cce_ui::widget::Element + 'static)];
-        if self.select_mode {
-            list.push(self.save_name_box as *mut (dyn cce_ui::widget::Element + 'static));
-        }
-        list
+    fn set_rect(&mut self, x: f32, y: f32, w: f32, h: f32) {
+        self.x = x;
+        self.y = y;
+        self.w = w;
+        self.h = h;
     }
 
-    fn parent(&self, _ctx: &cce_ui::widget::UiContext) -> Option<*mut (dyn cce_ui::widget::Element + 'static)> {
-        self.parent
+    fn left_w(&self) -> f32 {
+        self.frac * (self.w - self.gap).max(0.0)
     }
 
-    fn set_parent(&mut self, parent: Option<*mut (dyn cce_ui::widget::Element + 'static)>, ctx: &mut cce_ui::widget::UiContext) {
-        self.parent = parent;
-        if parent.is_some() {
-            let self_ptr = self as *mut Self;
-            let self_id = self.base.id();
-            unsafe {
-                let bc_id = (*self.breadcrumb).base().unwrap().id();
-                ctx.register_widget(bc_id, self.breadcrumb as *mut (dyn cce_ui::widget::Element + 'static));
-                ctx.link_ids(self_id, bc_id);
-                (*self.breadcrumb).set_parent(Some(self_ptr), ctx);
-
-                let lb_id = (*self.list_box).base().unwrap().id();
-                ctx.register_widget(lb_id, self.list_box as *mut (dyn cce_ui::widget::Element + 'static));
-                ctx.link_ids(self_id, lb_id);
-                (*self.list_box).set_parent(Some(self_ptr), ctx);
-
-                if self.select_mode {
-                    let sn_id = (*self.save_name_box).base().unwrap().id();
-                    ctx.register_widget(sn_id, self.save_name_box as *mut (dyn cce_ui::widget::Element + 'static));
-                    ctx.link_ids(self_id, sn_id);
-                    (*self.save_name_box).set_parent(Some(self_ptr), ctx);
-                }
-            }
-        }
+    fn left_rect(&self) -> (f32, f32, f32, f32) {
+        (self.x, self.y, self.left_w(), self.h)
     }
 
-    fn set_rect(&mut self, x: f32, y: f32, w: f32, h: f32) {
-        self.base.x = x;
-        self.base.y = y;
-        self.base.w = w;
-        self.base.h = h;
-
-        let gap = 12.0;
-        let breadcrumb_h = cce_ui::layout::button_height();
-        let textbox_h = 24.0;
-
-        unsafe {
-            // pages/browse::view re-renders the breadcrumb and the view dropdown on top of this
-            // container's paint. Position the breadcrumb here to EXACTLY match that layout
-            // (inset by the page margin, reserving the dropdown's width beside it) so this
-            // container's copy sits fully behind the page copy instead of leaking a dark strip
-            // behind the dropdown and margins. Keep these constants in sync with pages/browse.rs.
-            let page_margin = 12.0;
-            let dropdown_w = 120.0;
-            let page_breadcrumb_h = 24.0;
-            let inner_x = x + page_margin;
-            let inner_y = y + page_margin;
-            let inner_w = w - 2.0 * page_margin;
-            let bc_w = inner_w - dropdown_w - gap;
-            (*self.breadcrumb).set_rect(inner_x, inner_y, bc_w, page_breadcrumb_h);
-            let list_h = if self.select_mode {
-                h - breadcrumb_h - textbox_h - 2.0 * gap
-            } else {
-                h - breadcrumb_h - gap
-            };
-            (*self.list_box).set_rect(x, y + breadcrumb_h + gap, w, list_h);
-
-            if self.select_mode {
-                (*self.save_name_box).set_rect(x, y + h - textbox_h, w, textbox_h);
-                (*self.save_name_box).set_row_rect(x, w);
-            }
-        }
-    }
-}
-
-unsafe impl Send for BrowseContainer {}
-unsafe impl Sync for BrowseContainer {}
-
-struct NetworkContainer {
-    pub base: cce_ui::widget::Widget,
-    pub parent: Option<*mut (dyn cce_ui::widget::Element + 'static)>,
-    pub breadcrumb: *mut cce_ui::widget::Adapted<cce_ui::widget::Breadcrumb>,
-    pub graph: *mut cce_ui::widget::Adapted<cce_ui::widget::Graph>,
-}
-
-impl cce_ui::widget::Element for NetworkContainer {
-    cce_ui::impl_widget_base!(NetworkContainer);
-
-    fn color(&self) -> [f32; 4] {
-        [0.0, 0.0, 0.0, 0.0]
+    fn right_rect(&self) -> (f32, f32, f32, f32) {
+        let lx = self.x + self.left_w() + self.gap;
+        (lx, self.y, (self.x + self.w - lx).max(0.0), self.h)
     }
 
-    fn children(&self, _ctx: &cce_ui::widget::UiContext) -> Vec<*mut (dyn cce_ui::widget::Element + 'static)> {
-        vec![self.breadcrumb as *mut (dyn cce_ui::widget::Element + 'static), self.graph as *mut (dyn cce_ui::widget::Element + 'static)]
+    fn divider_rect(&self) -> (f32, f32, f32, f32) {
+        (self.x + self.left_w(), self.y, self.gap, self.h)
     }
 
-    fn parent(&self, _ctx: &cce_ui::widget::UiContext) -> Option<*mut (dyn cce_ui::widget::Element + 'static)> {
-        self.parent
+    fn hit_divider(&self, px: f32, py: f32) -> bool {
+        let (sx, sy, sw, sh) = self.divider_rect();
+        px >= sx && px <= sx + sw && py >= sy && py <= sy + sh
     }
 
-    fn set_parent(&mut self, parent: Option<*mut (dyn cce_ui::widget::Element + 'static)>, ctx: &mut cce_ui::widget::UiContext) {
-        self.parent = parent;
-        if parent.is_some() {
-            let self_ptr = self as *mut Self;
-            let self_id = self.base.id();
-            unsafe {
-                let bc_id = (*self.breadcrumb).base().unwrap().id();
-                ctx.register_widget(bc_id, self.breadcrumb as *mut (dyn cce_ui::widget::Element + 'static));
-                ctx.link_ids(self_id, bc_id);
-                (*self.breadcrumb).set_parent(Some(self_ptr), ctx);
-
-                let g_id = (*self.graph).base().unwrap().id();
-                ctx.register_widget(g_id, self.graph as *mut (dyn cce_ui::widget::Element + 'static));
-                ctx.link_ids(self_id, g_id);
-                (*self.graph).set_parent(Some(self_ptr), ctx);
+    /// Divider drag + hover (`SplitBox::on_cursor_moved`, two-child horizontal case).
+    fn cursor_moved(&mut self, px: f32, py: f32) -> bool {
+        let mut handled = false;
+        if self.dragging {
+            let combined = (self.w - self.gap).max(0.0);
+            if combined > 0.1 {
+                let new_left = (px - self.x - self.gap / 2.0)
+                    .clamp(self.min_left, (combined - self.min_right).max(self.min_left));
+                let new_frac = new_left / combined;
+                if (new_frac - self.frac).abs() > 0.0001 {
+                    self.frac = new_frac;
+                    handled = true;
+                }
             }
         }
+        let new_hovered = !self.dragging && self.hit_divider(px, py);
+        if new_hovered != self.hovered {
+            self.hovered = new_hovered;
+            handled = true;
+        }
+        handled
     }
 
-    fn set_rect(&mut self, x: f32, y: f32, w: f32, h: f32) {
-        self.base.x = x;
-        self.base.y = y;
-        self.base.w = w;
-        self.base.h = h;
+    /// Left press on the divider grabs it (`SplitBox::mouse_input`).
+    fn press(&mut self, px: f32, py: f32) -> bool {
+        if self.hit_divider(px, py) {
+            self.dragging = true;
+            return true;
+        }
+        false
+    }
 
-        let gap = 12.0;
-        let breadcrumb_h = cce_ui::layout::button_height();
+    /// Returns whether a drag was in progress (the legacy release consumed the event).
+    fn release(&mut self) -> bool {
+        std::mem::take(&mut self.dragging)
+    }
 
-        unsafe {
-            (*self.breadcrumb).set_rect(x, y, w, breadcrumb_h);
-            (*self.graph).set_rect(x, y + breadcrumb_h + gap, w, h - breadcrumb_h - gap);
+    /// The divider quad (`SplitBox::extra_quads`): accent while dragging, tint on hover,
+    /// hairline otherwise.
+    fn divider_quad(&self) -> (f32, f32, f32, f32, [f32; 4]) {
+        let (sx, sy, sw, sh) = self.divider_rect();
+        if self.dragging {
+            (sx + sw / 2.0 - 1.0, sy, 2.0, sh, [0.36, 0.56, 0.38, 0.8])
+        } else if self.hovered {
+            (sx + sw / 2.0 - 1.0, sy, 2.0, sh, [0.25, 0.25, 0.32, 0.6])
+        } else {
+            (sx + sw / 2.0 - 0.5, sy, 1.0, sh, [0.15, 0.15, 0.18, 0.4])
         }
     }
 }
 
-unsafe impl Send for NetworkContainer {}
-unsafe impl Sync for NetworkContainer {}
 
 struct FilesystemApp {
     current_page: Page,
@@ -324,10 +274,8 @@ 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>)>,
-    browse_splitter: cce_ui::widget::SplitBox,
-    network_splitter: cce_ui::widget::SplitBox,
-    browse_container: BrowseContainer,
-    network_container: NetworkContainer,
+    browse_split: SplitPane,
+    network_split: SplitPane,
     last_click_time: std::time::Instant,
     last_clicked_idx: Option<usize>,
 }
@@ -382,15 +330,6 @@ impl FilesystemApp {
     }
 
     fn rebuild_layout(&mut self) {
-        // Refresh the container child pointers on every rebuild. They point into
-        // self.browse / self.network, whose addresses change when the app value is
-        // first moved out of new(); re-taking them here (where self is at its final
-        // address) keeps them valid regardless of return-value optimization.
-        self.browse_container.breadcrumb = &mut self.browse.breadcrumb;
-        self.browse_container.list_box = &mut self.browse.list_box;
-        self.browse_container.save_name_box = &mut self.browse.save_name_box;
-        self.network_container.breadcrumb = &mut self.network.breadcrumb;
-        self.network_container.graph = &mut self.network.graph;
 
         self.ui_context.clear_hierarchy();
         self.browse.save_name_box.prepare_text(&mut self.font_system);
@@ -411,10 +350,6 @@ impl FilesystemApp {
         self.view_dropdown.clear_children(&mut self.ui_context); self.view_dropdown.set_parent(None, &mut self.ui_context);
         self.preview.clear_children(&mut self.ui_context); self.preview.set_parent(None, &mut self.ui_context);
 
-        self.browse_splitter.set_parent(None, &mut self.ui_context);
-        self.network_splitter.set_parent(None, &mut self.ui_context);
-        self.browse_container.clear_children(&mut self.ui_context); self.browse_container.set_parent(None, &mut self.ui_context);
-        self.network_container.clear_children(&mut self.ui_context); self.network_container.set_parent(None, &mut self.ui_context);
 
         self.browse.save_name_box.clear_children(&mut self.ui_context); self.browse.save_name_box.set_parent(None, &mut self.ui_context);
         self.browse.list_box.clear_children(&mut self.ui_context); self.browse.list_box.set_parent(None, &mut self.ui_context);
@@ -426,7 +361,6 @@ impl FilesystemApp {
             textbox.set_parent(None, &mut self.ui_context);
         }
 
-        use cce_ui::widget::focus::link_parent_child;
         let has_sidebar = false;
         let sidebar_w = if has_sidebar { self.paginator.sidebar_w() } else { 0.0 };
         let browse_x = if has_sidebar { sidebar_w + 17.0 } else { 16.0 };
@@ -452,35 +386,13 @@ impl FilesystemApp {
             }
         }
 
+        // SplitBox + pane containers DISSOLVED (Phase 6y): the split is app state; the
+        // pages lay out and render the left pane's content from the pane rect (they
+        // always did — the container copies just coincided), the preview renders into
+        // the right pane below.
         match self.current_page {
-            Page::Browse => {
-                if self.browse_splitter.children.is_empty() {
-                    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);
-                }
-                {
-                    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 => {
-                if self.network_splitter.children.is_empty() {
-                    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);
-                }
-                {
-                    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);
-            }
+            Page::Browse => self.browse_split.set_rect(browse_x, content_y, usable_w, content_h),
+            Page::Network => self.network_split.set_rect(browse_x, content_y, usable_w, content_h),
         }
 
         if let Some((_, textbox)) = &mut self.open_with_dialog {
@@ -507,14 +419,36 @@ impl FilesystemApp {
                 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);
+                // The dissolved splitter's paint: its divider quad, then the preview
+                // pane (the only pane content the pages don't render themselves). The
+                // left pane's container copy is gone — the legacy aggregate painted it
+                // UNDER the page's own copy, double-compositing every translucent quad.
+                {
+                    let split = match self.current_page {
+                        Page::Browse => &self.browse_split,
+                        Page::Network => &self.network_split,
+                    };
+                    let (dx, dy, dw, dh, dc) = split.divider_quad();
+                    plain_pc.rects.push((dc, dx, dy, dw, dh, 0.0, (true, true, true, true)));
+                    let (px_r, py_r, pw_r, ph_r) = split.right_rect();
+                    let mut preview_pc = pages::PageContent::new();
+                    cce_ui::layout::render_widget(&mut preview_pc, &mut (*self_ptr).preview, px_r, py_r, pw_r, ph_r, &mut self.ui_context);
+                    plain_pc.rects.extend(preview_pc.rects);
+                    // The legacy SplitBox clamped pane text to its bounds (None -> the
+                    // splitter rect); keep the preview's text inside its pane.
+                    for (t, size, tx, ty, col, font, bounds) in preview_pc.texts {
+                        let cb = match bounds {
+                            Some(b) => {
+                                let x0 = b[0].max(px_r);
+                                let y0 = b[1].max(py_r);
+                                let x1 = b[2].min(px_r + pw_r);
+                                let y1 = b[3].min(py_r + ph_r);
+                                if x1 <= x0 || y1 <= y0 { continue; }
+                                Some([x0, y0, x1, y1])
+                            }
+                            None => Some([px_r, py_r, px_r + pw_r, py_r + ph_r]),
+                        };
+                        plain_pc.texts.push((t, size, tx, ty, col, font, cb));
                     }
                 }
                 if let Some((_, textbox)) = &mut (*self_ptr).open_with_dialog {
@@ -543,7 +477,7 @@ impl FilesystemApp {
         let mut pc = pages::PageContent::new();
         match self.current_page {
             Page::Browse => {
-                let (bx, by, bw, bh) = self.browse_container.rect();
+                let (bx, by, bw, bh) = self.browse_split.left_rect();
                 let browse_pc = pages::browse::view(&mut self.browse, &mut self.view_dropdown, bx, by, bw, bh, self.select_mode, &mut self.ui_context);
 
                 pc.rects.extend(browse_pc.rects);
@@ -551,7 +485,7 @@ impl FilesystemApp {
                 pc.buttons.extend(browse_pc.buttons);
             }
             Page::Network => {
-                let (nx, ny, nw, nh) = self.network_container.rect();
+                let (nx, ny, nw, nh) = self.network_split.left_rect();
                 let network_pc = pages::network::view(&mut self.network, &self.browse, &mut self.view_dropdown, nx, ny, nw, nh, &mut self.ui_context);
 
                 pc.rects.extend(network_pc.rects);
@@ -863,11 +797,11 @@ impl Application for FilesystemApp {
             }
         }
         if self.current_page == Page::Browse {
-            if self.browse_splitter.dragging_idx.is_some() || self.browse_splitter.hovered_idx.is_some() {
+            if self.browse_split.dragging || self.browse_split.hovered {
                 return false;
             }
         } else if self.current_page == Page::Network {
-            if self.network_splitter.dragging_idx.is_some() || self.network_splitter.hovered_idx.is_some() {
+            if self.network_split.dragging || self.network_split.hovered {
                 return false;
             }
         }
@@ -898,7 +832,7 @@ 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 mut app = Self {
+        let app = Self {
             current_page: Page::Browse,
             browse,
             network: pages::network::NetworkState::default(),
@@ -932,34 +866,13 @@ impl Application for FilesystemApp {
                 hovered: None,
             },
             open_with_dialog: None,
-            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 {
-                base: cce_ui::widget::Widget::new(),
-                parent: None,
-                breadcrumb: std::ptr::null_mut(),
-                list_box: std::ptr::null_mut(),
-                save_name_box: std::ptr::null_mut(),
-                select_mode,
-            },
-            network_container: NetworkContainer {
-                base: cce_ui::widget::Widget::new(),
-                parent: None,
-                breadcrumb: std::ptr::null_mut(),
-                graph: std::ptr::null_mut(),
-            },
+            browse_split: SplitPane::new(0.49, 100.0, 100.0, 12.0),
+            network_split: SplitPane::new(0.49, 100.0, 100.0, 12.0),
             last_click_time: std::time::Instant::now(),
             last_clicked_idx: None,
         };
 
-        // Initialize container references
-        app.browse_container.breadcrumb = &mut app.browse.breadcrumb;
-        app.browse_container.list_box = &mut app.browse.list_box;
-        app.browse_container.save_name_box = &mut app.browse.save_name_box;
-
-        app.network_container.breadcrumb = &mut app.network.breadcrumb;
-        app.network_container.graph = &mut app.network.graph;
-
+        
         // Start initial directory loading via FsService
         app.fs_service.send(services::fs::FsRequest::ReadLastDir);
 
@@ -1268,11 +1181,11 @@ impl Application for FilesystemApp {
         }
 
         if self.current_page == Page::Browse {
-            if self.browse_splitter.on_cursor_moved(pos.x, pos.y, &mut self.ui_context) {
+            if self.browse_split.cursor_moved(pos.x, pos.y) {
                 changed = true;
             }
         } else if self.current_page == Page::Network {
-            if self.network_splitter.on_cursor_moved(pos.x, pos.y, &mut self.ui_context) {
+            if self.network_split.cursor_moved(pos.x, pos.y) {
                 changed = true;
             }
         }
@@ -1533,14 +1446,22 @@ impl Application for FilesystemApp {
             return None;
         }
 
-        if self.current_page == Page::Browse {
-            if self.browse_splitter.mouse_input(button, state, pos.x, pos.y, &mut self.ui_context) {
-                *needs_rebuild = true;
-                self.needs_rebuild = true;
-                return None;
-            }
-        } else if self.current_page == Page::Network {
-            if self.network_splitter.mouse_input(button, state, pos.x, pos.y, &mut self.ui_context) {
+        // The dissolved splitter's divider: a left press grabs it (stealing keyboard
+        // focus like the legacy ctx.set_focused_ptr / release's clear_focus pair did),
+        // a release ends the drag.
+        if button == MouseButton::Left {
+            let split = match self.current_page {
+                Page::Browse => &mut self.browse_split,
+                Page::Network => &mut self.network_split,
+            };
+            if state == ElementState::Pressed {
+                if split.press(pos.x, pos.y) {
+                    self.ui_context.clear_focus();
+                    *needs_rebuild = true;
+                    self.needs_rebuild = true;
+                    return None;
+                }
+            } else if split.release() {
                 *needs_rebuild = true;
                 self.needs_rebuild = true;
                 return None;