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

commitfe51f54e9e4c053d232ea8319d773574645babbe
parentd90298aa52
authorLucas Galante <[email protected]>
date2026-07-06 21:55
refactor: use integrated search box in List for browse page

 src/main.rs         |  54 ++++++++++++++--------------
 src/pages/browse.rs | 100 ++++++++++++++++++++++------------------------------
 2 files changed, 68 insertions(+), 86 deletions(-)

diff --git a/src/main.rs b/src/main.rs
index 25f3969..9f36180 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -127,7 +127,7 @@ impl FilesystemApp {
     fn rebuild_layout(&mut self) {
         self.ui_context.clear_hierarchy();
         self.browse.save_name_box.prepare_text(&mut self.font_system);
-        self.browse.search_box.prepare_text(&mut self.font_system);
+        self.browse.list_box.prepare_text(&mut self.font_system);
 
         let mut widgets = Vec::new();
         let mut text_items = Vec::new();
@@ -145,7 +145,7 @@ impl FilesystemApp {
         self.paginator.clear_children(&mut self.ui_context); self.paginator.set_parent(None, &mut self.ui_context);
         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.search_box.clear_children(&mut self.ui_context); self.browse.search_box.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);
         self.browse.breadcrumb.clear_children(&mut self.ui_context); self.browse.breadcrumb.set_parent(None, &mut self.ui_context);
@@ -185,8 +185,6 @@ impl FilesystemApp {
                 link_parent_child(&mut self.root_window, &mut self.browse.list_box, &mut self.ui_context);
                 if self.select_mode {
                     link_parent_child(&mut self.root_window, &mut self.browse.save_name_box, &mut self.ui_context);
-                } else {
-                    link_parent_child(&mut self.root_window, &mut self.browse.search_box, &mut self.ui_context);
                 }
             }
             Page::Network => {
@@ -1055,9 +1053,6 @@ impl Application for FilesystemApp {
         }
 
         if self.current_page == Page::Browse {
-            if self.browse.search_box.cursor_moved(pos.x, pos.y, &mut self.ui_context) {
-                changed = true;
-            }
             if self.select_mode {
                 if self.browse.save_name_box.cursor_moved(pos.x, pos.y, &mut self.ui_context) {
                     changed = true;
@@ -1325,13 +1320,6 @@ impl Application for FilesystemApp {
         }
 
         if self.current_page == Page::Browse {
-            if self.browse.search_box.mouse_input(button, state, pos.x, pos.y, &mut self.ui_context) {
-                if state == ElementState::Pressed {
-                    self.ui_context.set_focused(&mut self.browse.search_box);
-                }
-                *needs_rebuild = true;
-                self.needs_rebuild = true;
-            }
             if self.select_mode {
                 if self.browse.save_name_box.mouse_input(button, state, pos.x, pos.y, &mut self.ui_context) {
                     if state == ElementState::Pressed {
@@ -1491,10 +1479,10 @@ impl Application for FilesystemApp {
         }
 
         if state == ElementState::Pressed {
-            let clicked_search = self.current_page == Page::Browse && self.browse.search_box.hit_test(pos.x, pos.y, &self.ui_context);
+            let clicked_search = self.current_page == Page::Browse && self.browse.list_box.search_enabled && self.browse.list_box.search_visible && self.browse.list_box.search_box.hit_test(pos.x, pos.y, &self.ui_context);
             let clicked_save_name = self.select_mode && self.current_page == Page::Browse && self.browse.save_name_box.hit_test(pos.x, pos.y, &self.ui_context);
-            if !clicked_search {
-                self.browse.search_box.unfocus();
+            if !clicked_search && self.browse.list_box.search_enabled {
+                self.browse.list_box.search_box.unfocus();
             }
             if !clicked_save_name {
                 self.browse.save_name_box.unfocus();
@@ -1607,16 +1595,23 @@ impl Application for FilesystemApp {
         }
 
         // If the search textbox is focused, forward key inputs to it
-        if self.current_page == Page::Browse && self.browse.search_box.editing {
-            if self.browse.search_box.keyboard_input(event, &mut self.ui_context) {
-                *needs_rebuild = true;
-                self.needs_rebuild = true;
-                if self.browse.search_box.take_change() {
-                    return Some(Message::Browse(pages::browse::BrowseMessage::SearchChanged(
-                        self.browse.search_box.text.clone()
-                    )));
+        if self.current_page == Page::Browse {
+            let is_open_search = !self.browse.list_box.search_visible && {
+                let open_key = cce_ui::color::list_open_search_key();
+                event.state == ElementState::Pressed && cce_ui::widget::match_key_shortcut(event, &open_key)
+            };
+            
+            if self.browse.list_box.search_visible || is_open_search {
+                if self.browse.list_box.keyboard_input(event, &mut self.ui_context) {
+                    *needs_rebuild = true;
+                    self.needs_rebuild = true;
+                    if self.browse.list_box.search_box.take_change() {
+                        return Some(Message::Browse(pages::browse::BrowseMessage::SearchChanged(
+                            self.browse.list_box.search_box.text.clone()
+                        )));
+                    }
+                    return None;
                 }
-                return None;
             }
         }
 
@@ -1709,8 +1704,11 @@ impl Application for FilesystemApp {
                         return Some(Message::Browse(pages::browse::BrowseMessage::ToggleHidden));
                     }
                     Some("/") => {
-                        self.browse.search_box.focus();
-                        self.ui_context.set_focused(&mut self.browse.search_box);
+                        self.browse.list_box.search_visible = true;
+                        let (lx, ly, lw, lh) = (self.browse.list_box.base.x, self.browse.list_box.base.y, self.browse.list_box.base.w, self.browse.list_box.base.h);
+                        self.browse.list_box.set_rect(lx, ly, lw, lh);
+                        self.browse.list_box.search_box.focus();
+                        self.ui_context.set_focused(&mut self.browse.list_box.search_box);
                         *needs_rebuild = true;
                         self.needs_rebuild = true;
                     }
diff --git a/src/pages/browse.rs b/src/pages/browse.rs
index a6c295c..045147d 100644
--- a/src/pages/browse.rs
+++ b/src/pages/browse.rs
@@ -22,7 +22,6 @@ pub struct BrowseState {
     pub all_entries: Vec<DirEntry>,
     pub entries: Vec<DirEntry>,
     pub show_hidden: bool,
-    pub search_box: cce_ui::widget::TextBox,
     pub list_box: cce_ui::widget::List,
     pub selected: Option<usize>,
     pub breadcrumb: Breadcrumb,
@@ -40,33 +39,32 @@ impl Default for BrowseState {
             all_entries: Vec::new(),
             entries: Vec::new(),
             show_hidden: false,
-            search_box: cce_ui::widget::TextBox::new(String::new())
-                .with_max_width(None)
-                .with_placeholder("Search"),
-            list_box: cce_ui::widget::List::new(cce_ui::layout::button_height(), 2.0).with_columns(
-                vec![
-                    cce_ui::widget::ListColumn {
-                        name: "Name".to_string(),
-                        width: cce_ui::widget::ColumnWidth::Flex,
-                        justification: cce_ui::widget::Justification::Left,
-                    },
-                    cce_ui::widget::ListColumn {
-                        name: "Size".to_string(),
-                        width: cce_ui::widget::ColumnWidth::RightOffset(290.0),
-                        justification: cce_ui::widget::Justification::Left,
-                    },
-                    cce_ui::widget::ListColumn {
-                        name: "Permissions".to_string(),
-                        width: cce_ui::widget::ColumnWidth::RightOffset(210.0),
-                        justification: cce_ui::widget::Justification::Left,
-                    },
-                    cce_ui::widget::ListColumn {
-                        name: "Modified".to_string(),
-                        width: cce_ui::widget::ColumnWidth::RightOffset(120.0),
-                        justification: cce_ui::widget::Justification::Left,
-                    },
-                ],
-            ),
+            list_box: cce_ui::widget::List::new(cce_ui::layout::button_height(), 2.0)
+                .with_search(true)
+                .with_columns(
+                    vec![
+                        cce_ui::widget::ListColumn {
+                            name: "Name".to_string(),
+                            width: cce_ui::widget::ColumnWidth::Flex,
+                            justification: cce_ui::widget::Justification::Left,
+                        },
+                        cce_ui::widget::ListColumn {
+                            name: "Size".to_string(),
+                            width: cce_ui::widget::ColumnWidth::RightOffset(290.0),
+                            justification: cce_ui::widget::Justification::Left,
+                        },
+                        cce_ui::widget::ListColumn {
+                            name: "Permissions".to_string(),
+                            width: cce_ui::widget::ColumnWidth::RightOffset(210.0),
+                            justification: cce_ui::widget::Justification::Left,
+                        },
+                        cce_ui::widget::ListColumn {
+                            name: "Modified".to_string(),
+                            width: cce_ui::widget::ColumnWidth::RightOffset(120.0),
+                            justification: cce_ui::widget::Justification::Left,
+                        },
+                    ],
+                ),
             selected: None,
             breadcrumb,
             save_name_box: cce_ui::widget::TextBox::new(String::new()).with_max_width(None),
@@ -203,8 +201,11 @@ pub fn view(state: &mut BrowseState, view_dropdown: &mut cce_ui::widget::Dropdow
 
     // 2. Allocate and render List (ScrollBox)
     // The scrolling list height occupies the remaining vertical space:
-    // list_h = client_h - breadcrumb_h - textbox_h - (2 * gap)
-    let list_h_val = client_h - breadcrumb_h - textbox_h - 2.0 * gap;
+    let list_h_val = if select_mode {
+        client_h - breadcrumb_h - textbox_h - 2.0 * gap
+    } else {
+        client_h - breadcrumb_h - gap
+    };
     let (list_x, list_y, list_w, list_h) = layout.allocate(client_w, list_h_val);
 
     // Update List columns dynamically based on list width
@@ -299,27 +300,10 @@ pub fn view(state: &mut BrowseState, view_dropdown: &mut cce_ui::widget::Dropdow
     pc.text(&count_str, count_x, count_y, 11.0, text_dim);
 
     // 3. Allocate and render Textbox(es)
-    let (tx, ty, tw, th) = layout.allocate(client_w, textbox_h);
-
     if select_mode {
-        // Two columns at the bottom: Search and File Name
-        let sec_w = (tw - 12.0) / 2.0;
-
-        // Left column: Search
-        let search_x = tx;
-        let search_w = sec_w;
-        state.search_box.set_row_rect(search_x, search_w);
-        cce_ui::layout::render_widget(&mut pc, &mut state.search_box, search_x, ty, search_w, th, ctx);
-
-        // Right column: File Name
-        let filename_x = tx + sec_w + 12.0;
-        let filename_w = sec_w;
-        state.save_name_box.set_row_rect(filename_x, filename_w);
-        cce_ui::layout::render_widget(&mut pc, &mut state.save_name_box, filename_x, ty, filename_w, th, ctx);
-    } else {
-        // Full width search textbox
-        state.search_box.set_row_rect(tx, tw);
-        cce_ui::layout::render_widget(&mut pc, &mut state.search_box, tx, ty, tw, th, ctx);
+        let (tx, ty, tw, th) = layout.allocate(client_w, textbox_h);
+        state.save_name_box.set_row_rect(tx, tw);
+        cce_ui::layout::render_widget(&mut pc, &mut state.save_name_box, tx, ty, tw, th, ctx);
     }
 
     pc
@@ -328,10 +312,10 @@ pub fn view(state: &mut BrowseState, view_dropdown: &mut cce_ui::widget::Dropdow
 // ── Update ──────────────────────────────────────────────────────────
 
 fn apply_filters(state: &mut BrowseState) {
-    let search_query = if state.search_box.editing {
-        state.search_box.edit_buffer.to_lowercase()
+    let search_query = if state.list_box.search_box.editing {
+        state.list_box.search_box.edit_buffer.to_lowercase()
     } else {
-        state.search_box.text.to_lowercase()
+        state.list_box.search_box.text.to_lowercase()
     };
     state.entries = state
         .all_entries
@@ -351,7 +335,7 @@ fn apply_filters(state: &mut BrowseState) {
 pub fn update(state: &mut BrowseState, msg: BrowseMessage) -> Option<crate::services::fs::FsRequest> {
     match msg {
         BrowseMessage::SearchChanged(q) => {
-            state.search_box.text = q;
+            state.list_box.search_box.text = q;
             apply_filters(state);
             None
         }
@@ -373,8 +357,9 @@ pub fn update(state: &mut BrowseState, msg: BrowseMessage) -> Option<crate::serv
         BrowseMessage::DirectoryLoaded(path, entries) => {
             state.current_dir = path.clone();
             state.all_entries = entries;
-            state.search_box.text.clear();
-            state.search_box.edit_buffer.clear();
+            state.list_box.search_box.text.clear();
+            state.list_box.search_box.edit_buffer.clear();
+            state.list_box.search_visible = false;
             apply_filters(state);
             state.update_breadcrumb();
             Some(crate::services::fs::FsRequest::SaveLastDir(path))
@@ -445,8 +430,7 @@ mod tests {
                     modified: String::new(),
                 })
                 .collect(),
-            search_box: cce_ui::widget::TextBox::new(String::new()).with_max_width(None),
-            list_box: cce_ui::widget::List::new(cce_ui::layout::button_height(), 2.0),
+            list_box: cce_ui::widget::List::new(cce_ui::layout::button_height(), 2.0).with_search(true),
             ..BrowseState::default()
         }
     }