file manager
git clone https://git.lucas.co/cce-files.git
Restructure like cce-system-interface with root window object
Cargo.toml | 7 +
src/lib.rs | 18 ++
src/main.rs | 556 +++++++++++++++++++++----------------------------------
src/pages/mod.rs | 14 +-
4 files changed, 246 insertions(+), 349 deletions(-)
diff --git a/Cargo.toml b/Cargo.toml
index 937eadd..c2e12d6 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -14,3 +14,10 @@ chrono = "0.4.44"
tokio = { version = "1", features = ["full"] }
notify = "8.2.0"
+[lib]
+name = "cce_filesystem_interface"
+path = "src/lib.rs"
+
+[[bin]]
+name = "cce-filesystem-interface"
+path = "src/main.rs"
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..5f3fca8
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,18 @@
+pub mod pages;
+pub mod services;
+
+use cce_ui::widget::KeyEvent;
+use pages::Page;
+
+#[derive(Debug, Clone)]
+pub enum Message {
+ SwitchPage(Page),
+ Browse(pages::browse::BrowseMessage),
+ Preview(pages::preview::PreviewMessage),
+ KeyboardEvent(KeyEvent),
+ SelectOpen,
+ SelectCancel,
+ PromptOpenWith(std::path::PathBuf),
+ OpenWithSubmit,
+ OpenWithCancel,
+}
diff --git a/src/main.rs b/src/main.rs
index 8d464a5..40cf1dc 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,6 +1,3 @@
-mod pages;
-mod services;
-
use wayland_client::QueueHandle;
use glyphon::FontSystem;
@@ -10,8 +7,9 @@ use cce_ui::widget::{GraphController, PathController};
use notify::{Watcher, RecommendedWatcher, RecursiveMode, Config};
-use pages::Page;
-use pages::browse::is_project_dir;
+use cce_filesystem_interface::{Message, pages, services};
+use cce_filesystem_interface::pages::Page;
+use cce_filesystem_interface::pages::browse::is_project_dir;
// ── State ───────────────────────────────────────────────────────────
@@ -24,6 +22,8 @@ struct AppWidget {
hover_color: [f32; 4],
hovering: bool,
action: Option<Message>,
+ radius: f32,
+ corners: (bool, bool, bool, bool),
}
#[derive(Clone)]
@@ -65,24 +65,10 @@ struct FilesystemApp {
just_initialized: bool,
ui_context: cce_ui::context::UiContext,
watcher: Option<notify::RecommendedWatcher>,
- fs_service: crate::services::fs::FsService,
+ fs_service: services::fs::FsService,
context_menu: ContextMenu,
open_with_dialog: Option<(std::path::PathBuf, cce_ui::widget::TextBox)>,
-}
-
-// ── Messages ────────────────────────────────────────────────────────
-
-#[derive(Debug, Clone)]
-pub enum Message {
- SwitchPage(Page),
- Browse(pages::browse::BrowseMessage),
- Preview(pages::preview::PreviewMessage),
- KeyboardEvent(KeyEvent),
- SelectOpen,
- SelectCancel,
- PromptOpenWith(std::path::PathBuf),
- OpenWithSubmit,
- OpenWithCancel,
+ root_window: cce_ui::widget::Window,
}
@@ -102,7 +88,7 @@ impl FilesystemApp {
tokio::time::sleep(Duration::from_millis(150)).await;
while rx.try_recv().is_ok() {}
- let _ = fs_service.send(crate::services::fs::FsRequest::RefreshDirectory(path_clone.clone())).await;
+ let _ = fs_service.send(services::fs::FsRequest::RefreshDirectory(path_clone.clone())).await;
}
});
@@ -144,8 +130,33 @@ impl FilesystemApp {
cce_ui::widget::hover_animation::reset_frame_registration();
cce_ui::widget::hover_animation::set_cursor_pos(self.cursor_x, self.cursor_y);
- let mut pc = pages::PageContent::new();
+ // Update root window size, background color, opacity, corner radius
+ self.root_window.set_rect(0.0, 0.0, self.width as f32, self.height as f32);
+ let mut bg_color = cce_ui::color::page_low_color();
+ if let Some(opacity) = cce_ui::color::read_opacity_if_configured() {
+ bg_color[3] = opacity;
+ }
+ self.root_window.background_color = Some(bg_color);
+ self.root_window.radius = 12.0;
+
+ // Rebuild Element Focus Hierarchy
+ self.root_window.clear_children(&mut self.ui_context);
+
+ // Clear all widgets' hierarchy links
+ self.menubar.clear_children(&mut self.ui_context); self.menubar.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);
+ self.network.breadcrumb.clear_children(&mut self.ui_context); self.network.breadcrumb.set_parent(None, &mut self.ui_context);
+ self.network.graph.clear_children(&mut self.ui_context); self.network.graph.set_parent(None, &mut self.ui_context);
+ self.settings.color_selector.clear_children(&mut self.ui_context); self.settings.color_selector.set_parent(None, &mut self.ui_context);
+ if let Some((_, textbox)) = &mut self.open_with_dialog {
+ textbox.clear_children(&mut self.ui_context);
+ textbox.set_parent(None, &mut self.ui_context);
+ }
+ use cce_ui::widget::focus::link_parent_child;
let has_sidebar = !self.select_mode;
let sidebar_w = if has_sidebar { self.menubar.sidebar_w() } else { 0.0 };
let browse_x = if has_sidebar { sidebar_w + 17.0 } else { 16.0 };
@@ -162,19 +173,47 @@ impl FilesystemApp {
self.height as f32 - 32.0
};
- // Full window page background
- let bg_color = cce_ui::color::page_low_color();
- pc.rect(bg_color, 0.0, 0.0, self.width as f32, self.height as f32);
+ if has_sidebar {
+ link_parent_child(&mut self.root_window, &mut self.menubar, &mut self.ui_context);
+ }
- let mut menubar_pc = pages::PageContent::new();
+ match self.current_page {
+ Page::Browse => {
+ link_parent_child(&mut self.root_window, &mut self.browse.breadcrumb, &mut self.ui_context);
+ 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 => {
+ link_parent_child(&mut self.root_window, &mut self.network.breadcrumb, &mut self.ui_context);
+ link_parent_child(&mut self.root_window, &mut self.network.graph, &mut self.ui_context);
+ }
+ Page::Settings => {
+ link_parent_child(&mut self.root_window, &mut self.settings.color_selector, &mut self.ui_context);
+ }
+ }
+
+ if let Some((_, textbox)) = &mut self.open_with_dialog {
+ link_parent_child(&mut self.root_window, textbox, &mut self.ui_context);
+ }
+
+ // Layout widgets recursively inside the parent space
+ let mut dummy_pc = pages::PageContent::new();
if has_sidebar {
- // 1. Render the Menubar widget directly
let page_idx = Page::ALL.iter().position(|&p| p == self.current_page).unwrap_or(0);
self.menubar.menus.set_selected(Some(page_idx));
- cce_ui::layout::render_widget(&mut menubar_pc, &mut self.menubar, 0.0, 0.0, sidebar_w, self.height as f32, &mut self.ui_context);
+ cce_ui::layout::render_widget(&mut dummy_pc, &mut self.menubar, 0.0, 0.0, sidebar_w, self.height as f32, &mut self.ui_context);
}
- // 3. Draw Page content
+ // Render root window recursively
+ 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);
+
+ // 3. Draw Page custom/static content (drawn to pc)
+ let mut pc = pages::PageContent::new();
match self.current_page {
Page::Browse => {
// Draw background plates for columns
@@ -237,17 +276,6 @@ impl FilesystemApp {
}
}
- if has_sidebar {
- // Add Sidebar Background and elements directly from the menubar to pc
- pc.rects.extend(menubar_pc.rects);
-
-
-
- // Add the menubar's texts (tab labels) on top of the sidebar background
- pc.texts.extend(menubar_pc.texts);
- pc.buttons.extend(menubar_pc.buttons);
- }
-
// Draw bottom selection bar if select_mode is enabled
if self.select_mode {
let bar_y = self.height as f32 - select_bar_h - 16.0;
@@ -287,151 +315,12 @@ impl FilesystemApp {
);
}
- // 4. Translate PageContent into rendering quads and TextItems
- for (col, x, y, w, h) in &pc.rects {
- widgets.push(AppWidget {
- x: *x,
- y: *y,
- w: *w,
- h: *h,
- color: *col,
- hover_color: *col,
- hovering: false,
- action: None,
- });
- }
-
- // Add page buttons
- for (btn, action) in &pc.buttons {
- let base = btn.base().unwrap();
- let bg = btn.bg.unwrap_or([0.16, 0.16, 0.24, 1.0]);
- let hover_bg = btn.hover_bg.unwrap_or([0.25, 0.30, 0.26, 1.0]);
- let label = base.label.as_deref().unwrap_or("");
- let label_size = 12.0;
- let label_color = btn.label_color.unwrap_or([0.83, 0.83, 0.83, 1.0]);
-
- let hovering = self.cursor_x >= base.x && self.cursor_x <= base.x + base.w
- && self.cursor_y >= base.y && self.cursor_y <= base.y + base.h;
- let col = if hovering { hover_bg } else { bg };
-
- widgets.push(AppWidget {
- x: base.x,
- y: base.y,
- w: base.w,
- h: base.h,
- color: col,
- hover_color: hover_bg,
- hovering,
- action: Some(action.clone()),
- });
-
- // Draw button text
- let text_x = if btn.left_align {
- base.x + 8.0
- } else {
- let text_w = label.chars().count() as f32 * label_size * 0.65;
- base.x + (base.w - text_w) / 2.0
- };
- let text_y = base.y + (base.h - label_size * 1.4) / 2.0;
-
- text_items.push(TextItem::new(
- &mut self.font_system,
- label,
- label_size,
- text_x,
- text_y,
- glyphon::Color::rgb(
- (label_color[0] * 255.0) as u8,
- (label_color[1] * 255.0) as u8,
- (label_color[2] * 255.0) as u8,
- ),
- None,
- None,
- ));
- }
-
- // Add raw texts
- for (text, size, x, y, col, font, bounds) in &pc.texts {
- let mut final_bounds = *bounds;
- if self.context_menu.visible {
- let cx = self.context_menu.x;
- let cy = self.context_menu.y;
- let cw = self.context_menu.w;
- let ch = self.context_menu.h;
-
- let text_w = text.chars().count() as f32 * *size * 0.6;
- let text_h = *size;
-
- let tx1 = *x;
- let tx2 = *x + text_w;
- let ty1 = *y - text_h * 0.8;
- let ty2 = *y + text_h * 0.2;
-
- if tx1 < cx + cw && tx2 > cx && ty1 < cy + ch && ty2 > cy {
- let mut b_left = bounds.map(|b| b[0]).unwrap_or(0.0);
- let b_top = bounds.map(|b| b[1]).unwrap_or(0.0);
- let mut b_right = bounds.map(|b| b[2]).unwrap_or(99999.0);
- let b_bottom = bounds.map(|b| b[3]).unwrap_or(99999.0);
-
- if tx1 < cx && tx2 > cx {
- b_right = b_right.min(cx);
- } else if tx1 < cx + cw && tx2 > cx + cw {
- b_left = b_left.max(cx + cw);
- } else {
- b_right = b_left; // completely covered
- }
- final_bounds = Some([b_left, b_top, b_right, b_bottom]);
- }
- }
-
- if self.open_with_dialog.is_some() {
- let cx = (self.width as f32 - 400.0) / 2.0;
- let cy = (self.height as f32 - 160.0) / 2.0;
- let cw = 400.0;
- let ch = 160.0;
-
- let text_w = text.chars().count() as f32 * *size * 0.6;
- let text_h = *size;
-
- let tx1 = *x;
- let tx2 = *x + text_w;
- let ty1 = *y - text_h * 0.8;
- let ty2 = *y + text_h * 0.2;
-
- if tx1 < cx + cw && tx2 > cx && ty1 < cy + ch && ty2 > cy {
- let mut b_left = final_bounds.map(|b| b[0]).unwrap_or(0.0);
- let b_top = final_bounds.map(|b| b[1]).unwrap_or(0.0);
- let mut b_right = final_bounds.map(|b| b[2]).unwrap_or(99999.0);
- let b_bottom = final_bounds.map(|b| b[3]).unwrap_or(99999.0);
-
- if tx1 < cx && tx2 > cx {
- b_right = b_right.min(cx);
- } else if tx1 < cx + cw && tx2 > cx + cw {
- b_left = b_left.max(cx + cw);
- } else {
- b_right = b_left; // completely covered
- }
- final_bounds = Some([b_left, b_top, b_right, b_bottom]);
- }
- }
-
- text_items.push(TextItem::new(
- &mut self.font_system,
- text,
- *size,
- *x,
- *y,
- glyphon::Color::rgb(
- (col[0] * 255.0) as u8,
- (col[1] * 255.0) as u8,
- (col[2] * 255.0) as u8,
- ),
- font.as_deref(),
- final_bounds,
- ));
- }
+ // Gather all popovers
+ let mut popover_pc = pages::PageContent::new();
+ cce_ui::layout::render_popovers(&mut popover_pc, &mut self.ui_context);
- // Render context menu overlay if visible
+ // Gather context menu overlay if visible
+ let mut context_menu_pc = pages::PageContent::new();
if self.context_menu.visible {
let cx = self.context_menu.x;
let cy = self.context_menu.y;
@@ -439,53 +328,30 @@ impl FilesystemApp {
let ch = self.context_menu.h;
// Border
- widgets.push(AppWidget {
- x: cx, y: cy, w: cw, h: ch,
- color: [0.22, 0.22, 0.28, 1.0], hover_color: [0.22, 0.22, 0.28, 1.0],
- hovering: false,
- action: None,
- });
+ context_menu_pc.rect([0.22, 0.22, 0.28, 1.0], cx, cy, cw, ch);
// Background
- widgets.push(AppWidget {
- x: cx + 1.0, y: cy + 1.0, w: cw - 2.0, h: ch - 2.0,
- color: cce_ui::color::popover_bg_color(), hover_color: cce_ui::color::popover_bg_color(),
- hovering: false,
- action: None,
- });
+ context_menu_pc.rect(cce_ui::color::popover_bg_color(), cx + 1.0, cy + 1.0, cw - 2.0, ch - 2.0);
// Hover highlight
if let Some(h_idx) = self.context_menu.hovered {
let iy = cy + h_idx as f32 * 24.0;
- widgets.push(AppWidget {
- x: cx + 2.0, y: iy + 2.0, w: cw - 4.0, h: 20.0,
- color: [0.20, 0.40, 0.65, 0.6], hover_color: [0.20, 0.40, 0.65, 0.6],
- hovering: false,
- action: None,
- });
+ context_menu_pc.rect([0.20, 0.40, 0.65, 0.6], cx + 2.0, iy + 2.0, cw - 4.0, 20.0);
}
// Text options
for (idx, (opt, _)) in self.context_menu.options.iter().enumerate() {
let iy = cy + idx as f32 * 24.0 + (24.0 - 12.0) / 2.0;
let text_color = if idx == 0 {
- glyphon::Color::rgb(0x70, 0x70, 0x78)
+ [0.44, 0.44, 0.47, 1.0]
} else if self.context_menu.hovered == Some(idx) {
- glyphon::Color::rgb(0xff, 0xff, 0xff)
+ [1.0, 1.0, 1.0, 1.0]
} else {
- glyphon::Color::rgb(0xcc, 0xcc, 0xd4)
+ [0.8, 0.8, 0.83, 1.0]
};
- text_items.push(TextItem::new(
- &mut self.font_system,
- opt,
- 12.0,
- cx + 8.0,
- iy,
- text_color,
- None,
- None,
- ));
+ context_menu_pc.text(opt, cx + 8.0, iy, 12.0, text_color);
}
}
- // Render open-with dialog overlay if active
+ // 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 {
let dialog_w = 400.0;
let dialog_h = 160.0;
@@ -493,87 +359,18 @@ impl FilesystemApp {
let dialog_y = (self.height as f32 - dialog_h) / 2.0;
// Semi-transparent backdrop overlay
- widgets.push(AppWidget {
- x: 0.0, y: 0.0, w: self.width as f32, h: self.height as f32,
- color: [0.02, 0.02, 0.03, 0.6], hover_color: [0.02, 0.02, 0.03, 0.6],
- hovering: false,
- action: None,
- });
-
+ dialog_pc.rect([0.02, 0.02, 0.03, 0.6], 0.0, 0.0, self.width as f32, self.height as f32);
// Dialog panel border
- widgets.push(AppWidget {
- x: dialog_x, y: dialog_y, w: dialog_w, h: dialog_h,
- color: [0.22, 0.22, 0.28, 1.0], hover_color: [0.22, 0.22, 0.28, 1.0],
- hovering: false,
- action: None,
- });
-
+ dialog_pc.rect([0.22, 0.22, 0.28, 1.0], dialog_x, dialog_y, dialog_w, dialog_h);
// Dialog panel background
- widgets.push(AppWidget {
- x: dialog_x + 1.0, y: dialog_y + 1.0, w: dialog_w - 2.0, h: dialog_h - 2.0,
- color: [0.08, 0.08, 0.12, 1.0], hover_color: [0.08, 0.08, 0.12, 1.0],
- hovering: false,
- action: None,
- });
-
- // Dialog Title text
- text_items.push(TextItem::new(
- &mut self.font_system,
- "Open with...",
- 14.0,
- dialog_x + 20.0,
- dialog_y + 20.0,
- glyphon::Color::rgb(0xff, 0xff, 0xff),
- None,
- None,
- ));
-
- // Dialog description
- text_items.push(TextItem::new(
- &mut self.font_system,
- "Enter command:",
- 11.0,
- dialog_x + 20.0,
- dialog_y + 42.0,
- glyphon::Color::rgb(0x8a, 0x8a, 0x93),
- None,
- None,
- ));
-
- // Render textbox
- 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;
+ dialog_pc.rect([0.08, 0.08, 0.12, 1.0], dialog_x + 1.0, dialog_y + 1.0, dialog_w - 2.0, dialog_h - 2.0);
- textbox.prepare_text(&mut self.font_system);
- textbox.set_rect(tb_x, tb_y, tb_w, tb_h);
-
- // Textbox quads
- for (qx, qy, qw, qh, qc) in textbox.all_quads(&mut self.ui_context) {
- widgets.push(AppWidget {
- x: qx, y: qy, w: qw, h: qh,
- color: qc, hover_color: qc,
- hovering: false,
- action: None,
- });
- }
+ // Title text
+ dialog_pc.text("Open with...", dialog_x + 20.0, dialog_y + 20.0, 14.0, [1.0, 1.0, 1.0, 1.0]);
+ // 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 text label
- let font_opt = textbox.widget_font();
- for (label, bounds) in textbox.text_labels_with_bounds(&mut self.ui_context) {
- let color_rgb = glyphon::Color::rgb(label.color[0], label.color[1], label.color[2]);
- text_items.push(TextItem::new(
- &mut self.font_system,
- &label.text,
- label.font_size,
- label.x,
- label.y,
- color_rgb,
- font_opt.as_deref(),
- bounds,
- ));
- }
+ // 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.
// Render Buttons: Cancel & Open
let btn_cancel_x = dialog_x + dialog_w - 180.0;
@@ -586,56 +383,107 @@ impl FilesystemApp {
let btn_open_w = 80.0;
let btn_open_h = 28.0;
- // Cancel button background & hover
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;
let cancel_bg = if cancel_hover { [0.35, 0.15, 0.15, 0.8] } else { [0.25, 0.12, 0.12, 0.5] };
+ dialog_pc.button("Cancel", btn_cancel_x, btn_cancel_y, btn_cancel_w, btn_cancel_h, cancel_bg, [0.35, 0.15, 0.15, 0.8], [0.83, 0.83, 0.83, 1.0], Message::OpenWithCancel);
- widgets.push(AppWidget {
- x: btn_cancel_x, y: btn_cancel_y, w: btn_cancel_w, h: btn_cancel_h,
- color: cancel_bg, hover_color: [0.35, 0.15, 0.15, 0.8],
- hovering: cancel_hover,
- action: Some(Message::OpenWithCancel),
- });
- let cancel_text_w = "Cancel".chars().count() as f32 * 12.0 * 0.65;
- text_items.push(TextItem::new(
- &mut self.font_system,
- "Cancel",
- 12.0,
- btn_cancel_x + (btn_cancel_w - cancel_text_w) / 2.0,
- btn_cancel_y + (btn_cancel_h - 12.0 * 1.4) / 2.0,
- glyphon::Color::rgb(0xd4, 0xd4, 0xd4),
- None,
- None,
- ));
-
- // Open button background & hover
let open_hover = self.cursor_x >= btn_open_x && self.cursor_x <= btn_open_x + btn_open_w
&& self.cursor_y >= btn_open_y && self.cursor_y <= btn_open_y + btn_open_h;
let open_bg = if open_hover { [0.46, 0.66, 0.48, 1.0] } else { [0.36, 0.56, 0.38, 1.0] };
+ dialog_pc.button("Open", btn_open_x, btn_open_y, btn_open_w, btn_open_h, open_bg, [0.46, 0.66, 0.48, 1.0], [0.1, 0.16, 0.11, 1.0], Message::OpenWithSubmit);
+ }
- widgets.push(AppWidget {
- x: btn_open_x, y: btn_open_y, w: btn_open_w, h: btn_open_h,
- color: open_bg, hover_color: [0.46, 0.66, 0.48, 1.0],
- hovering: open_hover,
- action: Some(Message::OpenWithSubmit),
- });
- let open_text_w = "Open".chars().count() as f32 * 12.0 * 0.65;
- text_items.push(TextItem::new(
- &mut self.font_system,
- "Open",
- 12.0,
- btn_open_x + (btn_open_w - open_text_w) / 2.0,
- btn_open_y + (btn_open_h - 12.0 * 1.4) / 2.0,
- glyphon::Color::rgb(0x1a, 0x29, 0x1c),
- None,
- None,
- ));
+ // Translate everything into widgets and text_items!
+ // We collect from: window_pc, pc, popover_pc, context_menu_pc, dialog_pc
+ let mut page_buttons = Vec::new();
+
+ for pc_part in &[window_pc, pc, popover_pc, context_menu_pc, dialog_pc] {
+ for (c, x, y, w, h, r, corners) in &pc_part.rects {
+ widgets.push(AppWidget {
+ x: *x,
+ y: *y,
+ w: *w,
+ h: *h,
+ color: *c,
+ hover_color: *c,
+ hovering: false,
+ radius: *r,
+ corners: *corners,
+ action: None,
+ });
+ }
+ for (btn, action) in &pc_part.buttons {
+ let base = btn.base().unwrap();
+ let bg = btn.bg.unwrap_or([0.16, 0.16, 0.24, 1.0]);
+ let hover_bg = btn.hover_bg.unwrap_or([0.25, 0.30, 0.26, 1.0]);
+ let label = base.label.as_deref().unwrap_or("");
+ let label_size = 12.0;
+ let label_color = btn.label_color.unwrap_or([0.83, 0.83, 0.83, 1.0]);
+
+ let hovering = self.cursor_x >= base.x && self.cursor_x <= base.x + base.w
+ && self.cursor_y >= base.y && self.cursor_y <= base.y + base.h;
+ let col = if hovering { hover_bg } else { bg };
+
+ widgets.push(AppWidget {
+ x: base.x,
+ y: base.y,
+ w: base.w,
+ h: base.h,
+ color: col,
+ hover_color: hover_bg,
+ hovering,
+ radius: 4.0, // standard button radius
+ corners: (true, true, true, true),
+ action: Some(action.clone()),
+ });
+
+ let text_x = if btn.left_align {
+ base.x + 8.0
+ } else {
+ let text_w = label.chars().count() as f32 * label_size * 0.65;
+ base.x + (base.w - text_w) / 2.0
+ };
+ let text_y = base.y + (base.h - label_size * 1.4) / 2.0;
+
+ text_items.push(TextItem::new(
+ &mut self.font_system,
+ label,
+ label_size,
+ text_x,
+ text_y,
+ glyphon::Color::rgb(
+ (label_color[0] * 255.0) as u8,
+ (label_color[1] * 255.0) as u8,
+ (label_color[2] * 255.0) as u8,
+ ),
+ None,
+ None,
+ ));
+
+ page_buttons.push((btn.clone(), action.clone()));
+ }
+ for (text, size, x, y, col, font, bounds) in &pc_part.texts {
+ text_items.push(TextItem::new(
+ &mut self.font_system,
+ text,
+ *size,
+ *x,
+ *y,
+ glyphon::Color::rgb(
+ (col[0] * 255.0) as u8,
+ (col[1] * 255.0) as u8,
+ (col[2] * 255.0) as u8,
+ ),
+ font.as_deref(),
+ *bounds,
+ ));
+ }
}
self.widgets = widgets;
self.text_items = text_items;
- self.page_buttons = pc.buttons;
+ self.page_buttons = page_buttons;
self.needs_rebuild = false;
}
}
@@ -663,7 +511,13 @@ impl Application for FilesystemApp {
menubar.set_sidebar_label(Some("CLEAR".to_string()));
menubar.set_pages(pages_names);
- let fs_service = crate::services::fs::FsService::new(sender.clone());
+ 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::Window::new(0.0, 0.0, initial_w as f32, initial_h as f32)
+ .with_background(cce_ui::color::page_low_color())
+ .with_radius(12.0);
+
let mut app = Self {
current_page: Page::Browse,
browse,
@@ -681,8 +535,8 @@ impl Application for FilesystemApp {
fs
},
needs_rebuild: true,
- width: if select_mode { 900 } else { 1200 },
- height: if select_mode { 500 } else { 720 },
+ width: initial_w,
+ height: initial_h,
scale_factor: 1.0,
sender: sender.clone(),
page_buttons: Vec::new(),
@@ -703,10 +557,11 @@ impl Application for FilesystemApp {
hovered: None,
},
open_with_dialog: None,
+ root_window,
};
// Start initial directory loading via FsService
- app.fs_service.send(crate::services::fs::FsRequest::ReadLastDir);
+ app.fs_service.send(services::fs::FsRequest::ReadLastDir);
app.rebuild_layout();
app
@@ -777,7 +632,7 @@ impl Application for FilesystemApp {
None
};
if let Some(path) = selected_path {
- self.fs_service.send(crate::services::fs::FsRequest::ReadPreview(path));
+ self.fs_service.send(services::fs::FsRequest::ReadPreview(path));
} else {
pages::preview::update(&mut self.preview, pages::preview::PreviewMessage::Clear);
}
@@ -832,7 +687,7 @@ impl Application for FilesystemApp {
if !filename.is_empty() {
let path = self.browse.current_dir.join(filename);
if path.is_dir() && !is_project_dir(&path) {
- self.fs_service.send(crate::services::fs::FsRequest::ReadDirectory(path));
+ self.fs_service.send(services::fs::FsRequest::ReadDirectory(path));
} else {
println!("{}", path.display());
std::process::exit(0);
@@ -845,7 +700,7 @@ impl Application for FilesystemApp {
};
if let Some(path) = selected_path {
if path.is_dir() && !is_project_dir(&path) {
- self.fs_service.send(crate::services::fs::FsRequest::ReadDirectory(path));
+ self.fs_service.send(services::fs::FsRequest::ReadDirectory(path));
} else {
println!("{}", path.display());
std::process::exit(0);
@@ -928,7 +783,7 @@ impl Application for FilesystemApp {
}
}
- fn view(&mut self, quads: &mut Vec<(f32, f32, f32, f32, [f32; 4])>, size: LogicalSize, scale: f64) {
+ fn view(&mut self, _quads: &mut Vec<(f32, f32, f32, f32, [f32; 4])>, size: LogicalSize, scale: f64) {
if self.needs_rebuild || self.width != size.width as u32 || self.height != size.height as u32 || self.scale_factor != scale {
self.width = size.width as u32;
self.height = size.height as u32;
@@ -936,9 +791,18 @@ impl Application for FilesystemApp {
cce_ui::scale::set_scale_factor(scale as f32);
self.rebuild_layout();
}
+ }
+ fn view_rounded_quads(&mut self, quads: &mut Vec<(f32, f32, f32, f32, f32, [f32; 4], (bool, bool, bool, bool))>, size: LogicalSize, scale: f64) {
+ if self.needs_rebuild || self.width != size.width as u32 || self.height != size.height as u32 || self.scale_factor != scale {
+ self.width = size.width as u32;
+ self.height = size.height as u32;
+ self.scale_factor = scale;
+ cce_ui::scale::set_scale_factor(scale as f32);
+ self.rebuild_layout();
+ }
for w in &self.widgets {
- quads.push((w.x, w.y, w.w, w.h, w.color));
+ quads.push((w.x, w.y, w.w, w.h, w.radius, w.color, w.corners));
}
}
@@ -1262,7 +1126,7 @@ impl Application for FilesystemApp {
}
}
}
- self.fs_service.send(crate::services::fs::FsRequest::ReadDirectory(target_path));
+ self.fs_service.send(services::fs::FsRequest::ReadDirectory(target_path));
*needs_rebuild = true;
self.needs_rebuild = true;
}
@@ -1290,7 +1154,7 @@ impl Application for FilesystemApp {
}
}
}
- self.fs_service.send(crate::services::fs::FsRequest::ReadDirectory(target_path));
+ self.fs_service.send(services::fs::FsRequest::ReadDirectory(target_path));
changed = true;
}
}
@@ -1329,7 +1193,7 @@ impl Application for FilesystemApp {
}
let selected_path = Some(entry.path.clone());
if let Some(path) = selected_path {
- self.fs_service.send(crate::services::fs::FsRequest::ReadPreview(path));
+ self.fs_service.send(services::fs::FsRequest::ReadPreview(path));
}
}
changed = true;
@@ -1355,7 +1219,7 @@ impl Application for FilesystemApp {
if has_parent && dbl_idx == 0 {
if let Some(parent) = self.browse.current_dir.parent() {
let parent_path = parent.to_path_buf();
- self.fs_service.send(crate::services::fs::FsRequest::ReadDirectory(parent_path));
+ self.fs_service.send(services::fs::FsRequest::ReadDirectory(parent_path));
changed = true;
}
} else if dbl_idx >= offset {
@@ -1363,7 +1227,7 @@ impl Application for FilesystemApp {
if let Some(entry) = self.browse.entries.get(entry_idx) {
if entry.is_dir && !is_project_dir(&entry.path) {
let path = entry.path.clone();
- self.fs_service.send(crate::services::fs::FsRequest::ReadDirectory(path));
+ self.fs_service.send(services::fs::FsRequest::ReadDirectory(path));
changed = true;
} else if self.select_mode {
self.browse.save_name_box.text = entry.name.clone();
diff --git a/src/pages/mod.rs b/src/pages/mod.rs
index efa5e93..059e4df 100644
--- a/src/pages/mod.rs
+++ b/src/pages/mod.rs
@@ -37,7 +37,7 @@ impl Page {
}
pub struct PageContent {
- pub rects: Vec<([f32; 4], f32, f32, f32, f32)>,
+ pub rects: Vec<([f32; 4], f32, f32, f32, f32, f32, (bool, bool, bool, bool))>,
pub texts: Vec<(String, f32, f32, f32, [f32; 4], Option<String>, Option<[f32; 4]>)>,
pub buttons: Vec<(cce_ui::widget::Button, crate::Message)>,
}
@@ -52,7 +52,7 @@ impl PageContent {
}
pub fn rect(&mut self, color: [f32; 4], x: f32, y: f32, w: f32, h: f32) {
- self.rects.push((color, x, y, w, h));
+ self.rects.push((color, x, y, w, h, 0.0, (true, true, true, true)));
}
pub fn text(&mut self, content: &str, x: f32, y: f32, size: f32, color: [f32; 4]) {
@@ -107,7 +107,15 @@ impl PageContent {
impl RenderTarget for PageContent {
fn rect(&mut self, color: [f32; 4], x: f32, y: f32, w: f32, h: f32) {
- self.rects.push((color, x, y, w, h));
+ self.rects.push((color, x, y, w, h, 0.0, (true, true, true, true)));
+ }
+
+ fn rect_with_radius(&mut self, color: [f32; 4], x: f32, y: f32, w: f32, h: f32, radius: f32) {
+ self.rects.push((color, x, y, w, h, radius, (true, true, true, true)));
+ }
+
+ fn rect_with_radius_corners(&mut self, color: [f32; 4], x: f32, y: f32, w: f32, h: f32, radius: f32, corners: (bool, bool, bool, bool)) {
+ self.rects.push((color, x, y, w, h, radius, corners));
}
fn text(&mut self, content: &str, x: f32, y: f32, size: f32, color: [f32; 4]) {