file manager
git clone https://git.lucas.co/cce-files.git
refactor: hold widgets in Owned boxes (cce-ui Owned<W>)
The UiContext registry keeps raw pointers to registered widgets, and a
widget that moved while registered (its Vec reallocating, its struct
returned by value) left one pointing at the old address. Each widget
field is now an Owned<Adapted<..>> — a heap box whose allocation, not the
widget, carries the liveness token the registry watches — so the
registered address never moves and is dropped from the registry when the
box is freed. Field access is unchanged through Deref; construction
sites wrap in Owned::new.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
src/main.rs | 13 +++++++------
src/pages/browse.rs | 15 ++++++++-------
src/pages/mod.rs | 13 +++++++------
src/pages/network.rs | 9 +++++----
src/pages/space.rs | 5 +++--
5 files changed, 30 insertions(+), 25 deletions(-)
diff --git a/src/main.rs b/src/main.rs
index b279807..6d71275 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,4 +1,5 @@
use wayland_client::QueueHandle;
+use cce_ui::widget::Owned;
use cce_ui::cosmic_text::FontSystem;
use cce_ui::engine::{Application, LogicalPosition, LogicalSize, WindowSettings};
@@ -83,7 +84,7 @@ impl PromptKind {
struct PromptDialog {
kind: PromptKind,
path: std::path::PathBuf,
- textbox: cce_ui::widget::Adapted<cce_ui::widget::TextBox>,
+ textbox: Owned<cce_ui::widget::Adapted<cce_ui::widget::TextBox>>,
}
fn prompt_rects(win_w: f32, win_h: f32) -> PromptRects {
@@ -600,11 +601,11 @@ struct FilesystemApp {
width: u32,
height: u32,
scale_factor: f64,
- page_buttons: Vec<(cce_ui::widget::Adapted<cce_ui::widget::Button>, Message)>,
+ page_buttons: Vec<(Owned<cce_ui::widget::Adapted<cce_ui::widget::Button>>, Message)>,
hovered_button: Option<usize>,
cursor_x: f32,
cursor_y: f32,
- paginator: cce_ui::widget::Adapted<Paginator>,
+ paginator: Owned<cce_ui::widget::Adapted<Paginator>>,
just_initialized: bool,
ui_context: cce_ui::context::UiContext,
watcher: Option<notify::RecommendedWatcher>,
@@ -1569,7 +1570,7 @@ impl Application for FilesystemApp {
hovered_button: None,
cursor_x: 0.0,
cursor_y: 0.0,
- paginator,
+ paginator: Owned::new(paginator),
just_initialized: true,
ui_context: cce_ui::context::UiContext::new(),
watcher: None,
@@ -1796,7 +1797,7 @@ impl Application for FilesystemApp {
.with_placeholder("Program/Command");
tb.focus();
self.ui_context.set_focused(&mut tb);
- self.prompt_dialog = Some(PromptDialog { kind: PromptKind::OpenWith, path, textbox: tb });
+ self.prompt_dialog = Some(PromptDialog { kind: PromptKind::OpenWith, path, textbox: Owned::new(tb) });
*needs_rebuild = true;
self.needs_rebuild = true;
}
@@ -1816,7 +1817,7 @@ impl Application for FilesystemApp {
tb.all_selected = false;
tb.sync_editor_state();
self.ui_context.set_focused(&mut tb);
- self.prompt_dialog = Some(PromptDialog { kind: PromptKind::Rename, path, textbox: tb });
+ self.prompt_dialog = Some(PromptDialog { kind: PromptKind::Rename, path, textbox: Owned::new(tb) });
*needs_rebuild = true;
self.needs_rebuild = true;
}
diff --git a/src/pages/browse.rs b/src/pages/browse.rs
index e57026c..083abae 100644
--- a/src/pages/browse.rs
+++ b/src/pages/browse.rs
@@ -1,6 +1,7 @@
use std::path::{Path, PathBuf};
use crate::pages::PageContent;
+use cce_ui::widget::Owned;
use cce_ui::widget::{Adapted, WidgetHost, Breadcrumb, PathController};
use cce_ui::layout::{ColumnLayout, LayoutStrategy};
@@ -27,7 +28,7 @@ pub struct BrowseState {
pub show_hidden: bool,
pub list: crate::row_list::RowList,
pub search_visible: bool,
- pub search_box: cce_ui::widget::Adapted<cce_ui::widget::TextBox>,
+ pub search_box: Owned<cce_ui::widget::Adapted<cce_ui::widget::TextBox>>,
pub selected: Option<usize>,
/// The selection the list was last auto-scrolled to. The layout pass runs
/// every frame, and an unconditional scroll_into_view there UNDID every
@@ -37,8 +38,8 @@ pub struct BrowseState {
pub autoscrolled_to: Option<usize>,
/// A just-created folder to select once a refresh lists it.
pub pending_select: Option<PathBuf>,
- pub breadcrumb: Adapted<Breadcrumb>,
- pub save_name_box: cce_ui::widget::Adapted<cce_ui::widget::TextBox>,
+ pub breadcrumb: Owned<Adapted<Breadcrumb>>,
+ pub save_name_box: Owned<cce_ui::widget::Adapted<cce_ui::widget::TextBox>>,
}
impl Default for BrowseState {
@@ -54,14 +55,14 @@ impl Default for BrowseState {
show_hidden: false,
list: crate::row_list::RowList::new(cce_ui::layout::button_height(), 2.0),
search_visible: false,
- search_box: cce_ui::widget::TextBox::new(String::new())
+ search_box: Owned::new(cce_ui::widget::TextBox::new(String::new())
.with_placeholder("Search...")
- .with_update_on_type(true),
+ .with_update_on_type(true)),
selected: None,
autoscrolled_to: None,
pending_select: None,
- breadcrumb,
- save_name_box: cce_ui::widget::TextBox::new(String::new()).with_max_width(None),
+ breadcrumb: Owned::new(breadcrumb),
+ save_name_box: Owned::new(cce_ui::widget::TextBox::new(String::new()).with_max_width(None)),
};
state.update_breadcrumb();
state
diff --git a/src/pages/mod.rs b/src/pages/mod.rs
index 91c091e..7c783bd 100644
--- a/src/pages/mod.rs
+++ b/src/pages/mod.rs
@@ -3,6 +3,7 @@ pub mod preview;
pub mod network;
pub mod space;
+use cce_ui::widget::Owned;
use cce_ui::layout::RenderTarget;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -54,7 +55,7 @@ pub fn breadcrumb_h() -> f32 {
/// started their content 28px and 34px down against Browse's 24 + gap.
pub fn breadcrumb_header(
pc: &mut PageContent,
- breadcrumb: &mut cce_ui::widget::Adapted<cce_ui::widget::Breadcrumb>,
+ breadcrumb: &mut Owned<cce_ui::widget::Adapted<cce_ui::widget::Breadcrumb>>,
cx: f32,
cy: f32,
cw: f32,
@@ -100,7 +101,7 @@ pub fn breadcrumb_header(
/// this carve to `window_pc` would buy nothing.
pub fn breadcrumb_relief(
pc: &mut PageContent,
- breadcrumb: &cce_ui::widget::Adapted<cce_ui::widget::Breadcrumb>,
+ breadcrumb: &Owned<cce_ui::widget::Adapted<cce_ui::widget::Breadcrumb>>,
rect: cce_ui::scene::layout::Rect,
) {
let r = cce_ui::layout::dropdown_corner_radius();
@@ -172,7 +173,7 @@ impl Stroke {
pub struct PageContent {
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::Adapted<cce_ui::widget::Button>, crate::Message)>,
+ pub buttons: Vec<(Owned<cce_ui::widget::Adapted<cce_ui::widget::Button>>, crate::Message)>,
/// Relief steps for the control_relief styling — (x, y, w, h, radius, depth,
/// kind: [`RELIEF_RECESSED`]/[`RELIEF_RAISED`]/[`RELIEF_INSET`]). The flat rects
/// own the faces; these are the edges-only walls emitted over them (the
@@ -422,7 +423,7 @@ impl PageContent {
.with_bg(bg)
.with_hover_bg(hover_bg)
.with_label_color(label_color);
- self.buttons.push((btn, action));
+ self.buttons.push((Owned::new(btn), action));
}
/// A button wearing the toolkit's own face — no per-call colors. The
@@ -437,7 +438,7 @@ impl PageContent {
.with_label(label)
.with_bg([0.0, 0.0, 0.0, 0.0])
.with_hover_bg([1.0, 1.0, 1.0, 0.10]);
- self.buttons.push((btn, action));
+ self.buttons.push((Owned::new(btn), action));
}
pub fn button_left(
@@ -458,7 +459,7 @@ impl PageContent {
.with_hover_bg(hover_bg)
.with_label_color(label_color)
.with_left_align(true);
- self.buttons.push((btn, action));
+ self.buttons.push((Owned::new(btn), action));
}
}
diff --git a/src/pages/network.rs b/src/pages/network.rs
index c9af9bc..4b9cfc8 100644
--- a/src/pages/network.rs
+++ b/src/pages/network.rs
@@ -1,11 +1,12 @@
use std::path::{Path, PathBuf};
use crate::pages::PageContent;
use crate::pages::browse::{BrowseState, DirEntry};
+use cce_ui::widget::Owned;
use cce_ui::widget::{Adapted, Graph, GraphNode, Breadcrumb, GraphController, PathController};
pub struct NetworkState {
- pub graph: Adapted<Graph>,
- pub breadcrumb: Adapted<Breadcrumb>,
+ pub graph: Owned<Adapted<Graph>>,
+ pub breadcrumb: Owned<Adapted<Breadcrumb>>,
pub last_dir: PathBuf,
/// The graph pane `(x, y, w, h)` the nodes were last laid out for: a
/// pane of another size is laid out again (`view`), so the columns fit
@@ -41,8 +42,8 @@ impl Default for NetworkState {
breadcrumb.set_network_opacity(0.95);
Self {
- graph,
- breadcrumb,
+ graph: Owned::new(graph),
+ breadcrumb: Owned::new(breadcrumb),
last_dir: PathBuf::new(),
node_glyphs: Vec::new(),
laid_out_for: (0.0, 0.0, 0.0, 0.0),
diff --git a/src/pages/space.rs b/src/pages/space.rs
index 76f038e..12f47a6 100644
--- a/src/pages/space.rs
+++ b/src/pages/space.rs
@@ -19,6 +19,7 @@ use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
+use cce_ui::widget::Owned;
use cce_ui::widget::{Adapted, Breadcrumb, PathController};
use crate::pages::PageContent;
@@ -405,7 +406,7 @@ pub fn update(state: &mut SpaceState, msg: SpaceMessage) {
// ── Page state ──────────────────────────────────────────────────────
pub struct SpaceState {
- pub breadcrumb: Adapted<Breadcrumb>,
+ pub breadcrumb: Owned<Adapted<Breadcrumb>>,
/// The directory the current `tree` describes. Empty until a scan lands.
pub scanned_dir: PathBuf,
pub tree: Option<TreeNode>,
@@ -439,7 +440,7 @@ impl Default for SpaceState {
let mut breadcrumb = Breadcrumb::new();
breadcrumb.set_network_opacity(0.95);
Self {
- breadcrumb,
+ breadcrumb: Owned::new(breadcrumb),
scanned_dir: PathBuf::new(),
tree: None,
tiles: Vec::new(),