feat(model-space): theme-aware model space canvas and CAD selection/grip sysvars

Model space appearance now follows the active UI theme instead of a
hardcoded dark background. Canvas mode is user-selectable (Match Theme /
Classic CAD Dark / Custom RGB), with matching controls for the paper
sheet and desk surround backgrounds and grid opacity.
Implements industry-standard CAD system variables, all settable via
command name, SETVAR, and the Options dialog:
- COLORTHEME (0/1 dark/light UI scheme, preserves custom palettes)
- COLORSCHEME (interactive named-theme selection)
- BACKGROUND (THEME/CLASSIC/DEFAULT/DESK presets, RGB triplets)
- SELECTIONAREA, SELECTIONAREAOPACITY (translucent marquee fill)
- WINDOWSAREACOLOR/WINDOWAREACOLOR, CROSSINGAREACOLOR (ACI or theme)
- SELECTIONEFFECT, SELECTIONEFFECTCOLOR (GPU highlight pass, driven by
  selection_generation invalidation — no retessellation)
- SELECTIONPREVIEW (bit 0 = idle rollover, bit 1 = in-command hover)
- GRIPSIZE, GRIPCOLOR, GRIPHOT, GRIPHOVER (grip overlay styling)
This commit is contained in:
Karim Jerbi committed 2026-09-03 17:44:44 +01:00
1 parent 89b1a22850
commit 688785a6cf
18 files changed
+1949 -316

No files matched your search

+55 -4
View File
@@ -393,8 +393,8 @@ impl OpenCADStudio {
pick_add: self.pick_add,
pick_drag_rect: self.pick_drag_rect,
quick_properties: self.quick_properties,
bg_color: self.default_bg_color.map(f4_to_u3),
paper_bg_color: self.default_paper_bg_color.map(f4_to_u3),
bg_color: None,
paper_bg_color: None,
language: self.language,
cliprompt_lines: crate::app::settings::clamp_clipromptlines(self.cliprompt_lines),
block_mru: self.block_mru.clone(),
@@ -455,8 +455,7 @@ impl OpenCADStudio {
self.pick_add = s.pick_add;
self.pick_drag_rect = s.pick_drag_rect;
self.quick_properties = s.quick_properties;
self.default_bg_color = s.bg_color.map(u3_to_f4);
self.default_paper_bg_color = s.paper_bg_color.map(u3_to_f4);
// Legacy settings.bg_color / paper_bg_color are superseded by model_space config.
if crate::i18n::set_language(s.language).is_ok() {
self.language = s.language;
}
@@ -775,6 +774,7 @@ impl OpenCADStudio {
.map(|(key, command)| (key.clone(), command.clone()))
.collect(),
},
model_space: self.model_space.clone(),
}
}
@@ -784,6 +784,23 @@ impl OpenCADStudio {
self.ui_theme = cfg.theme.clone();
self.active_theme = self.ui_theme.to_iced();
self.theme_color_inputs = self.ui_theme.palette.hex_values();
self.model_space = cfg.model_space;
self.model_bg_input = self
.model_space
.custom_bg
.map(crate::app::config::rgb_to_hex)
.unwrap_or_default();
self.paper_bg_input = self
.model_space
.custom_paper_bg
.map(crate::app::config::rgb_to_hex)
.unwrap_or_default();
self.desk_bg_input = self
.model_space
.custom_desk_bg
.map(crate::app::config::rgb_to_hex)
.unwrap_or_default();
self.sync_model_space_theme(false);
self.recent_files = cfg
.recent
.files
@@ -831,6 +848,40 @@ impl OpenCADStudio {
}
}
/// Sync the active theme and model space configuration into all open tabs
/// and the application default background settings.
pub(in crate::app) fn sync_model_space_theme(&mut self, geometry_changed: bool) {
let model_bg = self.model_space.resolve_model_bg(&self.active_theme);
let paper_bg = self.model_space.resolve_paper_bg();
let sel_color = self.model_space.resolve_selection_color();
let sel_effect = self.model_space.selection_effect;
self.default_bg_color = Some(model_bg);
self.default_paper_bg_color = Some(paper_bg);
for tab in &mut self.tabs {
let old_bg = tab.scene.bg_color;
let old_paper = tab.scene.paper_bg_color;
let old_sel_color = tab.scene.selection_color;
let old_sel_effect = tab.scene.selection_effect;
tab.scene.bg_color = model_bg;
tab.scene.paper_bg_color = paper_bg;
tab.scene.selection_color = sel_color;
tab.scene.selection_effect = sel_effect;
tab.bg_color = Some(model_bg);
tab.paper_bg_color = Some(paper_bg);
if old_sel_color != sel_color || old_sel_effect != sel_effect {
tab.scene.selection_generation = tab.scene.selection_generation.wrapping_add(1);
}
if geometry_changed || old_bg != model_bg || old_paper != paper_bg {
tab.scene.recolor_meshes();
tab.scene.bump_geometry();
}
tab.scene.request_refresh(crate::scene::ViewportRefreshScope::All);
}
}
/// Record that the one-time default-association prompt has been answered and
/// flush it to disk, so the dialog never reappears on later launches.
pub(in crate::app) fn mark_assoc_prompted(&mut self) {