system settings
git clone https://git.lucas.co/cce-system-interface.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/app.rs | 13 +++++++------
src/main.rs | 17 +++++++++--------
src/pages/accounts.rs | 25 +++++++++++++------------
src/pages/audio.rs | 39 ++++++++++++++++++++-------------------
src/pages/bluetooth.rs | 7 ++++---
src/pages/browser.rs | 39 ++++++++++++++++++++-------------------
src/pages/default_apps.rs | 5 +++--
src/pages/network.rs | 7 ++++---
src/pages/notifications.rs | 19 ++++++++++---------
src/pages/packages.rs | 13 +++++++------
src/pages/power.rs | 17 +++++++++--------
src/pages/services.rs | 9 +++++----
src/pages/storage.rs | 15 ++++++++-------
src/pages/system_info.rs | 21 +++++++++++----------
src/pages/timers.rs | 25 +++++++++++++------------
src/renderer.rs | 2 +-
16 files changed, 144 insertions(+), 129 deletions(-)
diff --git a/src/app.rs b/src/app.rs
index b13e9ee..7e8b215 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -1,3 +1,4 @@
+use cce_ui::widget::Owned;
use cce_ui::layout::RenderTarget;
use crate::pages::audio;
@@ -204,7 +205,7 @@ pub struct PageContent {
/// (button, action, clip): clip is the innermost push_clip_rect at emission
/// time (page coords) — the renderer clamps the drawn quad, label bounds,
/// and the dispatch clone's hit rect to it.
- pub buttons: Vec<(cce_ui::widget::Adapted<cce_ui::widget::Button>, AppAction, Option<[f32; 4]>)>,
+ pub buttons: Vec<(Owned<cce_ui::widget::Adapted<cce_ui::widget::Button>>, AppAction, Option<[f32; 4]>)>,
/// Glyphs placed with [`PageContent::icon`] (and a widget's own, through
/// `RenderTarget::icon` — the expanded Dropdown's chevron).
pub icons: Vec<PageIcon>,
@@ -343,7 +344,7 @@ impl PageContent {
.with_hover_bg(hover_bg)
.with_label_color(label_color);
let clip = self.clip_stack.last().copied();
- self.buttons.push((btn, action, clip));
+ self.buttons.push((Owned::new(btn), action, clip));
}
/// A button whose face is a bundled cce-icons glyph instead of a label.
@@ -369,7 +370,7 @@ impl PageContent {
btn = btn.with_icon(id, iw as f32, ih as f32).with_icon_alpha(alpha);
}
let clip = self.clip_stack.last().copied();
- self.buttons.push((btn, action, clip));
+ self.buttons.push((Owned::new(btn), action, clip));
}
/// A row of a list, in the toolkit's list style (`Button::new_list_row`,
@@ -384,7 +385,7 @@ impl PageContent {
if self.measure_only { return; }
let btn = cce_ui::widget::Button::new_list_row(x, y, w, h);
let clip = self.clip_stack.last().copied();
- self.buttons.push((btn, action, clip));
+ self.buttons.push((Owned::new(btn), action, clip));
}
/// [`button_icon`](Self::button_icon) with the glyph tinted
@@ -404,7 +405,7 @@ impl PageContent {
btn = btn.with_icon(id, iw as f32, ih as f32).with_icon_alpha(label_color[3]);
}
let clip = self.clip_stack.last().copied();
- self.buttons.push((btn, action, clip));
+ self.buttons.push((Owned::new(btn), action, clip));
}
pub fn button_left(&mut self, label: &str, x: f32, y: f32, w: f32, h: f32,
@@ -418,7 +419,7 @@ impl PageContent {
.with_label_color(label_color)
.with_left_align(true);
let clip = self.clip_stack.last().copied();
- self.buttons.push((btn, action, clip));
+ self.buttons.push((Owned::new(btn), action, clip));
}
}
diff --git a/src/main.rs b/src/main.rs
index 3f8c2f7..7e5a096 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,3 +1,4 @@
+use cce_ui::widget::Owned;
use cce_ui::widget::hover_animation;
use cce_ui::cosmic_text::{Buffer, FontSystem};
@@ -74,7 +75,7 @@ struct SystemInterface {
/// (`PageContent::control_relief_marks`) — display_list slots each carve
/// back between the rects the widget drew before and after it.
popover_control_relief_marks: Vec<usize>,
- page_buttons: Vec<(cce_ui::widget::Adapted<cce_ui::widget::Button>, AppAction)>,
+ page_buttons: Vec<(Owned<cce_ui::widget::Adapted<cce_ui::widget::Button>>, AppAction)>,
sidebar_width: f32,
header_height: f32,
@@ -138,12 +139,12 @@ struct SystemInterface {
/// a frame that only replays the cached layout paints no widget, and
/// would otherwise read as the field closing.
text_claim: Option<[f32; 4]>,
- page_dropdown: cce_ui::widget::Adapted<cce_ui::widget::input::Dropdown>,
+ page_dropdown: Owned<cce_ui::widget::Adapted<cce_ui::widget::input::Dropdown>>,
// Switcher + Page DISSOLVED (Phase 6u): the current page is app.current_page, page
// scroll is scroll_y/max_scroll_y, and the page scrollbar is this app-owned widget
// (rendered into the window assembly, evented directly). content_h feeds it — the
// window pass reads last frame's value, exactly as the legacy Page did.
- page_scroll_bar: cce_ui::widget::Adapted<crate::scroll_bar::ScrollBar>,
+ page_scroll_bar: Owned<cce_ui::widget::Adapted<crate::scroll_bar::ScrollBar>>,
content_h: f32,
// Section wells: body box + title tab (page coords, pre-scroll) — carved by
// display_list.
@@ -185,7 +186,7 @@ struct SystemInterface {
scroll_logs: Vec<String>,
search_open: bool,
search_query: String,
- search_box: cce_ui::widget::Adapted<cce_ui::widget::input::TextBox>,
+ search_box: Owned<cce_ui::widget::Adapted<cce_ui::widget::input::TextBox>>,
}
@@ -301,8 +302,8 @@ impl cce_ui::engine::Application for SystemInterface {
laid_out_page: None,
refocus_rect: None,
text_claim: None,
- page_dropdown,
- page_scroll_bar: crate::scroll_bar::ScrollBar::new(),
+ page_dropdown: Owned::new(page_dropdown),
+ page_scroll_bar: Owned::new(crate::scroll_bar::ScrollBar::new()),
content_h: 0.0,
page_reliefs: Vec::new(),
page_control_reliefs: Vec::new(),
@@ -320,9 +321,9 @@ impl cce_ui::engine::Application for SystemInterface {
scroll_logs: Vec::new(),
search_open: false,
search_query: String::new(),
- search_box: cce_ui::widget::input::TextBox::new(String::new())
+ search_box: Owned::new(cce_ui::widget::input::TextBox::new(String::new())
.with_placeholder("Search sections & parameters...")
- .with_draw_bg_border(false),
+ .with_draw_bg_border(false)),
};
this.app.system_info.sender = Some(this.sender.clone());
diff --git a/src/pages/accounts.rs b/src/pages/accounts.rs
index 2cec31a..fc6c055 100644
--- a/src/pages/accounts.rs
+++ b/src/pages/accounts.rs
@@ -1,4 +1,5 @@
use crate::app::{AppAction, PageContent, SectionContextExt, section_divider, section_kv_row};
+use cce_ui::widget::Owned;
use cce_ui::layout::{PageLayoutBuilder, LayoutStrategy, RenderTarget};
use cce_ui::widget::ScrollRegion;
use cce_ui::widget::{TextBox, WidgetHost};
@@ -79,10 +80,10 @@ pub struct AccountsState {
pub accounts: Vec<AccountInfo>,
pub selected_idx: Option<usize>,
pub adding_new: bool,
- pub email_box: cce_ui::widget::Adapted<TextBox>,
- pub password_box: cce_ui::widget::Adapted<TextBox>,
- pub imap_box: cce_ui::widget::Adapted<TextBox>,
- pub smtp_box: cce_ui::widget::Adapted<TextBox>,
+ pub email_box: Owned<cce_ui::widget::Adapted<TextBox>>,
+ pub password_box: Owned<cce_ui::widget::Adapted<TextBox>>,
+ pub imap_box: Owned<cce_ui::widget::Adapted<TextBox>>,
+ pub smtp_box: Owned<cce_ui::widget::Adapted<TextBox>>,
pub status_msg: Option<String>,
pub status_msg_timer: f32,
pub oauth_listener_running: bool,
@@ -92,8 +93,8 @@ pub struct AccountsState {
pub editing_email: Option<String>,
/// Per-account OAuth credentials — the copy in `accounts.json` that
/// cce-mail actually refreshes with, not the global template.
- pub oauth_client_id_box: cce_ui::widget::Adapted<TextBox>,
- pub oauth_client_secret_box: cce_ui::widget::Adapted<TextBox>,
+ pub oauth_client_id_box: Owned<cce_ui::widget::Adapted<TextBox>>,
+ pub oauth_client_secret_box: Owned<cce_ui::widget::Adapted<TextBox>>,
/// Per-address keyring status from the last snapshot, plus optimistic
/// updates from Save/Delete (the 3s watcher pass corrects them).
pub keyring: std::collections::HashMap<String, KeyringStatus>,
@@ -108,19 +109,19 @@ pub struct AccountsState {
impl AccountsState {
pub fn default_mock() -> Self {
let mut state = Self::default();
- state.email_box = TextBox::new(String::new()).with_multiline(false).with_draw_bg_border(true).with_label("Email Address");
+ state.email_box = Owned::new(TextBox::new(String::new()).with_multiline(false).with_draw_bg_border(true).with_label("Email Address"));
state.password_box = {
let mut tb = TextBox::new(String::new()).with_multiline(false).with_draw_bg_border(true).with_label("Password / App Password");
tb.is_password = true;
- tb
+ Owned::new(tb)
};
- state.imap_box = TextBox::new(String::new()).with_multiline(false).with_draw_bg_border(true).with_label("IMAP Server");
- state.smtp_box = TextBox::new(String::new()).with_multiline(false).with_draw_bg_border(true).with_label("SMTP Server");
- state.oauth_client_id_box = TextBox::new(String::new()).with_multiline(false).with_draw_bg_border(true).with_label("Google Client ID");
+ state.imap_box = Owned::new(TextBox::new(String::new()).with_multiline(false).with_draw_bg_border(true).with_label("IMAP Server"));
+ state.smtp_box = Owned::new(TextBox::new(String::new()).with_multiline(false).with_draw_bg_border(true).with_label("SMTP Server"));
+ state.oauth_client_id_box = Owned::new(TextBox::new(String::new()).with_multiline(false).with_draw_bg_border(true).with_label("Google Client ID"));
state.oauth_client_secret_box = {
let mut tb = TextBox::new(String::new()).with_multiline(false).with_draw_bg_border(true).with_label("Google Client Secret");
tb.is_password = true;
- tb
+ Owned::new(tb)
};
state.list = ScrollRegion::new(cce_ui::layout::spinbox_height(), LIST_GAP).with_sink_behind(true);
state
diff --git a/src/pages/audio.rs b/src/pages/audio.rs
index a3dae0d..4f0978b 100644
--- a/src/pages/audio.rs
+++ b/src/pages/audio.rs
@@ -1,4 +1,5 @@
use crate::app::{AppAction, PageContent, SectionContextExt, section_divider};
+use cce_ui::widget::Owned;
use cce_ui::layout::{render_widget, PageLayoutBuilder, LayoutStrategy};
use cce_ui::widget::{Spinbox, Slider, WidgetHost};
@@ -25,10 +26,10 @@ pub struct AudioState {
pub loaded: bool,
pub sinks: Vec<AudioSink>,
pub sources: Vec<AudioSource>,
- pub sink_spinboxes: Vec<Box<cce_ui::widget::Adapted<cce_ui::widget::Spinbox>>>,
- pub source_spinboxes: Vec<Box<cce_ui::widget::Adapted<cce_ui::widget::Spinbox>>>,
- pub sink_sliders: Vec<Box<cce_ui::widget::Adapted<cce_ui::widget::Slider>>>,
- pub source_sliders: Vec<Box<cce_ui::widget::Adapted<cce_ui::widget::Slider>>>,
+ pub sink_spinboxes: Vec<Box<Owned<cce_ui::widget::Adapted<cce_ui::widget::Spinbox>>>>,
+ pub source_spinboxes: Vec<Box<Owned<cce_ui::widget::Adapted<cce_ui::widget::Spinbox>>>>,
+ pub sink_sliders: Vec<Box<Owned<cce_ui::widget::Adapted<cce_ui::widget::Slider>>>>,
+ pub source_sliders: Vec<Box<Owned<cce_ui::widget::Adapted<cce_ui::widget::Slider>>>>,
}
#[derive(Debug, Clone)]
@@ -232,8 +233,8 @@ fn device_row(
active: bool,
muted: bool,
volume: f32,
- slider: &mut cce_ui::widget::Adapted<Slider>,
- spin: &mut cce_ui::widget::Adapted<Spinbox>,
+ slider: &mut Owned<cce_ui::widget::Adapted<Slider>>,
+ spin: &mut Owned<cce_ui::widget::Adapted<Spinbox>>,
mute_action: AppAction,
ctx: &mut cce_ui::context::UiContext,
) {
@@ -356,10 +357,10 @@ pub fn update(state: &mut AudioState, msg: AudioMessage) {
state.loaded = new.loaded;
state.sinks = new.sinks;
state.sources = new.sources;
- state.sink_spinboxes.resize_with(state.sinks.len(), || Box::new(Spinbox::new(50, 0, 100, 1)));
- state.source_spinboxes.resize_with(state.sources.len(), || Box::new(Spinbox::new(50, 0, 100, 1)));
- state.sink_sliders.resize_with(state.sinks.len(), || Box::new(Slider::new().with_range(0.0, 1.0).with_scroll(true)));
- state.source_sliders.resize_with(state.sources.len(), || Box::new(Slider::new().with_range(0.0, 1.0).with_scroll(true)));
+ state.sink_spinboxes.resize_with(state.sinks.len(), || Box::new(Owned::new(Spinbox::new(50, 0, 100, 1))));
+ state.source_spinboxes.resize_with(state.sources.len(), || Box::new(Owned::new(Spinbox::new(50, 0, 100, 1))));
+ state.sink_sliders.resize_with(state.sinks.len(), || Box::new(Owned::new(Slider::new().with_range(0.0, 1.0).with_scroll(true))));
+ state.source_sliders.resize_with(state.sources.len(), || Box::new(Owned::new(Slider::new().with_range(0.0, 1.0).with_scroll(true))));
}
AudioMessage::SinkVolume(id, vol) => {
if let Some(sink) = state.sinks.iter_mut().find(|s| s.id == id) {
@@ -490,17 +491,17 @@ mod tests {
],
sources: vec![],
sink_spinboxes: vec![
- Box::new(Spinbox::new(57, 0, 100, 1)),
- Box::new(Spinbox::new(50, 0, 100, 1)),
- Box::new(Spinbox::new(50, 0, 100, 1)),
- Box::new(Spinbox::new(50, 0, 100, 1)),
+ Box::new(Owned::new(Spinbox::new(57, 0, 100, 1))),
+ Box::new(Owned::new(Spinbox::new(50, 0, 100, 1))),
+ Box::new(Owned::new(Spinbox::new(50, 0, 100, 1))),
+ Box::new(Owned::new(Spinbox::new(50, 0, 100, 1))),
],
source_spinboxes: vec![],
sink_sliders: vec![
- Box::new(Slider::new()),
- Box::new(Slider::new()),
- Box::new(Slider::new()),
- Box::new(Slider::new()),
+ Box::new(Owned::new(Slider::new())),
+ Box::new(Owned::new(Slider::new())),
+ Box::new(Owned::new(Slider::new())),
+ Box::new(Owned::new(Slider::new())),
],
source_sliders: vec![],
};
@@ -522,7 +523,7 @@ mod tests {
fn test_boxed_spinbox_right_click_crash() {
use cce_ui::widget::{WidgetHost, Spinbox};
let mut state = AudioState::default();
- state.sink_spinboxes.push(Box::new(Spinbox::new(50, 0, 100, 1)));
+ state.sink_spinboxes.push(Box::new(Owned::new(Spinbox::new(50, 0, 100, 1))));
let mut ctx = cce_ui::context::UiContext::new();
let sb = &mut state.sink_spinboxes[0];
sb.set_rect(0.0, 0.0, 100.0, 44.0);
diff --git a/src/pages/bluetooth.rs b/src/pages/bluetooth.rs
index 933db27..0c6254c 100644
--- a/src/pages/bluetooth.rs
+++ b/src/pages/bluetooth.rs
@@ -1,4 +1,5 @@
use crate::app::{AppAction, PageContent, SectionContextExt};
+use cce_ui::widget::Owned;
use cce_ui::layout::{render_widget, PageLayoutBuilder, LayoutStrategy};
use cce_ui::widget::{Adapted, Toggle};
@@ -18,7 +19,7 @@ pub struct BluetoothState {
pub enabled: bool,
pub devices: Vec<BluetoothDevice>,
pub scanning: bool,
- pub toggle: Adapted<Toggle>,
+ pub toggle: Owned<Adapted<Toggle>>,
}
impl Default for BluetoothState {
@@ -30,7 +31,7 @@ impl Default for BluetoothState {
enabled: false,
devices: Vec::new(),
scanning: false,
- toggle: Toggle::new(),
+ toggle: Owned::new(Toggle::new()),
}
}
}
@@ -80,7 +81,7 @@ pub async fn fetch_bluetooth_page_state() -> BluetoothState {
.unwrap_or(false);
let devices = if enabled { fetch_devices().await } else { Vec::new() };
- BluetoothState { loaded: true, installed, service_active, enabled, devices, scanning: false, toggle: Toggle::new() }
+ BluetoothState { loaded: true, installed, service_active, enabled, devices, scanning: false, toggle: Owned::new(Toggle::new()) }
}
async fn fetch_devices() -> Vec<BluetoothDevice> {
diff --git a/src/pages/browser.rs b/src/pages/browser.rs
index affa241..e9659b6 100644
--- a/src/pages/browser.rs
+++ b/src/pages/browser.rs
@@ -7,6 +7,7 @@
use std::fs;
+use cce_ui::widget::Owned;
use cce_ui::layout::{LayoutStrategy, PageLayoutBuilder};
use cce_ui::widget::input::{Dropdown, Toggle};
use cce_ui::widget::{TextBox, WidgetHost};
@@ -61,14 +62,14 @@ pub struct BrowserState {
pub vi_mode: bool,
pub bar_position: String,
pub color_scheme: String,
- pub homepage_box: cce_ui::widget::Adapted<TextBox>,
- pub search_menu: cce_ui::widget::Adapted<Dropdown>,
- pub bar_position_menu: cce_ui::widget::Adapted<Dropdown>,
- pub color_scheme_menu: cce_ui::widget::Adapted<Dropdown>,
- pub download_dir_box: cce_ui::widget::Adapted<TextBox>,
- pub history_toggle: cce_ui::widget::Adapted<Toggle>,
- pub raindrop_toggle: cce_ui::widget::Adapted<Toggle>,
- pub vi_mode_toggle: cce_ui::widget::Adapted<Toggle>,
+ pub homepage_box: Owned<cce_ui::widget::Adapted<TextBox>>,
+ pub search_menu: Owned<cce_ui::widget::Adapted<Dropdown>>,
+ pub bar_position_menu: Owned<cce_ui::widget::Adapted<Dropdown>>,
+ pub color_scheme_menu: Owned<cce_ui::widget::Adapted<Dropdown>>,
+ pub download_dir_box: Owned<cce_ui::widget::Adapted<TextBox>>,
+ pub history_toggle: Owned<cce_ui::widget::Adapted<Toggle>>,
+ pub raindrop_toggle: Owned<cce_ui::widget::Adapted<Toggle>>,
+ pub vi_mode_toggle: Owned<cce_ui::widget::Adapted<Toggle>>,
}
impl Default for BrowserState {
@@ -92,30 +93,30 @@ impl Default for BrowserState {
vi_mode: config.vi_mode,
bar_position: config.bar_position.clone(),
color_scheme: config.color_scheme.clone(),
- homepage_box,
- search_menu: Dropdown::new(
+ homepage_box: Owned::new(homepage_box),
+ search_menu: Owned::new(Dropdown::new(
SEARCH_ENGINES.iter().map(|(_, label)| label.to_string()).collect(),
search_index(&config.search),
)
- .with_label("Search Engine"),
- bar_position_menu: Dropdown::new(
+ .with_label("Search Engine")),
+ bar_position_menu: Owned::new(Dropdown::new(
BAR_POSITIONS.iter().map(|(_, label)| label.to_string()).collect(),
bar_position_index(&config.bar_position),
)
- .with_label("Navigation Bar Position"),
- color_scheme_menu: Dropdown::new(
+ .with_label("Navigation Bar Position")),
+ color_scheme_menu: Owned::new(Dropdown::new(
COLOR_SCHEMES.iter().map(|(_, label)| label.to_string()).collect(),
color_scheme_index(&config.color_scheme),
)
- .with_label("Page Color Scheme"),
- download_dir_box,
+ .with_label("Page Color Scheme")),
+ download_dir_box: Owned::new(download_dir_box),
// Left-aligned, as the designer's parameter pane sets its toggles:
// a toggle's run is half its width, so the seam where run meets
// well sits at the midpoint, and a centred label on a row-wide
// toggle had it drawn straight through the text.
- history_toggle: Toggle::new().with_label("Record History").with_left_align(true),
- raindrop_toggle: Toggle::new().with_label("Sync Bookmarks with Raindrop").with_left_align(true),
- vi_mode_toggle: Toggle::new().with_label("Vi Keys (qutebrowser-style)").with_left_align(true),
+ history_toggle: Owned::new(Toggle::new().with_label("Record History").with_left_align(true)),
+ raindrop_toggle: Owned::new(Toggle::new().with_label("Sync Bookmarks with Raindrop").with_left_align(true)),
+ vi_mode_toggle: Owned::new(Toggle::new().with_label("Vi Keys (qutebrowser-style)").with_left_align(true)),
}
}
}
diff --git a/src/pages/default_apps.rs b/src/pages/default_apps.rs
index 77fb85d..025aedc 100644
--- a/src/pages/default_apps.rs
+++ b/src/pages/default_apps.rs
@@ -11,6 +11,7 @@
//! entries, and startcce exports as `$TERMINAL` for everything else.
use crate::app::{AppAction, PageContent};
+use cce_ui::widget::Owned;
use cce_ui::layout::{PageLayoutBuilder, LayoutStrategy};
use cce_ui::widget::{Dropdown, WidgetHost};
use std::collections::HashMap;
@@ -60,7 +61,7 @@ pub struct CategoryEntry {
/// Applied value per dropdown option (desktop id for MIME categories, a
/// command for Terminal; None = the "not set" placeholder row).
pub option_ids: Vec<Option<String>>,
- pub dropdown: cce_ui::widget::Adapted<Dropdown>,
+ pub dropdown: Owned<cce_ui::widget::Adapted<Dropdown>>,
}
#[derive(Debug, Clone)]
@@ -80,7 +81,7 @@ impl Default for DefaultAppsState {
kind,
info: CategoryInfo { candidates: Vec::new(), current: None },
option_ids: vec![None],
- dropdown: Dropdown::new(vec![NOT_SET.to_string()], 0).with_label(label),
+ dropdown: Owned::new(Dropdown::new(vec![NOT_SET.to_string()], 0).with_label(label)),
})
.collect(),
}
diff --git a/src/pages/network.rs b/src/pages/network.rs
index 59c5266..f1df89d 100644
--- a/src/pages/network.rs
+++ b/src/pages/network.rs
@@ -1,4 +1,5 @@
use crate::app::{AppAction, PageContent};
+use cce_ui::widget::Owned;
use cce_ui::widget::ScrollRegion;
use cce_ui::layout::{PageLayoutBuilder, LayoutStrategy, RenderTarget};
use cce_ui::widget::{Adapted, Toggle};
@@ -21,7 +22,7 @@ pub struct NetworkState {
pub device: String,
pub available: Vec<WifiNetwork>,
pub wifi_list: ScrollRegion,
- pub wifi_toggle: Adapted<Toggle>,
+ pub wifi_toggle: Owned<Adapted<Toggle>>,
}
impl Default for NetworkState {
@@ -35,7 +36,7 @@ impl Default for NetworkState {
device: String::new(),
available: Vec::new(),
wifi_list: ScrollRegion::new(26.0, 4.0).with_sink_behind(true),
- wifi_toggle: Toggle::new(),
+ wifi_toggle: Owned::new(Toggle::new()),
}
}
}
@@ -99,7 +100,7 @@ pub async fn fetch_network_state() -> NetworkState {
wifi_enabled, connected_ssid, signal_strength: signal,
ip_address, device, available,
wifi_list: ScrollRegion::new(26.0, 4.0).with_sink_behind(true),
- wifi_toggle: Toggle::new(),
+ wifi_toggle: Owned::new(Toggle::new()),
}
}
diff --git a/src/pages/notifications.rs b/src/pages/notifications.rs
index 72fd50a..f00e1df 100644
--- a/src/pages/notifications.rs
+++ b/src/pages/notifications.rs
@@ -1,5 +1,6 @@
use std::fs;
use std::io::Write;
+use cce_ui::widget::Owned;
use cce_ui::widget::input::{Toggle, Dropdown, Spinbox};
use cce_ui::widget::WidgetHost;
use cce_ui::layout::{PageLayoutBuilder, LayoutStrategy};
@@ -18,11 +19,11 @@ pub struct NotificationsConfig {
pub struct NotificationsState {
pub loaded: bool,
pub enable: bool,
- pub enable_toggle: cce_ui::widget::Adapted<Toggle>,
+ pub enable_toggle: Owned<cce_ui::widget::Adapted<Toggle>>,
pub bell: String,
- pub bell_menu: cce_ui::widget::Adapted<Dropdown>,
+ pub bell_menu: Owned<cce_ui::widget::Adapted<Dropdown>>,
pub duration: i32,
- pub duration_spinbox: cce_ui::widget::Adapted<cce_ui::widget::Spinbox>,
+ pub duration_spinbox: Owned<cce_ui::widget::Adapted<cce_ui::widget::Spinbox>>,
}
impl Default for NotificationsState {
@@ -32,12 +33,12 @@ impl Default for NotificationsState {
enable: true,
// Left-aligned: centred, the label straddled the seam at the
// toggle's midpoint (see the Browser page's toggles).
- enable_toggle: Toggle::new()
+ enable_toggle: Owned::new(Toggle::new()
.with_label("Enable Notifications")
.with_left_align(true)
- .with_config(&get_config_path(), "enable"),
+ .with_config(&get_config_path(), "enable")),
bell: "none".to_string(),
- bell_menu: Dropdown::new(
+ bell_menu: Owned::new(Dropdown::new(
vec![
"None".to_string(),
"Bell".to_string(),
@@ -45,12 +46,12 @@ impl Default for NotificationsState {
"Message".to_string(),
],
0,
- ).with_label("Notification Sound"),
+ ).with_label("Notification Sound")),
duration: 5,
- duration_spinbox: Spinbox::new(5, 1, 60, 1)
+ duration_spinbox: Owned::new(Spinbox::new(5, 1, 60, 1)
.with_label("Notification Duration")
.with_unit("s")
- .with_config(&get_config_path(), "duration"),
+ .with_config(&get_config_path(), "duration")),
}
}
}
diff --git a/src/pages/packages.rs b/src/pages/packages.rs
index a81c524..6fccf4d 100644
--- a/src/pages/packages.rs
+++ b/src/pages/packages.rs
@@ -1,4 +1,5 @@
use crate::app::{AppAction, PageContent, SectionContextExt, section_divider};
+use cce_ui::widget::Owned;
use cce_ui::widget::ScrollRegion;
use cce_ui::layout::{render_widget, PageLayoutBuilder, LayoutStrategy, SectionContext, RenderTarget};
use cce_ui::widget::{WidgetHost, TextBox, InteractiveListItem};
@@ -67,11 +68,11 @@ pub struct PackagesState {
pub installed: Vec<PackageInfo>,
pub updates: Vec<UpdateInfo>,
pub active_tab: PackageTab,
- pub search_box: cce_ui::widget::Adapted<TextBox>,
+ pub search_box: Owned<cce_ui::widget::Adapted<TextBox>>,
pub installed_list: ScrollRegion,
- pub installed_items: Vec<cce_ui::widget::Adapted<cce_ui::widget::InteractiveListItem>>,
+ pub installed_items: Vec<Owned<cce_ui::widget::Adapted<cce_ui::widget::InteractiveListItem>>>,
pub updates_list: ScrollRegion,
- pub updates_items: Vec<cce_ui::widget::Adapted<cce_ui::widget::InteractiveListItem>>,
+ pub updates_items: Vec<Owned<cce_ui::widget::Adapted<cce_ui::widget::InteractiveListItem>>>,
pub updating: bool,
pub last_update_res: Option<Result<(), String>>,
pub selected_package: Option<String>,
@@ -97,7 +98,7 @@ impl Default for PackagesState {
installed: Vec::new(),
updates: Vec::new(),
active_tab: PackageTab::Installed,
- search_box: TextBox::new(String::new()).with_placeholder("Filter Packages..."),
+ search_box: Owned::new(TextBox::new(String::new()).with_placeholder("Filter Packages...")),
installed_list: ScrollRegion::new(32.0, 4.0).with_frame(false).with_sink_behind(true),
installed_items: Vec::new(),
updates_list: ScrollRegion::new(32.0, 4.0).with_frame(false).with_sink_behind(true),
@@ -919,7 +920,7 @@ pub fn view(
if state.installed_items.len() != filtered.len() {
state.installed_items.clear();
for _ in 0..filtered.len() {
- state.installed_items.push(InteractiveListItem::new(""));
+ state.installed_items.push(Owned::new(InteractiveListItem::new("")));
}
}
@@ -970,7 +971,7 @@ pub fn view(
if state.updates_items.len() != filtered.len() {
state.updates_items.clear();
for _ in 0..filtered.len() {
- state.updates_items.push(InteractiveListItem::new(""));
+ state.updates_items.push(Owned::new(InteractiveListItem::new("")));
}
}
diff --git a/src/pages/power.rs b/src/pages/power.rs
index fe4df2e..3d38c3b 100644
--- a/src/pages/power.rs
+++ b/src/pages/power.rs
@@ -44,6 +44,7 @@
use crate::app::{AppAction, PageContent};
use crate::power_plan::{self, Automation, ChargeLimit, Lever, Mode, PowerPlan, Source};
+use cce_ui::widget::Owned;
use cce_ui::layout::{LayoutStrategy, PageLayoutBuilder};
use cce_ui::widget::{Adapted, Dropdown, WidgetHost};
use std::path::{Path, PathBuf};
@@ -129,7 +130,7 @@ pub struct PowerFacts {
#[derive(Debug, Clone)]
pub struct LeverSet {
/// One dropdown per [`Lever::ALL`] entry, in that order.
- pub dds: Vec<Adapted<Dropdown>>,
+ pub dds: Vec<Owned<Adapted<Dropdown>>>,
/// The plan value behind each row of each dropdown (options are display
/// text). Row 0 is always the "Not set" row and holds the empty string;
/// an empty Vec means the interface is absent on this host and the
@@ -142,7 +143,7 @@ impl Default for LeverSet {
Self {
dds: Lever::ALL
.iter()
- .map(|l| Dropdown::new(vec!["—".to_string()], 0).with_label(l.label()))
+ .map(|l| Owned::new(Dropdown::new(vec!["—".to_string()], 0).with_label(l.label())))
.collect(),
rows: vec![Vec::new(); Lever::ALL.len()],
}
@@ -157,17 +158,17 @@ fn lever_index(lever: Lever) -> usize {
pub struct PowerState {
pub loaded: bool,
pub facts: PowerFacts,
- pub dd_limit: Adapted<Dropdown>,
+ pub dd_limit: Owned<Adapted<Dropdown>>,
/// The charge window per charge-limit dropdown row (options are display
/// text).
pub limit_values: Vec<ChargeLimit>,
/// Which mode the lever section is editing. Page state, not plan state:
/// it says what is on screen, never what the machine runs.
pub editing: Mode,
- pub dd_mode: Adapted<Dropdown>,
+ pub dd_mode: Owned<Adapted<Dropdown>>,
pub levers: LeverSet,
/// One mode picker per [`Source::ALL`] entry, in that order.
- pub dd_assign: Vec<Adapted<Dropdown>>,
+ pub dd_assign: Vec<Owned<Adapted<Dropdown>>>,
}
impl Default for PowerState {
@@ -175,14 +176,14 @@ impl Default for PowerState {
Self {
loaded: false,
facts: PowerFacts::default(),
- dd_limit: Dropdown::new(vec!["—".to_string()], 0).with_label("Battery Charge Limit"),
+ dd_limit: Owned::new(Dropdown::new(vec!["—".to_string()], 0).with_label("Battery Charge Limit")),
limit_values: Vec::new(),
editing: Mode::default(),
- dd_mode: Dropdown::new(mode_options(), 0).with_label("Mode"),
+ dd_mode: Owned::new(Dropdown::new(mode_options(), 0).with_label("Mode")),
levers: LeverSet::default(),
dd_assign: Source::ALL
.iter()
- .map(|s| Dropdown::new(mode_options(), 0).with_label(s.label()))
+ .map(|s| Owned::new(Dropdown::new(mode_options(), 0).with_label(s.label())))
.collect(),
}
}
diff --git a/src/pages/services.rs b/src/pages/services.rs
index 1d62535..568014e 100644
--- a/src/pages/services.rs
+++ b/src/pages/services.rs
@@ -1,4 +1,5 @@
use crate::app::{PageContent, SectionContextExt};
+use cce_ui::widget::Owned;
use cce_ui::widget::ScrollRegion;
use cce_ui::layout::{render_widget, PageLayoutBuilder, LayoutStrategy, RenderTarget};
use cce_ui::widget::{TextBox, InteractiveListItem, WidgetHost};
@@ -29,9 +30,9 @@ pub struct ServicesState {
pub loaded: bool,
pub services: Vec<ServiceInfo>,
pub active_tab: ServiceTab,
- pub search_box: cce_ui::widget::Adapted<TextBox>,
+ pub search_box: Owned<cce_ui::widget::Adapted<TextBox>>,
pub list: ScrollRegion,
- pub items: Vec<cce_ui::widget::Adapted<cce_ui::widget::InteractiveListItem>>,
+ pub items: Vec<Owned<cce_ui::widget::Adapted<cce_ui::widget::InteractiveListItem>>>,
}
impl Default for ServicesState {
@@ -40,7 +41,7 @@ impl Default for ServicesState {
loaded: false,
services: Vec::new(),
active_tab: ServiceTab::System,
- search_box: TextBox::new(String::new()).with_label("Filter Services"),
+ search_box: Owned::new(TextBox::new(String::new()).with_label("Filter Services")),
list: ScrollRegion::new(36.0, 6.0).with_frame(false).with_sink_behind(true),
items: Vec::new(),
}
@@ -148,7 +149,7 @@ pub fn view(state: &mut ServicesState, cx: f32, cy: f32, cw: f32, ch: f32, _root
if state.items.len() != filtered_services.len() {
state.items.clear();
for _ in 0..filtered_services.len() {
- state.items.push(InteractiveListItem::new(""));
+ state.items.push(Owned::new(InteractiveListItem::new("")));
}
}
diff --git a/src/pages/storage.rs b/src/pages/storage.rs
index 7527c3b..b3505f1 100644
--- a/src/pages/storage.rs
+++ b/src/pages/storage.rs
@@ -1,4 +1,5 @@
use crate::app::{AppAction, PageContent};
+use cce_ui::widget::Owned;
use cce_ui::layout::{render_widget, PageLayoutBuilder, LayoutStrategy};
use std::fs;
@@ -34,7 +35,7 @@ pub struct StorageState {
pub error_message: Option<String>,
/// Retained so it can hold keyboard focus: ctrl+i descends into the Full
/// System Backup section and lands here, and Enter/Space runs the backup.
- pub backup_button: cce_ui::widget::Adapted<cce_ui::widget::Button>,
+ pub backup_button: Owned<cce_ui::widget::Adapted<cce_ui::widget::Button>>,
}
impl Default for StorageState {
@@ -50,7 +51,7 @@ impl Default for StorageState {
last_backup_time: "Never".to_string(),
backup_size: "0 B".to_string(),
error_message: None,
- backup_button: cce_ui::widget::Button::new(0.0, 0.0, 0.0, 32.0)
+ backup_button: Owned::new(cce_ui::widget::Button::new(0.0, 0.0, 0.0, 32.0)
// Flat fill + border, not the SDF bevel: PageContent's RenderTarget
// has no `bevel`, so a raised plate silently draws nothing here
// (the label renders, the plate does not). The border path is also
@@ -59,7 +60,7 @@ impl Default for StorageState {
.with_label("Run Backup")
.with_bg(BTN_BG)
.with_hover_bg(BTN_HOVER)
- .with_label_color(WHITE),
+ .with_label_color(WHITE)),
}
}
}
@@ -249,8 +250,8 @@ pub fn view(state: &mut StorageState, cx: f32, cy: f32, cw: f32, ch: f32, sec_fo
let bar_w = sec_w - 2.0 * crate::app::section_margin();
let yt = sec.ay();
let disk_bar_x = sec.ax(crate::app::section_margin());
- let mut disk_bar = cce_ui::widget::UsageBar::new((disk_pct as f32 / 100.0).min(1.0))
- .with_colors([0.36, 0.60, 0.36, 1.0], [0.15, 0.15, 0.25, 1.0]);
+ let mut disk_bar = Owned::new(cce_ui::widget::UsageBar::new((disk_pct as f32 / 100.0).min(1.0))
+ .with_colors([0.36, 0.60, 0.36, 1.0], [0.15, 0.15, 0.25, 1.0]));
render_widget(sec.pc, &mut disk_bar, disk_bar_x, yt, bar_w, 8.0, ctx);
}
});
@@ -276,8 +277,8 @@ pub fn view(state: &mut StorageState, cx: f32, cy: f32, cw: f32, ch: f32, sec_fo
let bar_w = sec_w - 2.0 * crate::app::section_margin();
let yt = sec.ay();
let ram_bar_x = sec.ax(crate::app::section_margin());
- let mut ram_bar = cce_ui::widget::UsageBar::new((ram_pct as f32 / 100.0).min(1.0))
- .with_colors([0.50, 0.50, 0.65, 1.0], [0.15, 0.15, 0.25, 1.0]);
+ let mut ram_bar = Owned::new(cce_ui::widget::UsageBar::new((ram_pct as f32 / 100.0).min(1.0))
+ .with_colors([0.50, 0.50, 0.65, 1.0], [0.15, 0.15, 0.25, 1.0]));
render_widget(sec.pc, &mut ram_bar, ram_bar_x, yt, bar_w, 8.0, ctx);
}
});
diff --git a/src/pages/system_info.rs b/src/pages/system_info.rs
index 806b5e1..ee666c5 100644
--- a/src/pages/system_info.rs
+++ b/src/pages/system_info.rs
@@ -1,4 +1,5 @@
use crate::app::{AppAction, PageContent, SectionContextExt};
+use cce_ui::widget::Owned;
use cce_ui::layout::{PageLayoutBuilder, LayoutStrategy};
use cce_ui::widget::{Label, WidgetHost, Button};
@@ -50,9 +51,9 @@ pub struct SystemState {
pub gpus: Vec<String>,
pub gpu_strings: Vec<String>,
pub builds: Vec<InstalledBuild>,
- pub cpu_label: cce_ui::widget::Adapted<cce_ui::widget::Label>,
- pub cpu_usage_label: cce_ui::widget::Adapted<cce_ui::widget::Label>,
- pub cpu_temp_label: cce_ui::widget::Adapted<cce_ui::widget::Label>,
+ pub cpu_label: Owned<cce_ui::widget::Adapted<cce_ui::widget::Label>>,
+ pub cpu_usage_label: Owned<cce_ui::widget::Adapted<cce_ui::widget::Label>>,
+ pub cpu_temp_label: Owned<cce_ui::widget::Adapted<cce_ui::widget::Label>>,
// Power-related fields
@@ -60,8 +61,8 @@ pub struct SystemState {
pub initialized: bool,
pub sender: Option<calloop::channel::Sender<AppAction>>,
pub sysfiles: SysFiles,
- pub hostname_label: cce_ui::widget::Adapted<cce_ui::widget::Label>,
- pub uptime_label: cce_ui::widget::Adapted<cce_ui::widget::Label>,
+ pub hostname_label: Owned<cce_ui::widget::Adapted<cce_ui::widget::Label>>,
+ pub uptime_label: Owned<cce_ui::widget::Adapted<cce_ui::widget::Label>>,
}
impl std::fmt::Debug for SystemState {
@@ -89,16 +90,16 @@ impl Default for SystemState {
gpus: Vec::new(),
gpu_strings: Vec::new(),
builds: Vec::new(),
- cpu_label: Label::new("CPU Info"),
- cpu_usage_label: Label::new("CPU Usage"),
- cpu_temp_label: Label::new("CPU Temp"),
+ cpu_label: Owned::new(Label::new("CPU Info")),
+ cpu_usage_label: Owned::new(Label::new("CPU Usage")),
+ cpu_temp_label: Owned::new(Label::new("CPU Temp")),
initialized: false,
sender: None,
sysfiles: SysFiles::default(),
- hostname_label: Label::new(""),
- uptime_label: Label::new(""),
+ hostname_label: Owned::new(Label::new("")),
+ uptime_label: Owned::new(Label::new("")),
}
}
}
diff --git a/src/pages/timers.rs b/src/pages/timers.rs
index f7a4705..f8c884c 100644
--- a/src/pages/timers.rs
+++ b/src/pages/timers.rs
@@ -3,6 +3,7 @@
//! timer units (system scope through pkexec).
use crate::app::{PageContent, SectionContextExt};
+use cce_ui::widget::Owned;
use cce_ui::widget::ScrollRegion;
use cce_ui::layout::{render_widget, PageLayoutBuilder, LayoutStrategy, RenderTarget};
use cce_ui::widget::{StatusDot, DotStatus, InteractiveListItem, TextBox, WidgetHost};
@@ -34,13 +35,13 @@ pub struct TimersState {
pub timers: Vec<TimerInfo>,
pub active_tab: TimerTab,
pub list: ScrollRegion,
- pub items: Vec<cce_ui::widget::Adapted<cce_ui::widget::InteractiveListItem>>,
+ pub items: Vec<Owned<cce_ui::widget::Adapted<cce_ui::widget::InteractiveListItem>>>,
pub creating: bool,
/// Base unit name (without .timer) being edited, form shared with create.
pub editing: Option<String>,
- pub name_box: cce_ui::widget::Adapted<TextBox>,
- pub command_box: cce_ui::widget::Adapted<TextBox>,
- pub schedule_box: cce_ui::widget::Adapted<TextBox>,
+ pub name_box: Owned<cce_ui::widget::Adapted<TextBox>>,
+ pub command_box: Owned<cce_ui::widget::Adapted<TextBox>>,
+ pub schedule_box: Owned<cce_ui::widget::Adapted<TextBox>>,
pub status_msg: Option<String>,
}
@@ -54,12 +55,12 @@ impl Default for TimersState {
items: Vec::new(),
creating: false,
editing: None,
- name_box: TextBox::new(String::new()).with_draw_bg_border(true).with_label("Name")
- .with_placeholder("backup"),
- command_box: TextBox::new(String::new()).with_draw_bg_border(true).with_label("Command")
- .with_placeholder("/home/me/bin/backup.sh --fast"),
- schedule_box: TextBox::new(String::new()).with_draw_bg_border(true).with_label("Schedule (OnCalendar)")
- .with_placeholder("daily \u{2022} Mon 09:00 \u{2022} *-*-* 03:00:00"),
+ name_box: Owned::new(TextBox::new(String::new()).with_draw_bg_border(true).with_label("Name")
+ .with_placeholder("backup")),
+ command_box: Owned::new(TextBox::new(String::new()).with_draw_bg_border(true).with_label("Command")
+ .with_placeholder("/home/me/bin/backup.sh --fast")),
+ schedule_box: Owned::new(TextBox::new(String::new()).with_draw_bg_border(true).with_label("Schedule (OnCalendar)")
+ .with_placeholder("daily \u{2022} Mon 09:00 \u{2022} *-*-* 03:00:00")),
status_msg: None,
}
}
@@ -524,7 +525,7 @@ pub fn view(state: &mut TimersState, cx: f32, cy: f32, cw: f32, ch: f32, _root_f
if state.items.len() != filtered.len() {
state.items.clear();
for _ in 0..filtered.len() {
- state.items.push(InteractiveListItem::new(""));
+ state.items.push(Owned::new(InteractiveListItem::new("")));
}
}
@@ -571,7 +572,7 @@ pub fn view(state: &mut TimersState, cx: f32, cy: f32, cw: f32, ch: f32, _root_f
render_widget(sec.pc, item_btn, list_box_x + 24.0, draw_y, list_box_w - 44.0, item_h, ctx);
let dot_state = if timer.active { DotStatus::Active } else { DotStatus::Inactive };
- let mut dot = StatusDot::new(dot_state);
+ let mut dot = Owned::new(StatusDot::new(dot_state));
render_widget(sec.pc, &mut dot, list_box_x + 10.0, draw_y + (item_h - 10.0) / 2.0, 10.0, 10.0, ctx);
let active_txt = [0.90, 0.90, 0.95, 1.0];
diff --git a/src/renderer.rs b/src/renderer.rs
index 1657dab..f619710 100644
--- a/src/renderer.rs
+++ b/src/renderer.rs
@@ -489,7 +489,7 @@ impl SystemInterface {
// The button's own idle and hover faces: the page's colours when
// it passed them, else the toolkit's for the button's kind — a
// list row's transparent-until-hover wash (`PageContent::list_row`).
- let mut probe = (**btn).clone();
+ let mut probe = (***btn).clone();
probe.set_hovered(false);
let bg = cce_ui::widget::Paint::color(&probe);
probe.set_hovered(true);