graphic design tool
git clone https://git.lucas.co/cce-designer.git
refactor: migrate onto cce-ui's Application/engine::run runner
Replace the bespoke smithay+calloop Wayland loop (~1.2k lines across
main.rs/window.rs) with cce-ui's engine: src/application.rs implements
the Application trait, using the new extended hooks — renderer_init
(persistent meshes), stage_renderer (pending-mesh flush, app-shaped
text, raster/RT staging), handle_resize, standard_csd/cursor_icon/
take_window_action (detached circular window's radial CSD), and on_exit
(autosave). HTTP-server startup moves into the trait impl.
Deliberate deltas: the zcce_inspector_v1 integration is dropped (the
:3000 HTTP API is the introspection surface); trackpad scrolls arrive
as PixelDelta; pinch maps to ctrl+wheel zoom; the engine titlebar-drag
band applies to the main window.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01FtNJJtCxLtNC7TL1NVhr5Z
src/app.rs | 314 +++++++++++----------
src/application.rs | 338 ++++++++++++++++++++++
src/main.rs | 275 ++----------------
src/project.rs | 8 +-
src/render.rs | 11 +-
src/window.rs | 811 +++--------------------------------------------------
6 files changed, 566 insertions(+), 1191 deletions(-)
diff --git a/src/app.rs b/src/app.rs
index c145ec5..54b1f9d 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -45,7 +45,7 @@ use crate::geometry::*;
use crate::shortcut::{ShortcutManager, Action};
use cce_ui::vk::{SceneDraw, TextSpan};
use cce_ui::engine::Vertex;
-use crate::window::{AppState, WindowEvent};
+use crate::window::WindowEvent;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TouchPhase {
@@ -317,7 +317,7 @@ pub struct Project {
pub view_state: ProjectViewState,
}
-#[derive(Debug, Deserialize, Serialize)]
+#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(tag = "action", rename_all = "snake_case")]
pub enum HttpAction {
Up,
@@ -341,10 +341,14 @@ pub enum HttpAction {
MenuAction { label: String },
}
-#[derive(Debug)]
+#[derive(Debug, Clone)]
pub enum CustomEvent {
GetState(std::sync::mpsc::Sender<String>),
PostAction(HttpAction, std::sync::mpsc::Sender<Result<String, String>>),
+ /// App-requested exit (menu File > Exit, HTTP menu_action): the engine's
+ /// update hook is the only place with exit access, so input handlers that
+ /// see `exit_requested` route it here.
+ Exit,
}
#[derive(Clone)]
@@ -962,20 +966,48 @@ pub struct ViewportUniforms {
pub _padding: f32,
}
+/// The designer's persistent 3D meshes, created in `renderer_init` once the
+/// engine's renderer exists.
+#[derive(Clone, Copy)]
+pub struct SceneMeshes {
+ pub cube: cce_ui::vk::MeshId,
+ pub viewport_bg: cce_ui::vk::MeshId,
+ pub spheres: cce_ui::vk::MeshId,
+ pub grid: cce_ui::vk::MeshId,
+ pub origin: cce_ui::vk::MeshId,
+ pub pivot: cce_ui::vk::MeshId,
+}
+
+/// A left-press on the detached circular window's chrome that becomes an
+/// interactive move/resize once the pointer travels past a small threshold
+/// (so a plain click doesn't start a compositor grab).
+#[derive(Clone, Copy, Debug)]
+pub struct PendingWindowDrag {
+ pub start_x: f32,
+ pub start_y: f32,
+ pub action: cce_ui::engine::WindowAction,
+}
+
pub struct State {
- pub renderer: cce_ui::vk::VkRenderer,
pub font_system: FontSystem,
pub swash_cache: glyphon::SwashCache,
- pub window: XdgWindow,
- pub wl_surface: wl_surface::WlSurface,
+ /// Window title; the engine polls `Application::settings` and applies it.
+ pub title: String,
pub vertex_data: Vec<Vertex>,
- pub mesh_cube: cce_ui::vk::MeshId,
- pub mesh_viewport_bg: cce_ui::vk::MeshId,
- pub mesh_spheres: cce_ui::vk::MeshId,
- pub mesh_grid: cce_ui::vk::MeshId,
- pub mesh_origin: cce_ui::vk::MeshId,
- pub mesh_pivot: cce_ui::vk::MeshId,
+ /// GPU meshes — `None` until `renderer_init`.
+ pub meshes: Option<SceneMeshes>,
+ /// CPU-staged mesh updates, flushed in `stage_renderer`.
+ pub pending_grid: Option<Vec<Vertex3D>>,
+ pub pending_origin: Option<Vec<Vertex3D>>,
+ pub pending_pivot: Option<Vec<Vertex3D>>,
+ pub pending_viewport_bg: Option<Vec<Vertex3D>>,
+ /// The spheres mesh needs re-upload from `rt_sphere_verts`.
+ pub spheres_dirty: bool,
+ /// Renderer corner radius applied last frame (physical px); re-set on change.
+ pub last_corner_radius: f32,
+ pub pending_window_drag: Option<PendingWindowDrag>,
+ pub window_action: Option<cce_ui::engine::WindowAction>,
pub vertex_count_spheres: u32,
pub node_color: [f32; 3],
pub grid_color: [f32; 3],
@@ -1060,13 +1092,8 @@ pub struct State {
pub detached_circular_network: bool,
pub last_project_mod_time: Option<std::time::SystemTime>,
pub last_project_check: std::time::Instant,
- pub last_inspector_check: std::time::Instant,
- pub last_inspector_update: std::time::Instant,
- pub last_serialized: String,
pub needs_autosave: bool,
pub last_autosave_time: std::time::Instant,
- pub window_x: i32,
- pub window_y: i32,
pub active_menu_cloud_pid: Option<u32>,
pub active_menu_cloud_idx: Option<(usize, usize)>,
pub uniform_background: bool,
@@ -1288,28 +1315,33 @@ impl State {
+ // The engine owns the renderer, so geometry changes stage CPU-side here
+ // and flush to the GPU meshes in `stage_renderer`.
+
pub fn update_grid_geometry(&mut self) {
let linear_grid_color = cce_ui::colors::to_linear_rgb(self.grid_color);
- let grid_verts = grid_vertices(self.grid_thickness, linear_grid_color);
- self.renderer.update_mesh(self.mesh_grid, bytemuck::cast_slice(&grid_verts));
+ self.pending_grid = Some(grid_vertices(self.grid_thickness, linear_grid_color));
self.viewport_dirty = true;
}
pub fn update_origin_geometry(&mut self) {
- let origin_verts = origin_vectors_vertices(self.origin_size);
- self.renderer.update_mesh(self.mesh_origin, bytemuck::cast_slice(&origin_verts));
+ self.pending_origin = Some(origin_vectors_vertices(self.origin_size));
self.viewport_dirty = true;
}
pub fn update_pivot_geometry(&mut self) {
- let pivot_verts = camera_pivot_vertices(self.camera_pivot_size);
- self.renderer.update_mesh(self.mesh_pivot, bytemuck::cast_slice(&pivot_verts));
+ self.pending_pivot = Some(camera_pivot_vertices(self.camera_pivot_size));
self.viewport_dirty = true;
}
pub fn update_viewport_bg_geometry(&mut self) {
let bg_color = cce_ui::colors::to_linear_rgb(self.viewport().bg_color);
- let bg_verts = [
+ self.pending_viewport_bg = Some(Self::viewport_bg_vertices(bg_color));
+ self.viewport_dirty = true;
+ }
+
+ pub(crate) fn viewport_bg_vertices(bg_color: [f32; 3]) -> Vec<Vertex3D> {
+ vec![
Vertex3D { position: [-1.0, -1.0, 9.99], color: bg_color }, // Bottom-left
Vertex3D { position: [ 1.0, -1.0, 9.99], color: bg_color }, // Bottom-right
Vertex3D { position: [-1.0, 1.0, 9.99], color: bg_color }, // Top-left
@@ -1317,9 +1349,7 @@ impl State {
Vertex3D { position: [ 1.0, -1.0, 9.99], color: bg_color }, // Bottom-right
Vertex3D { position: [ 1.0, 1.0, 9.99], color: bg_color }, // Top-right
Vertex3D { position: [-1.0, 1.0, 9.99], color: bg_color }, // Top-left
- ];
- self.renderer.update_mesh(self.mesh_viewport_bg, bytemuck::cast_slice(&bg_verts));
- self.viewport_dirty = true;
+ ]
}
pub fn body_h(&self) -> f32 { self.height - HEADER_H - STATUS_H }
@@ -2422,69 +2452,23 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
- pub fn new(
- conn: &Connection,
- qh: &QueueHandle<AppState>,
- compositor_state: &CompositorState,
- xdg_shell_state: &XdgShell,
- pw: u32,
- ph: u32,
- scale: f64,
- is_detached_network: bool,
- ) -> Self {
- cce_ui::scale::set_scale_factor(scale as f32);
+ pub fn new(is_detached_network: bool) -> Self {
+ // The engine detected the output scale before constructing the app.
+ let scale = cce_ui::scale::scale_factor() as f64;
let settings = DesignSettings::load();
- let lw = pw as f32 / scale as f32;
- let lh = ph as f32 / scale as f32;
- let sw = lw;
-
- let wl_surface = compositor_state.create_surface(qh);
- wl_surface.set_buffer_scale(scale as i32);
- let window = xdg_shell_state.create_window(wl_surface.clone(), WindowDecorations::None, qh);
- if is_detached_network {
- window.set_title("Network Pane");
- window.set_app_id("circular-network-pane");
- window.set_min_size(Some((200, 200)));
+ let (lw, lh) = if is_detached_network {
+ (400.0f32, 400.0f32)
} else {
- window.set_title("Designer");
- window.set_app_id("cce-designer");
- window.set_min_size(Some((480, 320)));
- }
- window.commit();
-
- let display_ptr = conn.backend().display_id().as_ptr() as *mut std::ffi::c_void;
- let surface_ptr = wl_surface.id().as_ptr() as *mut std::ffi::c_void;
- let corner_radius = cce_ui::color::backplate_corner_radius() * scale as f32;
- let mut renderer = unsafe {
- cce_ui::vk::VkRenderer::new(display_ptr, surface_ptr, pw, ph, corner_radius)
+ (1280.0f32, 800.0f32)
};
+ let pw = (lw as f64 * scale) as u32;
+ let ph = (lh as f64 * scale) as u32;
+ let sw = lw;
+
// Bundled fonts only (the designer's UI uses bundled families).
let font_system = cce_ui::create_font_system();
let swash_cache = glyphon::SwashCache::new();
- // Static 3D meshes; the spheres mesh starts empty and is rebuilt from
- // the node graph (rebuild_scene_geometry).
- let cube_verts = cube_vertices();
- let mesh_cube = renderer.create_mesh(bytemuck::cast_slice(&cube_verts));
- let linear_grid_color = cce_ui::colors::to_linear_rgb(settings.viewport.grid_color);
- let grid_verts = grid_vertices(settings.viewport.grid_thickness, linear_grid_color);
- let mesh_grid = renderer.create_mesh(bytemuck::cast_slice(&grid_verts));
- let origin_verts = origin_vectors_vertices(settings.viewport.origin_size);
- let mesh_origin = renderer.create_mesh(bytemuck::cast_slice(&origin_verts));
- let pivot_verts = camera_pivot_vertices(settings.viewport.camera_pivot_size);
- let mesh_pivot = renderer.create_mesh(bytemuck::cast_slice(&pivot_verts));
- let bg_color = cce_ui::colors::to_linear_rgb(settings.viewport.bg_color);
- let bg_verts = [
- Vertex3D { position: [-1.0, -1.0, 9.99], color: bg_color }, // Bottom-left
- Vertex3D { position: [ 1.0, -1.0, 9.99], color: bg_color }, // Bottom-right
- Vertex3D { position: [-1.0, 1.0, 9.99], color: bg_color }, // Top-left
- Vertex3D { position: [ 1.0, -1.0, 9.99], color: bg_color }, // Bottom-right
- Vertex3D { position: [ 1.0, 1.0, 9.99], color: bg_color }, // Top-right
- Vertex3D { position: [-1.0, 1.0, 9.99], color: bg_color }, // Top-left
- ];
- let mesh_viewport_bg = renderer.create_mesh(bytemuck::cast_slice(&bg_verts));
- let mesh_spheres = renderer.create_mesh(&[]);
-
let splitter_layout = cce_ui::layout::SplitterLayout::new(sw, SPLITTER_W, MIN_COLUMN);
let templates_root = load_fs_tree();
let node_templates = flatten_node_templates(&templates_root);
@@ -2589,18 +2573,19 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
shortcut_manager.register("Ctrl+s", Action::Save).unwrap();
let mut state = Self {
- window,
- wl_surface,
- renderer,
font_system,
swash_cache,
+ title: String::new(),
vertex_data: Vec::with_capacity(4096),
- mesh_cube,
- mesh_viewport_bg,
- mesh_spheres,
- mesh_grid,
- mesh_origin,
- mesh_pivot,
+ meshes: None,
+ pending_grid: None,
+ pending_origin: None,
+ pending_pivot: None,
+ pending_viewport_bg: None,
+ spheres_dirty: false,
+ last_corner_radius: -1.0,
+ pending_window_drag: None,
+ window_action: None,
vertex_count_spheres: 0,
node_color: {
let nc = cce_ui::color::graph_node_color();
@@ -2684,13 +2669,8 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
std::fs::metadata(&default_proj_path).and_then(|m| m.modified()).ok()
},
last_project_check: std::time::Instant::now(),
- last_inspector_check: std::time::Instant::now(),
- last_inspector_update: std::time::Instant::now() - std::time::Duration::from_secs(1),
- last_serialized: String::new(),
needs_autosave: false,
last_autosave_time: std::time::Instant::now(),
- window_x: 0,
- window_y: 0,
active_menu_cloud_pid: None,
active_menu_cloud_idx: None,
uniform_background: false,
@@ -3624,16 +3604,19 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
- pub fn resize(&mut self, width: u32, height: u32) {
- if width > 0 && height > 0 {
+ /// Logical size + scale, from the engine's `handle_resize` hook (the
+ /// engine has already resized the renderer; the corner radius re-applies
+ /// on the next `stage_renderer` flush).
+ pub fn resize(&mut self, width: f32, height: f32, scale: f64) {
+ if width > 0.0 && height > 0.0 {
let old_width = self.width;
- self.physical_width = width;
- self.physical_height = height;
- self.width = width as f32 / self.scale as f32;
- self.height = height as f32 / self.scale as f32;
- self.renderer
- .set_corner_radius(cce_ui::color::backplate_corner_radius() * self.scale as f32);
- self.renderer.resize(width, height);
+ self.scale = scale;
+ cce_ui::scale::set_scale_factor(scale as f32);
+ self.physical_width = (width as f64 * scale) as u32;
+ self.physical_height = (height as f64 * scale) as u32;
+ self.width = width;
+ self.height = height;
+ self.last_corner_radius = -1.0;
if old_width > 0.0 {
let r = self.width / old_width;
@@ -3831,22 +3814,6 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
result
}
}
- WindowEvent::PinchGesture { delta, .. } => {
- let dialog_open = self.node_palette_visible;
- let in_network_pane = self.in_network_pane();
-
- if !dialog_open && in_network_pane {
- if delta.is_finite() && *delta != 0.0 {
- let factor = (1.0 + *delta as f32).clamp(0.8, 1.25);
- self.zoom(factor, Some((self.cursor_x, self.cursor_y)));
- true
- } else {
- false
- }
- } else {
- false
- }
- }
WindowEvent::CursorMoved { position, .. } => {
self.cursor_x = position.x as f32;
self.cursor_y = position.y as f32;
@@ -4498,10 +4465,6 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
}
changed
}
- WindowEvent::ModifiersChanged(mods) => {
- self.modifiers = mods.state();
- false
- }
WindowEvent::KeyboardInput { event, .. } => {
if event.logical_key == Key::Named(NamedKey::Space) {
self.space_pressed = event.state == ElementState::Pressed;
@@ -4840,9 +4803,11 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
}
}
- pub fn render(&mut self) -> bool {
+ /// Per-tick simulation (engine `tick` hook): config polling, widget
+ /// ticks, inertia, drag edge-panning. Returns true when the frame needs
+ /// a rebuild. The render half lives in [`State::stage_frame`].
+ pub fn tick_frame(&mut self, dt: f32) -> bool {
let now = Instant::now();
- let dt = now.duration_since(self.last_frame).as_secs_f32().min(0.1);
self.last_frame = now;
if now.duration_since(self.last_config_read).as_secs_f32() > 2.0 {
@@ -5047,7 +5012,67 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
self.upload_vertices();
}
- self.prepare_text();
+ tick_changed || panned
+ }
+
+ /// Flush CPU-staged mesh updates to the renderer's persistent meshes.
+ fn flush_pending_meshes(&mut self, renderer: &mut cce_ui::vk::VkRenderer) {
+ let Some(meshes) = self.meshes else { return };
+ if let Some(verts) = self.pending_grid.take() {
+ renderer.update_mesh(meshes.grid, bytemuck::cast_slice(&verts));
+ }
+ if let Some(verts) = self.pending_origin.take() {
+ renderer.update_mesh(meshes.origin, bytemuck::cast_slice(&verts));
+ }
+ if let Some(verts) = self.pending_pivot.take() {
+ renderer.update_mesh(meshes.pivot, bytemuck::cast_slice(&verts));
+ }
+ if let Some(verts) = self.pending_viewport_bg.take() {
+ renderer.update_mesh(meshes.viewport_bg, bytemuck::cast_slice(&verts));
+ }
+ if self.spheres_dirty {
+ self.spheres_dirty = false;
+ renderer.update_mesh(meshes.spheres, bytemuck::cast_slice(&self.rt_sphere_verts));
+ }
+ }
+
+ /// One-time renderer setup (engine `renderer_init` hook): the persistent
+ /// 3D meshes. The spheres mesh starts empty and fills from the node graph
+ /// via the pending-mesh flush.
+ pub fn init_renderer(&mut self, renderer: &mut cce_ui::vk::VkRenderer) {
+ let cube_verts = cube_vertices();
+ let linear_grid_color = cce_ui::colors::to_linear_rgb(self.grid_color);
+ let grid_verts = grid_vertices(self.grid_thickness, linear_grid_color);
+ let origin_verts = origin_vectors_vertices(self.origin_size);
+ let pivot_verts = camera_pivot_vertices(self.camera_pivot_size);
+ let bg_verts =
+ Self::viewport_bg_vertices(cce_ui::colors::to_linear_rgb(self.viewport().bg_color));
+ self.meshes = Some(SceneMeshes {
+ cube: renderer.create_mesh(bytemuck::cast_slice(&cube_verts)),
+ viewport_bg: renderer.create_mesh(bytemuck::cast_slice(&bg_verts)),
+ spheres: renderer.create_mesh(&[]),
+ grid: renderer.create_mesh(bytemuck::cast_slice(&grid_verts)),
+ origin: renderer.create_mesh(bytemuck::cast_slice(&origin_verts)),
+ pivot: renderer.create_mesh(bytemuck::cast_slice(&pivot_verts)),
+ });
+ // Scene geometry built during `State::new` (before the renderer
+ // existed) uploads on the first frame's flush.
+ self.spheres_dirty = !self.rt_sphere_verts.is_empty();
+ self.viewport_dirty = true;
+ }
+
+ /// Frame staging (engine `stage_renderer` hook): corner radius, pending
+ /// meshes, text, and the 3D scene / RT pane. Returns true while the path
+ /// tracer is still refining, to keep frames coming.
+ pub fn stage_frame(&mut self, renderer: &mut cce_ui::vk::VkRenderer) -> bool {
+ let radius = cce_ui::color::backplate_corner_radius() * self.scale as f32;
+ if radius != self.last_corner_radius {
+ self.last_corner_radius = radius;
+ renderer.set_corner_radius(radius);
+ }
+ self.flush_pending_meshes(renderer);
+ self.prepare_text(renderer);
+ let meshes = self.meshes.expect("stage_frame before renderer_init");
// 3D canvas: stage the scene into the renderer's backdrop when the
// viewport is visible and its inputs changed; unstaged frames reuse the
@@ -5162,23 +5187,23 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
// Same draw order as the wgpu pass: bg quad, grid, origin,
// pivot, cube, spheres.
- let mut draws = vec![SceneDraw { mesh: self.mesh_viewport_bg, mvp }];
+ let mut draws = vec![SceneDraw { mesh: meshes.viewport_bg, mvp }];
if self.viewport().show_grid {
- draws.push(SceneDraw { mesh: self.mesh_grid, mvp });
+ draws.push(SceneDraw { mesh: meshes.grid, mvp });
}
if self.viewport().show_origin {
- draws.push(SceneDraw { mesh: self.mesh_origin, mvp });
+ draws.push(SceneDraw { mesh: meshes.origin, mvp });
}
if self.viewport().show_camera_pivot {
- draws.push(SceneDraw { mesh: self.mesh_pivot, mvp: mvp_pivot });
+ draws.push(SceneDraw { mesh: meshes.pivot, mvp: mvp_pivot });
}
if self.viewport().show_cube {
- draws.push(SceneDraw { mesh: self.mesh_cube, mvp });
+ draws.push(SceneDraw { mesh: meshes.cube, mvp });
}
if self.vertex_count_spheres > 0 {
- draws.push(SceneDraw { mesh: self.mesh_spheres, mvp });
+ draws.push(SceneDraw { mesh: meshes.spheres, mvp });
}
- self.renderer.stage_scene((sx, sy, cw, ch), draws);
+ renderer.stage_scene((sx, sy, cw, ch), draws);
}
// Update viewport cache
@@ -5211,17 +5236,17 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
let key = (self.viewport().show_cube, self.rt_geometry_version);
if self.last_rt_scene_key != Some(key) {
let (rt_tris, rt_mats) = self.collect_rt_scene();
- self.renderer.set_rt_scene(&rt_tris, &rt_mats);
+ renderer.set_rt_scene(&rt_tris, &rt_mats);
self.last_rt_scene_key = Some(key);
}
let aspect = cw as f32 / ch as f32;
let (proj, view_mat, model) = self.viewport().get_matrices(aspect, Some(camera_pos), Some(Vec3::new(rx, ry, rz)), Some(pivot));
let inv_mvp = (proj * view_mat * model).inverse().to_cols_array_2d();
- self.renderer.stage_rt((sx, sy, cw, ch), cce_ui::vk::RtCamera { inv_mvp });
+ renderer.stage_rt((sx, sy, cw, ch), cce_ui::vk::RtCamera { inv_mvp });
}
} else if self.viewport_dirty {
// Zero-area pane: clear the backdrop once.
- self.renderer
+ renderer
.stage_scene((0, 0, self.physical_width, self.physical_height), Vec::new());
self.last_viewport_show_viewport = false;
self.viewport_dirty = false;
@@ -5229,19 +5254,18 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
} else if !self.is_detached_network && self.viewport_dirty {
// Viewport hidden: clear the backdrop (the old path's clear pass),
// and force a re-stage when it comes back.
- self.renderer
+ renderer
.stage_scene((0, 0, self.physical_width, self.physical_height), Vec::new());
self.last_viewport_show_viewport = false;
self.viewport_dirty = false;
}
- let _ = self.renderer.draw_frame(&self.vertex_data);
- // Keep frames coming while the path tracer is still refining.
- let rt_refining = !self.is_detached_network
+ // The engine draws the frame; keep frames coming while the path
+ // tracer is still refining.
+ !self.is_detached_network
&& self.show_viewport
&& self.viewport().rt_mode
- && self.renderer.rt_accumulating();
- tick_changed || panned || rt_refining
+ && renderer.rt_accumulating()
}
}
diff --git a/src/application.rs b/src/application.rs
new file mode 100644
index 0000000..b48b47d
--- /dev/null
+++ b/src/application.rs
@@ -0,0 +1,338 @@
+//! The designer on the cce-ui engine: `impl Application for State`.
+//!
+//! The engine owns the Wayland plumbing, event loop, and renderer; these
+//! hooks translate its callbacks into the designer's `WindowEvent`s, stage
+//! the 3D scene each frame, and run the detached circular window's
+//! non-rectangular CSD (radial border resize, top-arc move).
+
+use std::time::{Duration, Instant};
+
+use cce_ui::engine::{
+ xdg_toplevel::ResizeEdge, Application, CursorIcon, EngineState, LogicalPosition, LogicalSize,
+ Vertex, WindowAction, WindowSettings,
+};
+use cce_ui::vk::VkRenderer;
+use cce_ui::widget::{ElementState, KeyEvent, MouseButton, MouseScrollDelta};
+use wayland_client::QueueHandle;
+
+use crate::api::start_http_server;
+use crate::app::{CustomEvent, PendingWindowDrag, State, TouchPhase, LEFT_MENUBAR_IDX};
+use crate::window::{LocalPosition, WindowEvent};
+
+/// Pointer travel (logical px) before a chrome press becomes an interactive
+/// move/resize, so a plain click on the border doesn't start a grab.
+const DRAG_THRESHOLD: f32 = 4.0;
+
+impl State {
+ /// What the detached circular window's chrome at (lx, ly) would do:
+ /// radial border band → resize, top menubar arc → move, an open menu
+ /// always wins. `None` outside the chrome.
+ fn circular_chrome_at(&self, lx: f32, ly: f32) -> Option<WindowAction> {
+ let dx = lx - self.circular_network_layout.x;
+ let dy = ly - self.circular_network_layout.y;
+ let dist = (dx * dx + dy * dy).sqrt();
+ if dist <= f32::EPSILON {
+ return None;
+ }
+ let r = self.circular_network_layout.r;
+ let on_border = dist >= r - 12.0 && dist <= r;
+ let in_menubar_bg = dy < 0.0 && dist >= r - 35.0 && dist <= r;
+ if !(on_border || in_menubar_bg)
+ || self.menu(LEFT_MENUBAR_IDX).get_menu_items_at(lx, ly).is_some()
+ {
+ return None;
+ }
+ if on_border {
+ let nx = dx / dist;
+ let ny = dy / dist;
+ let edge = if ny < -0.382 {
+ if nx < -0.382 {
+ ResizeEdge::TopLeft
+ } else if nx > 0.382 {
+ ResizeEdge::TopRight
+ } else {
+ ResizeEdge::Top
+ }
+ } else if ny > 0.382 {
+ if nx < -0.382 {
+ ResizeEdge::BottomLeft
+ } else if nx > 0.382 {
+ ResizeEdge::BottomRight
+ } else {
+ ResizeEdge::Bottom
+ }
+ } else if nx < -0.382 {
+ ResizeEdge::Left
+ } else {
+ ResizeEdge::Right
+ };
+ Some(WindowAction::Resize(edge))
+ } else {
+ Some(WindowAction::Move)
+ }
+ }
+
+ /// The engine syncs modifier state into the UiContext (on key and wheel
+ /// events); mirror it into the designer's own `ModifiersState`.
+ fn sync_modifiers_from_ctx(&mut self) {
+ self.modifiers.ctrl = self.ui_context.ctrl_pressed;
+ self.modifiers.shift = self.ui_context.shift_pressed;
+ self.modifiers.alt = self.ui_context.alt_pressed;
+ self.modifiers.logo = self.ui_context.logo_pressed;
+ }
+
+ fn default_project_path() -> std::path::PathBuf {
+ std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("default_project.json")
+ }
+
+ /// Detached-window sync (both directions): debounced autosave of the
+ /// shared `default_project.json`, and mtime-polled reload when the other
+ /// window wrote it. Returns true when a reload happened.
+ fn poll_shared_project(&mut self) -> bool {
+ let mut redraw = false;
+ let syncing = self.is_detached_network || self.detached_circular_network;
+ if !syncing {
+ return false;
+ }
+
+ if self.needs_autosave {
+ let now = Instant::now();
+ if now.duration_since(self.last_autosave_time) >= Duration::from_millis(200) {
+ self.needs_autosave = false;
+ self.last_autosave_time = now;
+ let path = Self::default_project_path();
+ if let Err(e) = self.save_to_file(&path) {
+ eprintln!("Failed to auto-save default project: {:?}", e);
+ } else if let Ok(m) = std::fs::metadata(&path) {
+ if let Ok(mod_time) = m.modified() {
+ self.last_project_mod_time = Some(mod_time);
+ }
+ }
+ }
+ }
+
+ let now = Instant::now();
+ if now.duration_since(self.last_project_check) >= Duration::from_millis(100) {
+ self.last_project_check = now;
+ let path = Self::default_project_path();
+ if let Ok(m) = std::fs::metadata(&path) {
+ if let Ok(mod_time) = m.modified() {
+ if Some(mod_time) != self.last_project_mod_time {
+ self.last_project_mod_time = Some(mod_time);
+ if let Err(e) = self.load_from_file(&path) {
+ eprintln!("Failed to auto-reload project: {:?}", e);
+ } else {
+ redraw = true;
+ }
+ }
+ }
+ }
+ }
+ redraw
+ }
+
+ pub(crate) fn autosave_on_exit(&mut self) {
+ if self.needs_autosave && (self.is_detached_network || self.detached_circular_network) {
+ let _ = self.save_to_file(&Self::default_project_path());
+ }
+ }
+}
+
+impl Application for State {
+ type Message = CustomEvent;
+
+ fn new(
+ _qh: &QueueHandle<EngineState<Self>>,
+ sender: calloop::channel::Sender<CustomEvent>,
+ ) -> Self {
+ let is_detached_network = std::env::args().any(|arg| arg == "--detached-network");
+ let state = State::new(is_detached_network);
+ if !is_detached_network {
+ start_http_server(sender);
+ }
+ state
+ }
+
+ fn settings(&self) -> WindowSettings {
+ let (app_id, min_size) = if self.is_detached_network {
+ ("circular-network-pane", (200, 200))
+ } else {
+ ("cce-designer", (480, 320))
+ };
+ WindowSettings {
+ title: self.title.clone(),
+ app_id: app_id.to_string(),
+ width: self.width as u32,
+ height: self.height as u32,
+ fullscreen: false,
+ min_size: Some(min_size),
+ }
+ }
+
+ fn update(&mut self, msg: CustomEvent, needs_rebuild: &mut bool, exit: &mut bool) {
+ if matches!(msg, CustomEvent::Exit) {
+ self.autosave_on_exit();
+ *exit = true;
+ return;
+ }
+ if self.apply_custom_event(msg) {
+ *needs_rebuild = true;
+ }
+ // HTTP can reach File > Exit through menu_action.
+ if self.exit_requested {
+ self.autosave_on_exit();
+ *exit = true;
+ }
+ }
+
+ fn tick(&mut self, dt: f32, needs_rebuild: &mut bool) {
+ if self.tick_frame(dt) {
+ *needs_rebuild = true;
+ }
+ if self.poll_shared_project() {
+ *needs_rebuild = true;
+ }
+ }
+
+ fn ui_context(&self) -> Option<&cce_ui::context::UiContext> {
+ Some(&self.ui_context)
+ }
+
+ fn ui_context_mut(&mut self) -> Option<&mut cce_ui::context::UiContext> {
+ Some(&mut self.ui_context)
+ }
+
+ fn handle_pointer_move(&mut self, pos: LogicalPosition, needs_rebuild: &mut bool) {
+ self.sync_modifiers_from_ctx();
+ if let Some(pending) = self.pending_window_drag {
+ let dx = pos.x - pending.start_x;
+ let dy = pos.y - pending.start_y;
+ if (dx * dx + dy * dy).sqrt() > DRAG_THRESHOLD {
+ self.window_action = Some(pending.action);
+ self.pending_window_drag = None;
+ }
+ }
+ let ev = WindowEvent::CursorMoved {
+ position: LocalPosition { x: pos.x as f64, y: pos.y as f64 },
+ };
+ if self.process_window_event(ev) {
+ *needs_rebuild = true;
+ }
+ }
+
+ fn handle_mouse_input(
+ &mut self,
+ button: MouseButton,
+ state: ElementState,
+ pos: LogicalPosition,
+ needs_rebuild: &mut bool,
+ ) -> Option<CustomEvent> {
+ self.sync_modifiers_from_ctx();
+ self.cursor_x = pos.x;
+ self.cursor_y = pos.y;
+ if self.is_detached_network && button == MouseButton::Left {
+ match state {
+ ElementState::Pressed => {
+ if let Some(action) = self.circular_chrome_at(pos.x, pos.y) {
+ self.pending_window_drag = Some(PendingWindowDrag {
+ start_x: pos.x,
+ start_y: pos.y,
+ action,
+ });
+ return None; // consumed by the window chrome
+ }
+ }
+ ElementState::Released => {
+ self.pending_window_drag = None;
+ }
+ }
+ }
+ let ev = WindowEvent::MouseInput { state, button };
+ if self.process_window_event(ev) {
+ *needs_rebuild = true;
+ }
+ if self.exit_requested {
+ return Some(CustomEvent::Exit);
+ }
+ None
+ }
+
+ fn handle_mouse_wheel(
+ &mut self,
+ delta: &MouseScrollDelta,
+ pos: LogicalPosition,
+ needs_rebuild: &mut bool,
+ ) {
+ self.sync_modifiers_from_ctx();
+ self.cursor_x = pos.x;
+ self.cursor_y = pos.y;
+ let ev = WindowEvent::MouseWheel {
+ delta: delta.clone(),
+ phase: TouchPhase::Moved,
+ };
+ if self.process_window_event(ev) {
+ *needs_rebuild = true;
+ }
+ }
+
+ fn handle_key_input(&mut self, event: &KeyEvent, needs_rebuild: &mut bool) -> Option<CustomEvent> {
+ self.sync_modifiers_from_ctx();
+ let ev = WindowEvent::KeyboardInput { event: event.clone() };
+ if self.process_window_event(ev) {
+ *needs_rebuild = true;
+ }
+ if self.exit_requested {
+ return Some(CustomEvent::Exit);
+ }
+ None
+ }
+
+ fn custom_vertices(&mut self, verts: &mut Vec<Vertex>, _size: LogicalSize, _scale: f64) {
+ verts.extend_from_slice(&self.vertex_data);
+ }
+
+ fn renderer_init(&mut self, renderer: &mut VkRenderer) {
+ self.init_renderer(renderer);
+ }
+
+ fn stage_renderer(&mut self, renderer: &mut VkRenderer, _size: LogicalSize, _scale: f64) -> bool {
+ self.stage_frame(renderer)
+ }
+
+ fn handle_resize(&mut self, width: f32, height: f32, scale: f64) {
+ self.resize(width, height, scale);
+ }
+
+ fn standard_csd(&self) -> bool {
+ // The detached window's chrome is the circle, not the rect.
+ !self.is_detached_network
+ }
+
+ fn cursor_icon(&self, x: f32, y: f32) -> Option<CursorIcon> {
+ if !self.is_detached_network {
+ return None;
+ }
+ Some(match self.circular_chrome_at(x, y) {
+ Some(WindowAction::Resize(edge)) => match edge {
+ ResizeEdge::TopLeft => CursorIcon::NwResize,
+ ResizeEdge::Top => CursorIcon::NResize,
+ ResizeEdge::TopRight => CursorIcon::NeResize,
+ ResizeEdge::Left => CursorIcon::WResize,
+ ResizeEdge::Right => CursorIcon::EResize,
+ ResizeEdge::BottomLeft => CursorIcon::SwResize,
+ ResizeEdge::Bottom => CursorIcon::SResize,
+ ResizeEdge::BottomRight => CursorIcon::SeResize,
+ _ => CursorIcon::Default,
+ },
+ _ => CursorIcon::Default,
+ })
+ }
+
+ fn take_window_action(&mut self) -> Option<WindowAction> {
+ self.window_action.take()
+ }
+
+ fn on_exit(&mut self) {
+ self.autosave_on_exit();
+ }
+}
diff --git a/src/main.rs b/src/main.rs
index 5645248..e01d2e8 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,46 +1,10 @@
-#![allow(unused_imports)]
-use std::time::Instant;
-#[cfg(test)]
-use std::fs;
-#[cfg(test)]
-use std::path::Path;
-
-use serde::{Deserialize, Serialize};
-
-#[cfg(test)]
-use cce_ui::widget::{ElementState, MouseButton, MouseScrollDelta, KeyEvent};
-#[cfg(test)]
-use cce_ui::widget::{Key, NamedKey};
-
-use smithay_client_toolkit::{
- compositor::{CompositorHandler, CompositorState},
- delegate_compositor, delegate_keyboard, delegate_pointer, delegate_registry,
- delegate_seat, delegate_shm, delegate_xdg_shell, delegate_xdg_window, delegate_output,
- registry::{ProvidesRegistryState, RegistryState},
- output::OutputState,
- seat::{
- keyboard::KeyboardHandler,
- pointer::{PointerHandler, ThemedPointer, ThemeSpec, CursorIcon},
- Capability, SeatHandler, SeatState,
- },
- shell::{
- xdg::{
- window::{Window as XdgWindow, WindowConfigure, WindowDecorations},
- XdgShell,
- },
- },
- shm::{Shm, ShmHandler},
-};
-use wayland_client::{
- globals::registry_queue_init,
- Connection,
-};
-use calloop_wayland_source::WaylandSource;
-
-#[cfg(test)]
-use glam::{Mat4, Vec3};
pub mod app;
+pub mod application;
+
+// Root-level aliases some modules import via `crate::` paths.
+#[allow(unused_imports)]
+use app::{CustomEvent, HttpAction, ModifiersState};
pub mod viewport_3d;
pub mod api;
pub mod window;
@@ -50,9 +14,14 @@ pub mod render;
pub mod shortcut;
pub mod thumbnail;
-use app::{State, CustomEvent, HttpAction, ModifiersState};
-use window::{AppState, WindowEvent};
-use api::start_http_server;
+#[cfg(test)]
+mod test_prelude {
+ pub use std::fs;
+ pub use std::path::Path;
+ pub use glam::{Mat4, Vec3};
+ pub use cce_ui::widget::{Key, NamedKey};
+ pub use crate::app::{State, HttpAction, ModifiersState};
+}
fn main() {
let args: Vec<String> = std::env::args().collect();
@@ -84,224 +53,14 @@ fn main() {
}
}
- let is_detached_network = args.iter().any(|arg| arg == "--detached-network");
-
- let conn = Connection::connect_to_env().unwrap();
- let (globals, mut event_queue) = registry_queue_init(&conn).unwrap();
- let qh = event_queue.handle();
-
- let compositor_state = CompositorState::bind(&globals, &qh).unwrap();
- let xdg_shell_state = XdgShell::bind(&globals, &qh).unwrap();
- let shm_state = Shm::bind(&globals, &qh).unwrap();
- let seat_state = SeatState::new(&globals, &qh);
- let output_state = OutputState::new(&globals, &qh);
- let inspector = globals.bind(&qh, 1..=1, ()).ok();
- let (sender, channel) = calloop::channel::channel::<CustomEvent>();
-
- let mut app = AppState {
- registry_state: RegistryState::new(&globals),
- compositor_state,
- xdg_shell_state,
- shm_state,
- seat_state,
- output_state,
- seats: Vec::new(),
- pointer: None,
- keyboard: None,
- window: None,
- surface: None,
- state: None,
- exit: false,
- redraw: true,
- pressed_key: None,
- inspector,
- pending_resize: None,
- _sender: sender.clone(),
- };
-
- // Perform a roundtrip to populate output_state with active output scales
- event_queue.roundtrip(&mut app).unwrap();
-
- let scale = cce_ui::wayland::detect_scale_factor(&app.output_state);
-
- let (pw, ph) = if is_detached_network {
- ((400.0 * scale) as u32, (400.0 * scale) as u32)
- } else {
- ((1280.0 * scale) as u32, (800.0 * scale) as u32)
- };
-
- let state = State::new(
- &conn,
- &qh,
- &app.compositor_state,
- &app.xdg_shell_state,
- pw, ph,
- scale,
- is_detached_network,
- );
-
- app.window = Some(state.window.clone());
- app.surface = Some(state.wl_surface.clone());
- app.state = Some(state);
-
- if let Some(ref inspector) = app.inspector {
- if let Some(ref surface) = app.surface {
- inspector.register_client(surface);
- }
- }
-
- if !is_detached_network {
- start_http_server(sender.clone());
- }
-
- let mut event_loop = calloop::EventLoop::try_new().unwrap();
- let loop_handle = event_loop.handle();
-
- WaylandSource::new(conn, event_queue).insert(loop_handle.clone()).unwrap();
-
- loop_handle.insert_source(channel, |event, _metadata, app_state: &mut AppState| {
- if let calloop::channel::Event::Msg(msg) = event {
- app_state.handle_user_event(msg);
- }
- }).unwrap();
-
- const KEY_REPEAT_DELAY: std::time::Duration = std::time::Duration::from_millis(500);
- const KEY_REPEAT_INTERVAL: std::time::Duration = std::time::Duration::from_millis(50);
-
- loop {
- let timeout = if app.redraw {
- std::time::Duration::ZERO
- } else {
- std::time::Duration::from_millis(16)
- };
- event_loop.dispatch(timeout, &mut app).unwrap();
-
- if app.exit || app.state.as_ref().map(|s| s.exit_requested).unwrap_or(false) {
- if let Some(state) = &mut app.state {
- if state.needs_autosave && (state.is_detached_network || state.detached_circular_network) {
- let default_proj_path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("default_project.json");
- let _ = state.save_to_file(&default_proj_path);
- }
- }
- break;
- }
-
- if let Some(state) = &mut app.state {
- if let Some(ref inspector) = app.inspector {
- let now = std::time::Instant::now();
- if now.duration_since(state.last_inspector_check) >= std::time::Duration::from_millis(250) {
- state.last_inspector_check = now;
- inspector.get_inspected_surfaces();
- }
- }
-
- if state.needs_autosave && (state.is_detached_network || state.detached_circular_network) {
- let now = std::time::Instant::now();
- if now.duration_since(state.last_autosave_time) >= std::time::Duration::from_millis(200) {
- state.needs_autosave = false;
- state.last_autosave_time = now;
- let default_proj_path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("default_project.json");
- if let Err(e) = state.save_to_file(&default_proj_path) {
- eprintln!("Failed to auto-save default project in main loop: {:?}", e);
- } else if let Ok(m) = std::fs::metadata(&default_proj_path) {
- if let Ok(mod_time) = m.modified() {
- state.last_project_mod_time = Some(mod_time);
- }
- }
- }
- }
-
- if state.is_detached_network || state.detached_circular_network {
- let now = std::time::Instant::now();
- if now.duration_since(state.last_project_check) >= std::time::Duration::from_millis(100) {
- state.last_project_check = now;
- let default_proj_path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("default_project.json");
- if let Ok(m) = std::fs::metadata(&default_proj_path) {
- if let Ok(mod_time) = m.modified() {
- if Some(mod_time) != state.last_project_mod_time {
- state.last_project_mod_time = Some(mod_time);
- if let Err(e) = state.load_from_file(&default_proj_path) {
- eprintln!("Failed to auto-reload project: {:?}", e);
- } else {
- app.redraw = true;
- }
- }
- }
- }
- }
- }
- }
-
- if let Some(ref mut pk) = app.pressed_key {
- let now = std::time::Instant::now();
- if now.duration_since(pk.first_pressed) >= KEY_REPEAT_DELAY {
- if now.duration_since(pk.last_repeated) >= KEY_REPEAT_INTERVAL {
- pk.last_repeated = now;
- if let Some(st) = &mut app.state {
- let custom_event = cce_ui::widget::KeyEvent {
- state: cce_ui::widget::ElementState::Pressed,
- logical_key: pk.logical_key.clone(),
- text: pk.text.clone(),
- repeat: true,
- ctrl: st.modifiers.ctrl,
- shift: st.modifiers.shift,
- };
- let ev = WindowEvent::KeyboardInput { event: custom_event };
- app.process_event(ev);
- }
- }
- }
- }
-
-fn create_memfd_with_data(name: &str, data: &[u8]) -> std::io::Result<std::os::unix::io::RawFd> {
- use std::io::{Seek, Write};
- use std::os::unix::io::FromRawFd;
- use std::os::unix::io::IntoRawFd;
-
- let c_name = std::ffi::CString::new(name).unwrap();
- let fd = unsafe { libc::memfd_create(c_name.as_ptr(), libc::MFD_CLOEXEC) };
- if fd < 0 {
- return Err(std::io::Error::last_os_error());
- }
- let mut file = unsafe { std::fs::File::from_raw_fd(fd) };
- file.write_all(data)?;
- file.seek(std::io::SeekFrom::Start(0))?;
- Ok(file.into_raw_fd())
-}
-
- if app.redraw {
- app.redraw = false;
- if let Some(state) = &mut app.state {
- if state.render() {
- app.redraw = true;
- }
- if let Some(ref inspector) = app.inspector {
- if let Some(ref surface) = app.surface {
- let json = cce_ui::widget::serialize_widgets(&state.slots.dyn_refs());
- if json != state.last_serialized {
- let now = std::time::Instant::now();
- if now.duration_since(state.last_inspector_update) >= std::time::Duration::from_millis(100) {
- state.last_serialized = json.clone();
- state.last_inspector_update = now;
- if let Ok(raw_fd) = create_memfd_with_data("cce_ui_state", json.as_bytes()) {
- use std::os::unix::io::{FromRawFd, AsFd};
- let file = unsafe { std::fs::File::from_raw_fd(raw_fd) };
- inspector.update_state(surface, file.as_fd(), json.len() as u32);
- }
- } else {
- app.redraw = true;
- }
- }
- }
- }
- }
- }
- }
+ // Everything windowed runs on the cce-ui engine (application.rs holds the
+ // Application impl; --detached-network is read there).
+ cce_ui::engine::run::<app::State>();
}
#[cfg(test)]
mod tests {
- use super::*;
+ use crate::test_prelude::*;
use crate::app::{get_next_visible_pane, LEFT_MENUBAR_IDX, RIGHT_MENUBAR_IDX, PARAM_MENUBAR_IDX, SPREADSHEET_MENUBAR_IDX, DesignSettings, FsNode, Project, ProjectViewState};
use crate::shortcut::{Shortcut, ShortcutManager, Action};
use crate::geometry::{GAttribute, GVertex, Geometry, line_vertices};
diff --git a/src/project.rs b/src/project.rs
index b3bfbcd..3258aba 100644
--- a/src/project.rs
+++ b/src/project.rs
@@ -1,7 +1,6 @@
use std::fs;
use std::path::Path;
-use cce_ui::widget::Button;
use crate::app::{State, Project, FsNode, ProjectViewState, CONTENT_IDX, ParamDef};
fn color_to_hex(rgb: [f32; 3]) -> String {
@@ -19,7 +18,7 @@ fn hex_to_color(hex: &str) -> Option<[f32; 3]> {
impl State {
- pub(crate) fn update_window_title(&self) {
+ pub(crate) fn update_window_title(&mut self) {
let base_title = if self.is_detached_network {
"Network Pane"
} else {
@@ -38,8 +37,9 @@ impl State {
if self.has_unsaved_changes() {
title.push_str("*");
}
-
- self.window.set_title(&title);
+
+ // The engine polls `Application::settings` and applies title changes.
+ self.title = title;
}
pub(crate) fn load_recent_files() -> Vec<std::path::PathBuf> {
diff --git a/src/render.rs b/src/render.rs
index 0082042..073fce9 100644
--- a/src/render.rs
+++ b/src/render.rs
@@ -7,9 +7,9 @@ use crate::app::{
State, FsNode, WIDGET_COUNT,
make_text_buffer, make_text_buffer_with_font,
CONTENT_IDX, VIEWPORT_IDX, PARAM_IDX, PARAM_PLATE_IDX,
- BREADCRUMB_IDX, STATUS_IDX, HEADER_IDX, RIGHT_MENUBAR_IDX,
+ BREADCRUMB_IDX, HEADER_IDX, RIGHT_MENUBAR_IDX,
SPREADSHEET_MENUBAR_IDX,
- MENUBAR_H, STATUS_H,
+ MENUBAR_H,
LEFT_MENUBAR_IDX, PARAM_MENUBAR_IDX, NETWORK_PANEL_IDX,
push_circle_vertices, push_circle_border_vertices,
};
@@ -400,10 +400,12 @@ impl State {
let verts = geom.to_vertex3d_vec();
self.vertex_count_spheres = verts.len() as u32;
- self.renderer.update_mesh(self.mesh_spheres, bytemuck::cast_slice(&verts));
// Cache for the path tracer, so RT mode never re-runs the node
// graph / OpenCL kernels; the version bump invalidates its scene.
+ // The raster mesh uploads from this same cache on the next
+ // `stage_renderer` flush.
self.rt_sphere_verts = verts;
+ self.spheres_dirty = true;
self.rt_geometry_version += 1;
self.viewport_dirty = true;
}
@@ -430,7 +432,7 @@ impl State {
}
}
- pub(crate) fn prepare_text(&mut self) {
+ pub(crate) fn prepare_text(&mut self, renderer: &mut cce_ui::vk::VkRenderer) {
let mut current_popovers = Vec::new();
{
fn collect_popovers(
@@ -489,7 +491,6 @@ impl State {
let _ = sh;
let Self {
- ref mut renderer,
ref mut font_system,
ref mut swash_cache,
physical_width, physical_height, scale,
diff --git a/src/window.rs b/src/window.rs
index c873b8d..e7cef5e 100644
--- a/src/window.rs
+++ b/src/window.rs
@@ -1,33 +1,16 @@
-#![allow(unused_imports)]
-use std::path::Path;
+//! The designer's window-event layer on the cce-ui engine.
+//!
+//! The Wayland plumbing (seat/pointer/keyboard handlers, configure, CSD)
+//! lives in cce-ui's window runner; the `Application` impl (application.rs)
+//! translates the runner's hooks into [`WindowEvent`]s. What remains here is
+//! app policy: the post-event side-effect pass (`process_window_event`) and
+//! HTTP-action application (`apply_custom_event`).
-use smithay_client_toolkit::{
- compositor::{CompositorHandler, CompositorState},
- delegate_compositor, delegate_keyboard, delegate_pointer, delegate_registry,
- delegate_seat, delegate_shm, delegate_xdg_shell, delegate_xdg_window, delegate_output,
- registry::{ProvidesRegistryState, RegistryState},
- output::{OutputHandler, OutputState},
- seat::{
- keyboard::KeyboardHandler,
- pointer::{PointerHandler, ThemedPointer, ThemeSpec, CursorIcon},
- Capability, SeatHandler, SeatState,
- },
- shell::{
- xdg::{
- window::{Window as XdgWindow, WindowConfigure, WindowHandler},
- XdgShell,
- },
- },
- shm::{Shm, ShmHandler},
-};
-use wayland_client::{
- protocol::{wl_keyboard, wl_output, wl_pointer, wl_seat, wl_surface},
- Connection, QueueHandle,
-};
+use std::path::Path;
use cce_ui::widget::WidgetHost;
use crate::shortcut::Action;
-use crate::app::{State, CustomEvent, HttpAction, ModifiersState, TouchPhase, LEFT_MENUBAR_IDX, RIGHT_MENUBAR_IDX, PARAM_MENUBAR_IDX, SPREADSHEET_MENUBAR_IDX, HEADER_IDX, CONTENT_IDX, BREADCRUMB_IDX, VIEWPORT_IDX, PARAM_IDX, SPREADSHEET_IDX, WIDGET_COUNT, get_next_visible_pane, Project, ProjectViewState, ParamDef, param_display};
+use crate::app::{State, CustomEvent, HttpAction, TouchPhase, LEFT_MENUBAR_IDX, RIGHT_MENUBAR_IDX, PARAM_MENUBAR_IDX, SPREADSHEET_MENUBAR_IDX, HEADER_IDX, PARAM_IDX, WIDGET_COUNT, get_next_visible_pane, Project, ProjectViewState, ParamDef, param_display};
#[derive(Debug, Clone, Copy)]
pub struct LocalPosition {
@@ -37,745 +20,19 @@ pub struct LocalPosition {
pub enum WindowEvent {
MouseWheel { delta: cce_ui::widget::MouseScrollDelta, phase: TouchPhase },
- PinchGesture { delta: f64 },
CursorMoved { position: LocalPosition },
MouseInput { state: cce_ui::widget::ElementState, button: cce_ui::widget::MouseButton },
- ModifiersChanged(ModifiersState),
KeyboardInput { event: cce_ui::widget::KeyEvent },
}
-pub struct PressedKey {
- pub logical_key: cce_ui::widget::Key,
- pub text: Option<String>,
- pub first_pressed: std::time::Instant,
- pub last_repeated: std::time::Instant,
-}
-
-pub fn is_repeatable_key(key: &cce_ui::widget::Key) -> bool {
- use cce_ui::widget::{Key, NamedKey};
- match key {
- Key::Named(NamedKey::Backspace) |
- Key::Named(NamedKey::Delete) |
- Key::Named(NamedKey::ArrowLeft) |
- Key::Named(NamedKey::ArrowRight) |
- Key::Named(NamedKey::ArrowUp) |
- Key::Named(NamedKey::ArrowDown) |
- Key::Named(NamedKey::Home) |
- Key::Named(NamedKey::End) |
- Key::Character(_) => true,
- _ => false,
- }
-}
-
-pub struct PendingResize {
- pub serial: u32,
- pub edge: smithay_client_toolkit::reexports::protocols::xdg::shell::client::xdg_toplevel::ResizeEdge,
- pub start_x: f32,
- pub start_y: f32,
- pub is_move: bool,
-}
-
-pub struct AppState {
- pub registry_state: RegistryState,
- pub compositor_state: CompositorState,
- pub xdg_shell_state: XdgShell,
- pub shm_state: Shm,
- pub seat_state: SeatState,
- pub output_state: OutputState,
-
- pub seats: Vec<wl_seat::WlSeat>,
- pub pointer: Option<ThemedPointer>,
- pub keyboard: Option<wl_keyboard::WlKeyboard>,
-
- pub window: Option<XdgWindow>,
- pub surface: Option<wl_surface::WlSurface>,
-
- pub state: Option<State>,
- pub exit: bool,
- pub redraw: bool,
- pub pressed_key: Option<PressedKey>,
- pub inspector: Option<cce_ui::protocol::zcce_inspector_v1::ZcceInspectorV1>,
- pub pending_resize: Option<PendingResize>,
- pub _sender: calloop::channel::Sender<CustomEvent>,
-}
-
-impl CompositorHandler for AppState {
- fn scale_factor_changed(
- &mut self,
- _conn: &Connection,
- _qh: &QueueHandle<Self>,
- _surface: &wl_surface::WlSurface,
- scale_factor: i32,
- ) {
- _surface.set_buffer_scale(scale_factor);
- if let Some(state) = &mut self.state {
- state.scale = scale_factor as f64;
- cce_ui::scale::set_scale_factor(scale_factor as f32);
- let pw = (state.width as f64 * state.scale) as u32;
- let ph = (state.height as f64 * state.scale) as u32;
- state.resize(pw, ph);
- self.redraw = true;
- }
- }
-
- fn transform_changed(
- &mut self,
- _conn: &Connection,
- _qh: &QueueHandle<Self>,
- _surface: &wl_surface::WlSurface,
- _new_transform: wl_output::Transform,
- ) {}
-
- fn frame(
- &mut self,
- _conn: &Connection,
- _qh: &QueueHandle<Self>,
- _surface: &wl_surface::WlSurface,
- _time: u32,
- ) {}
-
- fn surface_enter(
- &mut self,
- _conn: &Connection,
- _qh: &QueueHandle<Self>,
- _surface: &wl_surface::WlSurface,
- _output: &wl_output::WlOutput,
- ) {}
-
- fn surface_leave(
- &mut self,
- _conn: &Connection,
- _qh: &QueueHandle<Self>,
- _surface: &wl_surface::WlSurface,
- _output: &wl_output::WlOutput,
- ) {}
-}
-
-impl OutputHandler for AppState {
- fn output_state(&mut self) -> &mut OutputState {
- &mut self.output_state
- }
-
- fn new_output(
- &mut self,
- _conn: &Connection,
- _qh: &QueueHandle<Self>,
- _output: wl_output::WlOutput,
- ) {}
-
- fn update_output(
- &mut self,
- _conn: &Connection,
- _qh: &QueueHandle<Self>,
- _output: wl_output::WlOutput,
- ) {}
-
- fn output_destroyed(
- &mut self,
- _conn: &Connection,
- _qh: &QueueHandle<Self>,
- _output: wl_output::WlOutput,
- ) {}
-}
-
-impl SeatHandler for AppState {
- fn seat_state(&mut self) -> &mut SeatState {
- &mut self.seat_state
- }
-
- fn new_seat(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, seat: wl_seat::WlSeat) {
- self.seats.push(seat);
- // eprintln!("DEBUG SEAT: new_seat called, total seats now: {}", self.seats.len());
- }
-
- fn new_capability(
- &mut self,
- _conn: &Connection,
- qh: &QueueHandle<Self>,
- seat: wl_seat::WlSeat,
- capability: Capability,
- ) {
- // eprintln!("DEBUG SEAT: new_capability: {:?}", capability);
- if capability == Capability::Pointer && self.pointer.is_none() {
- let surface = self.compositor_state.create_surface(qh);
- let themed_pointer = self.seat_state.get_pointer_with_theme(
- qh,
- &seat,
- self.shm_state.wl_shm(),
- surface,
- ThemeSpec::System,
- ).unwrap();
- self.pointer = Some(themed_pointer);
- }
- if capability == Capability::Keyboard && self.keyboard.is_none() {
- let keyboard = self
- .seat_state
- .get_keyboard(qh, &seat, None)
- .unwrap();
- self.keyboard = Some(keyboard);
- }
- }
-
- fn remove_capability(
- &mut self,
- _conn: &Connection,
- _qh: &QueueHandle<Self>,
- _seat: wl_seat::WlSeat,
- capability: Capability,
- ) {
- if capability == Capability::Pointer {
- self.pointer = None;
- }
- if capability == Capability::Keyboard {
- self.keyboard = None;
- }
- }
-
- fn remove_seat(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, seat: wl_seat::WlSeat) {
- self.seats.retain(|s| s != &seat);
- // eprintln!("DEBUG SEAT: remove_seat called, total seats now: {}", self.seats.len());
- }
-}
-
-impl ShmHandler for AppState {
- fn shm_state(&mut self) -> &mut Shm {
- &mut self.shm_state
- }
-}
-
-impl PointerHandler for AppState {
- fn pointer_frame(
- &mut self,
- _conn: &Connection,
- _qh: &QueueHandle<Self>,
- _pointer: &wl_pointer::WlPointer,
- events: &[smithay_client_toolkit::seat::pointer::PointerEvent],
- ) {
- use smithay_client_toolkit::seat::pointer::PointerEventKind;
- for event in events {
- if let Some(st) = &mut self.state {
- st.cursor_x = event.position.0 as f32;
- st.cursor_y = event.position.1 as f32;
- match &event.kind {
- PointerEventKind::Motion { .. } => {
- if st.is_detached_network {
- let lx = event.position.0 as f32;
- let ly = event.position.1 as f32;
- let dx = lx - st.circular_network_layout.x;
- let dy = ly - st.circular_network_layout.y;
- let dist = (dx * dx + dy * dy).sqrt();
- let on_border = dist >= st.circular_network_layout.r - 12.0 && dist <= st.circular_network_layout.r;
- let hits_any_menu = st.menu(LEFT_MENUBAR_IDX).get_menu_items_at(lx, ly).is_some();
-
- if let Some(ref themed_pointer) = self.pointer {
- if on_border && !hits_any_menu {
- let nx = dx / dist;
- let ny = dy / dist;
- let mut cursor = CursorIcon::Default;
- if ny < -0.382 {
- if nx < -0.382 {
- cursor = CursorIcon::NwResize;
- } else if nx > 0.382 {
- cursor = CursorIcon::NeResize;
- } else {
- cursor = CursorIcon::NResize;
- }
- } else if ny > 0.382 {
- if nx < -0.382 {
- cursor = CursorIcon::SwResize;
- } else if nx > 0.382 {
- cursor = CursorIcon::SeResize;
- } else {
- cursor = CursorIcon::SResize;
- }
- } else {
- if nx < -0.382 {
- cursor = CursorIcon::WResize;
- } else if nx > 0.382 {
- cursor = CursorIcon::EResize;
- }
- }
- let _ = themed_pointer.set_cursor(_conn, cursor);
- } else {
- let _ = themed_pointer.set_cursor(_conn, CursorIcon::Default);
- }
- }
- } else {
- let lx = event.position.0 as f32;
- let ly = event.position.1 as f32;
- let border = 8.0f32;
- let mut cursor = CursorIcon::Default;
- if ly < border {
- if lx < border {
- cursor = CursorIcon::NwResize;
- } else if lx > st.width - border {
- cursor = CursorIcon::NeResize;
- } else {
- cursor = CursorIcon::NResize;
- }
- } else if ly > st.height - border {
- if lx < border {
- cursor = CursorIcon::SwResize;
- } else if lx > st.width - border {
- cursor = CursorIcon::SeResize;
- } else {
- cursor = CursorIcon::SResize;
- }
- } else if lx < border {
- cursor = CursorIcon::WResize;
- } else if lx > st.width - border {
- cursor = CursorIcon::EResize;
- }
-
- if let Some(ref themed_pointer) = self.pointer {
- let _ = themed_pointer.set_cursor(_conn, cursor);
- }
- }
-
- if let Some(ref pending) = self.pending_resize {
- let lx = event.position.0 as f32;
- let ly = event.position.1 as f32;
- let rx = lx - pending.start_x;
- let ry = ly - pending.start_y;
- let rdist = (rx * rx + ry * ry).sqrt();
- if rdist > 4.0 {
- if let Some(ref window) = self.window {
- let seat = self.seats.first().cloned().or_else(|| self.seat_state.seats().next());
- if let Some(ref seat) = seat {
- if pending.is_move {
- // eprintln!("DEBUG DRAG INITIATING window.move_ with serial={}", pending.serial);
- window.move_(seat, pending.serial);
- } else {
- // eprintln!("DEBUG RESIZE INITIATING window.resize with edge={:?}, serial={}", pending.edge, pending.serial);
- window.resize(seat, pending.serial, pending.edge);
- }
- }
- }
- self.pending_resize = None;
- }
- }
-
- let ev = WindowEvent::CursorMoved {
- position: LocalPosition {
- x: event.position.0,
- y: event.position.1,
- },
- };
- self.process_event(ev);
- }
- PointerEventKind::Press { button, serial, .. } => {
- let btn = match *button {
- 272 => cce_ui::widget::MouseButton::Left,
- 273 => cce_ui::widget::MouseButton::Right,
- 274 => cce_ui::widget::MouseButton::Middle,
- _ => continue,
- };
- // eprintln!("DEBUG MOUSE PRESS: button={:?}, pos={:?}, local=({}, {})", btn, event.position, cx, cy);
-
- if let Some(ref st) = self.state {
- if st.is_detached_network && btn == cce_ui::widget::MouseButton::Left {
- let lx = event.position.0 as f32;
- let ly = event.position.1 as f32;
- let dx = lx - st.circular_network_layout.x;
- let dy = ly - st.circular_network_layout.y;
- let dist = (dx * dx + dy * dy).sqrt();
- let on_border = dist >= st.circular_network_layout.r - 12.0 && dist <= st.circular_network_layout.r;
- let in_menubar_bg = dy < 0.0 && dist >= st.circular_network_layout.r - 35.0 && dist <= st.circular_network_layout.r;
- let hits_any_menu = st.menu(LEFT_MENUBAR_IDX).get_menu_items_at(lx, ly).is_some();
-
- // eprintln!("DEBUG DRAG: lx={}, ly={}, cx={}, cy={}, r={}, dx={}, dy={}, dist={}, on_border={}, in_menubar_bg={}, hits_any_menu={}, seats_len={}, has_window={}",
- // lx, ly, st.circular_network_layout.x, st.circular_network_layout.y, st.circular_network_layout.r,
- // dx, dy, dist, on_border, in_menubar_bg, hits_any_menu, self.seats.len(), self.window.is_some());
-
- if (on_border || in_menubar_bg) && !hits_any_menu {
- if let Some(ref _window) = self.window {
- let seat = self.seats.first().cloned().or_else(|| self.seat_state.seats().next());
- if let Some(ref _seat) = seat {
- if on_border {
- use smithay_client_toolkit::reexports::protocols::xdg::shell::client::xdg_toplevel::ResizeEdge;
- let nx = dx / dist;
- let ny = dy / dist;
- let mut edge = ResizeEdge::None;
- if ny < -0.382 {
- if nx < -0.382 {
- edge = ResizeEdge::TopLeft;
- } else if nx > 0.382 {
- edge = ResizeEdge::TopRight;
- } else {
- edge = ResizeEdge::Top;
- }
- } else if ny > 0.382 {
- if nx < -0.382 {
- edge = ResizeEdge::BottomLeft;
- } else if nx > 0.382 {
- edge = ResizeEdge::BottomRight;
- } else {
- edge = ResizeEdge::Bottom;
- }
- } else {
- if nx < -0.382 {
- edge = ResizeEdge::Left;
- } else if nx > 0.382 {
- edge = ResizeEdge::Right;
- }
- }
- self.pending_resize = Some(PendingResize {
- serial: *serial,
- edge,
- start_x: lx,
- start_y: ly,
- is_move: false,
- });
- continue;
- } else {
- self.pending_resize = Some(PendingResize {
- serial: *serial,
- edge: smithay_client_toolkit::reexports::protocols::xdg::shell::client::xdg_toplevel::ResizeEdge::None,
- start_x: lx,
- start_y: ly,
- is_move: true,
- });
- continue;
- }
- }
- }
- }
- } else if !st.is_detached_network && btn == cce_ui::widget::MouseButton::Left {
- let lx = event.position.0 as f32;
- let ly = event.position.1 as f32;
- let border = 8.0f32;
- use smithay_client_toolkit::reexports::protocols::xdg::shell::client::xdg_toplevel::ResizeEdge;
- let mut edge = ResizeEdge::None;
- if ly < border {
- if lx < border {
- edge = ResizeEdge::TopLeft;
- } else if lx > st.width - border {
- edge = ResizeEdge::TopRight;
- } else {
- edge = ResizeEdge::Top;
- }
- } else if ly > st.height - border {
- if lx < border {
- edge = ResizeEdge::BottomLeft;
- } else if lx > st.width - border {
- edge = ResizeEdge::BottomRight;
- } else {
- edge = ResizeEdge::Bottom;
- }
- } else if lx < border {
- edge = ResizeEdge::Left;
- } else if lx > st.width - border {
- edge = ResizeEdge::Right;
- }
-
- if edge != ResizeEdge::None {
- self.pending_resize = Some(PendingResize {
- serial: *serial,
- edge,
- start_x: lx,
- start_y: ly,
- is_move: false,
- });
- continue;
- }
- }
- }
-
- let ev = WindowEvent::MouseInput {
- state: cce_ui::widget::ElementState::Pressed,
- button: btn,
- };
- self.process_event(ev);
- }
- PointerEventKind::Release { button, .. } => {
- let btn = match *button {
- 272 => cce_ui::widget::MouseButton::Left,
- 273 => cce_ui::widget::MouseButton::Right,
- 274 => cce_ui::widget::MouseButton::Middle,
- _ => continue,
- };
- // eprintln!("DEBUG MOUSE RELEASE: button={:?}, pos={:?}, local=({}, {})", btn, event.position, cx, cy);
- if btn == cce_ui::widget::MouseButton::Left {
- self.pending_resize = None;
- }
- let ev = WindowEvent::MouseInput {
- state: cce_ui::widget::ElementState::Released,
- button: btn,
- };
- self.process_event(ev);
- }
- PointerEventKind::Axis { horizontal, vertical, .. } => {
- let h_val = horizontal.absolute as f32;
- let v_val = vertical.absolute as f32;
- // eprintln!("DEBUG AXIS EVENT: horizontal={:?}, vertical={:?}, scale={}", horizontal, vertical, st.scale);
- let ev = WindowEvent::MouseWheel {
- delta: cce_ui::widget::MouseScrollDelta::LineDelta(-h_val / 10.0, -v_val / 10.0),
- phase: TouchPhase::Moved,
- };
- self.process_event(ev);
- }
- PointerEventKind::Enter { .. } => {
- if let Some(ref themed_pointer) = self.pointer {
- let _ = themed_pointer.set_cursor(_conn, CursorIcon::Default);
- }
- }
- _ => {}
- }
- }
- }
- }
-}
-
-impl KeyboardHandler for AppState {
- fn enter(
- &mut self,
- _conn: &Connection,
- _qh: &QueueHandle<Self>,
- _keyboard: &wl_keyboard::WlKeyboard,
- _surface: &wl_surface::WlSurface,
- _serial: u32,
- _raw_modifiers: &[u32],
- _keysyms: &[xkeysym::Keysym],
- ) {}
-
- fn leave(
- &mut self,
- _conn: &Connection,
- _qh: &QueueHandle<Self>,
- _keyboard: &wl_keyboard::WlKeyboard,
- _surface: &wl_surface::WlSurface,
- _serial: u32,
- ) {
- self.pressed_key = None;
- if let Some(st) = &mut self.state {
- st.modifiers = ModifiersState::default();
- }
- }
-
-
- fn press_key(
- &mut self,
- _conn: &Connection,
- _qh: &QueueHandle<Self>,
- _keyboard: &wl_keyboard::WlKeyboard,
- _serial: u32,
- event: smithay_client_toolkit::seat::keyboard::KeyEvent,
- ) {
- self.handle_key(event, cce_ui::widget::ElementState::Pressed);
- }
-
- fn release_key(
- &mut self,
- _conn: &Connection,
- _qh: &QueueHandle<Self>,
- _keyboard: &wl_keyboard::WlKeyboard,
- _serial: u32,
- event: smithay_client_toolkit::seat::keyboard::KeyEvent,
- ) {
- self.handle_key(event, cce_ui::widget::ElementState::Released);
- }
-
- fn update_modifiers(
- &mut self,
- _conn: &Connection,
- _qh: &QueueHandle<Self>,
- _keyboard: &wl_keyboard::WlKeyboard,
- _serial: u32,
- modifiers: smithay_client_toolkit::seat::keyboard::Modifiers,
- _layout: u32,
- ) {
- if let Some(st) = &mut self.state {
- st.modifiers.ctrl = modifiers.ctrl;
- st.modifiers.alt = modifiers.alt;
- st.modifiers.shift = modifiers.shift;
- st.modifiers.logo = modifiers.logo;
- }
- }
-}
-
-impl WindowHandler for AppState {
- fn configure(
- &mut self,
- _conn: &Connection,
- _qh: &QueueHandle<Self>,
- _window: &XdgWindow,
- configure: WindowConfigure,
- _serial: u32,
- ) {
- let (w, h) = configure.new_size;
- // eprintln!("DEBUG CONFIGURE: new_size={:?}, configure={:?}", configure.new_size, configure);
- if let (Some(w), Some(h)) = (w, h) {
- let width = w.get();
- let height = h.get();
- if let Some(state) = &mut self.state {
- let pw = (width as f64 * state.scale) as u32;
- let ph = (height as f64 * state.scale) as u32;
- state.resize(pw, ph);
- }
- }
- self.redraw = true;
- }
-
- fn request_close(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, _window: &XdgWindow) {
- self.exit = true;
- }
-}
-
-impl ProvidesRegistryState for AppState {
- fn registry(&mut self) -> &mut RegistryState {
- &mut self.registry_state
- }
-
- fn runtime_add_global(
- &mut self,
- _conn: &Connection,
- _qh: &QueueHandle<Self>,
- _name: u32,
- _interface: &str,
- _version: u32,
- ) {}
-
- fn runtime_remove_global(
- &mut self,
- _conn: &Connection,
- _qh: &QueueHandle<Self>,
- _name: u32,
- _interface: &str,
- ) {}
-}
-
-impl wayland_client::Dispatch<cce_ui::protocol::zcce_inspector_v1::ZcceInspectorV1, ()> for AppState {
- fn event(
- state: &mut Self,
- _proxy: &cce_ui::protocol::zcce_inspector_v1::ZcceInspectorV1,
- event: cce_ui::protocol::zcce_inspector_v1::Event,
- _data: &(),
- _conn: &Connection,
- _qh: &QueueHandle<Self>,
- ) {
- match event {
- cce_ui::protocol::zcce_inspector_v1::Event::InspectedSurface { app_id, x, y, .. } => {
- if let Some(ref mut st) = state.state {
- let expected_id = if st.is_detached_network {
- "circular-network-pane"
- } else {
- "cce-designer"
- };
- if app_id == expected_id {
- st.window_x = x;
- st.window_y = y;
- }
- }
- }
- _ => {}
- }
- }
-}
-
-delegate_compositor!(AppState);
-delegate_xdg_shell!(AppState);
-delegate_xdg_window!(AppState);
-delegate_shm!(AppState);
-delegate_seat!(AppState);
-delegate_pointer!(AppState);
-delegate_keyboard!(AppState);
-delegate_registry!(AppState);
-delegate_output!(AppState);
-
-impl AppState {
- fn handle_key(&mut self, event: smithay_client_toolkit::seat::keyboard::KeyEvent, state: cce_ui::widget::ElementState) {
- use cce_ui::widget::{Key, KeyEvent, NamedKey};
- let logical_key = match event.keysym {
- xkeysym::Keysym::Escape => Key::Named(NamedKey::Escape),
- xkeysym::Keysym::Return => Key::Named(NamedKey::Enter),
- xkeysym::Keysym::BackSpace => Key::Named(NamedKey::Backspace),
- xkeysym::Keysym::Down => Key::Named(NamedKey::ArrowDown),
- xkeysym::Keysym::Up => Key::Named(NamedKey::ArrowUp),
- xkeysym::Keysym::Left => Key::Named(NamedKey::ArrowLeft),
- xkeysym::Keysym::Right => Key::Named(NamedKey::ArrowRight),
- xkeysym::Keysym::Tab => Key::Named(NamedKey::Tab),
- xkeysym::Keysym::Delete => Key::Named(NamedKey::Delete),
- xkeysym::Keysym::space => Key::Named(NamedKey::Space),
- xkeysym::Keysym::comma => Key::Character(",".into()),
- xkeysym::Keysym::g | xkeysym::Keysym::G => Key::Character("g".into()),
- xkeysym::Keysym::e | xkeysym::Keysym::E => Key::Character("e".into()),
- xkeysym::Keysym::a | xkeysym::Keysym::A => Key::Character("a".into()),
- xkeysym::Keysym::d | xkeysym::Keysym::D => Key::Character("d".into()),
- xkeysym::Keysym::f | xkeysym::Keysym::F => Key::Character("f".into()),
- xkeysym::Keysym::h | xkeysym::Keysym::H => Key::Character("h".into()),
- xkeysym::Keysym::j | xkeysym::Keysym::J => Key::Character("j".into()),
- xkeysym::Keysym::k | xkeysym::Keysym::K => Key::Character("k".into()),
- xkeysym::Keysym::l | xkeysym::Keysym::L => Key::Character("l".into()),
- xkeysym::Keysym::s | xkeysym::Keysym::S => Key::Character("s".into()),
- xkeysym::Keysym::c | xkeysym::Keysym::C => Key::Character("c".into()),
- xkeysym::Keysym::x | xkeysym::Keysym::X => Key::Character("x".into()),
- xkeysym::Keysym::v | xkeysym::Keysym::V => Key::Character("v".into()),
- xkeysym::Keysym::grave => Key::Character("`".into()),
- _ => {
- if let Some(ref text) = event.utf8 {
- Key::Character(text.clone())
- } else if let Some(ch) = event.keysym.key_char() {
- Key::Character(ch.to_string())
- } else {
- return;
- }
- }
- };
-
- // eprintln!("DEBUG KEY: keysym={:?}, state={:?}, logical_key={:?}", event.keysym, state, logical_key);
-
- if let Some(st) = &mut self.state {
- match event.keysym {
- xkeysym::Keysym::Control_L | xkeysym::Keysym::Control_R => {
- st.modifiers.ctrl = state == cce_ui::widget::ElementState::Pressed;
- }
- xkeysym::Keysym::Alt_L | xkeysym::Keysym::Alt_R => {
- st.modifiers.alt = state == cce_ui::widget::ElementState::Pressed;
- }
- xkeysym::Keysym::Shift_L | xkeysym::Keysym::Shift_R => {
- st.modifiers.shift = state == cce_ui::widget::ElementState::Pressed;
- }
- xkeysym::Keysym::Super_L | xkeysym::Keysym::Super_R => {
- st.modifiers.logo = state == cce_ui::widget::ElementState::Pressed;
- }
- _ => {}
- }
-
- let custom_event = KeyEvent {
- state,
- logical_key,
- text: event.utf8.clone(),
- repeat: false,
- ctrl: st.modifiers.ctrl,
- shift: st.modifiers.shift,
- };
-
- if state == cce_ui::widget::ElementState::Pressed {
- if is_repeatable_key(&custom_event.logical_key) {
- self.pressed_key = Some(PressedKey {
- logical_key: custom_event.logical_key.clone(),
- text: custom_event.text.clone(),
- first_pressed: std::time::Instant::now(),
- last_repeated: std::time::Instant::now(),
- });
- } else {
- self.pressed_key = None;
- }
- } else if state == cce_ui::widget::ElementState::Released {
- if let Some(ref pk) = self.pressed_key {
- if pk.logical_key == custom_event.logical_key {
- self.pressed_key = None;
- }
- }
- }
-
- let ev = WindowEvent::KeyboardInput { event: custom_event };
- self.process_event(ev);
- }
- }
-
- pub fn process_event(&mut self, ev: WindowEvent) {
- if let Some(state) = &mut self.state {
+impl State {
+ /// Route a window event through `handle_event`, then run the post-event
+ /// side-effect pass (menu clicks, pane toggles, pending actions).
+ /// Returns true when a redraw is needed.
+ pub(crate) fn process_window_event(&mut self, ev: WindowEvent) -> bool {
+ let mut result = false;
+ {
+ let state = &mut *self;
let mut changed = state.handle_event(&ev);
if let Some(seg) = state.path_mut().path_click() {
@@ -1309,14 +566,18 @@ impl AppState {
if state.is_detached_network || state.detached_circular_network {
state.needs_autosave = true;
}
- self.redraw = true;
+ result = true;
}
}
+ result
}
- pub fn handle_user_event(&mut self, event: CustomEvent) {
+ /// Apply an HTTP/API event (the engine `update` hook). Returns true when
+ /// a redraw is needed.
+ pub(crate) fn apply_custom_event(&mut self, event: CustomEvent) -> bool {
let mut needs_redraw = false;
- if let Some(state) = &mut self.state {
+ {
+ let state = &mut *self;
match event {
CustomEvent::GetState(tx) => {
let proj = Project {
@@ -1572,7 +833,7 @@ impl AppState {
match validated {
Ok(label) => {
state.menu_mut(widget_idx).trigger_menu_click(menu_idx, item_idx);
- self.process_event(WindowEvent::CursorMoved { position: LocalPosition { x: -9999.0, y: -9999.0 } });
+ let _ = state.process_window_event(WindowEvent::CursorMoved { position: LocalPosition { x: -9999.0, y: -9999.0 } });
needs_redraw = true;
Ok(format!("Menu clicked: {label}"))
}
@@ -1601,26 +862,18 @@ impl AppState {
};
let _ = tx.send(res);
}
- }
- } else {
- match event {
- CustomEvent::GetState(tx) => {
- let _ = tx.send("null".to_string());
- }
- CustomEvent::PostAction(_, tx) => {
- let _ = tx.send(Err("State not initialized".to_string()));
- }
+ // Exit is handled by the Application::update wrapper
+ // (autosave + engine exit) before this is reached.
+ CustomEvent::Exit => {}
}
}
if needs_redraw {
- if let Some(state) = &mut self.state {
- state.update_window_title();
- if state.is_detached_network || state.detached_circular_network {
- state.needs_autosave = true;
- }
+ self.update_window_title();
+ if self.is_detached_network || self.detached_circular_network {
+ self.needs_autosave = true;
}
- self.redraw = true;
}
+ needs_redraw
}
}