Merge pull request #195 from KevinGriffin-new/feat/persist-background

feat(background): persist viewport background across sessions (#188)
This commit is contained in:
Hakan Seven authored and GitHub committed 2026-06-26 20:42:38 +03:00
commit f45d85c7ec
4 files changed
+110 -5

No files matched your search

+14 -4
View File
@@ -108,6 +108,9 @@ impl OpenCADStudio {
// ── Background color ───────────────────────────────────────────
// Usage: BACKGROUND <r> <g> <b> (0–255 each)
// BACKGROUND RESET (restore default)
// The chosen colour is also stored as the persisted default
// (`default_bg_color` / `default_paper_bg_color`) so it survives
// restarts and applies to new drawings (#188).
cmd if cmd == "BACKGROUND" || cmd.starts_with("BACKGROUND ") => {
let args = cmd.split_whitespace().skip(1).collect::<Vec<_>>();
let is_paper = self.tabs[i].scene.current_layout != "Model";
@@ -119,9 +122,11 @@ impl OpenCADStudio {
if is_paper {
self.tabs[i].paper_bg_color = None;
self.tabs[i].scene.paper_bg_color = [1.0, 1.0, 1.0, 1.0];
self.default_paper_bg_color = None;
} else {
self.tabs[i].bg_color = None;
self.tabs[i].scene.bg_color = [0.11, 0.11, 0.11, 1.0];
self.default_bg_color = None;
}
// Wire colour adaptation (`adapt_to_bg`) reads the bg
// at tessellation time, so the cached wires need to
@@ -139,12 +144,15 @@ impl OpenCADStudio {
let r = args[0].parse::<u8>().unwrap_or(0) as f32 / 255.0;
let g = args[1].parse::<u8>().unwrap_or(0) as f32 / 255.0;
let b = args[2].parse::<u8>().unwrap_or(0) as f32 / 255.0;
let rgba = [r, g, b, 1.0];
if is_paper {
self.tabs[i].paper_bg_color = Some([r, g, b, 1.0]);
self.tabs[i].scene.paper_bg_color = [r, g, b, 1.0];
self.tabs[i].paper_bg_color = Some(rgba);
self.tabs[i].scene.paper_bg_color = rgba;
self.default_paper_bg_color = Some(rgba);
} else {
self.tabs[i].bg_color = Some([r, g, b, 1.0]);
self.tabs[i].scene.bg_color = [r, g, b, 1.0];
self.tabs[i].bg_color = Some(rgba);
self.tabs[i].scene.bg_color = rgba;
self.default_bg_color = Some(rgba);
}
self.tabs[i].scene.recolor_meshes();
self.tabs[i].scene.bump_geometry();
@@ -152,6 +160,8 @@ impl OpenCADStudio {
"Background: rgb({}, {}, {})",
args[0], args[1], args[2]
));
// Persisted centrally after this message via
// `persist_settings_if_changed()`.
} else {
self.command_line
.push_info("Usage: BACKGROUND <r> <g> <b> (0–255) | BACKGROUND RESET");
+8
View File
@@ -245,6 +245,12 @@ pub(super) struct OpenCADStudio {
dyn_input: bool,
/// Controls whether the TEXTEDIT command repeats automatically (0 = Multiple, 1 = Single).
pub texteditmode: bool,
/// Persisted default viewport background, restored from settings and applied
/// to every drawing tab (new and opened) so a chosen background survives
/// restarts (#188). `None` = the built-in dark-grey / off-white defaults.
/// The `a` channel is always 1.0.
default_bg_color: Option<[f32; 4]>,
default_paper_bg_color: Option<[f32; 4]>,
/// `true` after a bare `VPORTS` in model space — the next command-line
/// entry is treated as the tiled-config option (SIngle/2H/2V/4).
awaiting_vports: bool,
@@ -1770,6 +1776,8 @@ impl OpenCADStudio {
show_grid: false,
dyn_input: true,
texteditmode: false,
default_bg_color: None,
default_paper_bg_color: None,
awaiting_vports: false,
tile_drag: None,
dyn_user_reshaped: false,
+32 -1
View File
@@ -58,6 +58,25 @@ fn snap_from_id(s: &str) -> Option<SnapType> {
SNAP_ORDER.iter().copied().find(|t| snap_id(*t) == s)
}
/// Parse a persisted `r,g,b` background triplet (each 0–255). Returns `None`
/// for an empty or malformed value so a missing/garbage key falls back to the
/// app default rather than a wrong colour.
fn parse_rgb(val: &str) -> Option<[u8; 3]> {
let mut it = val.split(',').map(|t| t.trim().parse::<u8>());
let r = it.next()?.ok()?;
let g = it.next()?.ok()?;
let b = it.next()?.ok()?;
if it.next().is_some() {
return None;
}
Some([r, g, b])
}
/// Serialize an optional background triplet back to `r,g,b`, or empty when unset.
fn rgb_to_str(c: Option<[u8; 3]>) -> String {
c.map(|[r, g, b]| format!("{r},{g},{b}")).unwrap_or_default()
}
/// A snapshot of the persisted preferences. Field defaults mirror the app's
/// in-code defaults so a missing key restores the same value the app boots
/// with.
@@ -84,6 +103,12 @@ pub struct UserSettings {
pub plugin_repos: Vec<String>,
/// Controls whether the TEXTEDIT command repeats automatically (0 = Multiple, 1 = Single).
pub texteditmode: bool,
/// Persisted viewport background colours (0–255 RGB); `None` = app default
/// (dark grey model / off-white paper). Applied to every drawing tab on
/// launch and to tabs opened later, so a chosen background survives restarts
/// (#188).
pub bg_color: Option<[u8; 3]>,
pub paper_bg_color: Option<[u8; 3]>,
}
impl Default for UserSettings {
@@ -108,6 +133,8 @@ impl Default for UserSettings {
disabled_plugins: Vec::new(),
plugin_repos: Vec::new(),
texteditmode: false,
bg_color: None,
paper_bg_color: None,
}
}
}
@@ -140,6 +167,8 @@ impl UserSettings {
}
"osnap" => s.snap_enabled = val == "1",
"otrack" => s.otrack = val == "1",
"bg_color" => s.bg_color = parse_rgb(val),
"paper_bg_color" => s.paper_bg_color = parse_rgb(val),
"default_assoc_prompted" => s.default_assoc_prompted = val == "1",
"texteditmode" => {
if let Some(v) =
@@ -189,7 +218,7 @@ impl UserSettings {
.collect::<Vec<_>>()
.join(",");
let body = format!(
"dyn={}\northo={}\npolar={}\npolar_increment_deg={}\nosnap={}\notrack={}\ndefault_assoc_prompted={}\nsnap_modes={}\ndisabled_plugins={}\nplugin_repos={}\ntexteditmode={}\n",
"dyn={}\northo={}\npolar={}\npolar_increment_deg={}\nosnap={}\notrack={}\ndefault_assoc_prompted={}\nsnap_modes={}\ndisabled_plugins={}\nplugin_repos={}\ntexteditmode={}\nbg_color={}\npaper_bg_color={}\n",
b(self.dyn_input),
b(self.ortho),
b(self.polar),
@@ -201,6 +230,8 @@ impl UserSettings {
self.disabled_plugins.join(","),
self.plugin_repos.join(","),
self.texteditmode,
rgb_to_str(self.bg_color),
rgb_to_str(self.paper_bg_color),
);
let _ = std::fs::write(path, body);
}
+56
View File
@@ -196,6 +196,8 @@ impl OpenCADStudio {
},
plugin_repos: self.plugin_repos.clone(),
texteditmode: self.texteditmode,
bg_color: self.default_bg_color.map(f4_to_u3),
paper_bg_color: self.default_paper_bg_color.map(f4_to_u3),
}
}
@@ -212,9 +214,42 @@ impl OpenCADStudio {
self.disabled_plugins = s.disabled_plugins.iter().cloned().collect();
self.plugin_repos = s.plugin_repos.clone();
self.texteditmode = s.texteditmode;
self.default_bg_color = s.bg_color.map(u3_to_f4);
self.default_paper_bg_color = s.paper_bg_color.map(u3_to_f4);
// Push the restored background onto every drawing tab that exists now
// (the start tab and any initial drawing). Tabs created later pick it
// up via `apply_bg_default` at their construction site.
for idx in 0..self.tabs.len() {
self.apply_bg_default(idx);
}
self.rebuild_ribbon_modules();
}
/// Apply the persisted default background(s) to tab `idx`. No-op for the
/// start tab or when no default is set. Refreshes the tab's cached wires
/// and meshes so background-adaptive colours pick up the change.
pub(super) fn apply_bg_default(&mut self, idx: usize) {
let bg = self.default_bg_color;
let paper_bg = self.default_paper_bg_color;
if bg.is_none() && paper_bg.is_none() {
return;
}
let tab = &mut self.tabs[idx];
if tab.is_start {
return;
}
if let Some(c) = bg {
tab.bg_color = Some(c);
tab.scene.bg_color = c;
}
if let Some(c) = paper_bg {
tab.paper_bg_color = Some(c);
tab.scene.paper_bg_color = c;
}
tab.scene.recolor_meshes();
tab.scene.bump_geometry();
}
/// Check if a suspended command exists on the active tab and resume it
/// with the outcome of the text editor.
pub(super) fn post_editor_closed(&mut self, committed: bool) -> Task<Message> {
@@ -555,6 +590,7 @@ impl OpenCADStudio {
self.tabs.push(new_tab);
let idx = self.tabs.len() - 1;
self.active_tab = idx;
self.apply_bg_default(idx);
idx
};
@@ -1346,6 +1382,8 @@ impl OpenCADStudio {
let new_tab = super::document::DocumentTab::new_drawing(self.tab_counter);
self.tabs.push(new_tab);
self.active_tab = self.tabs.len() - 1;
let idx = self.active_tab;
self.apply_bg_default(idx);
self.sync_ribbon_layers();
self.sync_ribbon_styles();
// #21: reset ribbon Color / Linetype / Lineweight to the
@@ -1397,6 +1435,7 @@ impl OpenCADStudio {
self.tab_counter += 1;
self.tabs[0] = super::document::DocumentTab::new_drawing(self.tab_counter);
self.active_tab = 0;
self.apply_bg_default(0);
} else {
self.tabs.remove(idx);
if self.active_tab >= self.tabs.len() {
@@ -6474,6 +6513,7 @@ impl OpenCADStudio {
self.tabs[0] =
super::document::DocumentTab::new_drawing(self.tab_counter);
self.active_tab = 0;
self.apply_bg_default(0);
} else {
self.tabs.remove(idx);
if self.active_tab >= self.tabs.len() {
@@ -9591,3 +9631,19 @@ fn parse_plot_scale(s: &str) -> (f64, f64) {
}
(1.0, 1.0)
}
/// Convert an internal `[r,g,b,a]` colour (0.0–1.0) to a persisted 0–255 RGB
/// triplet, dropping alpha (backgrounds are always opaque).
fn f4_to_u3([r, g, b, _]: [f32; 4]) -> [u8; 3] {
[
(r * 255.0).round().clamp(0.0, 255.0) as u8,
(g * 255.0).round().clamp(0.0, 255.0) as u8,
(b * 255.0).round().clamp(0.0, 255.0) as u8,
]
}
/// Inverse of [`f4_to_u3`]: a persisted 0–255 RGB triplet back to an opaque
/// `[r,g,b,a]` colour.
fn u3_to_f4([r, g, b]: [u8; 3]) -> [f32; 4] {
[r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0, 1.0]
}