graphic design tool
git clone https://git.lucas.co/cce-designer.git
refactor(panes): the pane geometry is the designer's own
SplitterLayout and CircularPaneLayout lived in cce-ui's layout module with the
designer as their only user; they are src/panes.rs now.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
src/app.rs | 8 ++---
src/main.rs | 1 +
src/panes.rs | 98 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 103 insertions(+), 4 deletions(-)
diff --git a/src/app.rs b/src/app.rs
index 636388e..4505fb3 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -2958,7 +2958,7 @@ pub struct State {
pub slots: Box<WidgetSlots>,
pub positions: Vec<(f32, f32, f32, f32)>,
- pub splitter_layout: cce_ui::layout::SplitterLayout,
+ pub splitter_layout: crate::panes::SplitterLayout,
/// The node right-click context menu: the targeted node slot and the
/// actions parallel to the visible items pushed into `context_menu::show`.
/// `None` when no menu is open. The menu's geometry/paint lives in the
@@ -3190,7 +3190,7 @@ pub struct State {
pub graph_scroll_friction: f32,
pub last_config_read: Instant,
pub circular_network_pane: bool,
- pub circular_network_layout: cce_ui::layout::CircularPaneLayout,
+ pub circular_network_layout: crate::panes::CircularPaneLayout,
pub is_detached_network: bool,
pub detached_circular_network: bool,
/// This process IS the detached window for one pane — the generic sibling
@@ -8118,7 +8118,7 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
// Bundled fonts only (the designer's UI uses bundled families).
- let splitter_layout = cce_ui::layout::SplitterLayout::new(sw, SPLITTER_W, MIN_COLUMN);
+ let splitter_layout = crate::panes::SplitterLayout::new(sw, SPLITTER_W, MIN_COLUMN);
let templates_root = load_fs_tree();
let node_templates = flatten_node_templates(&templates_root);
@@ -8397,7 +8397,7 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
graph_scroll_friction: 0.90,
last_config_read: Instant::now(),
circular_network_pane: is_detached_network || settings.viewport.circular_pane,
- circular_network_layout: cce_ui::layout::CircularPaneLayout::new(250.0, 300.0, 180.0),
+ circular_network_layout: crate::panes::CircularPaneLayout::new(250.0, 300.0, 180.0),
is_detached_network,
detached_circular_network: false,
detached_pane: None,
diff --git a/src/main.rs b/src/main.rs
index 1ef7d16..7141819 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -40,6 +40,7 @@ pub mod slots;
pub mod command;
pub mod dialog;
pub mod layout;
+pub mod panes;
pub mod mold;
pub mod hull;
pub mod scatter;
diff --git a/src/panes.rs b/src/panes.rs
new file mode 100644
index 0000000..0c946d0
--- /dev/null
+++ b/src/panes.rs
@@ -0,0 +1,98 @@
+//! The window's pane geometry: the three resizable columns and their splitters
+//! (`SplitterLayout`), and the circular network pane's hit zones
+//! (`CircularPaneLayout`). Plain arithmetic over rects and points. Moved from
+//! cce-ui's `layout` (2026-10-08), where the designer was its only user.
+
+pub struct SplitterLayout {
+ pub splitter1_x: f32,
+ pub splitter2_x: f32,
+ pub splitter_width: f32,
+ pub min_column_width: f32,
+}
+
+impl SplitterLayout {
+ pub fn new(width: f32, splitter_width: f32, min_column_width: f32) -> Self {
+ let s1 = (width - 2.0 * splitter_width) / 3.0;
+ let s2 = s1 + splitter_width + (width - 2.0 * splitter_width) / 3.0;
+ Self {
+ splitter1_x: s1,
+ splitter2_x: s2,
+ splitter_width,
+ min_column_width,
+ }
+ }
+
+ pub fn clamp(&mut self, total_width: f32, detached_circular_network: bool) {
+ if detached_circular_network {
+ let min_s2 = self.min_column_width;
+ let max_s2 = (total_width - self.min_column_width).max(min_s2);
+ self.splitter2_x = self.splitter2_x.clamp(min_s2, max_s2);
+ } else {
+ let min_s1 = self.min_column_width;
+ let max_s1 = (self.splitter2_x - self.splitter_width - self.min_column_width).max(min_s1);
+ self.splitter1_x = self.splitter1_x.clamp(min_s1, max_s1);
+ let min_s2 = self.splitter1_x + self.splitter_width + self.min_column_width;
+ let max_s2 = (total_width - self.min_column_width).max(min_s2);
+ self.splitter2_x = self.splitter2_x.clamp(min_s2, max_s2);
+ }
+ }
+
+ pub fn scale(&mut self, factor: f32) {
+ self.splitter1_x *= factor;
+ self.splitter2_x *= factor;
+ }
+
+ pub fn left_col(&self) -> (f32, f32) { // (x, width)
+ (0.0, self.splitter1_x)
+ }
+
+ pub fn center_col(&self) -> (f32, f32) { // (x, width)
+ let x = self.splitter1_x + self.splitter_width;
+ (x, self.splitter2_x - x)
+ }
+
+ pub fn right_col(&self, total_width: f32) -> (f32, f32) { // (x, width)
+ let x = self.splitter2_x + self.splitter_width;
+ (x, (total_width - x).max(0.0))
+ }
+}
+
+pub struct CircularPaneLayout {
+ pub x: f32,
+ pub y: f32,
+ pub r: f32,
+}
+
+impl CircularPaneLayout {
+ pub fn new(x: f32, y: f32, r: f32) -> Self {
+ Self { x, y, r }
+ }
+
+ pub fn hit_test_content(&self, cx: f32, cy: f32, menubar_h: f32, breadcrumb_h: f32) -> bool {
+ let dx = cx - self.x;
+ let dy = cy - self.y;
+ let dist_sq = dx * dx + dy * dy;
+ dist_sq <= self.r * self.r && cy >= self.y - self.r + 45.0 + menubar_h + breadcrumb_h
+ }
+
+ pub fn hit_test_menubar(&self, cx: f32, cy: f32, menubar_h: f32) -> bool {
+ let dx = cx - self.x;
+ let dy = cy - self.y;
+ let dist = (dx * dx + dy * dy).sqrt();
+ dist >= self.r - menubar_h && dist <= self.r && cy < self.y
+ }
+
+ pub fn hit_test_breadcrumb(&self, cx: f32, cy: f32, menubar_h: f32, breadcrumb_h: f32) -> bool {
+ let dx = cx - self.x;
+ let dy = cy - self.y;
+ let dist_sq = dx * dx + dy * dy;
+ dist_sq <= self.r * self.r && cy >= self.y - self.r + menubar_h && cy < self.y - self.r + 45.0 + menubar_h + breadcrumb_h
+ }
+
+ pub fn hit_test_border(&self, cx: f32, cy: f32, border_thickness: f32) -> bool {
+ let dx = cx - self.x;
+ let dy = cy - self.y;
+ let dist = (dx * dx + dy * dy).sqrt();
+ dist >= self.r - border_thickness && dist <= self.r
+ }
+}