GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git
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 {