file manager
git clone https://git.lucas.co/cce-files.git
feat: use dynamic cce-ui metrics and safe process spawning helpers
src/main.rs | 42 ++++++++++++++++++++++++++----------------
src/pages/browse.rs | 47 +++++++++++++++++++++++++++++++----------------
src/services/fs.rs | 10 +++++-----
3 files changed, 62 insertions(+), 37 deletions(-)
diff --git a/src/main.rs b/src/main.rs
index 7fa6b91..c23703f 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -240,14 +240,17 @@ impl FilesystemApp {
let accent = [0.36, 0.56, 0.38, 1.0];
let text_fg = [0.83, 0.83, 0.83, 1.0];
+ let btn_h = cce_ui::layout::button_height();
+ let btn_y = bar_y + (select_bar_h - btn_h) / 2.0;
+
// Cancel button
let cancel_x = self.width as f32 - 180.0;
pc.button(
"Cancel",
cancel_x,
- bar_y + 10.0,
+ btn_y,
70.0,
- 28.0,
+ btn_h,
[0.25, 0.12, 0.12, 0.5],
[0.35, 0.15, 0.15, 0.8],
text_fg,
@@ -260,9 +263,9 @@ impl FilesystemApp {
pc.button(
button_label,
open_x,
- bar_y + 10.0,
+ btn_y,
80.0,
- 28.0,
+ btn_h,
accent,
[0.46, 0.66, 0.48, 1.0],
[0.10, 0.16, 0.11, 1.0],
@@ -307,7 +310,7 @@ impl FilesystemApp {
// Gather open-with dialog backdrop & dialog panel if active (open_with_dialog uses textbox rendering manually but we can gather its other quads/texts)
let mut dialog_pc = pages::PageContent::new();
- if let Some((_path, _textbox)) = &mut self.open_with_dialog {
+ if let Some((_path, textbox)) = &mut self.open_with_dialog {
let dialog_w = 400.0;
let dialog_h = 160.0;
let dialog_x = (self.width as f32 - dialog_w) / 2.0;
@@ -325,18 +328,24 @@ impl FilesystemApp {
// Description
dialog_pc.text("Enter command:", dialog_x + 20.0, dialog_y + 42.0, 11.0, [0.54, 0.54, 0.58, 1.0]);
- // Textbox quads & labels are rendered via textbox.all_quads inside the layout, so we'll grab them from the textbox child widget since textbox is linked to root_window.
+ // Set textbox position dynamically using configured textbox height
+ let tb_x = dialog_x + 20.0;
+ let tb_y = dialog_y + 60.0;
+ let tb_w = dialog_w - 40.0;
+ let tb_h = cce_ui::layout::textbox_height();
+ textbox.set_rect(tb_x, tb_y, tb_w, tb_h);
// Render Buttons: Cancel & Open
+ let btn_h = cce_ui::layout::button_height();
let btn_cancel_x = dialog_x + dialog_w - 180.0;
- let btn_cancel_y = dialog_y + dialog_h - 44.0;
+ let btn_cancel_y = dialog_y + dialog_h - btn_h - 16.0;
let btn_cancel_w = 70.0;
- let btn_cancel_h = 28.0;
+ let btn_cancel_h = btn_h;
let btn_open_x = dialog_x + dialog_w - 100.0;
- let btn_open_y = dialog_y + dialog_h - 44.0;
+ let btn_open_y = dialog_y + dialog_h - btn_h - 16.0;
let btn_open_w = 80.0;
- let btn_open_h = 28.0;
+ let btn_open_h = btn_h;
let cancel_hover = self.cursor_x >= btn_cancel_x && self.cursor_x <= btn_cancel_x + btn_cancel_w
&& self.cursor_y >= btn_cancel_y && self.cursor_y <= btn_cancel_y + btn_cancel_h;
@@ -759,7 +768,7 @@ impl Application for FilesystemApp {
command.arg(arg);
}
command.arg(&path);
- let _ = command.spawn();
+ let _ = cce_ui::process::spawn_detached(command);
}
}
}
@@ -925,17 +934,18 @@ impl Application for FilesystemApp {
let tb_x = dialog_x + 20.0;
let tb_y = dialog_y + 60.0;
let tb_w = dialog_w - 40.0;
- let tb_h = 28.0;
+ let tb_h = cce_ui::layout::textbox_height();
+ let btn_h = cce_ui::layout::button_height();
let btn_cancel_x = dialog_x + dialog_w - 180.0;
- let btn_cancel_y = dialog_y + dialog_h - 44.0;
+ let btn_cancel_y = dialog_y + dialog_h - btn_h - 16.0;
let btn_cancel_w = 70.0;
- let btn_cancel_h = 28.0;
+ let btn_cancel_h = btn_h;
let btn_open_x = dialog_x + dialog_w - 100.0;
- let btn_open_y = dialog_y + dialog_h - 44.0;
+ let btn_open_y = dialog_y + dialog_h - btn_h - 16.0;
let btn_open_w = 80.0;
- let btn_open_h = 28.0;
+ let btn_open_h = btn_h;
if state == ElementState::Pressed {
let clicked_inside = pos.x >= dialog_x && pos.x <= dialog_x + dialog_w && pos.y >= dialog_y && pos.y <= dialog_y + dialog_h;
diff --git a/src/pages/browse.rs b/src/pages/browse.rs
index 64f31c4..aa0daa2 100644
--- a/src/pages/browse.rs
+++ b/src/pages/browse.rs
@@ -43,7 +43,7 @@ impl Default for BrowseState {
search_box: cce_ui::widget::TextBox::new(String::new())
.with_max_width(None)
.with_placeholder("Search"),
- list_box: cce_ui::widget::List::new(28.0, 2.0),
+ list_box: cce_ui::widget::List::new(cce_ui::layout::button_height(), 2.0),
selected: None,
breadcrumb,
save_name_box: cce_ui::widget::TextBox::new(String::new()).with_max_width(None),
@@ -88,7 +88,7 @@ pub enum BrowseNavigation {
}
pub fn is_project_dir(path: &Path) -> bool {
- path.is_dir() && path.join("state.json").exists()
+ path.is_dir() && (path.join("state.json").exists() || path.join("state.kdl").exists())
}
// ── Helpers ─────────────────────────────────────────────────────────
@@ -174,7 +174,7 @@ pub fn view(state: &mut BrowseState, cx: f32, cy: f32, cw: f32, ch: f32, select_
layout.init(client_x, client_y, client_w, client_h);
let breadcrumb_h = 24.0;
- let textbox_h = 28.0;
+ let textbox_h = cce_ui::layout::textbox_height();
// 1. Allocate and render Breadcrumb
let (bx, by, bw, bh) = layout.allocate(client_w, breadcrumb_h);
@@ -265,7 +265,7 @@ pub fn view(state: &mut BrowseState, cx: f32, cy: f32, cw: f32, ch: f32, select_
list_x + 4.0,
draw_y,
list_w - 24.0,
- 28.0,
+ cce_ui::layout::button_height(),
bg,
hover_bg,
fg,
@@ -273,7 +273,11 @@ pub fn view(state: &mut BrowseState, cx: f32, cy: f32, cw: f32, ch: f32, select_
);
// Draw contents inside the button boundary:
- pc.text(icon, list_x + 12.0, draw_y + 7.0, 13.0, fg);
+ let row_h = cce_ui::layout::button_height();
+ let y_text_13 = cce_ui::layout::center_text_y(draw_y, row_h, 13.0);
+ let y_text_11 = cce_ui::layout::center_text_y(draw_y, row_h, 11.0);
+
+ pc.text(icon, list_x + 12.0, y_text_13, 13.0, fg);
let show_size = list_w > 400.0;
let show_perm = list_w > 480.0;
@@ -300,15 +304,15 @@ pub fn view(state: &mut BrowseState, cx: f32, cy: f32, cw: f32, ch: f32, select_
entry.name.clone()
};
- pc.text(&name_truncated, list_x + 32.0, draw_y + 7.0, 13.0, fg);
+ pc.text(&name_truncated, list_x + 32.0, y_text_13, 13.0, fg);
if show_size {
- pc.text(&size_str, list_x + list_w - 290.0, draw_y + 8.0, 11.0, text_dim);
+ pc.text(&size_str, list_x + list_w - 290.0, y_text_11, 11.0, text_dim);
}
if show_perm {
- pc.text(&perm_str, list_x + list_w - 210.0, draw_y + 8.0, 11.0, text_dim);
+ pc.text(&perm_str, list_x + list_w - 210.0, y_text_11, 11.0, text_dim);
}
if show_modified {
- pc.text(&entry.modified, list_x + list_w - 120.0, draw_y + 8.0, 11.0, text_dim);
+ pc.text(&entry.modified, list_x + list_w - 120.0, y_text_11, 11.0, text_dim);
}
}
}
@@ -437,7 +441,7 @@ mod tests {
})
.collect(),
search_box: cce_ui::widget::TextBox::new(String::new()).with_max_width(None),
- list_box: cce_ui::widget::List::new(28.0, 2.0),
+ list_box: cce_ui::widget::List::new(cce_ui::layout::button_height(), 2.0),
..BrowseState::default()
}
}
@@ -511,21 +515,32 @@ mod tests {
let unique_dir = std::env::temp_dir().join(format!("clear_test_dir_{}", chrono::Utc::now().timestamp_nanos_opt().unwrap_or(0)));
std::fs::create_dir_all(&unique_dir).unwrap();
- // Initially, path is a directory but doesn't have state.json
+ // Initially, path is a directory but doesn't have state.json or state.kdl
assert!(!is_project_dir(&unique_dir));
// Create state.json
- let file_path = unique_dir.join("state.json");
- std::fs::write(&file_path, "{}").unwrap();
+ let file_path_json = unique_dir.join("state.json");
+ std::fs::write(&file_path_json, "{}").unwrap();
// Now it should be recognized as a project dir
assert!(is_project_dir(&unique_dir));
- // If it's a file rather than a directory, even if named state.json, it shouldn't be a project dir itself
- assert!(!is_project_dir(&file_path));
+ // Remove state.json and verify it's not a project dir
+ std::fs::remove_file(&file_path_json).unwrap();
+ assert!(!is_project_dir(&unique_dir));
+
+ // Create state.kdl
+ let file_path_kdl = unique_dir.join("state.kdl");
+ std::fs::write(&file_path_kdl, "name \"test\"").unwrap();
+
+ // Now it should be recognized as a project dir
+ assert!(is_project_dir(&unique_dir));
+
+ // If it's a file rather than a directory, even if named state.kdl, it shouldn't be a project dir itself
+ assert!(!is_project_dir(&file_path_kdl));
// Clean up
- let _ = std::fs::remove_file(&file_path);
+ let _ = std::fs::remove_file(&file_path_kdl);
let _ = std::fs::remove_dir(&unique_dir);
}
diff --git a/src/services/fs.rs b/src/services/fs.rs
index 79b9a2a..ffe5aa4 100644
--- a/src/services/fs.rs
+++ b/src/services/fs.rs
@@ -387,7 +387,7 @@ pub fn save_last_dir_internal(dir: &Path) {
}
pub fn get_mime_type(path: &Path) -> Option<String> {
- if path.is_dir() && path.join("state.json").exists() {
+ if path.is_dir() && (path.join("state.json").exists() || path.join("state.kdl").exists()) {
return Some("application/x-cce-project".to_string());
}
if let Some(ext) = path.extension().and_then(|e| e.to_str()) {
@@ -539,7 +539,7 @@ pub fn open_file(path: &Path) {
command.arg(arg);
}
command.arg(path);
- if command.spawn().is_ok() {
+ if cce_ui::process::spawn_detached(command).is_ok() {
opened = true;
}
}
@@ -547,9 +547,9 @@ pub fn open_file(path: &Path) {
}
}
if !opened {
- let _ = std::process::Command::new("xdg-open")
- .arg(path)
- .spawn();
+ let mut command = std::process::Command::new("xdg-open");
+ command.arg(path);
+ let _ = cce_ui::process::spawn_detached(command);
}
}