forked from 1b-agent/opencadstudio-upstream
Add named parameters, UI scale setting, OPTIONS/OP dispatch, and help manual
Named parameters (docs/named_parameters_design.md, stages 3-4): - ParameterTable (src/scene/named_parameters.rs): a document-wide table of named formulas, each resolved through a shared expression evaluator with cross-parameter references, cycle detection, and division-by-zero/parse error reporting. SketchConstraint::driving_param becomes DrivingValue (Literal | Named) instead of a bare f64, resolved at solve time by sketch_solve::build_constraint via Scene::named_parameters. - Persistence (src/scene/named_parameters_persist.rs): same lazy XRecord model sketch_persist.rs already established, synced around open/save. Bumps the sketch-constraint XRecord's own format version (1 -> 2) since DrivingValue's shape doesn't line up with a version-1 blob's bytes. - PARAMETERS command and its editor (src/ui/window/named_parameters.rs): add/remove/edit rows, live validation, duplicate-name detection: Apply defines the table and re-solves every constraint scope with a named-parameter-driven constraint (command_driver.rs). Distance/Angle constraint prompts (modules/draw/constrain/value.rs) now accept either a literal or an existing parameter's name, mirroring how the constraint's own value prompt already worked. - Undo scope-down carried over from this project's own established pattern (constraint-system stage 9): the geometry an Apply moves is one undo step, but the parameter table edit itself has no undo entry yet — a documented follow-up, not a silent gap. UI scale setting (src/app/settings.rs, app/mod.rs, ui/window/options.rs): a whole-interface scale percentage (50-200%), wired directly into iced's own per-window scale_factor hook rather than per-panel font-size plumbing, so it applies immediately to every panel, dialog, and the ribbon at once. Persisted alongside the existing lineweight-display-scale setting. OPTIONS/OP command dispatch (app/commands/view.rs, app/commands/mod.rs): OPTIONS was registered for autocomplete but had no dispatch arm — the Start-tab welcome screen's own button was the only way to reach it. Found live-testing the UI-scale setting, unrelated to that feature itself. "OP" is AutoCAD's own short alias. help/: a new self-contained static user-help manual (manual.md as the authored source, tools/build_help.py splits it into the committed static HTML pages plus search index) — not yet wired into the application. 64 new/updated tests across the named-parameters and constraint-prompt changes. Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
1 parent
e2d39b049d
commit
7cddf31bcb
47 files changed
+4136
-52
No files matched your search
@@ -302,6 +302,7 @@ impl OpenCADStudio {
|
||||
let i = self.active_tab;
|
||||
self.tabs[i].scene.document = acadrust::CadDocument::new();
|
||||
self.tabs[i].scene.sketch_constraints.clear();
|
||||
self.tabs[i].scene.named_parameters = crate::scene::named_parameters::ParameterTable::new();
|
||||
self.tabs[i].scene.deselect_all();
|
||||
self.tabs[i].current_path = None;
|
||||
// The headless session starts on the welcome (Start) tab, which
|
||||
@@ -331,6 +332,7 @@ impl OpenCADStudio {
|
||||
let i = self.active_tab;
|
||||
self.tabs[i].scene.document = doc;
|
||||
self.tabs[i].scene.load_sketch_constraints_from_document();
|
||||
self.tabs[i].scene.load_named_parameters_from_document();
|
||||
self.tabs[i].scene.deselect_all();
|
||||
crate::app::style_ops::ensure_standard_styles(
|
||||
&mut self.tabs[i].scene.document,
|
||||
@@ -1063,6 +1065,7 @@ mod tests {
|
||||
"CHANGELOG",
|
||||
"CUI",
|
||||
"ALIASEDIT",
|
||||
"OPTIONS",
|
||||
];
|
||||
for cmd in standalone {
|
||||
assert!(
|
||||
|
||||
@@ -951,6 +951,81 @@ impl OpenCADStudio {
|
||||
self.push_sketch_constraints_history(i, label, scope, constraints_before);
|
||||
}
|
||||
|
||||
/// PARAMETERS' Apply button (`docs/named_parameters_design.md` stage 4):
|
||||
/// rebuilds the active tab's `Scene::named_parameters` from the
|
||||
/// editor's working buffer, reports any row that failed to validate via
|
||||
/// the command line, and re-solves every scope with a constraint driven
|
||||
/// by a named parameter — the entire point of a *named* parameter is
|
||||
/// that editing it ripples to whatever references it, the same way
|
||||
/// editing a constraint's literal value already does.
|
||||
///
|
||||
/// **Deliberate scope-down, matching this project's established
|
||||
/// treatment of undo-granularity questions** (design doc §5 open
|
||||
/// question 3, and the sibling constraint-system project's stage 9): the
|
||||
/// geometry this ripple moves is bracketed into one undo step below
|
||||
/// (same `begin_undo`/`bump_entities`/`commit_undo_delta` pattern
|
||||
/// `resolve_one_sketch_conflict` above uses), but the parameter *table*
|
||||
/// edit itself has no undo entry of its own yet — that would need a new
|
||||
/// `HistorySnapshot` variant mirroring `SketchConstraints`
|
||||
/// (`push_sketch_constraints_history`). Left for a follow-up, not
|
||||
/// silently dropped: undoing after an Apply undoes the geometry it
|
||||
/// moved, not the parameter values themselves.
|
||||
pub(super) fn apply_named_parameter_editor_rows(&mut self) {
|
||||
let i = self.active_tab;
|
||||
// A name shared by more than one row can't be resolved by picking
|
||||
// one arbitrarily (`ParameterTable::set` would just let the last
|
||||
// one silently win) -- refuse every row sharing that name up front,
|
||||
// same as `named_parameters::preview`'s live check.
|
||||
let duplicates = crate::ui::window::named_parameters::duplicate_name_rows(&self.named_parameter_editor_rows);
|
||||
let mut table = crate::scene::named_parameters::ParameterTable::new();
|
||||
let mut failed = 0usize;
|
||||
for (idx, row) in self.named_parameter_editor_rows.iter().enumerate() {
|
||||
let name = row.name.trim();
|
||||
if name.is_empty() || duplicates.contains(&idx) {
|
||||
continue;
|
||||
}
|
||||
if let Err(e) = table.set(name, row.formula.trim()) {
|
||||
self.command_line.push_error(crate::tf!("Parameter '{}': {}", name, e).as_ref());
|
||||
failed += 1;
|
||||
}
|
||||
}
|
||||
if !duplicates.is_empty() {
|
||||
self.command_line.push_error(crate::tf!("{} row(s) skipped: duplicate parameter name.", duplicates.len()).as_ref());
|
||||
failed += duplicates.len();
|
||||
}
|
||||
let param_count = table.len();
|
||||
self.tabs[i].scene.named_parameters = table;
|
||||
|
||||
// The whole table just changed, not one isolated value, so there is
|
||||
// no cheaper "which handles actually moved" test worth doing here —
|
||||
// re-solve every scope with a constraint driven by *any* named
|
||||
// parameter (mirrors `solve_scope`'s own "full rebuild, not
|
||||
// incremental" philosophy).
|
||||
let touched: Vec<Handle> = self.tabs[i]
|
||||
.scene
|
||||
.sketch_constraints
|
||||
.iter()
|
||||
.flat_map(|set| set.constraints.iter())
|
||||
.filter(|c| c.enabled && matches!(c.driving_param, Some(crate::scene::named_parameters::DrivingValue::Named(_))))
|
||||
.flat_map(|c| c.refs.iter().map(|r| r.entity))
|
||||
.collect();
|
||||
|
||||
if !touched.is_empty() {
|
||||
let pending = self.begin_undo(i, "Apply named parameters", touched.len(), true);
|
||||
let changes: Vec<(Handle, crate::scene::ChangeKind)> =
|
||||
touched.into_iter().map(|h| (h, crate::scene::ChangeKind::Modified)).collect();
|
||||
self.tabs[i].scene.bump_entities(&changes);
|
||||
if let Some(pd) = pending {
|
||||
self.commit_undo_delta(i, pd);
|
||||
}
|
||||
}
|
||||
|
||||
self.tabs[i].dirty = true;
|
||||
if failed == 0 {
|
||||
self.command_line.push_info(crate::tf!("{} parameter(s) applied.", param_count).as_ref());
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_cmd_result_inner(&mut self, result: CmdResult) -> Task<Message> {
|
||||
let i = self.active_tab;
|
||||
let preserve_commit_layer = self.tabs[i]
|
||||
@@ -6075,6 +6150,93 @@ mod sketch_constraint_undo_tests {
|
||||
assert_eq!(set.constraints.len(), 2, "undo should restore the removed constraint");
|
||||
}
|
||||
|
||||
/// `named_parameters_design.md` stage 4: applying the PARAMETERS editor's
|
||||
/// working buffer both defines the parameter and re-solves whatever
|
||||
/// constraint already references it by name — the entire point of a
|
||||
/// *named* parameter, not just data plumbing.
|
||||
#[test]
|
||||
fn applying_named_parameter_rows_defines_and_resolves_referencing_constraints() {
|
||||
let mut app = OpenCADStudio::new_for_test();
|
||||
let _ = app.automation_op(r#"{"op":"new"}"#);
|
||||
let line = add_line(&mut app, 0.0, 0.0, 10.0, 0.0);
|
||||
|
||||
// The constraint is added *before* the parameter exists -- allowed,
|
||||
// same as `ParameterTable::set`'s own forward-reference tolerance;
|
||||
// it just doesn't resolve to anything until the parameter is defined.
|
||||
let _ = app.apply_cmd_result(CmdResult::AddSketchConstraint {
|
||||
kind: ConstraintKind::Distance,
|
||||
refs: vec![SketchRef::point(line, 0), SketchRef::point(line, 1)],
|
||||
driving_param: Some(crate::scene::named_parameters::DrivingValue::Named("target_len".to_string())),
|
||||
label: "Distance constraint",
|
||||
});
|
||||
|
||||
app.named_parameter_editor_rows =
|
||||
vec![crate::ui::window::named_parameters::ParamEditorRow { name: "target_len".to_string(), formula: "8".to_string() }];
|
||||
app.apply_named_parameter_editor_rows();
|
||||
|
||||
assert_eq!(app.tabs[app.active_tab].scene.named_parameters().resolve("target_len"), Ok(8.0));
|
||||
let (start, end) = match app.tabs[app.active_tab].scene.document.get_entity(line) {
|
||||
Some(acadrust::EntityType::Line(l)) => (l.start, l.end),
|
||||
other => panic!("expected a Line, got {other:?}"),
|
||||
};
|
||||
let len = ((end.x - start.x).powi(2) + (end.y - start.y).powi(2)).sqrt();
|
||||
assert!((len - 8.0).abs() < 1e-6, "length should track the applied parameter, got {len}");
|
||||
|
||||
// Redefining the parameter and re-applying should ripple again.
|
||||
app.named_parameter_editor_rows[0].formula = "3".to_string();
|
||||
app.apply_named_parameter_editor_rows();
|
||||
let (start, end) = match app.tabs[app.active_tab].scene.document.get_entity(line) {
|
||||
Some(acadrust::EntityType::Line(l)) => (l.start, l.end),
|
||||
other => panic!("expected a Line, got {other:?}"),
|
||||
};
|
||||
let len = ((end.x - start.x).powi(2) + (end.y - start.y).powi(2)).sqrt();
|
||||
assert!((len - 3.0).abs() < 1e-6, "length should track the redefined parameter, got {len}");
|
||||
}
|
||||
|
||||
/// A row that fails to validate (here, a formula that doesn't parse)
|
||||
/// must not silently vanish or corrupt the table: it's reported as an
|
||||
/// error and simply isn't added, while a valid row alongside it still
|
||||
/// commits — the same isolate-the-failure treatment
|
||||
/// `ParameterTable::resolve_all` already gives a bad reference.
|
||||
#[test]
|
||||
fn applying_a_row_with_a_malformed_formula_reports_an_error_and_keeps_the_good_row() {
|
||||
let mut app = OpenCADStudio::new_for_test();
|
||||
let _ = app.automation_op(r#"{"op":"new"}"#);
|
||||
|
||||
app.named_parameter_editor_rows = vec![
|
||||
crate::ui::window::named_parameters::ParamEditorRow { name: "good".to_string(), formula: "10".to_string() },
|
||||
crate::ui::window::named_parameters::ParamEditorRow { name: "bad".to_string(), formula: "1 +".to_string() },
|
||||
];
|
||||
app.apply_named_parameter_editor_rows();
|
||||
|
||||
assert_eq!(app.tabs[app.active_tab].scene.named_parameters().resolve("good"), Ok(10.0));
|
||||
assert!(!app.tabs[app.active_tab].scene.named_parameters().contains("bad"), "a malformed row must not be added");
|
||||
assert!(
|
||||
app.command_line.history.iter().any(|e| e.kind == crate::ui::command_line::EntryKind::Error),
|
||||
"a malformed row must be reported as an error, not silently dropped"
|
||||
);
|
||||
}
|
||||
|
||||
/// Two rows typed with the same name must not silently collapse into
|
||||
/// "whichever `set` call happens last wins" — both are refused, and an
|
||||
/// unrelated row still commits normally.
|
||||
#[test]
|
||||
fn applying_rows_with_a_duplicate_name_refuses_both_and_keeps_the_unrelated_row() {
|
||||
let mut app = OpenCADStudio::new_for_test();
|
||||
let _ = app.automation_op(r#"{"op":"new"}"#);
|
||||
|
||||
app.named_parameter_editor_rows = vec![
|
||||
crate::ui::window::named_parameters::ParamEditorRow { name: "x".to_string(), formula: "1".to_string() },
|
||||
crate::ui::window::named_parameters::ParamEditorRow { name: "x".to_string(), formula: "2".to_string() },
|
||||
crate::ui::window::named_parameters::ParamEditorRow { name: "y".to_string(), formula: "3".to_string() },
|
||||
];
|
||||
app.apply_named_parameter_editor_rows();
|
||||
|
||||
assert!(!app.tabs[app.active_tab].scene.named_parameters().contains("x"), "a duplicated name must not be added at all");
|
||||
assert_eq!(app.tabs[app.active_tab].scene.named_parameters().resolve("y"), Ok(3.0));
|
||||
assert!(app.command_line.history.iter().any(|e| e.kind == crate::ui::command_line::EntryKind::Error));
|
||||
}
|
||||
|
||||
/// Design doc §6.1's manual Coincident UI: two picks landing on two
|
||||
/// different lines' endpoints should resolve to a real `SketchRef` pair
|
||||
/// and add a Coincident constraint — the same `AddCoincidentConstraint`
|
||||
|
||||
@@ -292,6 +292,8 @@ pub fn start_allowed(cmd: &str) -> bool {
|
||||
| "PERF"
|
||||
| "CUI"
|
||||
| "ALIASEDIT"
|
||||
| "OPTIONS"
|
||||
| "OP"
|
||||
| "CUILOAD"
|
||||
| "CUIIMPORT"
|
||||
)
|
||||
@@ -312,6 +314,7 @@ inventory::submit!(crate::command::CommandRegistration {
|
||||
"CLEANSCREEN",
|
||||
"CUI",
|
||||
"DSETTINGS",
|
||||
"PARAMETERS",
|
||||
"GRID",
|
||||
"ISODRAFT",
|
||||
"ISOPLANE",
|
||||
|
||||
@@ -240,6 +240,25 @@ impl OpenCADStudio {
|
||||
return Some(Task::done(Message::AliasEditorOpen));
|
||||
}
|
||||
|
||||
// ── OPTIONS — app preferences dialog ────────────────────────────
|
||||
// Registered for autocomplete (`commands/mod.rs`'s "one-shot
|
||||
// commands" list) but had no actual dispatch arm anywhere — the
|
||||
// Start-tab welcome screen's "Options" button emits
|
||||
// `Message::OptionsOpen` directly and was the only way to reach
|
||||
// it. Found while live-testing the new UI-scale setting (nothing
|
||||
// to do with that feature itself, just discovered along the
|
||||
// way). "OP" is AutoCAD's own short alias for this command.
|
||||
"OPTIONS" | "OP" => {
|
||||
return Some(Task::done(Message::OptionsOpen));
|
||||
}
|
||||
|
||||
// ── PARAMETERS — named-parameter/expression table editor ───────
|
||||
// `docs/named_parameters_design.md` stage 4. AutoCAD's own
|
||||
// command name for the equivalent feature (Parameters Manager).
|
||||
"PARAMETERS" => {
|
||||
return Some(Task::done(Message::NamedParametersOpen));
|
||||
}
|
||||
|
||||
// ── CUI — keyboard shortcut / key-binding editor ───────────────
|
||||
"CUI" => {
|
||||
return Some(Task::done(Message::ShortcutsPanelOpen));
|
||||
|
||||
@@ -504,6 +504,12 @@ pub(super) struct OpenCADStudio {
|
||||
crosshair_color_input: String,
|
||||
/// Model-space lineweight preview scale, in percent (25..=200).
|
||||
lineweight_display_scale: i32,
|
||||
/// Whole-interface scale, in percent (50..=200) — drives iced's own
|
||||
/// per-window `scale_factor` (`run`'s `.scale_factor(...)` hook), so it
|
||||
/// resizes every panel, dialog, the ribbon, and their text uniformly
|
||||
/// rather than needing a separate font-size knob threaded through each
|
||||
/// panel's own view code.
|
||||
ui_scale: i32,
|
||||
/// Isometric drafting state and active axis pair.
|
||||
isometric_drafting: bool,
|
||||
iso_plane: settings::IsoPlane,
|
||||
@@ -1018,6 +1024,15 @@ pub(super) struct OpenCADStudio {
|
||||
/// edited. Seeded from `command_aliases` on open, committed back on close.
|
||||
alias_editor_rows: Vec<(String, String)>,
|
||||
|
||||
// ── Named Parameters (docs/named_parameters_design.md, stage 4) ────────
|
||||
/// Working buffer for the PARAMETERS modal. Unlike `alias_editor_rows`,
|
||||
/// this isn't a copy of a separate app-level store — the real state
|
||||
/// lives per-document at `Scene::named_parameters`; this buffer is
|
||||
/// seeded from the active tab's table on open and only written back to
|
||||
/// it on Apply (`apply_named_parameter_editor_rows`,
|
||||
/// `src/app/named_parameters.rs`).
|
||||
named_parameter_editor_rows: Vec<crate::ui::window::named_parameters::ParamEditorRow>,
|
||||
|
||||
// ── Layout Manager Panel ──────────────────────────────────────────────
|
||||
layout_manager_selected: String,
|
||||
layout_manager_rename_buf: String,
|
||||
@@ -1695,6 +1710,7 @@ pub enum ModalKind {
|
||||
AttributeEditor,
|
||||
LayerDeleteWarning,
|
||||
Aliases,
|
||||
NamedParameters,
|
||||
ScaleManager,
|
||||
/// Add / remove the annotation scales a single selected object has a
|
||||
/// per-object representation for.
|
||||
@@ -1937,6 +1953,8 @@ pub enum Message {
|
||||
CursorTypeChanged(settings::CursorType),
|
||||
/// Set the model-space lineweight preview scale from Options.
|
||||
LineweightDisplayScaleChanged(i32),
|
||||
/// Set the whole-interface scale (`ui_scale`) from Options.
|
||||
UiScaleChanged(i32),
|
||||
/// Set the ribbon's large-button icon-label font size from Options.
|
||||
RibbonLabelFontSizeChanged(i32),
|
||||
/// Set the ribbon's panel-title ("Draw", "Modify", …) font size from Options.
|
||||
@@ -2706,6 +2724,24 @@ pub enum Message {
|
||||
AliasEditorRemove(usize),
|
||||
/// Commit the edited rows to the alias table (Apply button); stays open.
|
||||
AliasEditorApply,
|
||||
// ── Named Parameters (PARAMETERS) ───────────────────────────────────
|
||||
/// Open the named-parameter editor, seeding rows from the active tab's
|
||||
/// `Scene::named_parameters`.
|
||||
NamedParametersOpen,
|
||||
/// Live edit of the name or formula in row `idx`.
|
||||
NamedParametersInput {
|
||||
idx: usize,
|
||||
field: crate::ui::window::named_parameters::ParamField,
|
||||
value: String,
|
||||
},
|
||||
/// Append a blank parameter row.
|
||||
NamedParametersAdd,
|
||||
/// Remove parameter row `idx`.
|
||||
NamedParametersRemove(usize),
|
||||
/// Commit the edited rows to `Scene::named_parameters` (Apply button)
|
||||
/// and re-solve every constraint that reads a named parameter; stays
|
||||
/// open.
|
||||
NamedParametersApply,
|
||||
// ── About window ────────────────────────────────────────────────────
|
||||
AboutOpen,
|
||||
/// Close whatever in-canvas modal dialog is open (Plan B).
|
||||
@@ -3378,6 +3414,7 @@ impl OpenCADStudio {
|
||||
crosshair_color: None,
|
||||
crosshair_color_input: String::new(),
|
||||
lineweight_display_scale: 100,
|
||||
ui_scale: 100,
|
||||
isometric_drafting: false,
|
||||
iso_plane: settings::IsoPlane::Left,
|
||||
snap_angle_deg: 0.0,
|
||||
@@ -3579,6 +3616,7 @@ impl OpenCADStudio {
|
||||
// Command aliases (populated from ocad.pgp just after construction)
|
||||
command_aliases: rustc_hash::FxHashMap::default(),
|
||||
alias_editor_rows: Vec::new(),
|
||||
named_parameter_editor_rows: Vec::new(),
|
||||
// Layout Manager
|
||||
layout_manager_selected: "Model".to_string(),
|
||||
layer_state_selected: None,
|
||||
@@ -3978,6 +4016,7 @@ pub fn run() -> iced::Result {
|
||||
..iced::Settings::default()
|
||||
})
|
||||
.subscription(OpenCADStudio::subscription)
|
||||
.scale_factor(|state: &OpenCADStudio, _window: window::Id| state.ui_scale as f32 / 100.0)
|
||||
.title(|state: &OpenCADStudio, window_id: window::Id| {
|
||||
let _ = window_id; // all dialogs are in-canvas modals now
|
||||
if let Some(tab) = state.tabs.get(state.active_tab) {
|
||||
@@ -4022,6 +4061,7 @@ pub fn run_web() -> iced::Result {
|
||||
OpenCADStudio::view_main,
|
||||
)
|
||||
.subscription(OpenCADStudio::subscription)
|
||||
.scale_factor(|state: &OpenCADStudio| state.ui_scale as f32 / 100.0)
|
||||
.title(|_state: &OpenCADStudio| {
|
||||
concat!("Open CAD Studio ", env!("OCS_APP_VERSION")).to_string()
|
||||
})
|
||||
|
||||
@@ -166,6 +166,14 @@ pub struct UserSettings {
|
||||
pub crosshair_color: Option<[u8; 3]>,
|
||||
/// Model-space lineweight preview scale as a percentage.
|
||||
pub lineweight_display_scale: i32,
|
||||
/// Whole-interface scale, as a percentage — every panel, dialog, the
|
||||
/// ribbon, and their text, not just one widget's font size. Wired
|
||||
/// straight into iced's own per-window `scale_factor` hook
|
||||
/// (`OpenCADStudio::run`), so a change takes effect immediately with no
|
||||
/// per-panel plumbing needed. Relies on the struct-level `#[serde(default)]`
|
||||
/// (via `impl Default` below) to backfill this field for a config file
|
||||
/// saved before this setting existed, same as every other field here.
|
||||
pub ui_scale: i32,
|
||||
/// Isometric drafting changes the grid and crosshair to the active axis pair.
|
||||
pub isometric_drafting: bool,
|
||||
pub iso_plane: IsoPlane,
|
||||
@@ -310,6 +318,7 @@ impl Default for UserSettings {
|
||||
cursor_type: CursorType::Crosshair,
|
||||
crosshair_color: None,
|
||||
lineweight_display_scale: 100,
|
||||
ui_scale: 100,
|
||||
isometric_drafting: false,
|
||||
iso_plane: IsoPlane::Left,
|
||||
snap_angle_deg: 0.0,
|
||||
|
||||
@@ -368,6 +368,7 @@ impl OpenCADStudio {
|
||||
cursor_type: self.cursor_type,
|
||||
crosshair_color: self.crosshair_color,
|
||||
lineweight_display_scale: self.lineweight_display_scale,
|
||||
ui_scale: self.ui_scale,
|
||||
isometric_drafting: self.isometric_drafting,
|
||||
iso_plane: self.iso_plane,
|
||||
snap_angle_deg: self.snap_angle_deg,
|
||||
@@ -425,6 +426,7 @@ impl OpenCADStudio {
|
||||
.map(crate::app::config::rgb_to_hex)
|
||||
.unwrap_or_default();
|
||||
self.lineweight_display_scale = s.lineweight_display_scale.clamp(25, 200);
|
||||
self.ui_scale = s.ui_scale.clamp(50, 200);
|
||||
self.isometric_drafting = s.isometric_drafting;
|
||||
self.iso_plane = s.iso_plane;
|
||||
self.snap_angle_deg = if s.snap_angle_deg.is_finite() {
|
||||
@@ -1394,6 +1396,9 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
|
||||
// Design doc §8 stage 4: read back any persisted sketch
|
||||
// constraint sets right after the document is installed.
|
||||
self.tabs[i].scene.load_sketch_constraints_from_document();
|
||||
// named_parameters_design.md stage 2: same load-time hook,
|
||||
// for the document-wide parameter table.
|
||||
self.tabs[i].scene.load_named_parameters_from_document();
|
||||
self.tabs[i].active_layer = self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
@@ -1660,6 +1665,9 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
|
||||
// Design doc §8 stage 4: write any sketch constraint sets into the
|
||||
// document right before it's serialized.
|
||||
self.tabs[i].scene.materialize_sketch_constraints_for_save();
|
||||
// named_parameters_design.md stage 2: same save-time hook, for the
|
||||
// document-wide parameter table.
|
||||
self.tabs[i].scene.materialize_named_parameters_for_save();
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
@@ -2611,6 +2619,7 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
|
||||
self.stamp_header_sysvars(i);
|
||||
self.sync_solid_models_for_save(i);
|
||||
self.tabs[i].scene.materialize_sketch_constraints_for_save();
|
||||
self.tabs[i].scene.materialize_named_parameters_for_save();
|
||||
let tab_id = self.tabs[i].id;
|
||||
let bounds = crate::ui::wrap_bar::dropdown_bounds(
|
||||
crate::app::view::VIEWPORT_CAPTURE_BOUNDS_ID,
|
||||
|
||||
@@ -5861,6 +5861,43 @@ impl OpenCADStudio {
|
||||
Task::none()
|
||||
}
|
||||
|
||||
// ── Named Parameters (PARAMETERS) ─────────────────────────────────
|
||||
Message::NamedParametersOpen => {
|
||||
let i = self.active_tab;
|
||||
self.named_parameter_editor_rows = self.tabs[i]
|
||||
.scene
|
||||
.named_parameters()
|
||||
.iter()
|
||||
.map(|p| crate::ui::window::named_parameters::ParamEditorRow { name: p.name.clone(), formula: p.source.clone() })
|
||||
.collect();
|
||||
self.active_modal = Some(super::ModalKind::NamedParameters);
|
||||
Task::none()
|
||||
}
|
||||
Message::NamedParametersInput { idx, field, value } => {
|
||||
use crate::ui::window::named_parameters::ParamField;
|
||||
if let Some(row) = self.named_parameter_editor_rows.get_mut(idx) {
|
||||
match field {
|
||||
ParamField::Name => row.name = value,
|
||||
ParamField::Formula => row.formula = value,
|
||||
}
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::NamedParametersAdd => {
|
||||
self.named_parameter_editor_rows.push(crate::ui::window::named_parameters::ParamEditorRow::default());
|
||||
Task::none()
|
||||
}
|
||||
Message::NamedParametersRemove(idx) => {
|
||||
if idx < self.named_parameter_editor_rows.len() {
|
||||
self.named_parameter_editor_rows.remove(idx);
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::NamedParametersApply => {
|
||||
self.apply_named_parameter_editor_rows();
|
||||
Task::none()
|
||||
}
|
||||
|
||||
// ── Options / About windows ───────────────────────────────────
|
||||
Message::OptionsOpen => {
|
||||
self.active_modal = Some(super::ModalKind::Options);
|
||||
@@ -5900,6 +5937,12 @@ impl OpenCADStudio {
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::UiScaleChanged(value) => {
|
||||
self.ui_scale = value.clamp(50, 200);
|
||||
self.persist_settings_if_changed();
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::RibbonLabelFontSizeChanged(value) => {
|
||||
self.ribbon.set_label_font_size(value);
|
||||
self.save_config();
|
||||
|
||||
@@ -13,6 +13,7 @@ impl OpenCADStudio {
|
||||
Some(K::About) => crate::tr!("modal", "about"),
|
||||
Some(K::Shortcuts) => crate::tr!("modal", "keyboard-shortcuts"),
|
||||
Some(K::Aliases) => crate::tr!("modal", "command-aliases"),
|
||||
Some(K::NamedParameters) => crate::t!("Named Parameters").into_owned(),
|
||||
Some(K::Options) => crate::tr!("action", "options"),
|
||||
Some(K::FindReplace) => crate::tr!("modal", "find-replace"),
|
||||
Some(K::PluginManager) => crate::tr!("modal", "plugin-manager"),
|
||||
@@ -173,6 +174,19 @@ impl OpenCADStudio {
|
||||
},
|
||||
)
|
||||
}
|
||||
super::super::ModalKind::NamedParameters => {
|
||||
sized_flow(
|
||||
ex,
|
||||
620,
|
||||
520,
|
||||
|flow| {
|
||||
crate::ui::window::named_parameters::view_window(
|
||||
&self.named_parameter_editor_rows,
|
||||
flow,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
super::super::ModalKind::Options => sized_flow(
|
||||
ex,
|
||||
540,
|
||||
@@ -191,6 +205,7 @@ impl OpenCADStudio {
|
||||
self.crosshair_color,
|
||||
&self.crosshair_color_input,
|
||||
self.lineweight_display_scale,
|
||||
self.ui_scale,
|
||||
self.ribbon.label_font_size(),
|
||||
self.ribbon.group_title_font_size(),
|
||||
&self.model_space,
|
||||
|
||||
Reference in new issue
Block a user