git.lucas.co / cce-designer
graphic design tool
git clone https://git.lucas.co/cce-designer.git

commit8e6e1613f90911d5a4347fa87d9395f1bdf9d83e
parent7219942440
authorLucas Galante <[email protected]>
date2026-06-16 15:34
Throttle inspector state updates and update cce-ui references

 Cargo.toml      |  2 +-
 src/app.rs      | 86 +++++++++++++++++++++++++++++----------------------------
 src/graphics.rs |  2 +-
 src/main.rs     | 31 ++++++++++++---------
 src/project.rs  | 38 ++++++++++++++++---------
 src/render.rs   | 22 +++++++--------
 src/shortcut.rs |  2 +-
 src/window.rs   | 74 ++++++++++++++++++++++++-------------------------
 8 files changed, 138 insertions(+), 119 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index bf2a8be..09b3fec 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -4,7 +4,7 @@ version = "0.1.0"
 edition = "2021"
 
 [dependencies]
-clear-ui = { path = "../clear-ui" }
+cce-ui = { path = "../cce-ui" }
 smithay-client-toolkit = "0.19.2"
 calloop = "0.13.0"
 calloop-wayland-source = "0.3.0"
diff --git a/src/app.rs b/src/app.rs
index c14f570..bbb5855 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -6,7 +6,7 @@ use std::io::BufReader;
 
 use serde::{Deserialize, Serialize};
 
-use clear_ui::widget::{ElementState, MouseButton, MouseScrollDelta, KeyEvent, Key, NamedKey};
+use cce_ui::widget::{ElementState, MouseButton, MouseScrollDelta, KeyEvent, Key, NamedKey};
 
 use smithay_client_toolkit::{
     compositor::{CompositorHandler, CompositorState},
@@ -36,9 +36,9 @@ use wayland_client::{
 use calloop_wayland_source::WaylandSource;
 
 use wgpu::util::DeviceExt;
-use clear_ui::widget::{Breadcrumb, Canvas, MenuBar, Plate, ParametersBg, Splitter, Spreadsheet, StatusBar, TextLabel, ViewportBg, Element, GraphNode, Graph, Paginator, Button, Checkbox, Slider, Spinbox, ScrollingList, Label, ColorSelector, Switcher};
-use clear_ui::colors;
-use clear_ui::engine::quad_vertices_with_clip;
+use cce_ui::widget::{Breadcrumb, Canvas, MenuBar, Plate, ParametersBg, Splitter, Spreadsheet, StatusBar, TextLabel, ViewportBg, Element, GraphNode, Graph, Paginator, Button, Checkbox, Slider, Spinbox, ScrollingList, Label, ColorSelector, Switcher};
+use cce_ui::colors;
+use cce_ui::engine::quad_vertices_with_clip;
 use glyphon::{Attrs, Buffer, Cache, FontSystem, Metrics, Resolution, SwashCache, TextArea, TextAtlas, TextBounds, TextRenderer, Viewport};
 use glam::{Mat4, Vec3};
 
@@ -47,7 +47,7 @@ use crate::project::*;
 use crate::render::*;
 use crate::shortcut::{Shortcut, ShortcutManager, Action};
 use crate::graphics::TexturedVertex;
-use clear_ui::engine::Vertex;
+use cce_ui::engine::Vertex;
 use crate::window::{AppState, WindowEvent, LocalPosition};
 
 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -450,7 +450,7 @@ impl Element for NodePalette {
     fn rect(&self) -> (f32, f32, f32, f32) { (self.x, self.y, self.w, self.h) }
     fn set_rect(&mut self, x: f32, y: f32, w: f32, h: f32) { self.x = x; self.y = y; self.w = w; self.h = h; }
     fn color(&self) -> [f32; 4] { [0.0, 0.0, 0.0, 0.0] }
-    fn hit_test(&self, px: f32, py: f32, ctx: &clear_ui::context::UiContext) -> bool {
+    fn hit_test(&self, px: f32, py: f32, ctx: &cce_ui::context::UiContext) -> bool {
         if ctx.is_coordinate_covered(self as *const Self as *const () as usize, px, py) {
             return false;
         }
@@ -526,10 +526,10 @@ pub fn make_text_buffer_with_font(font_system: &mut FontSystem, text: &str, size
     let metrics = Metrics::new(size, size * 1.4);
     let mut buffer = Buffer::new(font_system, metrics);
     let mut attrs = Attrs::new();
-    let family_name = font.map(|f| clear_ui::layout::parse_font_string(f).0);
+    let family_name = font.map(|f| cce_ui::layout::parse_font_string(f).0);
     if let Some(ref name) = family_name {
         let family = match name.as_str() {
-            "monospace" => glyphon::Family::Name(clear_ui::layout::get_system_monospace_font()),
+            "monospace" => glyphon::Family::Name(cce_ui::layout::get_system_monospace_font()),
             "sans-serif" => glyphon::Family::SansSerif,
             "serif" => glyphon::Family::Serif,
             _ => glyphon::Family::Name(name),
@@ -655,7 +655,7 @@ pub struct ResizeDirection {
 }
 
 pub struct State {
-    pub wgpu_adapter: clear_ui::backend::WgpuAdapter,
+    pub wgpu_adapter: cce_ui::backend::WgpuAdapter,
     pub render_pipeline: wgpu::RenderPipeline,
     pub vertex_buffer: wgpu::Buffer,
     pub window: XdgWindow,
@@ -722,7 +722,7 @@ pub struct State {
 
     pub widgets: Vec<Box<dyn Element>>,
     pub positions: Vec<(f32, f32, f32, f32)>,
-    pub splitter_layout: clear_ui::layout::SplitterLayout,
+    pub splitter_layout: cce_ui::layout::SplitterLayout,
     pub node_palette_visible: bool,
     pub node_palette_query: String,
     pub node_palette_filtered: Vec<usize>,
@@ -797,13 +797,14 @@ pub struct State {
     pub scroll_speed: f32,
     pub last_config_read: Instant,
     pub circular_network_pane: bool,
-    pub circular_network_layout: clear_ui::layout::CircularPaneLayout,
+    pub circular_network_layout: cce_ui::layout::CircularPaneLayout,
     pub is_detached_network: bool,
     pub detached_circular_network: bool,
     pub last_project_mod_time: Option<std::time::SystemTime>,
     pub last_project_check: std::time::Instant,
     pub last_inspector_check: std::time::Instant,
     pub last_inspector_update: std::time::Instant,
+    pub last_serialized: String,
     pub needs_autosave: bool,
     pub last_autosave_time: std::time::Instant,
     pub window_x: i32,
@@ -855,7 +856,7 @@ pub struct State {
     pub last_viewport_height: u32,
     pub last_viewport_active_camera: String,
     pub last_viewport_show_viewport: bool,
-    pub ui_context: clear_ui::context::UiContext,
+    pub ui_context: cce_ui::context::UiContext,
 }
 
 impl State {
@@ -904,51 +905,51 @@ impl State {
         self.widgets[NODE_PALETTE_IDX].as_any_mut().downcast_mut::<NodePalette>().expect("not a NodePalette")
     }
 
-    pub fn menu(&self, idx: usize) -> &dyn clear_ui::widget::MenuController {
+    pub fn menu(&self, idx: usize) -> &dyn cce_ui::widget::MenuController {
         self.widgets[idx].as_menu_controller().expect("not a MenuController")
     }
 
-    pub fn menu_mut(&mut self, idx: usize) -> &mut dyn clear_ui::widget::MenuController {
+    pub fn menu_mut(&mut self, idx: usize) -> &mut dyn cce_ui::widget::MenuController {
         self.widgets[idx].as_menu_controller_mut().expect("not a MenuController")
     }
 
-    pub fn graph(&self) -> &dyn clear_ui::widget::GraphController {
+    pub fn graph(&self) -> &dyn cce_ui::widget::GraphController {
         self.widgets[CONTENT_IDX].as_graph_controller().expect("not a GraphController")
     }
 
-    pub fn graph_mut(&mut self) -> &mut dyn clear_ui::widget::GraphController {
+    pub fn graph_mut(&mut self) -> &mut dyn cce_ui::widget::GraphController {
         self.widgets[CONTENT_IDX].as_graph_controller_mut().expect("not a GraphController")
     }
 
-    pub fn page_selector(&self, idx: usize) -> &dyn clear_ui::widget::PageSelector {
+    pub fn page_selector(&self, idx: usize) -> &dyn cce_ui::widget::PageSelector {
         self.widgets[idx].as_page_selector().expect("not a PageSelector")
     }
 
-    pub fn page_selector_mut(&mut self, idx: usize) -> &mut dyn clear_ui::widget::PageSelector {
+    pub fn page_selector_mut(&mut self, idx: usize) -> &mut dyn cce_ui::widget::PageSelector {
         self.widgets[idx].as_page_selector_mut().expect("not a PageSelector")
     }
 
-    pub fn param(&self) -> &dyn clear_ui::widget::ParamController {
+    pub fn param(&self) -> &dyn cce_ui::widget::ParamController {
         self.widgets[PARAM_IDX].as_param_controller().expect("not a ParamController")
     }
 
-    pub fn param_mut(&mut self) -> &mut dyn clear_ui::widget::ParamController {
+    pub fn param_mut(&mut self) -> &mut dyn cce_ui::widget::ParamController {
         self.widgets[PARAM_IDX].as_param_controller_mut().expect("not a ParamController")
     }
 
-    pub fn spreadsheet_mut(&mut self) -> &mut dyn clear_ui::widget::SpreadsheetController {
+    pub fn spreadsheet_mut(&mut self) -> &mut dyn cce_ui::widget::SpreadsheetController {
         self.widgets[SPREADSHEET_IDX].as_spreadsheet_controller_mut().expect("not a SpreadsheetController")
     }
 
-    pub fn path_mut(&mut self) -> &mut dyn clear_ui::widget::PathController {
+    pub fn path_mut(&mut self) -> &mut dyn cce_ui::widget::PathController {
         self.widgets[BREADCRUMB_IDX].as_path_controller_mut().expect("not a PathController")
     }
 
-    pub fn geom_mut(&mut self, idx: usize) -> &mut dyn clear_ui::widget::GeomController {
+    pub fn geom_mut(&mut self, idx: usize) -> &mut dyn cce_ui::widget::GeomController {
         self.widgets[idx].as_geom_controller_mut().expect("not a GeomController")
     }
 
-    pub fn scroll_mut(&mut self, idx: usize) -> &mut dyn clear_ui::widget::ScrollController {
+    pub fn scroll_mut(&mut self, idx: usize) -> &mut dyn cce_ui::widget::ScrollController {
         self.widgets[idx].as_scroll_controller_mut().expect("not a ScrollController")
     }
 
@@ -2155,7 +2156,7 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
         scale: f64,
         is_detached_network: bool,
     ) -> Self {
-        clear_ui::scale::set_scale_factor(scale as f32);
+        cce_ui::scale::set_scale_factor(scale as f32);
         let settings = DesignSettings::load();
         let lw = pw as f32 / scale as f32;
         let lh = ph as f32 / scale as f32;
@@ -2177,7 +2178,7 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
 
         let display_ptr = conn.backend().display_id().as_ptr() as *mut std::ffi::c_void;
         let surface_ptr = wl_surface.id().as_ptr() as *mut std::ffi::c_void;
-        let wgpu_adapter = clear_ui::backend::WgpuAdapter::new(display_ptr, surface_ptr, pw, ph).await;
+        let wgpu_adapter = cce_ui::backend::WgpuAdapter::new(display_ptr, surface_ptr, pw, ph).await;
 
         let device = &wgpu_adapter.device;
         let queue = &wgpu_adapter.queue;
@@ -2576,7 +2577,7 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
         });
 
 
-        let splitter_layout = clear_ui::layout::SplitterLayout::new(sw, SPLITTER_W, MIN_COLUMN);
+        let splitter_layout = cce_ui::layout::SplitterLayout::new(sw, SPLITTER_W, MIN_COLUMN);
         let (depth_texture, depth_texture_view) = {
             let tex = device.create_texture(&wgpu::TextureDescriptor {
                 label: Some("Depth Texture"),
@@ -2843,7 +2844,7 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
             scroll_speed: 1.0,
             last_config_read: Instant::now(),
             circular_network_pane: is_detached_network,
-            circular_network_layout: clear_ui::layout::CircularPaneLayout::new(250.0, 300.0, 180.0),
+            circular_network_layout: cce_ui::layout::CircularPaneLayout::new(250.0, 300.0, 180.0),
             is_detached_network,
             detached_circular_network: false,
             last_project_mod_time: {
@@ -2852,7 +2853,8 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
             },
             last_project_check: std::time::Instant::now(),
             last_inspector_check: std::time::Instant::now(),
-            last_inspector_update: std::time::Instant::now(),
+            last_inspector_update: std::time::Instant::now() - std::time::Duration::from_secs(1),
+            last_serialized: String::new(),
             needs_autosave: false,
             last_autosave_time: std::time::Instant::now(),
             window_x: 0,
@@ -2922,7 +2924,7 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
             last_viewport_height: 0,
             last_viewport_active_camera: String::new(),
             last_viewport_show_viewport: false,
-            ui_context: clear_ui::context::UiContext::new(),
+            ui_context: cce_ui::context::UiContext::new(),
         };
 
         state.update_inertial_settings();
@@ -2990,20 +2992,20 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
         graph.set_skipped_sizes(skipped_row_h, skipped_col_w);
         graph.set_grid_origin(active_node_area_x + pan_x, active_node_area_y + pan_y);
         graph.set_grid_snap_enabled(grid_snap_enabled);
-        if let Some(graph) = self.widgets[CONTENT_IDX].as_any_mut().downcast_mut::<clear_ui::widget::Graph>() {
+        if let Some(graph) = self.widgets[CONTENT_IDX].as_any_mut().downcast_mut::<cce_ui::widget::Graph>() {
             graph.set_uniform_background(self.uniform_background);
             graph.set_cell_opacity(self.cell_opacity);
             graph.set_gap_opacity(self.gap_opacity);
             graph.set_cell_color(self.cell_color);
             graph.set_gap_color(self.gap_color);
         }
-        if let Some(menubar) = self.widgets[LEFT_MENUBAR_IDX].as_any_mut().downcast_mut::<clear_ui::widget::MenuBar>() {
+        if let Some(menubar) = self.widgets[LEFT_MENUBAR_IDX].as_any_mut().downcast_mut::<cce_ui::widget::MenuBar>() {
             menubar.set_network_opacity(self.network_opacity);
         }
-        if let Some(breadcrumb) = self.widgets[BREADCRUMB_IDX].as_any_mut().downcast_mut::<clear_ui::widget::Breadcrumb>() {
+        if let Some(breadcrumb) = self.widgets[BREADCRUMB_IDX].as_any_mut().downcast_mut::<cce_ui::widget::Breadcrumb>() {
             breadcrumb.set_network_opacity(self.network_opacity);
         }
-        if let Some(plate) = self.widgets[NETWORK_PANEL_IDX].as_any_mut().downcast_mut::<clear_ui::widget::Plate>() {
+        if let Some(plate) = self.widgets[NETWORK_PANEL_IDX].as_any_mut().downcast_mut::<cce_ui::widget::Plate>() {
             plate.set_network_opacity(self.network_opacity);
         }
     }
@@ -3223,14 +3225,14 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
 
             self.positions[0] = (0.0, 0.0, 0.0, 0.0);
             self.positions[LEFT_MENUBAR_IDX] = (0.0, 0.0, 0.0, 0.0);
-            if let Some(menubar) = self.widgets[LEFT_MENUBAR_IDX].as_any_mut().downcast_mut::<clear_ui::widget::MenuBar>() {
+            if let Some(menubar) = self.widgets[LEFT_MENUBAR_IDX].as_any_mut().downcast_mut::<cce_ui::widget::MenuBar>() {
                 menubar.set_curved_circle(None);
             }
             self.positions[BREADCRUMB_IDX] = (cx - r, cy - r + 45.0, 2.0 * r, BREADCRUMB_H);
             self.positions[CONTENT_IDX] = (cx - r, cy - r + 45.0 + BREADCRUMB_H, 2.0 * r, 2.0 * r - (45.0 + BREADCRUMB_H));
             self.positions[NETWORK_PANEL_IDX] = (cx - r, cy - r, 2.0 * r, 2.0 * r);
             self.widgets[NETWORK_PANEL_IDX].set_rect(cx - r, cy - r, 2.0 * r, 2.0 * r);
-            if let Some(plate) = self.widgets[NETWORK_PANEL_IDX].as_any_mut().downcast_mut::<clear_ui::widget::Plate>() {
+            if let Some(plate) = self.widgets[NETWORK_PANEL_IDX].as_any_mut().downcast_mut::<cce_ui::widget::Plate>() {
                 plate.set_curved_circle(Some((cx, cy, r)));
             }
             self.positions[SPLITTER1_IDX] = (0.0, 0.0, 0.0, 0.0);
@@ -3427,14 +3429,14 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
                 let cy = self.circular_network_layout.y;
 
                 self.positions[LEFT_MENUBAR_IDX] = (0.0, 0.0, 0.0, 0.0);
-                if let Some(menubar) = self.widgets[LEFT_MENUBAR_IDX].as_any_mut().downcast_mut::<clear_ui::widget::MenuBar>() {
+                if let Some(menubar) = self.widgets[LEFT_MENUBAR_IDX].as_any_mut().downcast_mut::<cce_ui::widget::MenuBar>() {
                     menubar.set_curved_circle(None);
                 }
                 self.positions[BREADCRUMB_IDX] = (cx - r, cy - r + 45.0, 2.0 * r, BREADCRUMB_H);
                 self.positions[CONTENT_IDX] = (cx - r, cy - r + 45.0 + BREADCRUMB_H, 2.0 * r, 2.0 * r - (45.0 + BREADCRUMB_H));
                 self.positions[NETWORK_PANEL_IDX] = (cx - r, cy - r, 2.0 * r, 2.0 * r);
                 self.widgets[NETWORK_PANEL_IDX].set_rect(cx - r, cy - r, 2.0 * r, 2.0 * r);
-                if let Some(plate) = self.widgets[NETWORK_PANEL_IDX].as_any_mut().downcast_mut::<clear_ui::widget::Plate>() {
+                if let Some(plate) = self.widgets[NETWORK_PANEL_IDX].as_any_mut().downcast_mut::<cce_ui::widget::Plate>() {
                     plate.set_curved_circle(Some((cx, cy, r)));
                 }
                 self.widgets[NETWORK_PANEL_IDX].set_drag_bounds(0.0, 0.0, self.width, self.height);
@@ -3515,7 +3517,7 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
                     (0.0, 0.0, 0.0, 0.0)
                 };
 
-                if let Some(menubar) = self.widgets[LEFT_MENUBAR_IDX].as_any_mut().downcast_mut::<clear_ui::widget::MenuBar>() {
+                if let Some(menubar) = self.widgets[LEFT_MENUBAR_IDX].as_any_mut().downcast_mut::<cce_ui::widget::MenuBar>() {
                     menubar.set_curved_circle(None);
                 }
                 
@@ -3526,7 +3528,7 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
                 self.positions[CONTENT_IDX] = (px, py + mb_h + bc_h, pw, content_h);
                 self.positions[NETWORK_PANEL_IDX] = (px, py, pw, ph);
                 self.widgets[NETWORK_PANEL_IDX].set_rect(px, py, pw, ph);
-                if let Some(plate) = self.widgets[NETWORK_PANEL_IDX].as_any_mut().downcast_mut::<clear_ui::widget::Plate>() {
+                if let Some(plate) = self.widgets[NETWORK_PANEL_IDX].as_any_mut().downcast_mut::<cce_ui::widget::Plate>() {
                     plate.set_curved_circle(None);
                 }
                 self.positions[SPLITTER1_IDX] = (0.0, 0.0, 0.0, 0.0);
@@ -5276,7 +5278,7 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
             }
             if current_mod_time != self.last_config_mod_time {
                 self.last_config_mod_time = current_mod_time;
-                clear_ui::layout::reload_config();
+                cce_ui::layout::reload_config();
                 self.update_inertial_settings();
                 self.update_graph_settings_from_config();
                 self.rebuild_positions();
@@ -5380,7 +5382,7 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
                 tick_changed = true;
             }
         }
-        if clear_ui::widget::hover_animation::tick(dt) {
+        if cce_ui::widget::hover_animation::tick(dt) {
             tick_changed = true;
         }
         self.update_recent_files_layout();
diff --git a/src/graphics.rs b/src/graphics.rs
index e40ffaf..0f8202f 100644
--- a/src/graphics.rs
+++ b/src/graphics.rs
@@ -1,4 +1,4 @@
-use clear_ui::engine::Vertex;
+use cce_ui::engine::Vertex;
 
 #[repr(C)]
 #[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
diff --git a/src/main.rs b/src/main.rs
index a2c3668..d1b503e 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -4,7 +4,7 @@ use std::path::Path;
 
 use serde::{Deserialize, Serialize};
 
-use clear_ui::widget::{ElementState, MouseButton, MouseScrollDelta, KeyEvent, Key, NamedKey};
+use cce_ui::widget::{ElementState, MouseButton, MouseScrollDelta, KeyEvent, Key, NamedKey};
 
 use smithay_client_toolkit::{
     compositor::{CompositorHandler, CompositorState},
@@ -32,7 +32,7 @@ use wayland_client::{
 };
 use calloop_wayland_source::WaylandSource;
 
-use clear_ui::widget::{Breadcrumb, Canvas, MenuBar, Plate, ParametersBg, Splitter, Spreadsheet, StatusBar, ViewportBg, GraphNode, Graph, Paginator, Button, Checkbox, Slider, Spinbox, ScrollingList, Label, ColorSelector, Switcher};
+use cce_ui::widget::{Breadcrumb, Canvas, MenuBar, Plate, ParametersBg, Splitter, Spreadsheet, StatusBar, ViewportBg, GraphNode, Graph, Paginator, Button, Checkbox, Slider, Spinbox, ScrollingList, Label, ColorSelector, Switcher};
 use wgpu::util::DeviceExt;
 use glyphon::{Attrs, Buffer, Cache, FontSystem, Metrics, Resolution, SwashCache, TextArea, TextAtlas, TextBounds, TextRenderer, Viewport};
 use glam::{Mat4, Vec3};
@@ -90,7 +90,7 @@ fn main() {
     // Perform a roundtrip to populate output_state with active output scales
     event_queue.roundtrip(&mut app).unwrap();
 
-    let scale = clear_ui::wayland::detect_scale_factor(&app.output_state);
+    let scale = cce_ui::wayland::detect_scale_factor(&app.output_state);
 
     let (pw, ph) = if is_detached_network {
         ((400.0 * scale) as u32, (400.0 * scale) as u32)
@@ -206,8 +206,8 @@ fn main() {
                 if now.duration_since(pk.last_repeated) >= KEY_REPEAT_INTERVAL {
                     pk.last_repeated = now;
                     if let Some(st) = &mut app.state {
-                        let custom_event = clear_ui::widget::KeyEvent {
-                            state: clear_ui::widget::ElementState::Pressed,
+                        let custom_event = cce_ui::widget::KeyEvent {
+                            state: cce_ui::widget::ElementState::Pressed,
                             logical_key: pk.logical_key.clone(),
                             text: pk.text.clone(),
                             repeat: true,
@@ -245,14 +245,19 @@ fn create_memfd_with_data(name: &str, data: &[u8]) -> std::io::Result<std::os::u
                 }
                 if let Some(ref inspector) = app.inspector {
                     if let Some(ref surface) = app.surface {
-                        let now = std::time::Instant::now();
-                        if now.duration_since(state.last_inspector_update) >= std::time::Duration::from_millis(100) {
-                            state.last_inspector_update = now;
-                            let json = clear_ui::widget::serialize_widgets(&state.widgets);
-                            if let Ok(raw_fd) = create_memfd_with_data("clear_ui_state", json.as_bytes()) {
-                                use std::os::unix::io::{FromRawFd, AsFd};
-                                let file = unsafe { std::fs::File::from_raw_fd(raw_fd) };
-                                inspector.update_state(surface, file.as_fd(), json.len() as u32);
+                        let json = cce_ui::widget::serialize_widgets(&state.widgets);
+                        if json != state.last_serialized {
+                            let now = std::time::Instant::now();
+                            if now.duration_since(state.last_inspector_update) >= std::time::Duration::from_millis(100) {
+                                state.last_serialized = json.clone();
+                                state.last_inspector_update = now;
+                                if let Ok(raw_fd) = create_memfd_with_data("cce_ui_state", json.as_bytes()) {
+                                    use std::os::unix::io::{FromRawFd, AsFd};
+                                    let file = unsafe { std::fs::File::from_raw_fd(raw_fd) };
+                                    inspector.update_state(surface, file.as_fd(), json.len() as u32);
+                                }
+                            } else {
+                                app.redraw = true;
                             }
                         }
                     }
diff --git a/src/project.rs b/src/project.rs
index f88421f..bc276a2 100644
--- a/src/project.rs
+++ b/src/project.rs
@@ -1,9 +1,29 @@
 use std::fs;
 use std::path::Path;
 
-use clear_ui::widget::Button;
+use cce_ui::widget::Button;
 use crate::app::{State, Project, FsNode, ProjectViewState, CONTENT_IDX, ParamDef};
 
+fn color_to_hex(rgb: [f32; 3]) -> String {
+    format!("#{:02x}{:02x}{:02x}",
+        (rgb[0] * 255.0).round().clamp(0.0, 255.0) as u8,
+        (rgb[1] * 255.0).round().clamp(0.0, 255.0) as u8,
+        (rgb[2] * 255.0).round().clamp(0.0, 255.0) as u8
+    )
+}
+
+fn hex_to_color(hex: &str) -> Option<[f32; 3]> {
+    let s = hex.trim().strip_prefix('#').unwrap_or(hex.trim());
+    if s.len() == 6 {
+        let r = u8::from_str_radix(&s[0..2], 16).ok()? as f32 / 255.0;
+        let g = u8::from_str_radix(&s[2..4], 16).ok()? as f32 / 255.0;
+        let b = u8::from_str_radix(&s[4..6], 16).ok()? as f32 / 255.0;
+        Some([r, g, b])
+    } else {
+        None
+    }
+}
+
 impl State {
 
 
@@ -432,12 +452,8 @@ impl State {
         ensure_param(vp_node, "Grid Thickness", "spinbox", &((self.grid_thickness * 1000.0) as i32).to_string(), &[], Some(2.0), Some(200.0), Some(1.0));
         ensure_param(vp_node, "Origin Guide Size", "spinbox", &((self.origin_size * 10.0) as i32).to_string(), &[], Some(1.0), Some(50.0), Some(1.0));
         ensure_param(vp_node, "Camera Pivot Size", "spinbox", &((self.camera_pivot_size * 10.0) as i32).to_string(), &[], Some(1.0), Some(50.0), Some(1.0));
-        ensure_param(vp_node, "BG Color R", "spinbox", &((self.viewport_bg_color[0] * 255.0) as i32).to_string(), &[], Some(0.0), Some(255.0), Some(1.0));
-        ensure_param(vp_node, "BG Color G", "spinbox", &((self.viewport_bg_color[1] * 255.0) as i32).to_string(), &[], Some(0.0), Some(255.0), Some(1.0));
-        ensure_param(vp_node, "BG Color B", "spinbox", &((self.viewport_bg_color[2] * 255.0) as i32).to_string(), &[], Some(0.0), Some(255.0), Some(1.0));
-        ensure_param(vp_node, "Grid Color R", "spinbox", &((self.grid_color[0] * 255.0) as i32).to_string(), &[], Some(0.0), Some(255.0), Some(1.0));
-        ensure_param(vp_node, "Grid Color G", "spinbox", &((self.grid_color[1] * 255.0) as i32).to_string(), &[], Some(0.0), Some(255.0), Some(1.0));
-        ensure_param(vp_node, "Grid Color B", "spinbox", &((self.grid_color[2] * 255.0) as i32).to_string(), &[], Some(0.0), Some(255.0), Some(1.0));
+        ensure_param(vp_node, "Background Color", "color", &color_to_hex(self.viewport_bg_color), &[], None, None, None);
+        ensure_param(vp_node, "Grid Color", "color", &color_to_hex(self.grid_color), &[], None, None, None);
 
         // 4. Parameters subnet
         let param_node = find_or_create_subnet(&mut self.fs_root, "Parameters", "utility", (6.0, 0.0));
@@ -488,12 +504,8 @@ impl State {
                     "Grid Thickness" => if let Ok(val) = p.default.parse::<f32>() { self.grid_thickness = val / 1000.0; }
                     "Origin Guide Size" => if let Ok(val) = p.default.parse::<f32>() { self.origin_size = val / 10.0; }
                     "Camera Pivot Size" => if let Ok(val) = p.default.parse::<f32>() { self.camera_pivot_size = val / 10.0; }
-                    "BG Color R" => if let Ok(val) = p.default.parse::<f32>() { self.viewport_bg_color[0] = val / 255.0; }
-                    "BG Color G" => if let Ok(val) = p.default.parse::<f32>() { self.viewport_bg_color[1] = val / 255.0; }
-                    "BG Color B" => if let Ok(val) = p.default.parse::<f32>() { self.viewport_bg_color[2] = val / 255.0; }
-                    "Grid Color R" => if let Ok(val) = p.default.parse::<f32>() { self.grid_color[0] = val / 255.0; }
-                    "Grid Color G" => if let Ok(val) = p.default.parse::<f32>() { self.grid_color[1] = val / 255.0; }
-                    "Grid Color B" => if let Ok(val) = p.default.parse::<f32>() { self.grid_color[2] = val / 255.0; }
+                    "Background Color" => if let Some(col) = hex_to_color(&p.default) { self.viewport_bg_color = col; }
+                    "Grid Color" => if let Some(col) = hex_to_color(&p.default) { self.grid_color = col; }
                     "Square Aspect" => if let Ok(val) = p.default.parse::<bool>() { self.square_viewport = val; }
                     "Show Grid" => if let Ok(val) = p.default.parse::<bool>() { self.show_grid = val; }
                     "Cube" => if let Ok(val) = p.default.parse::<bool>() { self.show_cube = val; }
diff --git a/src/render.rs b/src/render.rs
index 33cc841..5233059 100644
--- a/src/render.rs
+++ b/src/render.rs
@@ -1,8 +1,8 @@
 
 use glyphon::{Buffer, Resolution, TextArea, TextBounds};
-use clear_ui::widget::TextLabel;
-use clear_ui::colors;
-use clear_ui::widget::Element;
+use cce_ui::widget::TextLabel;
+use cce_ui::colors;
+use cce_ui::widget::Element;
 
 use crate::app::{
     State, FsNode,
@@ -15,11 +15,11 @@ use crate::app::{
     push_circle_vertices, push_circle_border_vertices,
 };
 use crate::graphics::TexturedVertex;
-use clear_ui::engine::Vertex;
+use cce_ui::engine::Vertex;
 use crate::geometry::{
     network_sphere_vertices_with_errors,
 };
-use clear_ui::engine::{
+use cce_ui::engine::{
     push_widget_vertices, push_extra_quad_vertices,
     push_extra_quad_vertices_clipped, push_arc_background_vertices,
 };
@@ -262,7 +262,7 @@ impl State {
 
         // Dropdown popover
         if w.visible() && (self.focused_widget == Some(idx) || idx == PARAM_IDX) {
-            let mut popover_pc = clear_ui::layout::PopoverCollector::new();
+            let mut popover_pc = cce_ui::layout::PopoverCollector::new();
             w.render_popover(&mut popover_pc);
             for (color, px, py, pw, ph) in popover_pc.rects {
                 let ndc_x = (px / sw) * 2.0 - 1.0;
@@ -384,7 +384,7 @@ impl State {
             fn collect_popovers(
                 w: &dyn Element,
                 popovers: &mut Vec<(f32, f32, f32, f32)>,
-                ctx: &clear_ui::context::UiContext,
+                ctx: &cce_ui::context::UiContext,
             ) {
                 if let Some(rect) = w.popover_rect() {
                     popovers.push(rect);
@@ -448,7 +448,7 @@ impl State {
             ..
         } = self;
 
-        let clear_ui::backend::WgpuAdapter {
+        let cce_ui::backend::WgpuAdapter {
             ref device,
             ref queue,
             ref mut font_system,
@@ -515,7 +515,7 @@ impl State {
                 continue;
             }
             if self.focused_widget == Some(i) || i == PARAM_IDX {
-                let mut popover_pc = clear_ui::layout::PopoverCollector::new();
+                let mut popover_pc = cce_ui::layout::PopoverCollector::new();
                 w.render_popover(&mut popover_pc);
                 for (t, size, _x, _y, _tc, font_opt, _bounds) in popover_pc.texts {
                     let key = (t.clone(), (size * 100.0) as u32, font_opt.clone());
@@ -535,7 +535,7 @@ impl State {
         fn collect_popovers(
             w: &dyn Element,
             popovers: &mut Vec<(f32, f32, f32, f32)>,
-            ctx: &clear_ui::context::UiContext,
+            ctx: &cce_ui::context::UiContext,
         ) {
             if let Some(rect) = w.popover_rect() {
                 popovers.push(rect);
@@ -758,7 +758,7 @@ impl State {
                 continue;
             }
             if self.focused_widget == Some(i) || i == PARAM_IDX {
-                let mut popover_pc = clear_ui::layout::PopoverCollector::new();
+                let mut popover_pc = cce_ui::layout::PopoverCollector::new();
                 w.render_popover(&mut popover_pc);
                 for (t, size, x, y, tc, font_opt, label_bounds) in popover_pc.texts {
                     let key = (t.clone(), (size * 100.0) as u32, font_opt.clone());
diff --git a/src/shortcut.rs b/src/shortcut.rs
index c8ef0be..92970be 100644
--- a/src/shortcut.rs
+++ b/src/shortcut.rs
@@ -1,4 +1,4 @@
-use clear_ui::widget::{Key, NamedKey};
+use cce_ui::widget::{Key, NamedKey};
 use crate::ModifiersState;
 
 #[derive(Debug, Clone, Copy, PartialEq)]
diff --git a/src/window.rs b/src/window.rs
index 3135d77..3404e95 100644
--- a/src/window.rs
+++ b/src/window.rs
@@ -6,7 +6,7 @@ use std::net::TcpListener;
 
 use serde::{Deserialize, Serialize};
 
-use clear_ui::widget::{ElementState, MouseButton, MouseScrollDelta, KeyEvent, Key, NamedKey};
+use cce_ui::widget::{ElementState, MouseButton, MouseScrollDelta, KeyEvent, Key, NamedKey};
 
 use smithay_client_toolkit::{
     compositor::{CompositorHandler, CompositorState},
@@ -36,8 +36,8 @@ use wayland_client::{
 use calloop_wayland_source::WaylandSource;
 
 use wgpu::util::DeviceExt;
-use clear_ui::widget::{Breadcrumb, Canvas, MenuBar, Plate, ParametersBg, Splitter, Spreadsheet, StatusBar, TextLabel, ViewportBg, Element, GraphNode, Graph, Paginator, Button, Checkbox, Slider, Spinbox, ScrollingList, Label, ColorSelector, Switcher};
-use clear_ui::colors;
+use cce_ui::widget::{Breadcrumb, Canvas, MenuBar, Plate, ParametersBg, Splitter, Spreadsheet, StatusBar, TextLabel, ViewportBg, Element, GraphNode, Graph, Paginator, Button, Checkbox, Slider, Spinbox, ScrollingList, Label, ColorSelector, Switcher};
+use cce_ui::colors;
 use glyphon::{Attrs, Buffer, Cache, FontSystem, Metrics, Resolution, SwashCache, TextArea, TextAtlas, TextBounds, TextRenderer, Viewport};
 use glam::{Mat4, Vec3};
 
@@ -46,7 +46,7 @@ use crate::project::*;
 use crate::render::*;
 use crate::shortcut::{Shortcut, ShortcutManager, Action};
 use crate::app::{State, CustomEvent, HttpAction, ModifiersState, TouchPhase, DesignSettings, param_display, NodePalette, NODE_PALETTE_IDX, LEFT_MENUBAR_IDX, RIGHT_MENUBAR_IDX, PARAM_MENUBAR_IDX, SPREADSHEET_MENUBAR_IDX, HEADER_IDX, CONTENT_IDX, BREADCRUMB_IDX, VIEWPORT_IDX, PARAM_IDX, SPREADSHEET_IDX, PAGINATOR_IDX, get_next_visible_pane, Project, ProjectViewState, ParamDef};
-use clear_ui::engine::Vertex;
+use cce_ui::engine::Vertex;
 
 #[derive(Debug, Clone, Copy)]
 pub struct LocalPosition {
@@ -55,23 +55,23 @@ pub struct LocalPosition {
 }
 
 pub enum WindowEvent {
-    MouseWheel { delta: clear_ui::widget::MouseScrollDelta, phase: TouchPhase },
+    MouseWheel { delta: cce_ui::widget::MouseScrollDelta, phase: TouchPhase },
     PinchGesture { delta: f64 },
     CursorMoved { position: LocalPosition },
-    MouseInput { state: clear_ui::widget::ElementState, button: clear_ui::widget::MouseButton },
+    MouseInput { state: cce_ui::widget::ElementState, button: cce_ui::widget::MouseButton },
     ModifiersChanged(ModifiersState),
-    KeyboardInput { event: clear_ui::widget::KeyEvent },
+    KeyboardInput { event: cce_ui::widget::KeyEvent },
 }
 
 pub struct PressedKey {
-    pub logical_key: clear_ui::widget::Key,
+    pub logical_key: cce_ui::widget::Key,
     pub text: Option<String>,
     pub first_pressed: std::time::Instant,
     pub last_repeated: std::time::Instant,
 }
 
-pub fn is_repeatable_key(key: &clear_ui::widget::Key) -> bool {
-    use clear_ui::widget::{Key, NamedKey};
+pub fn is_repeatable_key(key: &cce_ui::widget::Key) -> bool {
+    use cce_ui::widget::{Key, NamedKey};
     match key {
         Key::Named(NamedKey::Backspace) |
         Key::Named(NamedKey::Delete) |
@@ -113,7 +113,7 @@ pub struct AppState {
     pub exit: bool,
     pub redraw: bool,
     pub pressed_key: Option<PressedKey>,
-    pub inspector: Option<clear_ui::protocol::zclear_inspector_v1::ZclearInspectorV1>,
+    pub inspector: Option<cce_ui::protocol::zclear_inspector_v1::ZclearInspectorV1>,
     pub pending_resize: Option<PendingResize>,
     pub _sender: calloop::channel::Sender<CustomEvent>,
 }
@@ -129,7 +129,7 @@ impl CompositorHandler for AppState {
         _surface.set_buffer_scale(scale_factor);
         if let Some(state) = &mut self.state {
             state.scale = scale_factor as f64;
-            clear_ui::scale::set_scale_factor(scale_factor as f32);
+            cce_ui::scale::set_scale_factor(scale_factor as f32);
             let pw = (state.width as f64 * state.scale) as u32;
             let ph = (state.height as f64 * state.scale) as u32;
             state.resize(pw, ph);
@@ -354,15 +354,15 @@ impl PointerHandler for AppState {
                     }
                     PointerEventKind::Press { button, serial, .. } => {
                         let btn = match *button {
-                            272 => clear_ui::widget::MouseButton::Left,
-                            273 => clear_ui::widget::MouseButton::Right,
-                            274 => clear_ui::widget::MouseButton::Middle,
+                            272 => cce_ui::widget::MouseButton::Left,
+                            273 => cce_ui::widget::MouseButton::Right,
+                            274 => cce_ui::widget::MouseButton::Middle,
                             _ => continue,
                         };
                         // eprintln!("DEBUG MOUSE PRESS: button={:?}, pos={:?}, local=({}, {})", btn, event.position, cx, cy);
 
                         if let Some(ref st) = self.state {
-                            if st.is_detached_network && btn == clear_ui::widget::MouseButton::Left {
+                            if st.is_detached_network && btn == cce_ui::widget::MouseButton::Left {
                                 let lx = event.position.0 as f32;
                                 let ly = event.position.1 as f32;
                                 let dx = lx - st.circular_network_layout.x;
@@ -433,24 +433,24 @@ impl PointerHandler for AppState {
                         }
 
                         let ev = WindowEvent::MouseInput {
-                            state: clear_ui::widget::ElementState::Pressed,
+                            state: cce_ui::widget::ElementState::Pressed,
                             button: btn,
                         };
                         self.process_event(ev);
                     }
                     PointerEventKind::Release { button, .. } => {
                         let btn = match *button {
-                            272 => clear_ui::widget::MouseButton::Left,
-                            273 => clear_ui::widget::MouseButton::Right,
-                            274 => clear_ui::widget::MouseButton::Middle,
+                            272 => cce_ui::widget::MouseButton::Left,
+                            273 => cce_ui::widget::MouseButton::Right,
+                            274 => cce_ui::widget::MouseButton::Middle,
                             _ => continue,
                         };
                         // eprintln!("DEBUG MOUSE RELEASE: button={:?}, pos={:?}, local=({}, {})", btn, event.position, cx, cy);
-                        if btn == clear_ui::widget::MouseButton::Left {
+                        if btn == cce_ui::widget::MouseButton::Left {
                             self.pending_resize = None;
                         }
                         let ev = WindowEvent::MouseInput {
-                            state: clear_ui::widget::ElementState::Released,
+                            state: cce_ui::widget::ElementState::Released,
                             button: btn,
                         };
                         self.process_event(ev);
@@ -460,7 +460,7 @@ impl PointerHandler for AppState {
                         let v_val = vertical.absolute as f32;
                         // eprintln!("DEBUG AXIS EVENT: horizontal={:?}, vertical={:?}, scale={}", horizontal, vertical, st.scale);
                         let ev = WindowEvent::MouseWheel {
-                            delta: clear_ui::widget::MouseScrollDelta::LineDelta(-h_val / 10.0, -v_val / 10.0),
+                            delta: cce_ui::widget::MouseScrollDelta::LineDelta(-h_val / 10.0, -v_val / 10.0),
                             phase: TouchPhase::Moved,
                         };
                         self.process_event(ev);
@@ -512,7 +512,7 @@ impl KeyboardHandler for AppState {
         _serial: u32,
         event: smithay_client_toolkit::seat::keyboard::KeyEvent,
     ) {
-        self.handle_key(event, clear_ui::widget::ElementState::Pressed);
+        self.handle_key(event, cce_ui::widget::ElementState::Pressed);
     }
 
     fn release_key(
@@ -523,7 +523,7 @@ impl KeyboardHandler for AppState {
         _serial: u32,
         event: smithay_client_toolkit::seat::keyboard::KeyEvent,
     ) {
-        self.handle_key(event, clear_ui::widget::ElementState::Released);
+        self.handle_key(event, cce_ui::widget::ElementState::Released);
     }
 
     fn update_modifiers(
@@ -595,17 +595,17 @@ impl ProvidesRegistryState for AppState {
     ) {}
 }
 
-impl wayland_client::Dispatch<clear_ui::protocol::zclear_inspector_v1::ZclearInspectorV1, ()> for AppState {
+impl wayland_client::Dispatch<cce_ui::protocol::zclear_inspector_v1::ZclearInspectorV1, ()> for AppState {
     fn event(
         state: &mut Self,
-        _proxy: &clear_ui::protocol::zclear_inspector_v1::ZclearInspectorV1,
-        event: clear_ui::protocol::zclear_inspector_v1::Event,
+        _proxy: &cce_ui::protocol::zclear_inspector_v1::ZclearInspectorV1,
+        event: cce_ui::protocol::zclear_inspector_v1::Event,
         _data: &(),
         _conn: &Connection,
         _qh: &QueueHandle<Self>,
     ) {
         match event {
-            clear_ui::protocol::zclear_inspector_v1::Event::InspectedSurface { app_id, x, y, .. } => {
+            cce_ui::protocol::zclear_inspector_v1::Event::InspectedSurface { app_id, x, y, .. } => {
                 if let Some(ref mut st) = state.state {
                     let expected_id = if st.is_detached_network {
                         "circular-network-pane"
@@ -634,8 +634,8 @@ delegate_registry!(AppState);
 delegate_output!(AppState);
 
 impl AppState {
-    fn handle_key(&mut self, event: smithay_client_toolkit::seat::keyboard::KeyEvent, state: clear_ui::widget::ElementState) {
-        use clear_ui::widget::{Key, KeyEvent, NamedKey};
+    fn handle_key(&mut self, event: smithay_client_toolkit::seat::keyboard::KeyEvent, state: cce_ui::widget::ElementState) {
+        use cce_ui::widget::{Key, KeyEvent, NamedKey};
         let logical_key = match event.keysym {
             xkeysym::Keysym::Escape => Key::Named(NamedKey::Escape),
             xkeysym::Keysym::Return => Key::Named(NamedKey::Enter),
@@ -678,16 +678,16 @@ impl AppState {
         if let Some(st) = &mut self.state {
             match event.keysym {
                 xkeysym::Keysym::Control_L | xkeysym::Keysym::Control_R => {
-                    st.modifiers.ctrl = state == clear_ui::widget::ElementState::Pressed;
+                    st.modifiers.ctrl = state == cce_ui::widget::ElementState::Pressed;
                 }
                 xkeysym::Keysym::Alt_L | xkeysym::Keysym::Alt_R => {
-                    st.modifiers.alt = state == clear_ui::widget::ElementState::Pressed;
+                    st.modifiers.alt = state == cce_ui::widget::ElementState::Pressed;
                 }
                 xkeysym::Keysym::Shift_L | xkeysym::Keysym::Shift_R => {
-                    st.modifiers.shift = state == clear_ui::widget::ElementState::Pressed;
+                    st.modifiers.shift = state == cce_ui::widget::ElementState::Pressed;
                 }
                 xkeysym::Keysym::Super_L | xkeysym::Keysym::Super_R => {
-                    st.modifiers.logo = state == clear_ui::widget::ElementState::Pressed;
+                    st.modifiers.logo = state == cce_ui::widget::ElementState::Pressed;
                 }
                 _ => {}
             }
@@ -701,7 +701,7 @@ impl AppState {
                 shift: st.modifiers.shift,
             };
 
-            if state == clear_ui::widget::ElementState::Pressed {
+            if state == cce_ui::widget::ElementState::Pressed {
                 if is_repeatable_key(&custom_event.logical_key) {
                     self.pressed_key = Some(PressedKey {
                         logical_key: custom_event.logical_key.clone(),
@@ -712,7 +712,7 @@ impl AppState {
                 } else {
                     self.pressed_key = None;
                 }
-            } else if state == clear_ui::widget::ElementState::Released {
+            } else if state == cce_ui::widget::ElementState::Released {
                 if let Some(ref pk) = self.pressed_key {
                     if pk.logical_key == custom_event.logical_key {
                         self.pressed_key = None;