structured data editor
git clone https://git.lucas.co/cce-data-editor.git
feat: root Backplate DISSOLVED — first container dissolution (Phase 6m)
The Backplate had three remaining jobs here, each replaced:
- its plate: emitted as prims replicating Backplate::color()/
corner_radius() exactly (page-low bg at active backplate opacity,
config radius);
- tree linkage: top-level widgets register directly in ui_context
(parentless) and the paint walk runs per top-level widget in the old
child order; the splitter keeps owning its two panes;
- window dragging: is_movable_backplate_at now answers via the new
UiContext::drag_allowed_at (the surface IS the movable plate).
Verified live: full-window A/B vs the 6i baseline shows 43px residual
above the 8% threshold (translucency noise — the plate replication is
exact); row selection, inline value editor, JSON-pane highlight, and
statusbar all exercised. Held window drag is not headlessly drivable
(wlrctl has no press/release) — covered by the drag_allowed_at unit
test; worth one manual drag check.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_016MjP3pGQEDLkJbV5WEmYBe
src/main.rs | 78 ++++++++++++++++++++++++++++++++++++++++---------------------
1 file changed, 52 insertions(+), 26 deletions(-)
diff --git a/src/main.rs b/src/main.rs
index 08bbd43..f25724a 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -3,7 +3,7 @@ use glyphon::FontSystem;
use cce_ui::engine::{Application, EngineState, LogicalPosition, LogicalSize, WindowSettings};
use cce_ui::widget::{
MouseButton, ElementState, MouseScrollDelta, KeyEvent, Element,
- TextBox, Button, Key, Backplate, TreeList, TreeElement, ColorSelector, Spinbox, FontSelector, Dropdown,
+ TextBox, Button, Key, TreeList, TreeElement, ColorSelector, Spinbox, FontSelector, Dropdown,
KeybindRecorder, MenuBar, StatusBar, Checkbox, SplitBox
};
@@ -260,7 +260,6 @@ struct DataEditorApp {
status_message: Option<(String, bool)>,
// UI state
- root_window: Backplate,
menubar: cce_ui::widget::Adapted<MenuBar>,
statusbar: cce_ui::widget::Adapted<StatusBar>,
width: u32,
@@ -482,7 +481,9 @@ impl Application for DataEditorApp {
if self.main_splitter.dragging_idx.is_some() || self.main_splitter.hovered_idx.is_some() {
return false;
}
- self.ui_context.is_movable_backplate_at(px, py)
+ // Root Backplate dissolved: the surface itself is the movable plate; drag anywhere a
+ // drag-blocking widget isn't.
+ self.ui_context.drag_allowed_at(px, py)
}
fn new(_qh: &QueueHandle<EngineState<Self>>, _sender: calloop::channel::Sender<Self::Message>) -> Self {
@@ -570,13 +571,9 @@ impl Application for DataEditorApp {
.with_bg_color([0.08, 0.08, 0.10, 1.0])
.with_text_offset_x(15.0);
- let root_window = Backplate::new(0.0, 0.0, 800.0, 600.0)
- .with_movable(true);
-
let main_splitter = cce_ui::widget::SplitBox::new(cce_ui::widget::SplitDirection::Horizontal, 10.0);
Self {
- root_window,
menubar,
statusbar,
btn_open,
@@ -1073,11 +1070,14 @@ impl Application for DataEditorApp {
// painter) serves its rows' text through the walk's bounded-getter emission.
if !self.widgets_registered {
self.widgets_registered = true;
+ // The root Backplate is DISSOLVED (Phase 6): top-level widgets register directly
+ // (parentless) and the window plate is emitted below as prims. The splitter still
+ // owns its two panes.
let self_ptr = self as *mut Self;
unsafe {
- self.ui_context.register_widget(self.root_window.base().unwrap().id(), &mut (*self_ptr).root_window as *mut Backplate as *mut (dyn Element + 'static));
self.ui_context.register_widget(self.btn_open.base().unwrap().id(), (*self_ptr).btn_open.as_ptr_mut());
self.ui_context.register_widget(self.tree_list.base().unwrap().id(), &mut (*self_ptr).tree_list as *mut TreeList as *mut (dyn Element + 'static));
+ self.ui_context.register_widget(self.selected_value_editor.base().unwrap().id(), (*self_ptr).selected_value_editor.as_ptr_mut());
self.ui_context.register_widget(self.selected_color_editor.base().unwrap().id(), &mut (*self_ptr).selected_color_editor as *mut ColorSelector as *mut (dyn Element + 'static));
self.ui_context.register_widget(self.selected_spinbox_editor.base().unwrap().id(), (*self_ptr).selected_spinbox_editor.as_ptr_mut());
self.ui_context.register_widget(self.selected_font_editor.base().unwrap().id(), &mut (*self_ptr).selected_font_editor as *mut FontSelector as *mut (dyn Element + 'static));
@@ -1087,24 +1087,11 @@ impl Application for DataEditorApp {
self.ui_context.register_widget(self.selected_button_editor.base().unwrap().id(), (*self_ptr).selected_button_editor.as_ptr_mut());
self.ui_context.register_widget(self.menubar.id(), (*self_ptr).menubar.as_ptr_mut());
self.ui_context.register_widget(self.statusbar.base().unwrap().id(), (*self_ptr).statusbar.as_ptr_mut());
+ self.ui_context.register_widget(self.raw_json_editor.base().unwrap().id(), (*self_ptr).raw_json_editor.as_ptr_mut());
self.ui_context.register_widget(self.main_splitter.base().unwrap().id(), &mut (*self_ptr).main_splitter as *mut SplitBox as *mut (dyn Element + 'static));
- self.root_window.add_child(self.menubar.as_ptr_mut(), &mut self.ui_context);
- self.root_window.add_child(self.statusbar.as_ptr_mut(), &mut self.ui_context);
-
- self.root_window.add_child(self.btn_open.as_ptr_mut(), &mut self.ui_context);
- self.root_window.add_child(self.selected_value_editor.as_ptr_mut(), &mut self.ui_context);
- self.root_window.add_child(self.selected_color_editor.as_ptr_mut(), &mut self.ui_context);
- self.root_window.add_child(self.selected_spinbox_editor.as_ptr_mut(), &mut self.ui_context);
- self.root_window.add_child(self.selected_font_editor.as_ptr_mut(), &mut self.ui_context);
- self.root_window.add_child(self.selected_choice_editor.as_ptr_mut(), &mut self.ui_context);
- self.root_window.add_child(self.selected_keybind_editor.as_ptr_mut(), &mut self.ui_context);
- self.root_window.add_child(self.selected_bool_editor.as_ptr_mut(), &mut self.ui_context);
- self.root_window.add_child(self.selected_button_editor.as_ptr_mut(), &mut self.ui_context);
-
self.main_splitter.add_child_with_proportion(self.tree_list.as_ptr_mut(), 0.49, 100.0);
self.main_splitter.add_child_with_proportion(self.raw_json_editor.as_ptr_mut(), 0.51, 100.0);
- self.root_window.add_child(self.main_splitter.as_ptr_mut(), &mut self.ui_context);
}
self.ui_context.rebuild_spatial_grid();
}
@@ -1123,8 +1110,6 @@ impl Application for DataEditorApp {
self.scale_factor = scale;
cce_ui::scale::set_scale_factor(scale as f32);
- self.root_window.set_rect(0.0, 0.0, self.width as f32, self.height as f32);
-
let status_y = self.height as f32 - 30.0;
self.menubar.set_rect(0.0, 0.0, self.width as f32, 42.0);
self.statusbar.set_rect(0.0, status_y, self.width as f32, 30.0);
@@ -1307,9 +1292,50 @@ impl Application for DataEditorApp {
cce_ui::widget::popovers::register(&self.btn_open);
}
- let root: *mut (dyn cce_ui::widget::Element + 'static) = self.root_window.as_ptr_mut();
let mut pc = cce_ui::scene::paint::PaintCtx::new();
- cce_ui::scene::painter::paint_root_into(&self.ui_context, root, &mut pc);
+
+ // The dissolved root Backplate's plate — its exact legacy paint: page-low background
+ // at the active backplate opacity, config corner radius (Backplate::color /
+ // corner_radius defaults; this window set no border/bevel).
+ {
+ use cce_ui::scene::layout::Rect;
+ let mut plate_color = cce_ui::color::page_low_color();
+ if plate_color[3] > 0.001 {
+ plate_color[3] = cce_ui::color::active_backplate_opacity();
+ }
+ let rect = Rect { x: 0.0, y: 0.0, width: self.width as f32, height: self.height as f32 };
+ let radius = cce_ui::colors::backplate_corner_radius();
+ if radius > 0.1 {
+ pc.rounded_rect(rect, radius, (true, true, true, true), plate_color);
+ } else if plate_color[3] > 0.001 {
+ pc.quad(rect, plate_color);
+ }
+ }
+
+ // Top-level widgets walked in the old child order (menubar, statusbar, top bar,
+ // inline editors, splitter last).
+ {
+ let self_ptr = self as *mut Self;
+ let tops: [*mut (dyn cce_ui::widget::Element + 'static); 12] = unsafe {
+ [
+ (*self_ptr).menubar.as_ptr_mut(),
+ (*self_ptr).statusbar.as_ptr_mut(),
+ (*self_ptr).btn_open.as_ptr_mut(),
+ (*self_ptr).selected_value_editor.as_ptr_mut(),
+ (*self_ptr).selected_color_editor.as_ptr_mut(),
+ (*self_ptr).selected_spinbox_editor.as_ptr_mut(),
+ (*self_ptr).selected_font_editor.as_ptr_mut(),
+ (*self_ptr).selected_choice_editor.as_ptr_mut(),
+ (*self_ptr).selected_keybind_editor.as_ptr_mut(),
+ (*self_ptr).selected_bool_editor.as_ptr_mut(),
+ (*self_ptr).selected_button_editor.as_ptr_mut(),
+ &mut (*self_ptr).main_splitter as *mut cce_ui::widget::SplitBox as *mut (dyn cce_ui::widget::Element + 'static),
+ ]
+ };
+ for top in tops {
+ cce_ui::scene::painter::paint_root_into(&self.ui_context, top, &mut pc);
+ }
+ }
// Toolbar file label — app chrome, not owned by any widget.
let file_name_str = match &self.current_file_path {