git.lucas.co / cce-color-editor
color picker and palette editor
git clone https://git.lucas.co/cce-color-editor.git

commit819d6225e3c28841d79c1924498eb1bf4430d2a6
parentf624773346
authorLucas Galante <lsgalante12@gmail.com>
date2026-10-08 20:02
refactor: the context owns the widgets; the app keeps handles

docs/rfc-owning-registry.md phase 3 (cce-ui ef2d668): every widget is moved
into the UiContext with insert and named by a Handle; accesses go through the
context (ui_context[h]), so the borrow checker keeps them from overlapping a
context call. Focus and popover registration go by id; the registration step
went.

The raw self_ptr the slider and button loops used to reach the widgets while
holding the context is gone: render_widget_h lends each one.

Checked in a cce-shadow against the previous build: identical to the pixel
at every step driven.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

 src/main.rs | 97 +++++++++++++++++++++++++++++--------------------------------
 1 file changed, 46 insertions(+), 51 deletions(-)

diff --git a/src/main.rs b/src/main.rs
index ccc6678..42f0d53 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,4 +1,4 @@
-use cce_ui::widget::Owned;
+use cce_ui::widget::Handle;
 use cce_ui::engine::{Application, WindowSettings, LogicalSize, LogicalPosition};
 use cce_ui::widget::{
     Adapted, Button, WidgetHost, EventCtx, UiContext, MouseButton, ElementState, KeyEvent,
@@ -405,9 +405,9 @@ enum Message {
 // ── ColorApp State ───────────────────────────────────────────────────
 
 struct ColorApp {
-    apply_btn: Owned<cce_ui::widget::Adapted<cce_ui::widget::Button>>,
-    cancel_btn: Owned<cce_ui::widget::Adapted<cce_ui::widget::Button>>,
-    sliders: Vec<Owned<Adapted<ColorSlider>>>,
+    apply_btn: Handle<cce_ui::widget::Adapted<cce_ui::widget::Button>>,
+    cancel_btn: Handle<cce_ui::widget::Adapted<cce_ui::widget::Button>>,
+    sliders: Vec<Handle<Adapted<ColorSlider>>>,
 
     red: f32,
     green: f32,
@@ -459,7 +459,8 @@ impl ColorApp {
     }
 
     fn update_sliders_color_state(&mut self) {
-        for slider in &mut self.sliders {
+        for &h in &self.sliders {
+            let slider = &mut self.ui_context[h];
             slider.r = self.red;
             slider.g = self.green;
             slider.b = self.blue;
@@ -529,14 +530,15 @@ impl ColorApp {
     }
 
     fn sync_slider_values(&mut self) {
-        self.sliders[0].value = self.red;
-        self.sliders[1].value = self.green;
-        self.sliders[2].value = self.blue;
-        self.sliders[3].value = self.hue;
-        self.sliders[4].value = self.saturation;
-        self.sliders[5].value = self.lightness;
+        let (ui, s) = (&mut self.ui_context, &self.sliders);
+        ui[s[0]].value = self.red;
+        ui[s[1]].value = self.green;
+        ui[s[2]].value = self.blue;
+        ui[s[3]].value = self.hue;
+        ui[s[4]].value = self.saturation;
+        ui[s[5]].value = self.lightness;
         if self.with_alpha {
-            self.sliders[6].value = self.alpha;
+            ui[s[6]].value = self.alpha;
         }
     }
 
@@ -554,9 +556,9 @@ impl ColorApp {
         let gap = cce_ui::layout::root_plate_gap();
         let content_w = self.width as f32 - 2.0 * inset;
 
-        for (i, slider) in self.sliders.iter_mut().enumerate() {
+        for (i, &h) in self.sliders.iter().enumerate() {
             let row_y = inset + i as f32 * SLIDER_ROW_H;
-            slider.set_rect(inset, row_y, content_w, SLIDER_ROW_H);
+            self.ui_context[h].set_rect(inset, row_y, content_w, SLIDER_ROW_H);
         }
 
         let preview_x = inset;
@@ -568,8 +570,8 @@ impl ColorApp {
 
         if self.expecting_output {
             let btn_h = cce_ui::layout::button_height();
-            self.apply_btn.set_rect(apply_x, button_y, BUTTON_W, btn_h);
-            self.cancel_btn.set_rect(cancel_x, button_y, BUTTON_W, btn_h);
+            self.ui_context[self.apply_btn].set_rect(apply_x, button_y, BUTTON_W, btn_h);
+            self.ui_context[self.cancel_btn].set_rect(cancel_x, button_y, BUTTON_W, btn_h);
         }
 
         // 2. Each top-level widget rendered through the same immediate-mode path the root
@@ -577,21 +579,14 @@ impl ColorApp {
         // The window base is not in this list — `display_list` emits the standard root
         // plate first, under everything gathered here.
         let mut window_pc = PageContent::new();
-        {
-            let self_ptr = self as *mut Self;
-            unsafe {
-                for slider in (*self_ptr).sliders.iter_mut() {
-                    let (x, y, w, h) = slider.rect();
-                    cce_ui::layout::render_widget(&mut window_pc, slider, x, y, w, h, &mut self.ui_context);
-                }
-            }
-            unsafe {
-                if self.expecting_output {
-                    let (x, y, w, h) = (*self_ptr).apply_btn.rect();
-                    cce_ui::layout::render_widget(&mut window_pc, &mut (*self_ptr).apply_btn, x, y, w, h, &mut self.ui_context);
-                    let (x, y, w, h) = (*self_ptr).cancel_btn.rect();
-                    cce_ui::layout::render_widget(&mut window_pc, &mut (*self_ptr).cancel_btn, x, y, w, h, &mut self.ui_context);
-                }
+        for &h in &self.sliders {
+            let (x, y, w, hh) = self.ui_context[h].rect();
+            cce_ui::layout::render_widget_h(&mut window_pc, h, x, y, w, hh, &mut self.ui_context);
+        }
+        if self.expecting_output {
+            for h in [self.apply_btn, self.cancel_btn] {
+                let (x, y, w, hh) = self.ui_context[h].rect();
+                cce_ui::layout::render_widget_h(&mut window_pc, h, x, y, w, hh, &mut self.ui_context);
             }
         }
 
@@ -740,10 +735,12 @@ impl Application for ColorApp {
             sliders.push(ColorSlider::new("A", 6));
         }
 
+        // The context owns the widgets; the app keeps their handles.
+        let mut ui_context = UiContext::new();
         let mut app = Self {
-            apply_btn: Owned::new(apply_btn),
-            cancel_btn: Owned::new(cancel_btn),
-            sliders: sliders.into_iter().map(Owned::new).collect(),
+            apply_btn: ui_context.insert(apply_btn),
+            cancel_btn: ui_context.insert(cancel_btn),
+            sliders: sliders.into_iter().map(|w| ui_context.insert(w)).collect(),
             red: r,
             green: g,
             blue: b,
@@ -761,7 +758,7 @@ impl Application for ColorApp {
             height: initial_h,
             scale_factor: 1.0,
             needs_rebuild: true,
-            ui_context: UiContext::new(),
+            ui_context,
             widgets: Vec::new(),
             texts: Vec::new(),
         };
@@ -828,19 +825,15 @@ impl Application for ColorApp {
         // mouse_wheel; the buttons never scrolled).
         let mut changed_slider = None;
         let mut any = false;
-        {
-            let self_ptr = self as *mut Self;
-            unsafe {
-                for slider in (*self_ptr).sliders.iter_mut() {
-                    if self.ui_context.propagate_event(&event, slider.id()) {
-                        any = true;
-                        break;
-                    }
-                }
+        for &h in &self.sliders {
+            if self.ui_context.propagate_event(&event, h.id()) {
+                any = true;
+                break;
             }
         }
         if any {
-            for (i, slider) in self.sliders.iter_mut().enumerate() {
+            for (i, &h) in self.sliders.iter().enumerate() {
+                let slider = &mut self.ui_context[h];
                 if slider.just_changed {
                     slider.just_changed = false;
                     changed_slider = Some((i, slider.value));
@@ -923,13 +916,14 @@ impl Application for ColorApp {
             let cancel = self.cancel_btn.id();
             self.ui_context.propagate_event(&ev, cancel);
         }
-        let slider_roots: Vec<_> = self.sliders.iter().map(|s| s.id()).collect();
+        let slider_roots: Vec<_> = self.sliders.iter().map(|h| h.id()).collect();
         for root in slider_roots {
             self.ui_context.propagate_event(&ev, root);
         }
         // Drain the drag's value change like the wheel path does.
         let mut changed_slider = None;
-        for (i, slider) in self.sliders.iter_mut().enumerate() {
+        for (i, &h) in self.sliders.iter().enumerate() {
+            let slider = &mut self.ui_context[h];
             if slider.just_changed {
                 slider.just_changed = false;
                 changed_slider = Some((i, slider.value));
@@ -970,23 +964,24 @@ impl Application for ColorApp {
                 self.needs_rebuild = true;
             }
 
-            if self.apply_btn.take_click() {
+            if self.ui_context[self.apply_btn].take_click() {
                 return Some(Message::Apply);
             }
-            if self.cancel_btn.take_click() {
+            if self.ui_context[self.cancel_btn].take_click() {
                 return Some(Message::Cancel);
             }
         }
 
         if !handled {
-            let slider_roots: Vec<_> = self.sliders.iter().map(|s| s.id()).collect();
+            let slider_roots: Vec<_> = self.sliders.iter().map(|h| h.id()).collect();
             for root in slider_roots {
                 if self.ui_context.propagate_event(&ev, root) {
                     break;
                 }
             }
             let mut changed_slider = None;
-            for (i, slider) in self.sliders.iter_mut().enumerate() {
+            for (i, &h) in self.sliders.iter().enumerate() {
+                let slider = &mut self.ui_context[h];
                 if slider.just_changed {
                     slider.just_changed = false;
                     changed_slider = Some((i, slider.value));