graphic design tool
git clone https://git.lucas.co/cce-designer.git
Convert grid and background colors to linear space
src/app.rs | 441 +++++++++++++++++++++++++++++++++--------------------
src/project.rs | 9 +-
src/render.rs | 59 +++++--
src/shader_3d.wgsl | 6 +-
4 files changed, 329 insertions(+), 186 deletions(-)
diff --git a/src/app.rs b/src/app.rs
index 4e2a4b7..083dc2b 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -35,7 +35,7 @@ use wayland_client::{
};
use wgpu::util::DeviceExt;
-use cce_ui::widget::{Breadcrumb, Canvas, MenuBar, Plate, ParametersBg, Splitter, Spreadsheet, StatusBar, TextLabel, ViewportBg, Element, GraphNode, Graph, Button, Checkbox, ScrollingList, Label};
+use cce_ui::widget::{Breadcrumb, Canvas, MenuBar, Plate, ParametersBg, Splitter, Spreadsheet, StatusBar, TextLabel, ViewportBg, Element, GraphNode, Graph, Button, Checkbox, ScrollingList, Label, Dropdown};
use cce_ui::colors;
use glyphon::{Attrs, Buffer, Cache, FontSystem, Metrics, Resolution, TextAtlas, TextRenderer, Viewport};
use glam::{Mat4, Vec3};
@@ -89,6 +89,7 @@ pub const SPREADSHEET_MENUBAR_IDX: usize = 15;
pub const NETWORK_PANEL_IDX: usize = 16;
+
pub const HEADER_H: f32 = 0.0;
pub const STATUS_H: f32 = 0.0;
pub const MENUBAR_H: f32 = 0.0;
@@ -320,14 +321,29 @@ pub fn load_fs_tree() -> FsNode {
}
}
+
+
fn default_grid_thickness() -> f32 { 0.03 }
-fn default_show_camera_pivot() -> bool { false }
-fn default_camera_pivot_size() -> f32 { 1.0 }
-fn default_node_color() -> [f32; 3] { [0.10, 0.45, 0.70] }
fn default_grid_color() -> [f32; 3] { [0.35, 0.35, 0.40] }
-fn default_cell_color() -> [f32; 3] { [0.13, 0.13, 0.16] }
-fn default_gap_color() -> [f32; 3] { [0.07, 0.07, 0.09] }
+#[derive(Serialize, Deserialize, Clone, Debug)]
+pub struct ViewportSettings {
+ pub bg_color: [f32; 3],
+ pub square: bool,
+ pub show_camera_pivot_enabled: bool,
+ pub camera_pivot_size: f32,
+}
+
+impl Default for ViewportSettings {
+ fn default() -> Self {
+ Self {
+ bg_color: [0.05, 0.05, 0.10],
+ square: false,
+ show_camera_pivot_enabled: false,
+ camera_pivot_size: 1.0,
+ }
+ }
+}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct DesignSettings {
@@ -339,22 +355,11 @@ pub struct DesignSettings {
pub show_cube_enabled: bool,
pub show_origin_enabled: bool,
pub origin_size: f32,
- pub viewport_bg_color: [f32; 3],
- pub square_viewport: bool,
#[serde(default = "default_grid_thickness")]
pub grid_thickness: f32,
- #[serde(default = "default_show_camera_pivot")]
- pub show_camera_pivot_enabled: bool,
- #[serde(default = "default_camera_pivot_size")]
- pub camera_pivot_size: f32,
- #[serde(default = "default_node_color")]
- pub node_color: [f32; 3],
#[serde(default = "default_grid_color")]
pub grid_color: [f32; 3],
- #[serde(default = "default_cell_color")]
- pub cell_color: [f32; 3],
- #[serde(default = "default_gap_color")]
- pub gap_color: [f32; 3],
+ pub viewport: ViewportSettings,
}
impl Default for DesignSettings {
@@ -368,36 +373,84 @@ impl Default for DesignSettings {
show_cube_enabled: false,
show_origin_enabled: true,
origin_size: 1.0,
- viewport_bg_color: [0.05, 0.05, 0.10],
- square_viewport: false,
- grid_thickness: 0.03,
- show_camera_pivot_enabled: false,
- camera_pivot_size: 1.0,
- node_color: default_node_color(),
+ grid_thickness: default_grid_thickness(),
grid_color: default_grid_color(),
- cell_color: default_cell_color(),
- gap_color: default_gap_color(),
+ viewport: ViewportSettings::default(),
}
}
}
+fn float_array_to_hex(rgb: &[f32; 3]) -> String {
+ let r = (rgb[0] * 255.0).clamp(0.0, 255.0).round() as u8;
+ let g = (rgb[1] * 255.0).clamp(0.0, 255.0).round() as u8;
+ let b = (rgb[2] * 255.0).clamp(0.0, 255.0).round() as u8;
+ format!("#{:02x}{:02x}{:02x}", r, g, b)
+}
+
+fn hex_to_float_array(hex: &str) -> Option<[f32; 3]> {
+ let s = hex.trim_start_matches('#');
+ if s.len() == 6 {
+ let r = u8::from_str_radix(&s[0..2], 16).ok()? as f32 / 255.0;
+ let g = u8::from_str_radix(&s[2..4], 16).ok()? as f32 / 255.0;
+ let b = u8::from_str_radix(&s[4..6], 16).ok()? as f32 / 255.0;
+ Some([r, g, b])
+ } else {
+ None
+ }
+}
+
impl DesignSettings {
fn file_path() -> std::path::PathBuf {
let home = std::env::var("HOME").unwrap_or_else(|_| "/home/lsgalante".to_string());
let mut path = std::path::PathBuf::from(home);
path.push(".config");
path.push("cce");
- path.push("design.json");
+ path.push("cce-designer");
+ path.push("design.kdl");
path
}
fn load() -> Self {
let path = Self::file_path();
- if let Ok(content) = fs::read_to_string(&path) {
- serde_json::from_str::<Self>(&content).unwrap_or_else(|_| Self::default())
- } else {
- Self::default()
+ if path.exists() {
+ if let Ok(content) = fs::read_to_string(&path) {
+ let mut json_val = cce_ui::config::parse_kdl_to_json(&content);
+ // Convert hex strings back to color arrays
+ if let Some(obj) = json_val.as_object_mut() {
+ if let Some(viewport) = obj.get_mut("viewport").and_then(|v| v.as_object_mut()) {
+ if let Some(serde_json::Value::String(hex_str)) = viewport.get("bg_color") {
+ if let Some(arr) = hex_to_float_array(hex_str) {
+ if let Ok(arr_val) = serde_json::to_value(arr) {
+ viewport.insert("bg_color".to_string(), arr_val);
+ }
+ }
+ }
+ }
+ if let Some(serde_json::Value::String(hex_str)) = obj.get("grid_color") {
+ if let Some(arr) = hex_to_float_array(hex_str) {
+ if let Ok(arr_val) = serde_json::to_value(arr) {
+ obj.insert("grid_color".to_string(), arr_val);
+ }
+ }
+ }
+ }
+ return serde_json::from_value::<Self>(json_val).unwrap_or_else(|_| Self::default());
+ }
}
+
+ // Migration fallback: load from design.json, save to design.kdl, and delete the old JSON
+ let mut old_path = path.clone();
+ old_path.set_extension("json");
+ if old_path.exists() {
+ if let Ok(content) = fs::read_to_string(&old_path) {
+ if let Ok(settings) = serde_json::from_str::<Self>(&content) {
+ settings.save();
+ let _ = fs::remove_file(old_path);
+ return settings;
+ }
+ }
+ }
+ Self::default()
}
fn save(&self) {
@@ -405,8 +458,26 @@ impl DesignSettings {
if let Some(parent) = path.parent() {
let _ = fs::create_dir_all(parent);
}
- if let Ok(content) = serde_json::to_string_pretty(self) {
- let _ = fs::write(path, content);
+ if let Ok(mut json_val) = serde_json::to_value(self) {
+ // Convert color arrays to hex strings
+ if let Some(obj) = json_val.as_object_mut() {
+ if let Some(viewport) = obj.get_mut("viewport").and_then(|v| v.as_object_mut()) {
+ if let Some(val) = viewport.get("bg_color") {
+ if let Ok(arr) = serde_json::from_value::<[f32; 3]>(val.clone()) {
+ let hex_str = float_array_to_hex(&arr);
+ viewport.insert("bg_color".to_string(), serde_json::Value::String(hex_str));
+ }
+ }
+ }
+ if let Some(val) = obj.get("grid_color") {
+ if let Ok(arr) = serde_json::from_value::<[f32; 3]>(val.clone()) {
+ let hex_str = float_array_to_hex(&arr);
+ obj.insert("grid_color".to_string(), serde_json::Value::String(hex_str));
+ }
+ }
+ }
+ let kdl_str = cce_ui::config::json_to_kdl_string(&json_val);
+ let _ = fs::write(path, kdl_str);
}
}
}
@@ -441,6 +512,12 @@ impl NodePalette {
impl Element for NodePalette {
fn as_any(&self) -> &dyn std::any::Any { self }
fn as_any_mut(&mut self) -> &mut dyn std::any::Any { self }
+ 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 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] }
@@ -460,7 +537,7 @@ impl Element for NodePalette {
let mut quads = Vec::new();
quads.push((self.x, self.y, self.w, self.h, [0.0, 0.0, 0.0, 0.45]));
let (px, py, pw, ph) = self.panel_rect();
- quads.push((px, py, pw, ph, colors::PANEL_MENU_BG));
+ quads.push((px, py, pw, ph, colors::popover_bg_color()));
quads.push((px + 16.0, py + 48.0, pw - 32.0, 32.0, [0.10, 0.10, 0.14, 1.0]));
let list_y = py + 92.0;
let row_h = 24.0;
@@ -675,6 +752,7 @@ pub struct State {
pub bind_group_pivot: wgpu::BindGroup,
pub uniform_buffer_pivot: wgpu::Buffer,
pub vertex_buffer_3d: wgpu::Buffer,
+ pub vertex_buffer_viewport_bg: wgpu::Buffer,
pub vertex_count_3d: u32,
pub vertex_buffer_spheres: wgpu::Buffer,
pub vertex_count_spheres: u32,
@@ -818,8 +896,6 @@ pub struct State {
pub active_menu_cloud_idx: Option<(usize, usize)>,
pub uniform_background: bool,
pub network_opacity: f32,
- pub cell_opacity: f32,
- pub gap_opacity: f32,
pub last_design_mod_time: Option<std::time::SystemTime>,
pub last_config_mod_time: Option<std::time::SystemTime>,
pub floating_network_layout: (f32, f32, f32, f32),
@@ -968,7 +1044,23 @@ impl State {
- pub fn update_recent_files_layout(&mut self) {}
+ pub fn update_recent_files_layout(&mut self) {
+ let mut opts = vec!["- Select -".to_string()];
+ for path in &self.recent_files {
+ opts.push(path.to_string_lossy().to_string());
+ }
+ opts.push("Other".to_string());
+
+ if let Some(main_node) = self.fs_root.children.iter_mut().find(|c| c.name == "Main") {
+ if let Some(p) = main_node.params.iter_mut().find(|p| p.name == "Open") {
+ p.options = opts;
+ if !p.options.contains(&p.default) {
+ p.default = "- Select -".to_string();
+ }
+ }
+ }
+ self.sync_parameters_pane();
+ }
pub fn save_settings(&mut self) {
let settings = DesignSettings {
@@ -980,15 +1072,14 @@ impl State {
show_cube_enabled: self.show_cube,
show_origin_enabled: self.show_origin,
origin_size: self.origin_size,
- viewport_bg_color: self.viewport_bg_color,
- node_color: self.node_color,
grid_color: self.grid_color,
- cell_color: self.cell_color,
- gap_color: self.gap_color,
- square_viewport: self.square_viewport,
grid_thickness: self.grid_thickness,
- show_camera_pivot_enabled: self.show_camera_pivot,
- camera_pivot_size: self.camera_pivot_size,
+ viewport: ViewportSettings {
+ bg_color: self.viewport_bg_color,
+ square: self.square_viewport,
+ show_camera_pivot_enabled: self.show_camera_pivot,
+ camera_pivot_size: self.camera_pivot_size,
+ },
};
settings.save();
self.last_design_mod_time = {
@@ -1000,7 +1091,8 @@ impl State {
pub fn update_grid_geometry(&mut self) {
- let grid_verts = grid_vertices(self.grid_thickness, self.grid_color);
+ 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.wgpu_adapter.queue.write_buffer(&self.vertex_buffer_grid, 0, bytemuck::cast_slice(&grid_verts));
self.viewport_dirty = true;
}
@@ -1017,6 +1109,21 @@ impl State {
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 = [
+ 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
+ ];
+ self.wgpu_adapter.queue.write_buffer(&self.vertex_buffer_viewport_bg, 0, bytemuck::cast_slice(&bg_verts));
+ self.viewport_dirty = true;
+ }
+
pub fn body_h(&self) -> f32 { self.height - HEADER_H - STATUS_H }
pub fn get_col_geometries(&self) -> (f32, f32, f32, f32, f32, f32) {
@@ -1126,10 +1233,10 @@ impl State {
}
pub fn sync_parameters_to_project(&mut self) {
+ let mut file_to_open = None;
if !self.is_detached_network {
if let Some(slot_idx) = self.graph().selected_node() {
let updated_params = self.param().node_params();
- let mut recent_file_to_open = None;
let dir = self.current_dir_mut();
if let Some(child) = dir.children.get_mut(slot_idx) {
let mut param_changed = false;
@@ -1143,24 +1250,20 @@ impl State {
triggered_buttons.push(p.name.clone());
p.default = "".to_string();
}
- if p.name == "Open Recent" && p.default != "- Select -" && !p.default.is_empty() {
- recent_file_to_open = Some(p.default.clone());
+ if p.name == "Open" && p.default != "- Select -" && !p.default.is_empty() {
+ file_to_open = Some(p.default.clone());
p.default = "- Select -".to_string();
+ triggered_buttons.push("Open".to_string());
}
}
}
}
- if !triggered_buttons.is_empty() || recent_file_to_open.is_some() {
+ if !triggered_buttons.is_empty() {
let mut disp_params = self.param().node_params();
for btn_name in &triggered_buttons {
if let Some(pos) = disp_params.iter().position(|p| p.0 == *btn_name) {
- disp_params[pos].1 = "".to_string();
- }
- }
- if recent_file_to_open.is_some() {
- if let Some(pos) = disp_params.iter().position(|p| p.0 == "Open Recent") {
- disp_params[pos].1 = "- Select -".to_string();
+ disp_params[pos].1 = if btn_name == "Open" { "- Select -".to_string() } else { "".to_string() };
}
}
self.param_mut().set_display_params(&disp_params);
@@ -1423,15 +1526,20 @@ impl State {
}
}
- if let Some(path_str) = recent_file_to_open {
- let path = std::path::PathBuf::from(path_str);
- if let Err(e) = self.load_from_file(&path) {
- eprintln!("Failed to load recent file: {:?}", e);
- self.update_status_text(&format!("Failed to load: {:?}", e));
- } else {
- self.update_status_text(&format!("Loaded project from {}", path.display()));
- self.add_recent_file(path);
- }
+
+ }
+ }
+ if let Some(option_text) = file_to_open {
+ if option_text == "Other" {
+ self.open_file_chooser();
+ } else {
+ let path = std::path::PathBuf::from(option_text);
+ if let Err(e) = self.load_from_file(&path) {
+ eprintln!("Failed to load recent file: {:?}", e);
+ self.update_status_text(&format!("Failed to load: {:?}", e));
+ } else {
+ self.update_status_text(&format!("Loaded project from {}", path.display()));
+ self.add_recent_file(path);
}
}
}
@@ -2130,7 +2238,7 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
window.set_app_id("circular-network-pane");
window.set_min_size(Some((200, 200)));
} else {
- window.set_title("Clear Design Interface");
+ window.set_title("Designer");
window.set_app_id("cce-designer");
window.set_min_size(Some((480, 320)));
}
@@ -2378,7 +2486,8 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
mapped_at_creation: false,
});
- let grid_verts = grid_vertices(settings.grid_thickness, settings.grid_color);
+ let linear_grid_color = cce_ui::colors::to_linear_rgb(settings.grid_color);
+ let grid_verts = grid_vertices(settings.grid_thickness, linear_grid_color);
let vertex_count_grid = grid_verts.len() as u32;
let vertex_buffer_grid = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Grid Vertex Buffer"),
@@ -2394,7 +2503,7 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
});
- let pivot_verts = camera_pivot_vertices(settings.camera_pivot_size);
+ let pivot_verts = camera_pivot_vertices(settings.viewport.camera_pivot_size);
let vertex_count_pivot = pivot_verts.len() as u32;
let vertex_buffer_pivot = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Camera Pivot Vertex Buffer"),
@@ -2402,6 +2511,22 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
});
+ 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 vertex_buffer_viewport_bg = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
+ label: Some("Viewport BG Vertex Buffer"),
+ contents: bytemuck::cast_slice(&bg_verts),
+ usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
+ });
+
let pipeline_3d = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("3D Pipeline"),
layout: Some(&pipeline_layout_3d),
@@ -2636,7 +2761,7 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
"Main Menu".to_string(),
];
let mut widgets: Vec<Box<dyn Element>> = vec![
- Box::new(MenuBar::new(0.0, 0.0, 0.0, HEADER_H).with_title("Clear Design Interface").with_label("Main Menu Bar").with_item("File", &["New Project", "Open", "Save", "Save As", "Exit"]).with_item("Edit", &["Undo", "Redo"]).with_item("View", &["Zoom In", "Zoom Out", "Reset Zoom", "Detach Circular Window", "Show Network Pane", "Show Viewport Pane", "Show Parameters Pane", "Show Spreadsheet Pane"]).with_item("Help", &["About"]).with_z_index(110).with_context_options(context_opts.clone(), 4)),
+ Box::new(MenuBar::new(0.0, 0.0, 0.0, HEADER_H).with_title("Designer").with_label("Main Menu Bar").with_item("File", &["New Project", "Save", "Save As", "Exit"]).with_item("Edit", &["Undo", "Redo"]).with_item("View", &["Zoom In", "Zoom Out", "Reset Zoom", "Detach Circular Window", "Show Network Pane", "Show Viewport Pane", "Show Parameters Pane", "Show Spreadsheet Pane"]).with_item("Help", &["About"]).with_z_index(110).with_context_options(context_opts.clone(), 4)),
Box::new(Graph::new()),
Box::new(Splitter::new(SPLITTER_W)),
Box::new(ViewportBg::new()),
@@ -2644,7 +2769,7 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
Box::new(Plate::new(0.0, 0.0, 0.0, 0.0).with_color(colors::PARAM_BG).with_blur(true)),
Box::new(ParametersBg::new()),
Box::new(Canvas::new()),
- Box::new(MenuBar::new(0.0, 0.0, 0.0, MENUBAR_H).with_title("0: Network").with_label("Network Menu Bar").with_item("File", &["New", "Open", "Save", "Save As"]).with_item("Edit", &["Undo", "Redo"]).with_item("View", &["Zoom In", "Zoom Out", "Circular Pane", "Detach Pane", "Close Pane"]).with_context_options(context_opts.clone(), 0)),
+ Box::new(MenuBar::new(0.0, 0.0, 0.0, MENUBAR_H).with_title("0: Network").with_label("Network Menu Bar").with_item("File", &["New", "Save", "Save As"]).with_item("Edit", &["Undo", "Redo"]).with_item("View", &["Zoom In", "Zoom Out", "Circular Pane", "Detach Pane", "Close Pane"]).with_context_options(context_opts.clone(), 0)),
Box::new(MenuBar::new(0.0, 0.0, 0.0, MENUBAR_H).with_title("1: Viewport").with_label("Viewport Menu Bar").with_item("Camera", &["Perspective", "Orthographic"]).with_item("Display", &["Square Aspect"]).with_item("Guides", &["Show Grid", "Cube", "Origin", "Camera Pivot"]).with_item("View", &["Close Pane"]).with_context_options(context_opts.clone(), 1)),
Box::new(MenuBar::new(0.0, 0.0, 0.0, MENUBAR_H).with_title("2: Parameters").with_label("Parameters Menu Bar").with_item("Preset", &["Default", "Custom"]).with_item("Reset", &["All"]).with_item("View", &["Close Pane"]).with_context_options(context_opts.clone(), 2)),
Box::new(StatusBar::new().with_text("Ready")),
@@ -2653,6 +2778,8 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
Box::new(Spreadsheet::new()),
];
+ let recent_files = Self::load_recent_files();
+
let mut spreadsheet_menubar = MenuBar::new(0.0, 0.0, 0.0, MENUBAR_H).with_title("3: Spreadsheet").with_label("Spreadsheet Menu Bar").with_item("View", &["Close Pane"]).with_context_options(context_opts.clone(), 3);
spreadsheet_menubar.set_visible(false);
widgets.push(Box::new(spreadsheet_menubar));
@@ -2663,7 +2790,6 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
let mut positions = Vec::with_capacity(widgets.len());
positions.resize_with(widgets.len(), || (0.0, 0.0, 0.0, 0.0));
- let recent_files = Self::load_recent_files();
let recent_files_list = ScrollingList::new(22.0, 2.0);
let mut recent_files_buttons = Vec::new();
for file in &recent_files {
@@ -2707,6 +2833,7 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
bind_group_pivot,
uniform_buffer_pivot,
vertex_buffer_3d,
+ vertex_buffer_viewport_bg,
vertex_count_3d: cube_vertices().len() as u32,
vertex_buffer_spheres,
vertex_count_spheres: 0,
@@ -2732,18 +2859,21 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
show_grid: settings.show_grid_enabled,
show_cube: settings.show_cube_enabled,
show_origin: settings.show_origin_enabled,
- show_camera_pivot: settings.show_camera_pivot_enabled,
- viewport_bg_color: settings.viewport_bg_color,
- node_color: settings.node_color,
+ show_camera_pivot: settings.viewport.show_camera_pivot_enabled,
+ viewport_bg_color: settings.viewport.bg_color,
+ node_color: {
+ let nc = cce_ui::color::graph_node_color();
+ [nc[0], nc[1], nc[2]]
+ },
grid_color: settings.grid_color,
- cell_color: settings.cell_color,
- gap_color: settings.gap_color,
+ cell_color: cce_ui::color::graph_cell_color(),
+ gap_color: cce_ui::color::graph_gap_color(),
vertex_buffer_origin,
vertex_count_origin,
vertex_buffer_pivot,
vertex_count_pivot,
origin_size: settings.origin_size,
- camera_pivot_size: settings.camera_pivot_size,
+ camera_pivot_size: settings.viewport.camera_pivot_size,
fs_root: fs_root.clone(),
node_templates,
current_path,
@@ -2788,7 +2918,7 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
physical_width: pw,
physical_height: ph,
scale,
- square_viewport: settings.square_viewport,
+ square_viewport: settings.viewport.square,
grid_snap_enabled: true,
network_grid_visible: true,
grid_size_x: settings.grid_size_x,
@@ -2850,8 +2980,6 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
active_menu_cloud_idx: None,
uniform_background: false,
network_opacity: 0.95,
- cell_opacity: 0.95,
- gap_opacity: 0.95,
last_design_mod_time: {
let design_path = DesignSettings::file_path();
std::fs::metadata(&design_path).and_then(|m| m.modified()).ok()
@@ -2916,9 +3044,9 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
state.update_graph_settings_from_config();
state.update_window_title();
colors::set_node_color([
- settings.node_color[0],
- settings.node_color[1],
- settings.node_color[2],
+ state.node_color[0],
+ state.node_color[1],
+ state.node_color[2],
1.0,
]);
state.ensure_menubar_subnets();
@@ -2982,8 +3110,7 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
graph.set_grid_snap_enabled(grid_snap_enabled);
if let Some(graph) = self.widgets[CONTENT_IDX].as_any_mut().downcast_mut::<cce_ui::widget::Graph>() {
graph.set_uniform_background(self.uniform_background);
- graph.set_cell_opacity(self.cell_opacity);
- graph.set_gap_opacity(self.gap_opacity);
+ graph.set_network_opacity(self.network_opacity);
graph.set_cell_color(self.cell_color);
graph.set_gap_color(self.gap_color);
}
@@ -3008,15 +3135,17 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
if let Ok(content) = std::fs::read_to_string(config_path) {
let val = cce_ui::config::parse_kdl_to_json(&content);
- if let Some(inertial) = val.get("inertial") {
- if let Some(val) = inertial.get("inertial_scroll").and_then(|v| v.as_bool()) {
- enabled = val;
- }
- if let Some(friction_val) = inertial.get("scroll_friction").and_then(|v| v.as_i64()) {
- friction = (friction_val as f32 / 1000.0).clamp(0.1, 0.999);
- }
- if let Some(speed_val) = inertial.get("scroll_speed").and_then(|v| v.as_f64()) {
- speed = speed_val as f32;
+ if let Some(input) = val.get("input") {
+ if let Some(inertial) = input.get("inertial") {
+ if let Some(val) = inertial.get("inertial_scroll").and_then(|v| v.as_bool()) {
+ enabled = val;
+ }
+ if let Some(friction_val) = inertial.get("scroll_friction").and_then(|v| v.as_i64()) {
+ friction = (friction_val as f32 / 1000.0).clamp(0.1, 0.999);
+ }
+ if let Some(speed_val) = inertial.get("scroll_speed").and_then(|v| v.as_f64()) {
+ speed = speed_val as f32;
+ }
}
}
}
@@ -3027,70 +3156,37 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
}
pub fn update_graph_settings_from_config(&mut self) {
- let config_paths = [
- "/home/lsgalante/.config/cce/config.kdl",
- "/home/lsgalante/.config/ccec/config.kdl",
- ];
+ cce_ui::layout::reload_config();
+
+ let opacity = cce_ui::color::graph_opacity();
+ let cell_color = cce_ui::color::graph_cell_color();
+ let gap_color = cce_ui::color::graph_gap_color();
+ let snap_enabled = cce_ui::layout::graph_grid_snap();
+ let node_c = cce_ui::color::graph_node_color();
+ let node_color = [node_c[0], node_c[1], node_c[2]];
- let mut show_grid = None;
- let mut snap_enabled = None;
- let mut uniform_background = None;
- let mut cell_opacity = None;
- let mut gap_opacity = None;
- let mut network_opacity = None;
-
- for path in &config_paths {
- if let Ok(content) = std::fs::read_to_string(path) {
- let val = cce_ui::config::parse_kdl_to_json(&content);
- if let Some(layout) = val.get("layout") {
- show_grid = layout.get("graph_show_grid").and_then(|v| v.as_bool());
- snap_enabled = layout.get("graph_snap_enabled").and_then(|v| v.as_bool());
- uniform_background = layout.get("graph_uniform_background").and_then(|v| v.as_bool());
- network_opacity = layout.get("graph_network_opacity").and_then(|v| v.as_f64()).map(|n| n as f32);
- cell_opacity = layout.get("graph_cell_opacity").and_then(|v| v.as_f64()).map(|n| n as f32).or(network_opacity);
- gap_opacity = layout.get("graph_gap_opacity").and_then(|v| v.as_f64()).map(|n| n as f32).or(network_opacity);
- break;
- }
- }
- }
-
let mut changed = false;
- if let Some(val) = show_grid {
- if self.show_grid != val {
- self.show_grid = val;
- self.menu_mut(RIGHT_MENUBAR_IDX).set_item_checked(2, 0, val);
- changed = true;
- }
+
+ if (self.network_opacity - opacity).abs() > 0.001 {
+ self.network_opacity = opacity;
+ changed = true;
}
- if let Some(val) = snap_enabled {
- if self.grid_snap_enabled != val {
- self.grid_snap_enabled = val;
- changed = true;
- }
+ if self.cell_color != cell_color {
+ self.cell_color = cell_color;
+ changed = true;
}
- if let Some(val) = uniform_background {
- if self.uniform_background != val {
- self.uniform_background = val;
- changed = true;
- }
+ if self.gap_color != gap_color {
+ self.gap_color = gap_color;
+ changed = true;
}
- if let Some(val) = network_opacity {
- if (self.network_opacity - val).abs() > 0.001 {
- self.network_opacity = val;
- changed = true;
- }
+ if self.node_color != node_color {
+ self.node_color = node_color;
+ colors::set_node_color([node_color[0], node_color[1], node_color[2], 1.0]);
+ changed = true;
}
- if let Some(val) = cell_opacity {
- if (self.cell_opacity - val).abs() > 0.001 {
- self.cell_opacity = val;
- changed = true;
- }
- }
- if let Some(val) = gap_opacity {
- if (self.gap_opacity - val).abs() > 0.001 {
- self.gap_opacity = val;
- changed = true;
- }
+ if self.grid_snap_enabled != snap_enabled {
+ self.grid_snap_enabled = snap_enabled;
+ changed = true;
}
if changed {
@@ -4631,6 +4727,7 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
if i == PARAM_IDX {
self.sync_parameters_to_project();
}
+
}
if self.widgets[i].draggable() {
self.widgets[i].set_modifiers(self.modifiers.control_key(), self.modifiers.shift_key(), self.modifiers.alt_key());
@@ -5147,7 +5244,7 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
if Some(mod_time) != self.last_design_mod_time {
self.last_design_mod_time = Some(mod_time);
let settings = DesignSettings::load();
- self.square_viewport = settings.square_viewport;
+ self.square_viewport = settings.viewport.square;
self.grid_size_x = settings.grid_size_x;
self.grid_size_y = settings.grid_size_y;
self.skipped_row_h = settings.skipped_row_h;
@@ -5156,20 +5253,22 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
self.show_grid = settings.show_grid_enabled;
self.show_cube = settings.show_cube_enabled;
self.show_origin = settings.show_origin_enabled;
- self.show_camera_pivot = settings.show_camera_pivot_enabled;
- self.viewport_bg_color = settings.viewport_bg_color;
- self.node_color = settings.node_color;
+ self.show_camera_pivot = settings.viewport.show_camera_pivot_enabled;
+ self.viewport_bg_color = settings.viewport.bg_color;
+ let node_c = cce_ui::color::graph_node_color();
+ self.node_color = [node_c[0], node_c[1], node_c[2]];
self.grid_color = settings.grid_color;
self.origin_size = settings.origin_size;
- self.camera_pivot_size = settings.camera_pivot_size;
- self.cell_color = settings.cell_color;
- self.gap_color = settings.gap_color;
+ self.camera_pivot_size = settings.viewport.camera_pivot_size;
+ self.cell_color = cce_ui::color::graph_cell_color();
+ self.gap_color = cce_ui::color::graph_gap_color();
colors::set_node_color([self.node_color[0], self.node_color[1], self.node_color[2], 1.0]);
self.update_origin_geometry();
self.update_grid_geometry();
self.update_pivot_geometry();
+ self.update_viewport_bg_geometry();
self.sync_grid_settings();
self.upload_vertices();
}
@@ -5525,10 +5624,10 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color {
- r: self.viewport_bg_color[0] as f64,
- g: self.viewport_bg_color[1] as f64,
- b: self.viewport_bg_color[2] as f64,
- a: 1.0,
+ r: 0.0,
+ g: 0.0,
+ b: 0.0,
+ a: 0.0,
}),
store: wgpu::StoreOp::Store,
},
@@ -5548,6 +5647,11 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
pass.set_scissor_rect(sx, sy, cw, ch);
pass.set_pipeline(&self.pipeline_3d);
+ // Draw viewport background quad (rounds corners via shader)
+ pass.set_bind_group(0, &self.bind_group_3d, &[]);
+ pass.set_vertex_buffer(0, self.vertex_buffer_viewport_bg.slice(..));
+ pass.draw(0..6, 0..1);
+
if self.show_grid {
pass.set_bind_group(0, &self.bind_group_grid, &[]);
pass.set_vertex_buffer(0, self.vertex_buffer_grid.slice(..));
@@ -5605,11 +5709,14 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
view: &self.backdrop_texture_view,
resolve_target: None,
ops: wgpu::Operations {
- load: wgpu::LoadOp::Clear(wgpu::Color {
- r: self.viewport_bg_color[0] as f64,
- g: self.viewport_bg_color[1] as f64,
- b: self.viewport_bg_color[2] as f64,
- a: 1.0,
+ load: wgpu::LoadOp::Clear({
+ let linear_bg = cce_ui::colors::to_linear_rgb(self.viewport_bg_color);
+ wgpu::Color {
+ r: linear_bg[0] as f64,
+ g: linear_bg[1] as f64,
+ b: linear_bg[2] as f64,
+ a: 1.0,
+ }
}),
store: wgpu::StoreOp::Store,
},
diff --git a/src/project.rs b/src/project.rs
index 79a5141..a0dae7f 100644
--- a/src/project.rs
+++ b/src/project.rs
@@ -31,7 +31,7 @@ impl State {
let base_title = if self.is_detached_network {
"Network Pane"
} else {
- "Clear Design Interface"
+ "Designer"
};
let mut title = base_title.to_string();
@@ -312,8 +312,10 @@ impl State {
for path in &self.recent_files {
recent_options.push(path.to_string_lossy().to_string());
}
+ recent_options.push("Other".to_string());
let recent_options_refs: Vec<&str> = recent_options.iter().map(|s| s.as_str()).collect();
+
fn find_or_create_subnet<'a>(parent: &'a mut FsNode, name: &str, node_type: &str, pos: (f32, f32)) -> &'a mut FsNode {
if let Some(idx) = parent.children.iter().position(|c| c.name == name) {
let node = &mut parent.children[idx];
@@ -360,15 +362,14 @@ impl State {
ensure_param(main_node, "File", "section", "", &[], None, None, None);
ensure_param(main_node, "New Project", "button", "", &[], None, None, None);
- ensure_param(main_node, "Open", "button", "", &[], None, None, None);
- if let Some(p) = main_node.params.iter_mut().find(|p| p.name == "Open Recent") {
+ if let Some(p) = main_node.params.iter_mut().find(|p| p.name == "Open") {
p.options = recent_options.clone();
if !p.options.contains(&p.default) {
p.default = "- Select -".to_string();
}
} else {
- ensure_param(main_node, "Open Recent", "choice", "- Select -", &recent_options_refs, None, None, None);
+ ensure_param(main_node, "Open", "choice", "- Select -", &recent_options_refs, None, None, None);
}
ensure_param(main_node, "Save", "button", "", &[], None, None, None);
diff --git a/src/render.rs b/src/render.rs
index f61830d..72eab1f 100644
--- a/src/render.rs
+++ b/src/render.rs
@@ -22,6 +22,7 @@ use crate::geometry::{
use cce_ui::engine::{
push_widget_vertices, push_extra_quad_vertices,
push_extra_quad_vertices_clipped, push_arc_background_vertices,
+ push_plate_solid_border_vertices,
};
impl State {
@@ -204,10 +205,24 @@ impl State {
let thickness = 2.0;
let mut color = colors::highlight_primary_color();
color[3] = 0.9;
- push_extra_quad_vertices_clipped(w.as_ref(), cx, cy, cw, thickness, sw, sh, color, clip, active_clip_circle, verts);
- push_extra_quad_vertices_clipped(w.as_ref(), cx, cy + ch - thickness, cw, thickness, sw, sh, color, clip, active_clip_circle, verts);
- push_extra_quad_vertices_clipped(w.as_ref(), cx, cy + thickness, thickness, ch - thickness * 2.0, sw, sh, color, clip, active_clip_circle, verts);
- push_extra_quad_vertices_clipped(w.as_ref(), cx + cw - thickness, cy + thickness, thickness, ch - thickness * 2.0, sw, sh, color, clip, active_clip_circle, verts);
+ if self.graph().is_node_rect(cx, cy, cw, ch) {
+ let r = cce_ui::layout::graph_node_corner_radius();
+ let radii = cce_ui::widget::CornerRadii::new(r, r, r, r);
+ push_plate_solid_border_vertices(
+ cx, cy, cw, ch,
+ radii,
+ thickness,
+ sw, sh,
+ color,
+ active_clip_circle,
+ verts,
+ );
+ } else {
+ push_extra_quad_vertices_clipped(w.as_ref(), cx, cy, cw, thickness, sw, sh, color, clip, active_clip_circle, verts);
+ push_extra_quad_vertices_clipped(w.as_ref(), cx, cy + ch - thickness, cw, thickness, sw, sh, color, clip, active_clip_circle, verts);
+ push_extra_quad_vertices_clipped(w.as_ref(), cx, cy + thickness, thickness, ch - thickness * 2.0, sw, sh, color, clip, active_clip_circle, verts);
+ push_extra_quad_vertices_clipped(w.as_ref(), cx + cw - thickness, cy + thickness, thickness, ch - thickness * 2.0, sw, sh, color, clip, active_clip_circle, verts);
+ }
}
} else if idx == LEFT_MENUBAR_IDX && self.circular_network_pane {
let cx = self.circular_network_layout.x;
@@ -479,8 +494,6 @@ impl State {
}
// 2. Destructure self
- let splitter1_x = self.splitter_layout.splitter1_x;
- let height = self.height;
let sw = self.width;
let sh = self.height;
let circular_network_pane = self.circular_network_pane;
@@ -616,6 +629,7 @@ impl State {
let mut legacy_buffers: Vec<&Buffer> = Vec::new();
let mut legacy_labels: Vec<TextLabel> = Vec::new();
let mut legacy_bounds: Vec<TextBounds> = Vec::new();
+ let mut legacy_is_network: Vec<bool> = Vec::new();
let mut curved_labels = Vec::new();
@@ -631,7 +645,6 @@ impl State {
let is_network_part = i == CONTENT_IDX || i == LEFT_MENUBAR_IDX || i == BREADCRUMB_IDX || i == NETWORK_PANEL_IDX;
let mut bounds = if is_node {
- let node_area_y = self.positions[CONTENT_IDX].1;
if circular_network_pane {
TextBounds {
left: ((network_circle_x - network_circle_radius) * s) as i32,
@@ -640,11 +653,12 @@ impl State {
bottom: (((network_circle_y + network_circle_radius) * s) as i32).max(0),
}
} else {
+ let (gx, gy, gw, gh) = self.positions[CONTENT_IDX];
TextBounds {
- left: 0,
- top: (node_area_y * s) as i32,
- right: (splitter1_x * s) as i32,
- bottom: (((height - STATUS_H) * s) as i32).max(0),
+ left: (gx * s) as i32,
+ top: (gy * s) as i32,
+ right: ((gx + gw) * s) as i32,
+ bottom: (((gy + gh) * s) as i32).max(0),
}
}
} else {
@@ -698,13 +712,24 @@ impl State {
}
}
+ let mut final_color = color;
+ if is_network_part {
+ let val = color.0;
+ let a = (val >> 24) & 0xFF;
+ let r = (val >> 16) & 0xFF;
+ let g = (val >> 8) & 0xFF;
+ let b = val & 0xFF;
+ let new_a = (a as f32 * self.network_opacity).clamp(0.0, 255.0) as u8;
+ final_color = glyphon::Color::rgba(r as u8, g as u8, b as u8, new_a);
+ }
+
areas.push(TextArea {
buffer: buf,
left: (x * s).round(),
top: (y * s).round(),
scale: s,
bounds,
- default_color: color,
+ default_color: final_color,
custom_glyphs: &[],
});
}
@@ -752,20 +777,26 @@ impl State {
legacy_buffers.push(buf_ref);
legacy_labels.push(label);
legacy_bounds.push(item_bounds);
+ legacy_is_network.push(is_network_part);
}
}
}
}
// Add the legacy buffered items
- for ((buf, label), bounds) in legacy_buffers.iter().zip(legacy_labels.iter()).zip(legacy_bounds.iter()) {
+ for (((buf, label), bounds), is_net) in legacy_buffers.iter()
+ .zip(legacy_labels.iter())
+ .zip(legacy_bounds.iter())
+ .zip(legacy_is_network.iter())
+ {
+ let alpha = if *is_net { (self.network_opacity * 255.0).clamp(0.0, 255.0) as u8 } else { 255 };
areas.push(TextArea {
buffer: *buf,
left: (label.x * s).round(),
top: (label.y * s).round(),
scale: s,
bounds: *bounds,
- default_color: glyphon::Color::rgb(label.color[0], label.color[1], label.color[2]),
+ default_color: glyphon::Color::rgba(label.color[0], label.color[1], label.color[2], alpha),
custom_glyphs: &[],
});
}
diff --git a/src/shader_3d.wgsl b/src/shader_3d.wgsl
index 6ac7527..193dcda 100644
--- a/src/shader_3d.wgsl
+++ b/src/shader_3d.wgsl
@@ -54,7 +54,11 @@ fn vs_main(
@location(1) color: vec3f,
) -> VertexOutput {
var out: VertexOutput;
- out.position = uniforms.mvp * vec4f(position, 1.0);
+ if (abs(position.z - 9.99) < 0.01) {
+ out.position = vec4f(position.xy, 0.9999, 1.0);
+ } else {
+ out.position = uniforms.mvp * vec4f(position, 1.0);
+ }
out.color = color;
return out;
}