git.lucas.co / cce-ui
GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git

commit8f86195e6cfc624a3331a7717ad6e7bd650950c1
parent457b486732
authorLucas Galante <[email protected]>
date2026-07-12 08:27
refactor(widget)!: Canvas + Viewport3D move into cce-designer (Phase 6ay part 2)

Both were designer-only (display/preview.rs's "canvas" is its own
internal WidgetCanvas, unrelated): the transparent hit-through Canvas
pane (25 lines, inlined into app.rs beside the other app-local
widgets) and the Viewport3D preview pane (verbatim file). Their
Element impls die with the machinery retype, app-side.

cce-ui's raw `impl Element` surface is now exactly ONE production
type: ButtonStrip (the ctx-registered embed of MenuBar/Paginator) —
everything else on the trait is Adapted<W> or app-local.

168 tests pass; full workspace builds; designer A/B AE=0.

Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018u7qTwzX95dd5ysAkaSCLk

 src/widget/display/mod.rs         |   2 -
 src/widget/display/viewport_3d.rs | 309 --------------------------------------
 src/widget/input/canvas.rs        |  25 ---
 src/widget/input/mod.rs           |   2 -
 src/widget/mod.rs                 |   5 +-
 5 files changed, 2 insertions(+), 341 deletions(-)

diff --git a/src/widget/display/mod.rs b/src/widget/display/mod.rs
index 33bfa7e..50a7a2f 100644
--- a/src/widget/display/mod.rs
+++ b/src/widget/display/mod.rs
@@ -40,6 +40,4 @@ pub use self::info_box::InfoBox;
 pub use self::status_dot::{DotStatus, StatusDot};
 pub use self::preview::{PreviewState, ImagePreviewData};
 pub use self::text_sizer::{measure_text_width, measure_text};
-pub mod viewport_3d;
-pub use self::viewport_3d::Viewport3D;
 
diff --git a/src/widget/display/viewport_3d.rs b/src/widget/display/viewport_3d.rs
deleted file mode 100644
index ccd77c2..0000000
--- a/src/widget/display/viewport_3d.rs
+++ /dev/null
@@ -1,309 +0,0 @@
-use crate::colors;
-use crate::widget::*;
-use glam::{Mat4, Vec3};
-
-#[derive(Debug, Clone)]
-pub struct Viewport3D {
-    base: Widget,
-    pub rotation_x: f32,
-    pub rotation_y: f32,
-    pub zoom: f32,
-    pub active_camera: String,
-    pub bg_color: [f32; 3],
-    pub grid_color: [f32; 3],
-    pub show_grid: bool,
-    pub show_cube: bool,
-    pub show_origin: bool,
-    pub show_camera_pivot: bool,
-
-    // Pending rotation to be consumed by the application when not using the default camera
-    pub pending_yaw: f32,
-    pub pending_pitch: f32,
-
-    // Modifiers state
-    ctrl_pressed: bool,
-    shift_pressed: bool,
-    alt_pressed: bool,
-
-    // Drag & scroll state tracking
-    is_rotating: bool,
-    is_zooming: bool,
-    scroll_lock: u8,
-    rotate_accum_yaw: f32,
-    rotate_accum_pitch: f32,
-    zoom_accum: f32,
-    rotate_velocity_yaw: f32,
-    rotate_velocity_pitch: f32,
-    zoom_velocity: f32,
-    last_rotate_time: std::time::Instant,
-    last_zoom_time: std::time::Instant,
-    pub scroll_speed: f32,
-    pub inertial_scroll: bool,
-    pub scroll_friction: f32,
-}
-
-impl Viewport3D {
-    pub fn new() -> Self {
-        let base = Widget::new();
-        Self {
-            base,
-            rotation_x: 0.0,
-            rotation_y: 0.0,
-            zoom: 1.0,
-            active_camera: "Default Camera".to_string(),
-            bg_color: [0.10, 0.10, 0.13],
-            grid_color: [0.18, 0.18, 0.22],
-            show_grid: true,
-            show_cube: true,
-            show_origin: true,
-            show_camera_pivot: true,
-            pending_yaw: 0.0,
-            pending_pitch: 0.0,
-            ctrl_pressed: false,
-            shift_pressed: false,
-            alt_pressed: false,
-            is_rotating: false,
-            is_zooming: false,
-            scroll_lock: 0,
-            rotate_accum_yaw: 0.0,
-            rotate_accum_pitch: 0.0,
-            zoom_accum: 0.0,
-            rotate_velocity_yaw: 0.0,
-            rotate_velocity_pitch: 0.0,
-            zoom_velocity: 0.0,
-            last_rotate_time: std::time::Instant::now(),
-            last_zoom_time: std::time::Instant::now(),
-            scroll_speed: 1.0,
-            inertial_scroll: true,
-            scroll_friction: 0.90,
-        }
-    }
-
-    pub fn with_scroll_speed(mut self, speed: f32) -> Self {
-        self.scroll_speed = speed;
-        self
-    }
-
-    pub fn with_inertial_scroll(mut self, enabled: bool) -> Self {
-        self.inertial_scroll = enabled;
-        self
-    }
-
-    pub fn with_scroll_friction(mut self, friction: f32) -> Self {
-        self.scroll_friction = friction;
-        self
-    }
-
-    pub fn reset_velocity(&mut self) {
-        self.rotate_velocity_yaw = 0.0;
-        self.rotate_velocity_pitch = 0.0;
-        self.zoom_velocity = 0.0;
-        self.is_rotating = false;
-        self.is_zooming = false;
-        self.scroll_lock = 0;
-    }
-
-    pub fn get_matrices(&self, aspect: f32, custom_camera_pos: Option<Vec3>, custom_camera_rot: Option<Vec3>, custom_pivot: Option<Vec3>) -> (Mat4, Mat4, Mat4) {
-        let camera_pos = custom_camera_pos.unwrap_or(Vec3::new(2.5, 1.8, 2.5));
-        let pivot = custom_pivot.unwrap_or(Vec3::ZERO);
-        let rot = custom_camera_rot.unwrap_or(Vec3::ZERO);
-        let rx = rot.x;
-        let ry = rot.y;
-        let rz = rot.z;
-
-        let base_offset = camera_pos - pivot;
-        let distance = base_offset.length();
-        let yaw0 = base_offset.x.atan2(base_offset.z);
-        let pitch0 = (base_offset.y / distance.max(1e-5)).asin();
-
-        let total_ry = ry.to_radians() + yaw0;
-        let total_rx = rx.to_radians() + pitch0;
-
-        let view_rot_pos = Mat4::from_rotation_y(total_ry) * Mat4::from_rotation_x(-total_rx);
-        let camera_up = view_rot_pos.transform_vector3(Vec3::Y);
-        let camera_world_pos = pivot + view_rot_pos.transform_vector3(Vec3::new(0.0, 0.0, distance) * self.zoom);
-        let view_mat = Mat4::from_rotation_z(rz.to_radians()) * Mat4::look_at_rh(camera_world_pos, pivot, camera_up);
-
-        let model = Mat4::from_rotation_y(self.rotation_y) * Mat4::from_rotation_x(self.rotation_x);
-        let proj = Mat4::perspective_rh(0.9, aspect, 0.1, 100.0);
-
-        (proj, view_mat, model)
-    }
-}
-
-impl Element for Viewport3D {
-    crate::impl_widget_base!(Viewport3D);
-
-    fn color(&self) -> [f32; 4] {
-        colors::VIEWPORT_BG
-    }
-
-    fn set_modifiers(&mut self, ctrl: bool, shift: bool, alt: bool) {
-        self.ctrl_pressed = ctrl;
-        self.shift_pressed = shift;
-        self.alt_pressed = alt;
-    }
-
-    fn hit_test(&self, px: f32, py: f32, _ctx: &UiContext) -> bool {
-        let (x, y, w, h) = self.rect();
-        px >= x && px <= x + w && py >= y && py <= y + h
-    }
-
-    fn mouse_wheel(&mut self, delta: &MouseScrollDelta, px: f32, py: f32, ctx: &mut UiContext) -> bool {
-        if !self.hit_test(px, py, ctx) {
-            return false;
-        }
-        let scale = crate::scale::scale_factor();
-        if self.ctrl_pressed {
-            match delta {
-                MouseScrollDelta::LineDelta(_x, y) => {
-                    let dy = *y * 0.15 * self.scroll_speed;
-                    self.zoom *= (-dy).exp();
-                    self.zoom = self.zoom.clamp(0.05, 20.0);
-                    self.is_zooming = false;
-                    
-                    let dt = 0.016;
-                    self.zoom_velocity = self.zoom_velocity * 0.4 + (dy / dt) * 0.6;
-                    true
-                }
-                MouseScrollDelta::PixelDelta(pos) => {
-                    let dy = (pos.y as f32 / scale) * 0.005 * self.scroll_speed;
-                    self.zoom *= (-dy).exp();
-                    self.zoom = self.zoom.clamp(0.05, 20.0);
-                    self.is_zooming = true;
-                    self.last_zoom_time = std::time::Instant::now();
-                    self.zoom_accum += dy;
-                    true
-                }
-            }
-        } else {
-            match delta {
-                MouseScrollDelta::LineDelta(x, y) => {
-                    self.scroll_lock = 0;
-                    let dx = *x * 0.05 * self.scroll_speed;
-                    let dy = *y * 0.05 * self.scroll_speed;
-                    
-                    if self.active_camera != "Default Camera" {
-                        self.pending_yaw += dx;
-                        self.pending_pitch += -dy;
-                    } else {
-                        self.rotation_y += dx;
-                        self.rotation_x -= dy;
-                    }
-                    
-                    self.is_rotating = false;
-                    let dt = 0.016;
-                    self.rotate_velocity_yaw = self.rotate_velocity_yaw * 0.4 + (dx / dt) * 0.6;
-                    self.rotate_velocity_pitch = self.rotate_velocity_pitch * 0.4 + (-dy / dt) * 0.6;
-                    true
-                }
-                MouseScrollDelta::PixelDelta(pos) => {
-                    let mut dx = (pos.x as f32 / scale) * 0.005 * self.scroll_speed;
-                    let mut dy = (pos.y as f32 / scale) * 0.005 * self.scroll_speed;
-
-                    self.rotate_accum_yaw += dx;
-                    self.rotate_accum_pitch -= dy;
-                    if self.scroll_lock == 0 {
-                        if self.rotate_accum_yaw.abs() > 0.002 || self.rotate_accum_pitch.abs() > 0.002 {
-                            if self.rotate_accum_pitch.abs() > 1.2 * self.rotate_accum_yaw.abs() {
-                                self.scroll_lock = 2;
-                            } else if self.rotate_accum_yaw.abs() > 1.2 * self.rotate_accum_pitch.abs() {
-                                self.scroll_lock = 1;
-                            }
-                        }
-                    } else {
-                        if self.scroll_lock == 1 {
-                            dy = 0.0;
-                        } else {
-                            dx = 0.0;
-                        }
-                    }
-
-                    if self.active_camera != "Default Camera" {
-                        self.pending_yaw += dx;
-                        self.pending_pitch += -dy;
-                    } else {
-                        self.rotation_y += dx;
-                        self.rotation_x -= dy;
-                    }
-
-                    self.is_rotating = true;
-                    self.last_rotate_time = std::time::Instant::now();
-                    true
-                }
-            }
-        }
-    }
-
-    fn tick(&mut self, dt: f32, _ctx: &mut UiContext) -> bool {
-        let now = std::time::Instant::now();
-        let mut changed = false;
-
-        if self.is_rotating {
-            if now.duration_since(self.last_rotate_time).as_secs_f32() > 0.05 {
-                self.is_rotating = false;
-                self.scroll_lock = 0;
-            } else if dt > 1e-5 {
-                let vel_yaw = self.rotate_accum_yaw / dt;
-                let vel_pitch = self.rotate_accum_pitch / dt;
-                self.rotate_velocity_yaw = self.rotate_velocity_yaw * 0.4 + vel_yaw * 0.6;
-                self.rotate_velocity_pitch = self.rotate_velocity_pitch * 0.4 + vel_pitch * 0.6;
-            }
-            self.rotate_accum_yaw = 0.0;
-            self.rotate_accum_pitch = 0.0;
-        }
-
-        if self.is_zooming {
-            if now.duration_since(self.last_zoom_time).as_secs_f32() > 0.05 {
-                self.is_zooming = false;
-            } else if dt > 1e-5 {
-                let vel_zoom = self.zoom_accum / dt;
-                self.zoom_velocity = self.zoom_velocity * 0.4 + vel_zoom * 0.6;
-            }
-            self.zoom_accum = 0.0;
-        }
-
-        if !self.is_rotating && (self.rotate_velocity_yaw.abs() > 0.001 || self.rotate_velocity_pitch.abs() > 0.001) {
-            if !self.inertial_scroll {
-                self.rotate_velocity_yaw = 0.0;
-                self.rotate_velocity_pitch = 0.0;
-            } else {
-                let dx = self.rotate_velocity_yaw * dt;
-                let dy = self.rotate_velocity_pitch * dt;
-                
-                if self.active_camera != "Default Camera" {
-                    self.pending_yaw += dx;
-                    self.pending_pitch += dy;
-                } else {
-                    self.rotation_y += dx;
-                    self.rotation_x += dy;
-                }
-
-                let decay = self.scroll_friction.powf(dt * 60.0);
-                self.rotate_velocity_yaw *= decay;
-                self.rotate_velocity_pitch *= decay;
-
-                if self.rotate_velocity_yaw.abs() < 0.01 { self.rotate_velocity_yaw = 0.0; }
-                if self.rotate_velocity_pitch.abs() < 0.01 { self.rotate_velocity_pitch = 0.0; }
-                changed = true;
-            }
-        }
-
-        if !self.is_zooming && self.zoom_velocity.abs() > 0.001 {
-            if !self.inertial_scroll {
-                self.zoom_velocity = 0.0;
-            } else {
-                let d_zoom = self.zoom_velocity * dt;
-                self.zoom *= (-d_zoom).exp();
-                self.zoom = self.zoom.clamp(0.05, 20.0);
-
-                let decay = self.scroll_friction.powf(dt * 60.0);
-                self.zoom_velocity *= decay;
-                if self.zoom_velocity.abs() < 0.01 { self.zoom_velocity = 0.0; }
-                changed = true;
-            }
-        }
-
-        changed
-    }
-}
diff --git a/src/widget/input/canvas.rs b/src/widget/input/canvas.rs
deleted file mode 100644
index f88b092..0000000
--- a/src/widget/input/canvas.rs
+++ /dev/null
@@ -1,25 +0,0 @@
-use crate::widget::*;
-
-pub struct Canvas {
-    x: f32, y: f32, w: f32, h: f32,
-    hovered: bool,
-}
-
-impl Canvas {
-    pub fn new() -> Self { Self { x: 0.0, y: 0.0, w: 0.0, h: 0.0, hovered: false } }
-}
-
-impl Element for Canvas {
-    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 as_ptr(&self) -> *mut (dyn Element + 'static) {
-        self as *const Self as *mut Self as *mut (dyn Element + 'static)
-    }
-    fn as_ptr_mut(&mut self) -> *mut (dyn Element + 'static) {
-        self as *mut Self as *mut (dyn Element + 'static)
-    }
-    fn set_hovered(&mut self, v: bool) { self.hovered = v; }
-    fn hovered(&self) -> bool { self.hovered }
-    fn hit_test(&self, _px: f32, _py: f32, _ctx: &UiContext) -> bool { false }
-}
diff --git a/src/widget/input/mod.rs b/src/widget/input/mod.rs
index 9e6f665..0c7115e 100644
--- a/src/widget/input/mod.rs
+++ b/src/widget/input/mod.rs
@@ -1,4 +1,3 @@
-pub mod canvas;
 pub mod button;
 pub mod checkbox;
 pub mod slider;
@@ -12,7 +11,6 @@ pub mod button_strip;
 pub mod keybind_recorder;
 pub mod ramp;
 
-pub use canvas::Canvas;
 pub use button::{Button, ButtonKind, PageButton};
 pub use checkbox::{Checkbox, Toggle};
 pub use slider::{Slider, RangeSlider, ActiveThumb};
diff --git a/src/widget/mod.rs b/src/widget/mod.rs
index 9ec6bbe..37ace6d 100644
--- a/src/widget/mod.rs
+++ b/src/widget/mod.rs
@@ -726,7 +726,7 @@ pub use self::core::{Widget, focus, hover_animation, clipboard, context_menu, cl
 pub use self::core::focus::link_parent_child;
 pub use self::input::{
     Button, TextBox, Spinbox, Dropdown, Checkbox, Toggle, Slider, RangeSlider,
-    ColorSelector, Finger, Trackpad, Canvas, get_font_db, ActiveThumb, FontSelector,
+    ColorSelector, Finger, Trackpad, get_font_db, ActiveThumb, FontSelector,
     ButtonStrip, KeybindRecorder, Ramp, RampKey, ColorRamp, ColorRampKey
 };
 pub use self::container::{
@@ -740,8 +740,7 @@ pub use self::display::{
     TextLabel, Label, StyledLabel, LabelPrim, TextItem, UsageBar,
     LayoutPreview, FontPreview, InfoBox, StatusDot, InteractiveListItem,
     GraphNode, Graph, Float3, ProgressBar, StatusBar, Splitter, Node, Separator,
-    DotStatus, PreviewLayoutMode, Sidebar, Panel, PreviewState, ImagePreviewData, serialize_widgets,
-    Viewport3D
+    DotStatus, PreviewLayoutMode, Sidebar, Panel, PreviewState, ImagePreviewData, serialize_widgets
 };
 
 pub trait PageSelector {