forked from 1b-agent/opencadstudio-upstream
Merge pull request #195 from KevinGriffin-new/feat/persist-background
feat(background): persist viewport background across sessions (#188)
This commit is contained in:
4 files changed
+110
-5
No files matched your search
+14
-4
@@ -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");
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
}
|
||||
Reference in new issue
Block a user