feat(alias): centralize command aliases in user-editable ocad.pgp (#286/#288)
Command aliases (L→LINE, CC→COPYCLIP) were scattered across dispatch match
arms and CommandRegistration name arrays. Move them all into a single
user-editable file:
- assets/ocad.pgp: shipped default alias table (ALIAS,*COMMAND format),
embedded and copied to config/ocad.pgp on first launch; only that user
copy is read after. AA→AREA default closes #286.
- resolve_alias() rewrites the leading command token at the top of
dispatch_command_inner, so aliases resolve for every path (families,
plugins, Repeat menu). Args after the first space are untouched.
- Autocomplete hides alias keys and, when the whole input is an alias,
surfaces its target command as the top (highlighted) suggestion so the
highlight matches what Enter runs (AA→AREA, L→LINE).
- ALIASEDIT opens a new editor modal (add/edit/delete rows) with an Apply
button; Apply persists to ocad.pgp, closing discards unapplied edits.
- Strip alias tokens from all 8 dispatch match-arm files and 71 module
CommandRegistration arrays, keeping canonical/synonym/distinct-command
names. ocad.pgp is now the sole source: deleting or remapping an alias
there takes effect. CUI stays on the keyboard-shortcut panel.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 12:39:22 +03:00
|
|
|
|
mod alias;
|
2026-06-17 21:05:12 +03:00
|
|
|
|
#[cfg(not(target_arch = "wasm32"))]
|
|
|
|
|
|
mod automation;
|
refactor(config): consolidate settings into one grouped settings.json
App preferences were spread across six flat plain-text files (settings.txt,
recent.txt, recent_limit.txt, statusbar.txt, ribbon.txt, plot.txt), each with a
bespoke line parser and no grouping. Replace them with a single grouped JSON,
serialized via serde (enabling the derive feature; serde_json was already a
dependency).
- New app/config.rs: AppConfig { settings, recent, statusbar, ribbon, plot } →
<config>/settings.json (pretty JSON), load()/save().
- Make UserSettings, StatusBarConfig/StatusPill, CollapseMode and
PlotDialogState serde-derived; #[serde(skip)] marks the plot dialog's runtime
fields so only print preferences persist. Their bespoke .txt load/save is
removed.
- App gains current_config/apply_config/save_config: one AppConfig::load at
startup distributes into live state; every settings-change site
(persist_settings_if_changed, recent add/remove/limit, status-bar pill
toggle, ribbon density, plot commit) routes through save_config, which diffs
against the last write to avoid thrashing.
- Clean start: the old flat files are ignored (no migration). ocad.pgp stays a
separate hand-editable file.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 16:41:34 +03:00
|
|
|
|
mod config;
|
2026-06-17 21:05:12 +03:00
|
|
|
|
#[cfg(not(target_arch = "wasm32"))]
|
2026-06-20 13:59:20 +03:00
|
|
|
|
pub use automation::{export_headless, serve};
|
refactor: group ui/ style+popup dirs, rename cmd_result driver
- Group the flat src/ui/ dir: move the 9 style editors into ui/style/
(dimstyle, mleaderstyle, mlstyle, plotstyle, textstyle, tablestyle,
point_style, style_list, style_manager) and the 6 popups into
ui/popup/ (cycle, isolate, scale, selection_filter, snap, units).
Call sites repointed to crate::ui::style::/crate::ui::popup::; no
compat re-exports. ui/ root drops from 36 to 21 files.
- Rename app/cmd_result.rs -> app/command_driver.rs: the file is the
StepInput->feed_command driver, not the CmdResult type (which lives
in command.rs). Name now matches contents.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 23:34:10 +03:00
|
|
|
|
mod command_driver;
|
2026-05-12 10:36:15 +03:00
|
|
|
|
mod commands;
|
2026-03-22 22:44:32 +03:00
|
|
|
|
mod document;
|
2026-06-07 13:17:46 -04:00
|
|
|
|
mod expr_eval;
|
2026-03-22 22:44:32 +03:00
|
|
|
|
mod helpers;
|
|
|
|
|
|
mod history;
|
|
|
|
|
|
mod layers;
|
2026-05-31 19:27:06 +03:00
|
|
|
|
mod model_ops;
|
feat(styles): unify the five style managers behind a shared frame
The text/dimension/table/multileader/multiline managers were hand-copied,
so their list CRUD kept drifting and breaking: Dimension's New created
nothing (#73), new text styles never reached the ribbon dropdown (#74),
and several were added with a NULL handle (dropped on DWG save, #67).
Collapse the duplicated parts into three shared pieces and leave only the
per-manager property editor bespoke:
- app/style_ops.rs: one StyleKind-dispatched CRUD layer (new / copy /
delete / rename) handling handle allocation, ribbon sync, Table re-key
and name-reference rewrites in a single place.
- ui/style_manager.rs: the window scaffold — uniform toolbar (New / Copy /
Delete on the left, Set Current + Apply on the right), style list and
chrome. Each manager now only builds its editor.
- ui/style_list.rs: the list row — single click selects, double click
starts an inline rename.
New capabilities, now consistent across all five:
- Copy duplicates the selected style.
- Double-click a name to rename it inline.
- Right side is always Set Current + Apply (added Table set-current;
MLine apply is a documented placeholder until its editor is editable).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 15:03:34 +03:00
|
|
|
|
mod mtext_editor;
|
2026-06-22 02:03:34 +03:00
|
|
|
|
#[cfg(not(target_arch = "wasm32"))]
|
feat(styles): unify the five style managers behind a shared frame
The text/dimension/table/multileader/multiline managers were hand-copied,
so their list CRUD kept drifting and breaking: Dimension's New created
nothing (#73), new text styles never reached the ribbon dropdown (#74),
and several were added with a NULL handle (dropped on DWG save, #67).
Collapse the duplicated parts into three shared pieces and leave only the
per-manager property editor bespoke:
- app/style_ops.rs: one StyleKind-dispatched CRUD layer (new / copy /
delete / rename) handling handle allocation, ribbon sync, Table re-key
and name-reference rewrites in a single place.
- ui/style_manager.rs: the window scaffold — uniform toolbar (New / Copy /
Delete on the left, Set Current + Apply on the right), style list and
chrome. Each manager now only builds its editor.
- ui/style_list.rs: the list row — single click selects, double click
starts an inline rename.
New capabilities, now consistent across all five:
- Copy duplicates the selected style.
- Double-click a name to rename it inline.
- Right side is always Set Current + Apply (added Table set-current;
MLine apply is a documented placeholder until its editor is editable).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 15:03:34 +03:00
|
|
|
|
pub mod plugin_host;
|
2026-05-12 10:36:15 +03:00
|
|
|
|
mod properties;
|
feat(ui): replace app-menu logo with a quick-access file toolbar
Remove the OCS-logo button and the dropdown menu it opened; the top strip
now shows direct New / Open / Save / Save As / Print buttons ahead of the
undo/redo controls. Drop the HELP and OPTIONS commands (HELP dumped text to
the command line, OPTIONS aliased the drafting-settings popup).
The app-menu overlay is gone, so its rendering code, MENU_ITEMS, four dead
messages, and the LOGO_RED / GEAR / HELP assets are removed. The recents
list it held is the only survivor — moved into OpenCADStudio.recent_files
via a new app/recent.rs, backing the Start page's Recent Documents panel.
Quick-access icons are redrawn in a cleaner style (file-plus, folder-open,
device-floppy, file-export, printer) and tinted light so they read on the
dark strip; FOLDER / DOC keep their old glyphs for the file browser.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 09:51:49 +03:00
|
|
|
|
mod recent;
|
feat(settings): persist UI preferences across sessions (#68)
DYN, ORTHO, POLAR, the polar increment, the grid toggle and the
object-snap configuration (OSNAP on/off, active snap modes, OTRACK)
reset to defaults on every launch. Persist them to a per-user
settings.txt, matching the existing recent-files / status-bar stores
(plain key=value, no serialization crate).
Settings load on boot and apply to live state; the update wrapper
snapshots them after each message and writes only on change, so a
toggle survives a restart without thrashing the file. LWT is left out
on purpose — it is a drawing header variable, not a UI preference.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 19:50:23 +03:00
|
|
|
|
mod settings;
|
feat(styles): unify the five style managers behind a shared frame
The text/dimension/table/multileader/multiline managers were hand-copied,
so their list CRUD kept drifting and breaking: Dimension's New created
nothing (#73), new text styles never reached the ribbon dropdown (#74),
and several were added with a NULL handle (dropped on DWG save, #67).
Collapse the duplicated parts into three shared pieces and leave only the
per-manager property editor bespoke:
- app/style_ops.rs: one StyleKind-dispatched CRUD layer (new / copy /
delete / rename) handling handle allocation, ribbon sync, Table re-key
and name-reference rewrites in a single place.
- ui/style_manager.rs: the window scaffold — uniform toolbar (New / Copy /
Delete on the left, Set Current + Apply on the right), style list and
chrome. Each manager now only builds its editor.
- ui/style_list.rs: the list row — single click selects, double click
starts an inline rename.
New capabilities, now consistent across all five:
- Copy duplicates the selected style.
- Double-click a name to rename it inline.
- Right side is always Set Current + Apply (added Table set-current;
MLine apply is a documented placeholder until its editor is editable).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 15:03:34 +03:00
|
|
|
|
mod style_ops;
|
2026-05-31 14:44:18 +03:00
|
|
|
|
mod text_inline;
|
2026-03-22 22:44:32 +03:00
|
|
|
|
mod update;
|
2026-05-12 10:36:15 +03:00
|
|
|
|
mod view;
|
2026-06-11 20:11:19 +03:00
|
|
|
|
mod visibility;
|
2026-03-22 22:44:32 +03:00
|
|
|
|
|
feat(styles): unify the five style managers behind a shared frame
The text/dimension/table/multileader/multiline managers were hand-copied,
so their list CRUD kept drifting and breaking: Dimension's New created
nothing (#73), new text styles never reached the ribbon dropdown (#74),
and several were added with a NULL handle (dropped on DWG save, #67).
Collapse the duplicated parts into three shared pieces and leave only the
per-manager property editor bespoke:
- app/style_ops.rs: one StyleKind-dispatched CRUD layer (new / copy /
delete / rename) handling handle allocation, ribbon sync, Table re-key
and name-reference rewrites in a single place.
- ui/style_manager.rs: the window scaffold — uniform toolbar (New / Copy /
Delete on the left, Set Current + Apply on the right), style list and
chrome. Each manager now only builds its editor.
- ui/style_list.rs: the list row — single click selects, double click
starts an inline rename.
New capabilities, now consistent across all five:
- Copy duplicates the selected style.
- Double-click a name to rename it inline.
- Right side is always Set Current + Apply (added Table set-current;
MLine apply is a documented placeholder until its editor is editable).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 15:03:34 +03:00
|
|
|
|
pub use style_ops::StyleKind;
|
|
|
|
|
|
|
2026-03-22 22:44:32 +03:00
|
|
|
|
use document::DocumentTab;
|
|
|
|
|
|
|
|
|
|
|
|
use crate::modules::ModuleEvent;
|
2026-06-27 11:50:11 +03:00
|
|
|
|
use crate::scene::CubeRegion;
|
feat(grip): multi-functional hover popup menu
Phase 2 of the grip overhaul: when the cursor dwells on a selected
entity's grip the overlay opens a popup menu with entity-specific
options (Stretch / Add Vertex / Remove Vertex / Convert to
Arc-Line / …). Picking an item dispatches `apply_grip_menu`; the
default `Stretch` entry is a no-op that lets the user click the
grip to start the normal drag.
- `GripMenuItem` + `GripMenuAction` (full vocabulary covering
stretch, add/remove vertex, convert to arc/line, reverse arrows,
text-position variants, hatch parameters, tangent direction).
- `Grippable` trait gains `grip_menu` + `apply_grip_menu` with
trait-default `Stretch`-only impls so every existing entity gets
the popup for free.
- `EntityTypeOps` dispatches both methods across the same entity
list as the existing grip / property machinery.
- `OpenCADStudio` carries `grip_hover` (dwell tracker) +
`grip_popup` (open menu). `update_grip_hover` runs from
`ViewportMove`: hit-tests grips, refreshes the dwell timer, and
opens the popup after 600 ms. Cursor drift past 80 px or a grip
click dismisses it.
- `view.rs` paints the popup as an absolute-positioned column of
buttons at the grip's screen anchor; each button publishes
`Message::GripMenuPick(idx)`.
- `LwPolyline` overrides `Grippable` manually (skips the standard
macro) so its vertex grip exposes Add/Remove Vertex and its
segment-midpoint grip exposes Add Vertex + Convert to Arc/Line.
`apply_grip_menu` performs the edits: vertex insertion at the
chord midpoint inheriting the previous bulge, vertex removal,
and bulge toggle for the arc/line conversion.
2026-05-29 09:30:54 +03:00
|
|
|
|
|
2026-06-27 01:12:45 +03:00
|
|
|
|
/// Which UCS-icon grip is being dragged. `Origin` slides the UCS origin within
|
|
|
|
|
|
/// its own plane; `XAxis`/`YAxis` rotate the UCS so that axis points at the
|
|
|
|
|
|
/// cursor (the other in-plane axis follows, Z fixed). The cursor is mapped onto
|
|
|
|
|
|
/// the UCS plane every move, so no extra drag-start state is needed.
|
|
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
|
|
|
|
pub enum UcsGripKind {
|
|
|
|
|
|
Origin,
|
|
|
|
|
|
XAxis,
|
|
|
|
|
|
YAxis,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
feat(grip): multi-functional hover popup menu
Phase 2 of the grip overhaul: when the cursor dwells on a selected
entity's grip the overlay opens a popup menu with entity-specific
options (Stretch / Add Vertex / Remove Vertex / Convert to
Arc-Line / …). Picking an item dispatches `apply_grip_menu`; the
default `Stretch` entry is a no-op that lets the user click the
grip to start the normal drag.
- `GripMenuItem` + `GripMenuAction` (full vocabulary covering
stretch, add/remove vertex, convert to arc/line, reverse arrows,
text-position variants, hatch parameters, tangent direction).
- `Grippable` trait gains `grip_menu` + `apply_grip_menu` with
trait-default `Stretch`-only impls so every existing entity gets
the popup for free.
- `EntityTypeOps` dispatches both methods across the same entity
list as the existing grip / property machinery.
- `OpenCADStudio` carries `grip_hover` (dwell tracker) +
`grip_popup` (open menu). `update_grip_hover` runs from
`ViewportMove`: hit-tests grips, refreshes the dwell timer, and
opens the popup after 600 ms. Cursor drift past 80 px or a grip
click dismisses it.
- `view.rs` paints the popup as an absolute-positioned column of
buttons at the grip's screen anchor; each button publishes
`Message::GripMenuPick(idx)`.
- `LwPolyline` overrides `Grippable` manually (skips the standard
macro) so its vertex grip exposes Add/Remove Vertex and its
segment-midpoint grip exposes Add Vertex + Convert to Arc/Line.
`apply_grip_menu` performs the edits: vertex insertion at the
chord midpoint inheriting the previous bulge, vertex removal,
and bulge toggle for the arc/line conversion.
2026-05-29 09:30:54 +03:00
|
|
|
|
/// Cursor dwelling over a grip. When `started.elapsed() >=` the popup
|
|
|
|
|
|
/// threshold the multi-functional menu opens (`grip_popup`).
|
|
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
|
|
pub struct GripHover {
|
|
|
|
|
|
pub handle: acadrust::Handle,
|
|
|
|
|
|
pub grip_id: usize,
|
|
|
|
|
|
pub screen: iced::Point,
|
2026-06-16 17:53:56 +03:00
|
|
|
|
pub started: iced::time::Instant,
|
feat(grip): multi-functional hover popup menu
Phase 2 of the grip overhaul: when the cursor dwells on a selected
entity's grip the overlay opens a popup menu with entity-specific
options (Stretch / Add Vertex / Remove Vertex / Convert to
Arc-Line / …). Picking an item dispatches `apply_grip_menu`; the
default `Stretch` entry is a no-op that lets the user click the
grip to start the normal drag.
- `GripMenuItem` + `GripMenuAction` (full vocabulary covering
stretch, add/remove vertex, convert to arc/line, reverse arrows,
text-position variants, hatch parameters, tangent direction).
- `Grippable` trait gains `grip_menu` + `apply_grip_menu` with
trait-default `Stretch`-only impls so every existing entity gets
the popup for free.
- `EntityTypeOps` dispatches both methods across the same entity
list as the existing grip / property machinery.
- `OpenCADStudio` carries `grip_hover` (dwell tracker) +
`grip_popup` (open menu). `update_grip_hover` runs from
`ViewportMove`: hit-tests grips, refreshes the dwell timer, and
opens the popup after 600 ms. Cursor drift past 80 px or a grip
click dismisses it.
- `view.rs` paints the popup as an absolute-positioned column of
buttons at the grip's screen anchor; each button publishes
`Message::GripMenuPick(idx)`.
- `LwPolyline` overrides `Grippable` manually (skips the standard
macro) so its vertex grip exposes Add/Remove Vertex and its
segment-midpoint grip exposes Add Vertex + Convert to Arc/Line.
`apply_grip_menu` performs the edits: vertex insertion at the
chord midpoint inheriting the previous bulge, vertex removal,
and bulge toggle for the arc/line conversion.
2026-05-29 09:30:54 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-20 20:43:33 +03:00
|
|
|
|
/// Cursor dwell awaiting a rollover hit-test. Refreshed on every idle
|
|
|
|
|
|
/// move; `HoverDwellTick` runs the pick once `last_move_at.elapsed()`
|
|
|
|
|
|
/// crosses `HOVER_DWELL_MS`. `point` and `tile_size` are tile-local so
|
|
|
|
|
|
/// the deferred pick uses the same projection the move handler would
|
|
|
|
|
|
/// have — picking with the full canvas bounds in a tiled layout matches
|
|
|
|
|
|
/// the wrong entity under the cursor.
|
|
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
|
|
pub struct HoverDwell {
|
|
|
|
|
|
pub last_move_at: iced::time::Instant,
|
|
|
|
|
|
pub point: iced::Point,
|
|
|
|
|
|
pub tile_size: (f32, f32),
|
|
|
|
|
|
pub tab: usize,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// How long the cursor must sit still before the idle rollover pick runs.
|
|
|
|
|
|
pub const HOVER_DWELL_MS: u128 = 120;
|
|
|
|
|
|
|
feat(grip): multi-functional hover popup menu
Phase 2 of the grip overhaul: when the cursor dwells on a selected
entity's grip the overlay opens a popup menu with entity-specific
options (Stretch / Add Vertex / Remove Vertex / Convert to
Arc-Line / …). Picking an item dispatches `apply_grip_menu`; the
default `Stretch` entry is a no-op that lets the user click the
grip to start the normal drag.
- `GripMenuItem` + `GripMenuAction` (full vocabulary covering
stretch, add/remove vertex, convert to arc/line, reverse arrows,
text-position variants, hatch parameters, tangent direction).
- `Grippable` trait gains `grip_menu` + `apply_grip_menu` with
trait-default `Stretch`-only impls so every existing entity gets
the popup for free.
- `EntityTypeOps` dispatches both methods across the same entity
list as the existing grip / property machinery.
- `OpenCADStudio` carries `grip_hover` (dwell tracker) +
`grip_popup` (open menu). `update_grip_hover` runs from
`ViewportMove`: hit-tests grips, refreshes the dwell timer, and
opens the popup after 600 ms. Cursor drift past 80 px or a grip
click dismisses it.
- `view.rs` paints the popup as an absolute-positioned column of
buttons at the grip's screen anchor; each button publishes
`Message::GripMenuPick(idx)`.
- `LwPolyline` overrides `Grippable` manually (skips the standard
macro) so its vertex grip exposes Add/Remove Vertex and its
segment-midpoint grip exposes Add Vertex + Convert to Arc/Line.
`apply_grip_menu` performs the edits: vertex insertion at the
chord midpoint inheriting the previous bulge, vertex removal,
and bulge toggle for the arc/line conversion.
2026-05-29 09:30:54 +03:00
|
|
|
|
/// Open multi-functional-grip popup state.
|
|
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
|
|
pub struct GripPopup {
|
|
|
|
|
|
pub handle: acadrust::Handle,
|
|
|
|
|
|
pub grip_id: usize,
|
|
|
|
|
|
pub anchor: iced::Point,
|
2026-06-16 11:33:46 +03:00
|
|
|
|
pub items: Vec<crate::scene::model::object::GripMenuItem>,
|
feat(grip): multi-functional hover popup menu
Phase 2 of the grip overhaul: when the cursor dwells on a selected
entity's grip the overlay opens a popup menu with entity-specific
options (Stretch / Add Vertex / Remove Vertex / Convert to
Arc-Line / …). Picking an item dispatches `apply_grip_menu`; the
default `Stretch` entry is a no-op that lets the user click the
grip to start the normal drag.
- `GripMenuItem` + `GripMenuAction` (full vocabulary covering
stretch, add/remove vertex, convert to arc/line, reverse arrows,
text-position variants, hatch parameters, tangent direction).
- `Grippable` trait gains `grip_menu` + `apply_grip_menu` with
trait-default `Stretch`-only impls so every existing entity gets
the popup for free.
- `EntityTypeOps` dispatches both methods across the same entity
list as the existing grip / property machinery.
- `OpenCADStudio` carries `grip_hover` (dwell tracker) +
`grip_popup` (open menu). `update_grip_hover` runs from
`ViewportMove`: hit-tests grips, refreshes the dwell timer, and
opens the popup after 600 ms. Cursor drift past 80 px or a grip
click dismisses it.
- `view.rs` paints the popup as an absolute-positioned column of
buttons at the grip's screen anchor; each button publishes
`Message::GripMenuPick(idx)`.
- `LwPolyline` overrides `Grippable` manually (skips the standard
macro) so its vertex grip exposes Add/Remove Vertex and its
segment-midpoint grip exposes Add Vertex + Convert to Arc/Line.
`apply_grip_menu` performs the edits: vertex insertion at the
chord midpoint inheriting the previous bulge, vertex removal,
and bulge toggle for the arc/line conversion.
2026-05-29 09:30:54 +03:00
|
|
|
|
pub selected: usize,
|
|
|
|
|
|
}
|
2026-05-29 14:58:35 +03:00
|
|
|
|
|
|
|
|
|
|
/// Pending follow-up value for grip-menu actions that need a number
|
|
|
|
|
|
/// (Lengthen / Radius / Arc Length / Rotate Text). The next number
|
|
|
|
|
|
/// typed in the command line is parsed and routed into
|
|
|
|
|
|
/// `apply_grip_menu_value` for `(handle, grip_id, action)`.
|
|
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
|
|
pub struct GripPendingValue {
|
|
|
|
|
|
pub handle: acadrust::Handle,
|
|
|
|
|
|
pub grip_id: usize,
|
2026-06-16 11:33:46 +03:00
|
|
|
|
pub action: crate::scene::model::object::GripMenuAction,
|
2026-05-29 14:58:35 +03:00
|
|
|
|
pub label: &'static str,
|
|
|
|
|
|
}
|
2026-05-29 20:30:56 +03:00
|
|
|
|
|
feat(qselect): per-property filter with operator + value (#28)
Quick Select used to filter only on Object Type and Layer. The classic
QSELECT model — pick a type, pick one of its properties, compare with a
typed value via a logical operator — is much more general: a Line entity
can now be filtered on Start X, Length, Angle, Delta Y, …, and any
entity on the common Layer / Color / Linetype / Lineweight.
Panel layout: Object type → Property → Operator → Value → Append. The
Property dropdown is type-aware: it always lists the four common
properties; picking a specific Object type appends that type's
`geometry_properties` rows, sampled from the first entity of that type
in the active layout (so `Start X`, `Radius`, `Pattern Angle`, … only
appear when their entity type is selected). Changing the Object type
drops the Property when the new type no longer offers it.
Operators: `= Equals`, `!= Not equal`, `> Greater than`, `< Less than`,
`* Any value`. Eq/Neq do case-insensitive string compare against the
canonical property string (so "ByLayer" matches "bylayer"); Gt/Lt parse
both sides as `f64` and reject anything non-numeric; Any skips the
value test entirely. The Value text field is disabled when no Property
is picked or the operator is Any. Append behaves the same as before.
The new value reader is hand-rolled for the four common properties
(layer name, color formatted as `ByLayer`/`ByBlock`/`<aci>`/`r,g,b`,
linetype name, lineweight formatted as `0.30mm`) and falls back through
`geometry_properties()` for type-specific fields — extracting the
property's value out of the `PropValue` variant so EditText / Choice /
ColorChoice all canonicalise the same way the panel will display them.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 21:09:56 +03:00
|
|
|
|
/// Operator the Quick Select filter applies between an entity's
|
|
|
|
|
|
/// property value and the user-typed test value.
|
|
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
|
|
|
|
pub enum QSelectOp {
|
|
|
|
|
|
/// `*Any value` — the entity matches as long as the type filter
|
|
|
|
|
|
/// passes; the value column is ignored.
|
|
|
|
|
|
Any,
|
|
|
|
|
|
Eq,
|
|
|
|
|
|
Neq,
|
|
|
|
|
|
Gt,
|
|
|
|
|
|
Lt,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl std::fmt::Display for QSelectOp {
|
|
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
|
|
let s = match self {
|
|
|
|
|
|
QSelectOp::Any => "* Any value",
|
|
|
|
|
|
QSelectOp::Eq => "= Equals",
|
|
|
|
|
|
QSelectOp::Neq => "!= Not equal",
|
|
|
|
|
|
QSelectOp::Gt => "> Greater than",
|
|
|
|
|
|
QSelectOp::Lt => "< Less than",
|
|
|
|
|
|
};
|
|
|
|
|
|
f.write_str(s)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// One row in the Quick Select Properties pick_list. `field` is the
|
|
|
|
|
|
/// stable identifier (`"layer"`, `"start_x"`, …) used to look up the
|
|
|
|
|
|
/// value on each candidate entity; `label` is the human label rendered
|
|
|
|
|
|
/// in the dropdown. Equality only compares `field` so the pick_list
|
|
|
|
|
|
/// round-trips selection correctly even when labels are duplicated.
|
|
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
|
|
pub struct QSelectPropertyChoice {
|
|
|
|
|
|
pub field: String,
|
|
|
|
|
|
pub label: String,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl PartialEq for QSelectPropertyChoice {
|
|
|
|
|
|
fn eq(&self, other: &Self) -> bool {
|
|
|
|
|
|
self.field == other.field
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl Eq for QSelectPropertyChoice {}
|
|
|
|
|
|
|
|
|
|
|
|
impl std::fmt::Display for QSelectPropertyChoice {
|
|
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
|
|
f.write_str(&self.label)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Open Quick Select panel state. The filter is one
|
|
|
|
|
|
/// `(type, property, op, value)` row plus an Append-to-current-selection
|
|
|
|
|
|
/// toggle, mirroring the classic QSELECT dialog: the panel filters
|
|
|
|
|
|
/// candidate entities (entire layout) by type, then by the chosen
|
|
|
|
|
|
/// property compared to the typed value using the operator.
|
2026-05-29 20:30:56 +03:00
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
|
|
pub struct QSelectState {
|
feat(qselect): per-property filter with operator + value (#28)
Quick Select used to filter only on Object Type and Layer. The classic
QSELECT model — pick a type, pick one of its properties, compare with a
typed value via a logical operator — is much more general: a Line entity
can now be filtered on Start X, Length, Angle, Delta Y, …, and any
entity on the common Layer / Color / Linetype / Lineweight.
Panel layout: Object type → Property → Operator → Value → Append. The
Property dropdown is type-aware: it always lists the four common
properties; picking a specific Object type appends that type's
`geometry_properties` rows, sampled from the first entity of that type
in the active layout (so `Start X`, `Radius`, `Pattern Angle`, … only
appear when their entity type is selected). Changing the Object type
drops the Property when the new type no longer offers it.
Operators: `= Equals`, `!= Not equal`, `> Greater than`, `< Less than`,
`* Any value`. Eq/Neq do case-insensitive string compare against the
canonical property string (so "ByLayer" matches "bylayer"); Gt/Lt parse
both sides as `f64` and reject anything non-numeric; Any skips the
value test entirely. The Value text field is disabled when no Property
is picked or the operator is Any. Append behaves the same as before.
The new value reader is hand-rolled for the four common properties
(layer name, color formatted as `ByLayer`/`ByBlock`/`<aci>`/`r,g,b`,
linetype name, lineweight formatted as `0.30mm`) and falls back through
`geometry_properties()` for type-specific fields — extracting the
property's value out of the `PropValue` variant so EditText / Choice /
ColorChoice all canonicalise the same way the panel will display them.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 21:09:56 +03:00
|
|
|
|
/// `None` = "(Any type)".
|
2026-05-29 20:30:56 +03:00
|
|
|
|
pub type_filter: Option<String>,
|
feat(qselect): per-property filter with operator + value (#28)
Quick Select used to filter only on Object Type and Layer. The classic
QSELECT model — pick a type, pick one of its properties, compare with a
typed value via a logical operator — is much more general: a Line entity
can now be filtered on Start X, Length, Angle, Delta Y, …, and any
entity on the common Layer / Color / Linetype / Lineweight.
Panel layout: Object type → Property → Operator → Value → Append. The
Property dropdown is type-aware: it always lists the four common
properties; picking a specific Object type appends that type's
`geometry_properties` rows, sampled from the first entity of that type
in the active layout (so `Start X`, `Radius`, `Pattern Angle`, … only
appear when their entity type is selected). Changing the Object type
drops the Property when the new type no longer offers it.
Operators: `= Equals`, `!= Not equal`, `> Greater than`, `< Less than`,
`* Any value`. Eq/Neq do case-insensitive string compare against the
canonical property string (so "ByLayer" matches "bylayer"); Gt/Lt parse
both sides as `f64` and reject anything non-numeric; Any skips the
value test entirely. The Value text field is disabled when no Property
is picked or the operator is Any. Append behaves the same as before.
The new value reader is hand-rolled for the four common properties
(layer name, color formatted as `ByLayer`/`ByBlock`/`<aci>`/`r,g,b`,
linetype name, lineweight formatted as `0.30mm`) and falls back through
`geometry_properties()` for type-specific fields — extracting the
property's value out of the `PropValue` variant so EditText / Choice /
ColorChoice all canonicalise the same way the panel will display them.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 21:09:56 +03:00
|
|
|
|
/// `None` = no property filter; the type filter alone applies.
|
|
|
|
|
|
pub property: Option<QSelectPropertyChoice>,
|
|
|
|
|
|
pub operator: QSelectOp,
|
|
|
|
|
|
pub value: String,
|
2026-05-29 20:30:56 +03:00
|
|
|
|
pub append: bool,
|
|
|
|
|
|
}
|
2026-03-22 22:44:32 +03:00
|
|
|
|
use crate::snap::Snapper;
|
feat(ui): replace app-menu logo with a quick-access file toolbar
Remove the OCS-logo button and the dropdown menu it opened; the top strip
now shows direct New / Open / Save / Save As / Print buttons ahead of the
undo/redo controls. Drop the HELP and OPTIONS commands (HELP dumped text to
the command line, OPTIONS aliased the drafting-settings popup).
The app-menu overlay is gone, so its rendering code, MENU_ITEMS, four dead
messages, and the LOGO_RED / GEAR / HELP assets are removed. The recents
list it held is the only survivor — moved into OpenCADStudio.recent_files
via a new app/recent.rs, backing the Start page's Recent Documents panel.
Quick-access icons are redrawn in a cleaner style (file-plus, folder-open,
device-floppy, file-export, printer) and tinted light so they read on the
dark strip; FOLDER / DOC keep their old glyphs for the file browser.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 09:51:49 +03:00
|
|
|
|
use crate::ui::{CommandLine, Ribbon, StatusBar};
|
2026-05-12 10:36:15 +03:00
|
|
|
|
use acadrust::types::{Color as AcadColor, LineWeight};
|
|
|
|
|
|
use acadrust::CadDocument;
|
2026-03-22 22:44:32 +03:00
|
|
|
|
|
|
|
|
|
|
use iced::time::Instant;
|
|
|
|
|
|
use iced::window;
|
|
|
|
|
|
use iced::{mouse, Point, Task, Theme};
|
2026-05-13 09:59:07 +03:00
|
|
|
|
use std::sync::atomic::AtomicU8;
|
|
|
|
|
|
use std::sync::Arc;
|
2026-03-22 22:44:32 +03:00
|
|
|
|
|
|
|
|
|
|
pub(super) const POLY_START_DELAY_MS: u128 = 150;
|
|
|
|
|
|
pub(super) const VARIES_LABEL: &str = "*VARIES*";
|
|
|
|
|
|
|
2026-05-13 09:59:07 +03:00
|
|
|
|
// ── File-open progress ─────────────────────────────────────────────────────
|
|
|
|
|
|
// Phase encoding for OpenProgress.phase atomic. Updated from the background
|
|
|
|
|
|
// loader thread, read by the UI overlay on every frame.
|
|
|
|
|
|
pub const OPEN_PHASE_READING: u8 = 0;
|
|
|
|
|
|
pub const OPEN_PHASE_PARSING: u8 = 1;
|
|
|
|
|
|
pub const OPEN_PHASE_CACHING: u8 = 2;
|
|
|
|
|
|
pub const OPEN_PHASE_FINALIZING: u8 = 3;
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
2026-05-14 11:12:08 +03:00
|
|
|
|
pub struct OpenProgress {
|
2026-05-13 09:59:07 +03:00
|
|
|
|
pub name: String,
|
|
|
|
|
|
pub size_bytes: u64,
|
|
|
|
|
|
pub phase: Arc<AtomicU8>,
|
|
|
|
|
|
pub started: Instant,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-22 22:44:32 +03:00
|
|
|
|
// ── Application state ──────────────────────────────────────────────────────
|
|
|
|
|
|
|
2026-07-03 16:05:54 +03:00
|
|
|
|
/// Drawing defaults ADDSELECTED overrides while it draws a new object matching a
|
|
|
|
|
|
/// template, then restores so the current layer/colour/linetype/lineweight are
|
|
|
|
|
|
/// left unchanged (issue #239).
|
|
|
|
|
|
struct AddSelectedRestore {
|
|
|
|
|
|
layer_name: String,
|
|
|
|
|
|
layer_handle: acadrust::types::Handle,
|
|
|
|
|
|
color: AcadColor,
|
|
|
|
|
|
linetype_name: String,
|
|
|
|
|
|
linetype_handle: acadrust::types::Handle,
|
|
|
|
|
|
line_weight: i16,
|
|
|
|
|
|
lt_scale: f64,
|
|
|
|
|
|
dimstyle_name: String,
|
|
|
|
|
|
dimstyle_handle: acadrust::types::Handle,
|
|
|
|
|
|
tab_active_layer: String,
|
|
|
|
|
|
tab_layers_current: String,
|
|
|
|
|
|
ribbon_layer: String,
|
|
|
|
|
|
ribbon_color: AcadColor,
|
|
|
|
|
|
ribbon_linetype: String,
|
|
|
|
|
|
ribbon_lineweight: LineWeight,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-05 20:53:11 +03:00
|
|
|
|
/// Which Start-page section a narrow (tabbed) Start page is showing.
|
|
|
|
|
|
#[derive(Clone, Copy, PartialEq, Eq, Default, Debug)]
|
|
|
|
|
|
pub enum StartSection {
|
|
|
|
|
|
Recent,
|
|
|
|
|
|
#[default]
|
|
|
|
|
|
Welcome,
|
|
|
|
|
|
Supporters,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-23 17:16:15 +03:00
|
|
|
|
pub(super) struct OpenCADStudio {
|
2026-03-22 22:44:32 +03:00
|
|
|
|
start: Instant,
|
|
|
|
|
|
tabs: Vec<DocumentTab>,
|
|
|
|
|
|
active_tab: usize,
|
|
|
|
|
|
tab_counter: usize,
|
|
|
|
|
|
ribbon: Ribbon,
|
feat(ui): replace app-menu logo with a quick-access file toolbar
Remove the OCS-logo button and the dropdown menu it opened; the top strip
now shows direct New / Open / Save / Save As / Print buttons ahead of the
undo/redo controls. Drop the HELP and OPTIONS commands (HELP dumped text to
the command line, OPTIONS aliased the drafting-settings popup).
The app-menu overlay is gone, so its rendering code, MENU_ITEMS, four dead
messages, and the LOGO_RED / GEAR / HELP assets are removed. The recents
list it held is the only survivor — moved into OpenCADStudio.recent_files
via a new app/recent.rs, backing the Start page's Recent Documents panel.
Quick-access icons are redrawn in a cleaner style (file-plus, folder-open,
device-floppy, file-export, printer) and tinted light so they read on the
dark strip; FOLDER / DOC keep their old glyphs for the file browser.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 09:51:49 +03:00
|
|
|
|
/// Recently opened files, newest first — backs the Start page panel.
|
|
|
|
|
|
recent_files: Vec<std::path::PathBuf>,
|
2026-07-10 23:03:23 +03:00
|
|
|
|
/// Decoded DWG preview thumbnails for the Start page, keyed by path.
|
|
|
|
|
|
/// `None` = the file has no readable preview (DXF, missing, WMF). Filled
|
|
|
|
|
|
/// lazily by `refresh_recent_thumbs`; never read from a `view` call.
|
|
|
|
|
|
recent_thumbs:
|
|
|
|
|
|
std::collections::HashMap<std::path::PathBuf, Option<iced::widget::image::Handle>>,
|
2026-07-06 01:06:01 +03:00
|
|
|
|
/// How many recent files to keep (user-set on the Start page, persisted).
|
|
|
|
|
|
recent_limit: usize,
|
|
|
|
|
|
/// Live text of the recent-limit input box (may differ from `recent_limit`
|
|
|
|
|
|
/// mid-edit; applied on Enter). Kept in sync when the +/- buttons change it.
|
|
|
|
|
|
recent_limit_input: String,
|
2026-03-22 22:44:32 +03:00
|
|
|
|
command_line: CommandLine,
|
2026-07-02 01:00:22 +03:00
|
|
|
|
/// Paying Patreon supporters shown on the Start page (name, pledge cents),
|
|
|
|
|
|
/// fetched once at boot, highest pledge first.
|
|
|
|
|
|
patrons: Vec<(String, i64)>,
|
2026-07-05 20:53:11 +03:00
|
|
|
|
/// Which Start-page section is shown when the page is too narrow for all
|
|
|
|
|
|
/// three side by side and falls back to a tab bar.
|
|
|
|
|
|
start_section: StartSection,
|
2026-07-05 21:02:05 +03:00
|
|
|
|
/// When the window is too narrow the properties panel collapses to a
|
|
|
|
|
|
/// vertical bar; this is the user's toggle to expand it back out.
|
|
|
|
|
|
props_expanded: bool,
|
2026-07-01 18:17:46 +03:00
|
|
|
|
/// Read-only editor buffer backing the command-line history dropdown, so
|
|
|
|
|
|
/// the log can be drag-selected across lines and copied (issue #232).
|
|
|
|
|
|
/// Rebuilt from the history each time the dropdown is opened.
|
|
|
|
|
|
history_content: iced::widget::text_editor::Content,
|
2026-03-22 22:44:32 +03:00
|
|
|
|
status_bar: StatusBar,
|
|
|
|
|
|
cursor_pos: Point,
|
|
|
|
|
|
vp_size: (f32, f32),
|
feat(ui): anchor every ribbon dropdown and status-bar popup to its widget, flipping at window edges
All ribbon dropdowns (tool dropdowns, undo/redo history, and the layer /
colour / linetype / lineweight / style combos) and all status-bar popups
(scale, units, OSNAP, isolate, filter, customization menu, layout list)
now open directly at their widget instead of at a layout-estimated
position, and flip to the other side when the panel would cross a window
edge.
Each dropdown/pill widget is wrapped in a transparent PosReport that
records its screen bounds into a thread-local map on draw. The overlays
read those bounds:
- Ribbon: Ribbon::dd_anchor(id, panel_w, win_w) returns
(align_right, h_pad, top); position_ribbon_dropdown places the panel
below the widget, flipping right-aligned near the right edge.
- Status bar: popup builders take (pill_bounds, win_size) and call
popup::position_statusbar_popup / popup_anchor, opening just above the
pill and flipping left/right at the window edge.
The window size is now tracked (win_size, set on WindowResized). The old
layout estimators compute_dropdown_left / compute_layer_combo_left /
compute_prop_combo_left / compute_history_dropdown_left and
osnap_popup_right_offset are deleted.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 18:51:54 +03:00
|
|
|
|
/// Full window size (w, h); used to anchor status-bar popups to their pill.
|
|
|
|
|
|
win_size: (f32, f32),
|
2026-03-22 22:44:32 +03:00
|
|
|
|
snapper: Snapper,
|
|
|
|
|
|
snap_popup_open: bool,
|
2026-05-06 17:50:46 +03:00
|
|
|
|
scale_popup_open: bool,
|
2026-07-09 21:35:47 +03:00
|
|
|
|
/// True while the polar-tracking angle picker is open.
|
|
|
|
|
|
polar_popup_open: bool,
|
|
|
|
|
|
/// Live text of the polar picker's custom-angle field.
|
|
|
|
|
|
polar_custom_input: String,
|
2026-06-04 19:14:04 +03:00
|
|
|
|
/// True while the status-bar customization menu is open.
|
|
|
|
|
|
statusbar_menu_open: bool,
|
2026-06-27 14:24:47 +03:00
|
|
|
|
/// True while the leftmost hamburger's Model/layout list dropdown is open.
|
|
|
|
|
|
layout_list_open: bool,
|
2026-06-04 22:06:42 +03:00
|
|
|
|
/// True while the drawing-units picker is open.
|
|
|
|
|
|
units_popup_open: bool,
|
2026-06-04 23:17:21 +03:00
|
|
|
|
/// True while the Isolate pill's action menu is open.
|
|
|
|
|
|
isolate_popup_open: bool,
|
2026-06-04 23:39:48 +03:00
|
|
|
|
/// True while the selection-filter type picker is open.
|
|
|
|
|
|
selection_filter_popup_open: bool,
|
2026-06-04 19:14:04 +03:00
|
|
|
|
/// Clean-screen mode: hide ribbon and side panels for a full canvas.
|
|
|
|
|
|
clean_screen: bool,
|
2026-06-04 23:31:36 +03:00
|
|
|
|
/// Quick Properties: show a compact floating property panel on selection.
|
|
|
|
|
|
quick_properties: bool,
|
2026-06-04 23:56:24 +03:00
|
|
|
|
/// Selection cycling: clicking where objects overlap opens a list box
|
|
|
|
|
|
/// to pick which one; the pick is added to the current selection.
|
2026-06-04 23:44:39 +03:00
|
|
|
|
selection_cycling: bool,
|
2026-06-05 01:07:17 +03:00
|
|
|
|
/// Frame-budget HUD (Phase 5.3): overlays the last wire re-tessellation
|
|
|
|
|
|
/// cost on the active viewport. Toggled by the `PERF` command.
|
|
|
|
|
|
perf_hud: bool,
|
2026-06-04 23:56:24 +03:00
|
|
|
|
/// When set, the cycling list box is open: (canvas point, candidates).
|
|
|
|
|
|
cycle_candidates: Option<(iced::Point, Vec<acadrust::Handle>)>,
|
2026-06-04 19:14:04 +03:00
|
|
|
|
/// Which status-bar pills the user has chosen to show (persisted).
|
refactor: group ui/ statusbar and window subdirs
Continue tidying the flat ui/ root:
- ui/statusbar/: statusbar.rs becomes statusbar/mod.rs (its
crate::ui::statusbar::* paths are preserved) and absorbs
statusbar_config.rs + statusbar_menu.rs.
- ui/window/: the standalone manager/dialog windows move here —
about, layout_manager, page_setup, plugin_manager, shortcuts,
layers, update_notice, open_progress. The crate::ui::LayerPanel
re-export is repointed so callers are unchanged.
Call sites repointed to the new paths; no compat re-exports beyond the
existing LayerPanel one. ui/ root drops from 21 to 10 flat files with
five subdirs (ribbon, style, popup, statusbar, window).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 23:52:26 +03:00
|
|
|
|
statusbar_config: crate::ui::statusbar::statusbar_config::StatusBarConfig,
|
feat(settings): persist UI preferences across sessions (#68)
DYN, ORTHO, POLAR, the polar increment, the grid toggle and the
object-snap configuration (OSNAP on/off, active snap modes, OTRACK)
reset to defaults on every launch. Persist them to a per-user
settings.txt, matching the existing recent-files / status-bar stores
(plain key=value, no serialization crate).
Settings load on boot and apply to live state; the update wrapper
snapshots them after each message and writes only on change, so a
toggle survives a restart without thrashing the file. LWT is left out
on purpose — it is a drawing header variable, not a UI preference.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 19:50:23 +03:00
|
|
|
|
/// Last persisted user preferences (DYN/OSNAP/OTRACK/POLAR/…). Compared
|
|
|
|
|
|
/// after each message so a change is written to disk exactly once.
|
refactor(config): consolidate settings into one grouped settings.json
App preferences were spread across six flat plain-text files (settings.txt,
recent.txt, recent_limit.txt, statusbar.txt, ribbon.txt, plot.txt), each with a
bespoke line parser and no grouping. Replace them with a single grouped JSON,
serialized via serde (enabling the derive feature; serde_json was already a
dependency).
- New app/config.rs: AppConfig { settings, recent, statusbar, ribbon, plot } →
<config>/settings.json (pretty JSON), load()/save().
- Make UserSettings, StatusBarConfig/StatusPill, CollapseMode and
PlotDialogState serde-derived; #[serde(skip)] marks the plot dialog's runtime
fields so only print preferences persist. Their bespoke .txt load/save is
removed.
- App gains current_config/apply_config/save_config: one AppConfig::load at
startup distributes into live state; every settings-change site
(persist_settings_if_changed, recent add/remove/limit, status-bar pill
toggle, ribbon density, plot commit) routes through save_config, which diffs
against the last write to avoid thrashing.
- Clean start: the old flat files are ignored (no migration). ocad.pgp stays a
separate hand-editable file.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 16:41:34 +03:00
|
|
|
|
last_saved_config: Option<config::AppConfig>,
|
2026-06-09 23:38:08 +03:00
|
|
|
|
/// Active OTRACK alignment `(tracking_point, unit_direction)` when the
|
|
|
|
|
|
/// cursor is on a tracking ray. Lets a typed distance place a point along
|
|
|
|
|
|
/// the ray from the tracking point (issue #69). `None` when not aligned.
|
|
|
|
|
|
otrack_active: Option<(glam::Vec3, glam::Vec3)>,
|
2026-03-22 22:44:32 +03:00
|
|
|
|
/// Whether Tangent snap was enabled before a tangent-pick command started.
|
|
|
|
|
|
pre_cmd_tangent: Option<bool>,
|
2026-07-03 16:05:54 +03:00
|
|
|
|
/// Drawing defaults captured by ADDSELECTED before it adopts the template
|
|
|
|
|
|
/// object's properties, restored when the launched draw command ends so
|
|
|
|
|
|
/// ADDSELECTED doesn't permanently change CLAYER / CECOLOR / … (issue #239).
|
|
|
|
|
|
add_selected_restore: Option<AddSelectedRestore>,
|
2026-06-16 09:53:32 +01:00
|
|
|
|
/// Whether Ortho mode was temporarily suppressed by a command (e.g. RECTANG).
|
|
|
|
|
|
rect_suppressed_ortho: bool,
|
2026-03-22 22:44:32 +03:00
|
|
|
|
/// Orthogonal drawing constraint (F8): constrains picks to 0°/90°/180°/270°.
|
|
|
|
|
|
ortho_mode: bool,
|
2026-04-07 19:30:25 +03:00
|
|
|
|
/// Polar tracking (F10): constrains picks to configurable angle increments.
|
2026-03-22 22:44:32 +03:00
|
|
|
|
polar_mode: bool,
|
2026-04-07 19:30:25 +03:00
|
|
|
|
/// Polar tracking angle increment in degrees (15 / 30 / 45 / 90).
|
|
|
|
|
|
polar_increment_deg: f32,
|
2026-03-22 22:44:32 +03:00
|
|
|
|
/// Show grid lines in the viewport (F7).
|
|
|
|
|
|
show_grid: bool,
|
2026-04-08 00:12:09 +03:00
|
|
|
|
/// Dynamic input overlay (F12): show coordinate tooltip near cursor.
|
|
|
|
|
|
dyn_input: bool,
|
2026-06-18 14:55:08 +01:00
|
|
|
|
/// Controls whether the TEXTEDIT command repeats automatically (0 = Multiple, 1 = Single).
|
|
|
|
|
|
pub texteditmode: bool,
|
2026-06-29 12:49:02 +03:00
|
|
|
|
/// When true (default), saving over an existing file first writes a `.bak`
|
|
|
|
|
|
/// copy of it for recovery (#205). Toggle with the ISAVEBAK command.
|
|
|
|
|
|
pub backup_on_save: bool,
|
2026-06-29 12:58:23 +03:00
|
|
|
|
/// When true (default), the app registers itself as a .dwg/.dxf/.bak file
|
|
|
|
|
|
/// handler on each launch. Toggle with the FILEASSOC command.
|
|
|
|
|
|
pub file_assoc_enabled: bool,
|
2026-06-29 13:17:40 +03:00
|
|
|
|
/// Minutes between autosaves to a `.sv$` recovery file (SAVETIME command);
|
|
|
|
|
|
/// 0 disables autosave.
|
|
|
|
|
|
pub savetime_min: i32,
|
2026-06-26 10:21:46 -07:00
|
|
|
|
/// 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]>,
|
2026-05-27 22:52:55 +03:00
|
|
|
|
/// `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,
|
2026-07-03 12:42:55 +03:00
|
|
|
|
/// Set to the variable name after a bare system-variable query (e.g. a lone
|
|
|
|
|
|
/// `MIRRTEXT`) — the next command-line entry is its new value, empty keeps
|
|
|
|
|
|
/// the current one.
|
|
|
|
|
|
pending_setvar: Option<String>,
|
2026-06-27 01:12:45 +03:00
|
|
|
|
/// Cursor is hovering over the UCS icon body — drives the hover highlight.
|
|
|
|
|
|
ucs_icon_hover: bool,
|
|
|
|
|
|
/// UCS icon is selected (clicked): its grips are shown and draggable.
|
|
|
|
|
|
ucs_icon_selected: bool,
|
|
|
|
|
|
/// Active direct-drag of a UCS icon grip (origin slide or axis rotate).
|
|
|
|
|
|
/// Set on press over a grip, updated on move, committed on release.
|
|
|
|
|
|
ucs_grip_drag: Option<UcsGripKind>,
|
2026-06-27 11:50:11 +03:00
|
|
|
|
/// Pane-move drag in progress: the source pane's tile index, armed by the
|
|
|
|
|
|
/// controls-bar drag handle. On release over another pane the two swap.
|
|
|
|
|
|
pane_move_from: Option<usize>,
|
2026-05-29 00:57:37 +03:00
|
|
|
|
/// `true` once the user has reshaped the dynamic-input field set via
|
|
|
|
|
|
/// the `,` separator during the current command iteration. Tells
|
|
|
|
|
|
/// `sync_dyn_fields` to preserve the user's chosen shape instead of
|
|
|
|
|
|
/// reverting to the command-default when `has_base` flips. Cleared
|
|
|
|
|
|
/// on point commit / command start. See #35.
|
|
|
|
|
|
dyn_user_reshaped: bool,
|
feat(grip): multi-functional hover popup menu
Phase 2 of the grip overhaul: when the cursor dwells on a selected
entity's grip the overlay opens a popup menu with entity-specific
options (Stretch / Add Vertex / Remove Vertex / Convert to
Arc-Line / …). Picking an item dispatches `apply_grip_menu`; the
default `Stretch` entry is a no-op that lets the user click the
grip to start the normal drag.
- `GripMenuItem` + `GripMenuAction` (full vocabulary covering
stretch, add/remove vertex, convert to arc/line, reverse arrows,
text-position variants, hatch parameters, tangent direction).
- `Grippable` trait gains `grip_menu` + `apply_grip_menu` with
trait-default `Stretch`-only impls so every existing entity gets
the popup for free.
- `EntityTypeOps` dispatches both methods across the same entity
list as the existing grip / property machinery.
- `OpenCADStudio` carries `grip_hover` (dwell tracker) +
`grip_popup` (open menu). `update_grip_hover` runs from
`ViewportMove`: hit-tests grips, refreshes the dwell timer, and
opens the popup after 600 ms. Cursor drift past 80 px or a grip
click dismisses it.
- `view.rs` paints the popup as an absolute-positioned column of
buttons at the grip's screen anchor; each button publishes
`Message::GripMenuPick(idx)`.
- `LwPolyline` overrides `Grippable` manually (skips the standard
macro) so its vertex grip exposes Add/Remove Vertex and its
segment-midpoint grip exposes Add Vertex + Convert to Arc/Line.
`apply_grip_menu` performs the edits: vertex insertion at the
chord midpoint inheriting the previous bulge, vertex removal,
and bulge toggle for the arc/line conversion.
2026-05-29 09:30:54 +03:00
|
|
|
|
/// Grip the cursor is currently dwelling on. Set when the cursor
|
|
|
|
|
|
/// stops within `GRIP_THRESHOLD_PX` of a grip; cleared when it
|
|
|
|
|
|
/// drifts away. The instant lets `ViewportMove` detect when the
|
|
|
|
|
|
/// dwell crosses the popup-open threshold.
|
|
|
|
|
|
grip_hover: Option<GripHover>,
|
|
|
|
|
|
/// Open multi-functional grip popup. Persists across mouse moves
|
|
|
|
|
|
/// until dismissed (click outside, ESC, cursor leaves the grip).
|
|
|
|
|
|
grip_popup: Option<GripPopup>,
|
2026-05-29 14:58:35 +03:00
|
|
|
|
grip_pending: Option<GripPendingValue>,
|
2026-06-11 20:11:19 +03:00
|
|
|
|
/// Open dynamic-block visibility-state dropdown.
|
|
|
|
|
|
visibility_popup: Option<visibility::VisibilityPopup>,
|
2026-06-03 16:17:29 +03:00
|
|
|
|
/// A leader line just added via the "Add Leader" grip menu whose arrow is
|
|
|
|
|
|
/// being placed (follows the cursor). `(entity handle, new-arrow grip id)`.
|
|
|
|
|
|
/// Esc before the placement click removes it again.
|
|
|
|
|
|
grip_add_provisional: Option<(acadrust::Handle, usize)>,
|
2026-06-05 10:33:08 +03:00
|
|
|
|
/// Handle hidden from the base tessellation during an in-progress grip
|
|
|
|
|
|
/// drag. While dragging, the edited entity is excluded from the cached
|
|
|
|
|
|
/// wire set and shown as a cheap overlay preview instead, so each move
|
|
|
|
|
|
/// updates only the overlay rather than re-tessellating the whole model.
|
|
|
|
|
|
/// Committed (un-hidden + one re-tess) when the drag ends. `None` = idle.
|
|
|
|
|
|
grip_preview_handle: Option<acadrust::Handle>,
|
2026-06-20 20:43:33 +03:00
|
|
|
|
/// Pending rollover hit-test. Each idle cursor move stashes
|
|
|
|
|
|
/// `(last_move_at, point, tab)` here and clears the live highlight;
|
|
|
|
|
|
/// `HoverDwellTick` runs the pick once the cursor has been still for
|
|
|
|
|
|
/// `HOVER_DWELL_MS`. Skipping the pick mid-stroke avoids the per-frame
|
|
|
|
|
|
/// O(N) wire+hatch+mesh sweep that froze the cursor on large drawings.
|
|
|
|
|
|
hover_dwell: Option<HoverDwell>,
|
2026-06-05 10:44:31 +03:00
|
|
|
|
/// Snapshot of the edited entity taken at the start of a grip drag, used to
|
|
|
|
|
|
/// restore it if the user presses Esc to cancel the drag. The drag mutates
|
|
|
|
|
|
/// the document live (so grips / properties track), so cancel reverts from
|
|
|
|
|
|
/// this backup. Dropped (kept) on a normal commit.
|
|
|
|
|
|
grip_original: Option<acadrust::EntityType>,
|
2026-07-10 10:39:44 +03:00
|
|
|
|
/// Drag-start snapshot of the dragged entity's SDF glyph quads. A whole-
|
|
|
|
|
|
/// entity text move slides these each frame (translating the already-shaped
|
|
|
|
|
|
/// glyphs) instead of re-tessellating the run every cursor move (issue #316).
|
|
|
|
|
|
grip_text_verts: Vec<crate::scene::pipeline::text_gpu::TextVertex>,
|
|
|
|
|
|
/// True when the current grip drag is a rigid whole-entity move of pure text
|
|
|
|
|
|
/// (TEXT / MTEXT, no wire geometry, insertion grip) — the case where the
|
|
|
|
|
|
/// glyphs can just be slid. A reshape grip (e.g. an MTEXT width handle) or an
|
|
|
|
|
|
/// entity with wires re-tessellates instead so the preview stays exact.
|
|
|
|
|
|
grip_text_slide: bool,
|
2026-05-29 20:30:56 +03:00
|
|
|
|
/// Open Quick Select panel state. `None` = panel closed. Filters are
|
|
|
|
|
|
/// applied via `Message::QSelectApply`; the panel is dismissed on
|
|
|
|
|
|
/// Apply / Cancel / Esc / outside-click.
|
|
|
|
|
|
qselect: Option<QSelectState>,
|
2026-04-03 19:25:55 +03:00
|
|
|
|
/// Show the UCS icon in the bottom-left corner of model space (UCSICON).
|
|
|
|
|
|
show_ucs_icon: bool,
|
2026-06-27 01:12:45 +03:00
|
|
|
|
/// Anchor the UCS icon to the projected UCS origin when it is on-screen,
|
|
|
|
|
|
/// falling back to the corner otherwise (UCSICON ORigin / NOorigin).
|
|
|
|
|
|
ucs_icon_at_origin: bool,
|
2026-04-24 01:23:06 +03:00
|
|
|
|
/// Whether the ViewCube 3D gizmo is visible in model space (NAVVCUBE).
|
|
|
|
|
|
show_viewcube: bool,
|
2026-07-10 14:58:48 +03:00
|
|
|
|
/// Measured natural width (px, as `f32` bits) of the active viewport's
|
|
|
|
|
|
/// render-mode control bar, written each frame by its `DensitySwap` and read
|
|
|
|
|
|
/// next frame to decide whether the ViewCube still has room beside it — so
|
|
|
|
|
|
/// the two corner widgets adapt to the bar's real width, not an estimate.
|
|
|
|
|
|
render_bar_w: std::sync::Arc<std::sync::atomic::AtomicU32>,
|
2026-04-24 01:23:06 +03:00
|
|
|
|
/// Whether the Properties panel is shown on the left (PROPERTIES).
|
|
|
|
|
|
show_properties: bool,
|
|
|
|
|
|
/// Whether the document file tabs are shown at the top (FILETAB).
|
|
|
|
|
|
show_file_tabs: bool,
|
|
|
|
|
|
/// Whether the layout/paper-space tabs are shown at the bottom (LAYOUTTAB).
|
|
|
|
|
|
show_layout_tabs: bool,
|
2026-03-22 22:44:32 +03:00
|
|
|
|
/// Last point committed by a drawing command — used as ortho/polar base.
|
fix(precision): keep typed coordinates exact in f64 (#311)
Typed values were quantized to the f32 grid before reaching the
entity (e.g. a length of 2000.8 committed as 2000.8020), worsening
with magnitude. The interaction point layer was all glam::Vec3 (f32).
Two-layer fix:
1. Coordinate backbone -> f64. last_point, last_cursor_world,
dyn_anchor, dyn_ref become DVec3; UcsXform transforms, parse_coord,
the ucs helper fns, dyn_resolve_point, the OTRACK typed-distance
paths, command-line coordinate entry and the coordinate readout all
compute in f64. Screen/GPU/alignment stays f32 (snap engine, render
geometry, projection, UCS-icon/viewcube drawing) with casts only at
those boundaries.
2. Per-command committed storage -> DVec3. on_point already received a
DVec3, but many command impls narrowed it back to f32 (let pt =
pt.as_vec3(); Vec<Vec3> fields) and re-widened at build, quantizing
the committed coordinate. Migrated PLINE, SPLINE, MLINE, LENGTHEN,
INSERT, 3D primitives, every dimension/leader/mleader, ray, revcloud,
wipeout, attdef, table, tolerance, array center, mview, plot_window,
plus the QDIM_PLACE handler and qdim_collect_points. Preview,
hit-test and rubber-band paths keep f32 at the boundary.
Hatch manual boundaries are left for a follow-up: HatchModel.boundary
is structurally Arc<Vec<[f32;2]>> and needs a model-level widen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 21:26:40 +03:00
|
|
|
|
last_point: Option<glam::DVec3>,
|
2026-07-08 20:39:40 +03:00
|
|
|
|
/// Endpoint + unit exit-tangent of the most recently drawn line/arc, so
|
|
|
|
|
|
/// `ARC_CONT` (Arc → Continue) can start tangentially from where drawing
|
|
|
|
|
|
/// ended. `None` once a non-line/arc entity is committed.
|
|
|
|
|
|
cont_anchor: Option<(glam::DVec3, glam::DVec3)>,
|
2026-03-22 22:44:32 +03:00
|
|
|
|
/// OS window Id for the floating Layer Properties Manager (None when closed).
|
|
|
|
|
|
/// OS window Id of the primary application window.
|
|
|
|
|
|
main_window: Option<window::Id>,
|
2026-04-08 22:36:28 +03:00
|
|
|
|
// ── Floating panel windows ────────────────────────────────────────────
|
2026-06-14 11:14:14 +03:00
|
|
|
|
/// Standalone "Select Color" palette window + the field it targets.
|
|
|
|
|
|
color_pick_target: Option<ColorPickTarget>,
|
2026-06-16 19:07:25 +03:00
|
|
|
|
/// The open in-canvas modal dialog, if any (Plan B: shared overlay instead
|
|
|
|
|
|
/// of OS windows).
|
|
|
|
|
|
active_modal: Option<ModalKind>,
|
2026-06-29 11:47:37 +03:00
|
|
|
|
/// Set once the user acknowledges the AEC-drop warning, so re-entering the
|
|
|
|
|
|
/// save path proceeds instead of re-showing the warning.
|
|
|
|
|
|
aec_drop_acknowledged: bool,
|
|
|
|
|
|
/// Number of unsupported objects shown in the AEC-drop warning modal.
|
|
|
|
|
|
aec_drop_count: usize,
|
2026-07-01 17:44:19 +03:00
|
|
|
|
/// Layers awaiting a "delete non-empty layer(s)" confirmation: `(names,
|
|
|
|
|
|
/// total object count)`. Set when the user deletes one or more layers that
|
|
|
|
|
|
/// still have objects; the warning modal reads it, and confirming erases
|
|
|
|
|
|
/// those objects too.
|
|
|
|
|
|
layer_delete_pending: Option<(Vec<String>, usize)>,
|
2026-06-17 09:59:17 +03:00
|
|
|
|
/// Pixel offset of the active modal from screen-centre (drag-to-move).
|
|
|
|
|
|
/// Reset to zero whenever a modal closes so each dialog opens centred.
|
|
|
|
|
|
modal_offset: iced::Vector,
|
|
|
|
|
|
/// Cursor position from the previous drag-move while the modal title bar is
|
|
|
|
|
|
/// held; `None` before the first move of a drag.
|
|
|
|
|
|
modal_drag_last: Option<Point>,
|
|
|
|
|
|
/// True while the modal title bar is held (a drag is in progress).
|
|
|
|
|
|
modal_dragging: bool,
|
2026-07-01 08:05:25 +03:00
|
|
|
|
// ── Attribute editor dialog (ATTEDIT / double-click a block) ───────────
|
|
|
|
|
|
/// INSERT whose attributes the editor modal is editing (`None` = closed).
|
|
|
|
|
|
attr_editor_handle: Option<acadrust::Handle>,
|
|
|
|
|
|
/// Block name shown in the editor's title bar.
|
|
|
|
|
|
attr_editor_block: String,
|
feat(attedit): enhanced attribute editor with 3 tabs
Grow the attribute editor into a three-tab enhanced editor:
- Attribute: the tag/prompt/value list; click a row to select it, edit
its value below.
- Text Options: the selected attribute's Text Style, Justification,
Height, Rotation, Width Factor, Oblique Angle, Backwards, Upside down.
- Properties: the selected attribute's Layer, Linetype, Color,
Lineweight (the colour list also surfaces a non-standard current
colour so it displays and round-trips).
Applying writes value, text-formatting and common-property edits back to
each attribute positionally (guarded on block name + count), with undo
and a repaint. OK/Cancel are replaced by a single Apply button in a top
toolbar that commits but keeps the dialog open, matching the other modal
windows — the frame ✕ closes and discards un-applied edits. The frame
adopts the shared style-window palette, tab styling and sizing.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 09:06:03 +03:00
|
|
|
|
/// Working copy of the block's attributes, in the same order as
|
|
|
|
|
|
/// `Insert::attributes`. Each row carries the value plus the text-option and
|
|
|
|
|
|
/// common-property fields the editor edits; written back on OK.
|
|
|
|
|
|
attr_editor_rows: Vec<crate::ui::window::attribute_editor::AttrRow>,
|
|
|
|
|
|
/// Which editor tab is showing.
|
|
|
|
|
|
attr_editor_tab: crate::ui::window::attribute_editor::AttrTab,
|
|
|
|
|
|
/// Highlighted attribute row driving the Text Options / Properties tabs.
|
|
|
|
|
|
attr_editor_selected: usize,
|
2026-06-17 10:15:38 +03:00
|
|
|
|
/// Plugin ids the user turned off in the Plugin Manager. Disabled plugins
|
|
|
|
|
|
/// keep their manifest listed but drop their ribbon tab and command
|
|
|
|
|
|
/// dispatch. Persisted via [`settings::UserSettings::disabled_plugins`].
|
|
|
|
|
|
disabled_plugins: rustc_hash::FxHashSet<String>,
|
2026-06-17 11:43:24 +03:00
|
|
|
|
/// External add-on packages found in the plugins folder, refreshed when the
|
2026-06-17 14:48:56 +03:00
|
|
|
|
/// Plugin Manager opens.
|
2026-06-17 11:43:24 +03:00
|
|
|
|
external_plugins: Vec<crate::plugin::external::ExternalPlugin>,
|
2026-06-17 14:48:56 +03:00
|
|
|
|
/// Ids of external packages actually loaded this session (a subset of
|
|
|
|
|
|
/// `external_plugins` — compatible, with a library, dlopen'd at startup).
|
|
|
|
|
|
loaded_plugin_ids: rustc_hash::FxHashSet<String>,
|
2026-06-17 15:24:56 +03:00
|
|
|
|
/// Curated plugin registry fetched from the OpenCADStudio repo.
|
|
|
|
|
|
plugin_registry: Vec<crate::plugin::external::RegistryEntry>,
|
|
|
|
|
|
/// User-linked plugin source repos (`owner/repo`) beyond the curated list.
|
|
|
|
|
|
plugin_repos: Vec<String>,
|
|
|
|
|
|
/// Add-repository text field in the Plugin Manager.
|
|
|
|
|
|
plugin_repo_input: String,
|
|
|
|
|
|
/// Installable release tags fetched per linked repo (for the dropdown).
|
|
|
|
|
|
repo_release_tags: rustc_hash::FxHashMap<String, Vec<String>>,
|
|
|
|
|
|
/// The release tag currently selected per linked repo.
|
|
|
|
|
|
repo_selected_tag: rustc_hash::FxHashMap<String, String>,
|
|
|
|
|
|
/// Last marketplace status / error line shown in the Plugin Manager.
|
|
|
|
|
|
marketplace_status: String,
|
2026-06-17 11:13:12 +03:00
|
|
|
|
/// PDSIZE text buffer for the Point Style (DDPTYPE) dialog.
|
|
|
|
|
|
point_size_buf: String,
|
|
|
|
|
|
/// Point Style size mode: `true` = relative to screen, `false` = absolute.
|
|
|
|
|
|
/// Tracked separately from the PDSIZE sign so a size of 0 (sign-less) still
|
|
|
|
|
|
/// remembers which radio is active.
|
|
|
|
|
|
point_size_relative: bool,
|
2026-05-16 19:55:57 +03:00
|
|
|
|
/// New-release notification window — opened on startup when the
|
|
|
|
|
|
/// GitHub releases API reports a newer version than this build.
|
2026-06-12 07:50:41 +00:00
|
|
|
|
/// First-launch "make Open CAD Studio the default for .dwg/.dxf?" prompt
|
|
|
|
|
|
/// window. Shown once, gated on `default_assoc_prompted`.
|
|
|
|
|
|
/// Whether the one-time default-association prompt has already been shown.
|
|
|
|
|
|
/// Persisted via [`settings::UserSettings`] so it survives restarts.
|
|
|
|
|
|
default_assoc_prompted: bool,
|
2026-06-20 13:59:20 +03:00
|
|
|
|
/// Read-only session (`--read-only`): editing is allowed but every save
|
|
|
|
|
|
/// path is refused. Set once at boot from the CLI config.
|
|
|
|
|
|
read_only: bool,
|
2026-05-16 19:55:57 +03:00
|
|
|
|
/// Tag of the latest available release (without the leading "v"),
|
|
|
|
|
|
/// e.g. `"0.3.0"`. `None` when up-to-date or check hasn't returned.
|
|
|
|
|
|
update_notice_version: Option<String>,
|
2026-05-22 12:55:37 +03:00
|
|
|
|
/// Release-notes body for the version above (GitHub release "body"
|
|
|
|
|
|
/// markdown, as returned by the API). May be empty when the release
|
|
|
|
|
|
/// shipped without notes.
|
|
|
|
|
|
update_notice_body: Option<String>,
|
2026-03-22 22:44:32 +03:00
|
|
|
|
/// In-memory clipboard: cloned entities waiting to be pasted.
|
|
|
|
|
|
clipboard: Vec<acadrust::EntityType>,
|
feat(commands): expand standard CAD command coverage
Integrate a large batch of standard CAD commands and fix several
name/behaviour mismatches, with no product references in code.
3D solids (reusing the truck B-rep pipeline):
- SLICE, INTERFERE, PRESSPULL, POLYSOLID, PYRAMID, SECTION
- 3DROTATE, 3DMIRROR, 3DALIGN (cached-solid transforms)
Annotation / text:
- ARCTEXT, SPLINEFIT (Catmull-Rom to Bezier), DIMJOGGED, OBJECTSCALE
(XData markers honoured by the existing geometry-gen / tessellator)
Blocks / misc:
- NCOPY (block-content extraction via explode_entity)
- OPTIONS, SYNCPVIEWPORTS, THICKEN, plus earlier toggles, aliases,
system variables, and layer/style/view commands
POINTCLOUD/RECAP and UNDERLAYLAYERS/UOSNAP are intentionally left out:
they require a proprietary binary / PDF-layer parser, a GPU point
renderer, and real test files.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 08:23:58 +03:00
|
|
|
|
/// Entities removed by the most recent ERASE, kept so OOPS can restore them.
|
|
|
|
|
|
oops_cache: Vec<acadrust::EntityType>,
|
2026-07-10 11:16:13 +03:00
|
|
|
|
/// Paste anchor: lower-left corner of the clipboard entities' bounding box
|
|
|
|
|
|
/// (or the point picked by COPYBASE). This point lands under the cursor at
|
|
|
|
|
|
/// paste time.
|
|
|
|
|
|
clipboard_base: glam::DVec3,
|
2026-06-18 14:34:32 +03:00
|
|
|
|
/// Table records (layer / linetype / text + dim style) the clipboard
|
|
|
|
|
|
/// entities reference, captured from the source drawing at copy time so a
|
|
|
|
|
|
/// paste into a *different* drawing can recreate any that are missing —
|
|
|
|
|
|
/// otherwise the pasted entities would dangle on a non-existent layer.
|
|
|
|
|
|
clipboard_deps: ClipboardDeps,
|
2026-05-30 21:54:35 +03:00
|
|
|
|
/// True while the Shift key is held — drives subtractive pick (Shift+click
|
|
|
|
|
|
/// removes the picked entity from the selection). Tracked from keyboard
|
|
|
|
|
|
/// modifier-change events since mouse click messages carry no modifiers.
|
|
|
|
|
|
shift_down: bool,
|
2026-07-01 17:44:19 +03:00
|
|
|
|
/// True while Ctrl/Cmd is held — drives Ctrl-click multi-select in the
|
|
|
|
|
|
/// Layer Manager. Tracked from modifier-change events.
|
|
|
|
|
|
ctrl_down: bool,
|
feat: Implement in-place MText editor with formatting toolbar and live preview
- Added SVG icons for text formatting options (bold, italic, underline, overline, strike, uppercase, lowercase).
- Introduced a new `MTextEditorState` struct to manage the state of the MText editor.
- Implemented functionality to open the MText editor from the command line and edit existing MText entities.
- Added message handling for various MText editor actions (formatting, height, style, font, justification, alignment, line spacing).
- Created a dedicated preview area for MText rendering within the editor.
- Updated the command structure to support opening the MText editor and handling text input.
- Refactored existing MText command logic to integrate with the new editor.
2026-05-30 23:24:06 +03:00
|
|
|
|
/// Open in-place MText editor (toolbar + text area + live preview), if any.
|
|
|
|
|
|
mtext_editor: Option<mtext_editor::MTextEditorState>,
|
2026-05-31 14:44:18 +03:00
|
|
|
|
/// Open in-place single-line TEXT editor (plain text-entry box), if any.
|
|
|
|
|
|
text_inline: Option<text_inline::TextInlineState>,
|
2026-03-31 07:13:36 +03:00
|
|
|
|
/// Which layout tab has its context menu open (None = closed).
|
|
|
|
|
|
layout_context_menu: Option<String>,
|
|
|
|
|
|
/// Inline rename state: (original_name, current_edit_value).
|
|
|
|
|
|
layout_rename_state: Option<(String, String)>,
|
2026-03-31 07:24:16 +03:00
|
|
|
|
/// Timestamp of the previous viewport left-click release (for double-click detection).
|
|
|
|
|
|
last_vp_click_time: Option<Instant>,
|
|
|
|
|
|
/// Screen position of the previous viewport left-click release.
|
|
|
|
|
|
last_vp_click_pos: Option<Point>,
|
feat(mtext): structured editor with selection, copy, and visible bold
Rewrite the in-app MTEXT editor to run on acadrust's structured
MTextDocument model end-to-end: load via parse_mtext, edit a flat Cell
model, serialize via to_mtext_string. Retires the hand-rolled format-code
string handling, the parallel visible_spans parser, the composed_value
prefix, and the Edit-raw-code tab.
Selection and copy:
- double-click selects the word, triple-click selects all (click-counting)
- Ctrl+A selects all text
- Ctrl+C copies the selected text to the clipboard
Formatting:
- per-selection Bold/Italic/Underline/Overline/Strike/Case/Alignment
- Bold renders as a visibly thicker SDF pen, keyed (family, char, bold)
through the whole text pipeline; the resolved style font name is stamped
so the \f...|b1; code survives serialization
- per-selection font and color changes
- editor color dropdown reuses the Properties panel color selector
Global toolbar defaults (font/color/oblique/width/tracking) fold onto the
document as real span properties instead of a leading code prefix; a
trailing empty paragraph is preserved so Enter at the end keeps its line.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 20:18:50 +03:00
|
|
|
|
/// MText preview click tracking for double-click (select word) / triple-click
|
|
|
|
|
|
/// (select all): the previous click's time + visible offset, and the run
|
|
|
|
|
|
/// length of quick same-spot clicks (1..=3).
|
|
|
|
|
|
mtext_click_time: Option<Instant>,
|
|
|
|
|
|
mtext_click_off: usize,
|
|
|
|
|
|
mtext_click_count: u8,
|
feat(plot): unified plot/print dialog, retire Page Setup
Replace the old Page Setup panel with a single OCS-styled plot/print
dialog (dropdowns for printer, paper, orientation, rotation, plot area,
scale, quality, shaded plot; checkboxes for plot options). PLOT / PRINT
/ PAGESETUP all open it.
- Printer output via lp: -d printer, -n copies, -o ColorModel=Gray,
print-quality, Resolution; Windows printto verb; list_printers via
lpstat. Preview opens a temp PDF in the OS viewer (never a save
dialog). PDF export reuses the tested pipeline.
- Window pick returns to the new dialog (was the old Page Setup) and the
clipped window plot drives PDF / printer / preview.
- Remember the user's print preferences across sessions in
config/plot.txt (printer, copies, quality, output options, area,
scale); drawing-specific fields (offset/center/rotation) seed from the
layout. Default plot area Window, scale Fit.
- Delete src/ui/window/page_setup.rs and its ModalKind / Message /
buffer state; fold on_page_setup_commit into apply_plot_page_settings.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 10:27:54 +03:00
|
|
|
|
/// Plot scale for model-space window plots: "Fit" | "1:1" | … | "2:1".
|
|
|
|
|
|
plot_scale: String,
|
2026-07-05 23:12:52 +02:00
|
|
|
|
/// Pending model-space plot window (x0, y0, x1, y1) in world XY, or None.
|
|
|
|
|
|
plot_window: Option<(f64, f64, f64, f64)>,
|
|
|
|
|
|
plot_format: crate::io::paper_sizes::PaperSize,
|
|
|
|
|
|
plot_orientation: crate::io::paper_sizes::Orientation,
|
feat(plot): unified plot/print dialog, retire Page Setup
Replace the old Page Setup panel with a single OCS-styled plot/print
dialog (dropdowns for printer, paper, orientation, rotation, plot area,
scale, quality, shaded plot; checkboxes for plot options). PLOT / PRINT
/ PAGESETUP all open it.
- Printer output via lp: -d printer, -n copies, -o ColorModel=Gray,
print-quality, Resolution; Windows printto verb; list_printers via
lpstat. Preview opens a temp PDF in the OS viewer (never a save
dialog). PDF export reuses the tested pipeline.
- Window pick returns to the new dialog (was the old Page Setup) and the
clipped window plot drives PDF / printer / preview.
- Remember the user's print preferences across sessions in
config/plot.txt (printer, copies, quality, output options, area,
scale); drawing-specific fields (offset/center/rotation) seed from the
layout. Default plot area Window, scale Fit.
- Delete src/ui/window/page_setup.rs and its ModalKind / Message /
buffer state; fold on_page_setup_commit into apply_plot_page_settings.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 10:27:54 +03:00
|
|
|
|
/// Backing state for the full Plot / Print dialog.
|
|
|
|
|
|
plot_dialog: crate::ui::window::plot::PlotDialogState,
|
2026-07-09 12:25:46 +03:00
|
|
|
|
/// Snapshot of the dialog's settings taken when it opened, restored by the
|
|
|
|
|
|
/// `<previous>` list entry.
|
|
|
|
|
|
plot_prev: Option<crate::ui::window::plot::PlotDialogState>,
|
2026-04-06 14:25:13 +03:00
|
|
|
|
|
2026-04-06 14:37:23 +03:00
|
|
|
|
// ── Plot Style Table ──────────────────────────────────────────────────
|
|
|
|
|
|
/// Currently loaded CTB/STB table (None = no override).
|
|
|
|
|
|
active_plot_style: Option<crate::io::plot_style::PlotStyleTable>,
|
|
|
|
|
|
|
2026-04-06 14:54:05 +03:00
|
|
|
|
// ── MLineStyle Dialog ─────────────────────────────────────────────────
|
|
|
|
|
|
mlstyle_selected: String,
|
|
|
|
|
|
|
2026-06-03 20:25:14 +03:00
|
|
|
|
// ── MLeaderStyle Dialog ───────────────────────────────────────────────
|
|
|
|
|
|
mleaderstyle_selected: String,
|
2026-06-14 10:42:39 +03:00
|
|
|
|
/// Colour field whose expanded palette is open (line/text/block).
|
|
|
|
|
|
mls_color_open: Option<&'static str>,
|
2026-06-03 20:25:14 +03:00
|
|
|
|
mls_landing_distance: String,
|
|
|
|
|
|
mls_landing_gap: String,
|
|
|
|
|
|
mls_arrowhead_size: String,
|
|
|
|
|
|
mls_text_height: String,
|
|
|
|
|
|
mls_scale_factor: String,
|
|
|
|
|
|
mls_break_gap: String,
|
|
|
|
|
|
mls_first_seg_angle: String,
|
|
|
|
|
|
mls_second_seg_angle: String,
|
|
|
|
|
|
mls_max_points: String,
|
|
|
|
|
|
mls_default_text: String,
|
|
|
|
|
|
mls_line_color: String,
|
|
|
|
|
|
mls_text_color: String,
|
feat(styles): complete MLeader field coverage + Dimension DIMUNIT
MLeader Style editor now exposes every MultiLeaderStyle field: line
weight, line type / arrowhead block / text style / block-content blocks
(handle dropdowns from the document's tables), the four text attachments
+ attachment direction, the full block-content group (color, connection,
rotation, X/Y/Z scale, enable scale/rotation), leader & multileader draw
order, align space, and description.
Dimension Style editor adds the last uncovered DIMUNIT field.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 21:04:22 +03:00
|
|
|
|
mls_description: String,
|
|
|
|
|
|
mls_line_weight: String,
|
|
|
|
|
|
mls_align_space: String,
|
|
|
|
|
|
mls_block_color: String,
|
|
|
|
|
|
mls_block_rotation: String,
|
|
|
|
|
|
mls_block_scale_x: String,
|
|
|
|
|
|
mls_block_scale_y: String,
|
|
|
|
|
|
mls_block_scale_z: String,
|
2026-06-03 20:25:14 +03:00
|
|
|
|
|
2026-04-06 14:57:11 +03:00
|
|
|
|
// ── TableStyle Dialog ─────────────────────────────────────────────────
|
|
|
|
|
|
tablestyle_selected: String,
|
2026-06-03 20:13:30 +03:00
|
|
|
|
/// Edit buffers for the table style's general margins.
|
|
|
|
|
|
ts_hmargin: String,
|
|
|
|
|
|
ts_vmargin: String,
|
2026-06-03 21:09:37 +03:00
|
|
|
|
/// General table-style description buffer.
|
|
|
|
|
|
ts_description: String,
|
2026-06-03 20:28:58 +03:00
|
|
|
|
/// Per-cell edit buffers, indexed 0=Data, 1=Header, 2=Title.
|
2026-06-14 10:42:39 +03:00
|
|
|
|
/// Table cell colour field (row class, "textcolor"/"fillcolor") whose
|
|
|
|
|
|
/// expanded palette is open.
|
|
|
|
|
|
ts_color_open: Option<(u8, &'static str)>,
|
2026-06-03 20:28:58 +03:00
|
|
|
|
ts_cell_textstyle: [String; 3],
|
|
|
|
|
|
ts_cell_height: [String; 3],
|
|
|
|
|
|
ts_cell_textcolor: [String; 3],
|
|
|
|
|
|
ts_cell_fillcolor: [String; 3],
|
2026-06-03 21:09:37 +03:00
|
|
|
|
ts_cell_datatype: [String; 3],
|
|
|
|
|
|
ts_cell_unittype: [String; 3],
|
|
|
|
|
|
ts_cell_format: [String; 3],
|
|
|
|
|
|
/// Per-cell, per-border numeric buffers ([cell][border], border order:
|
|
|
|
|
|
/// 0=left 1=right 2=top 3=bottom 4=horizontal-inside 5=vertical-inside).
|
|
|
|
|
|
ts_border_lw: [[String; 6]; 3],
|
|
|
|
|
|
ts_border_color: [[String; 6]; 3],
|
|
|
|
|
|
ts_border_spacing: [[String; 6]; 3],
|
2026-04-06 14:57:11 +03:00
|
|
|
|
|
feat(styles): unify the five style managers behind a shared frame
The text/dimension/table/multileader/multiline managers were hand-copied,
so their list CRUD kept drifting and breaking: Dimension's New created
nothing (#73), new text styles never reached the ribbon dropdown (#74),
and several were added with a NULL handle (dropped on DWG save, #67).
Collapse the duplicated parts into three shared pieces and leave only the
per-manager property editor bespoke:
- app/style_ops.rs: one StyleKind-dispatched CRUD layer (new / copy /
delete / rename) handling handle allocation, ribbon sync, Table re-key
and name-reference rewrites in a single place.
- ui/style_manager.rs: the window scaffold — uniform toolbar (New / Copy /
Delete on the left, Set Current + Apply on the right), style list and
chrome. Each manager now only builds its editor.
- ui/style_list.rs: the list row — single click selects, double click
starts an inline rename.
New capabilities, now consistent across all five:
- Copy duplicates the selected style.
- Double-click a name to rename it inline.
- Right side is always Set Current + Apply (added Table set-current;
MLine apply is a documented placeholder until its editor is editable).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 15:03:34 +03:00
|
|
|
|
// ── Shared style-manager inline rename ────────────────────────────────
|
|
|
|
|
|
/// Original name of the style currently being renamed inline (double-click
|
|
|
|
|
|
/// a style name in any style manager). `None` when not renaming.
|
|
|
|
|
|
style_rename: Option<String>,
|
|
|
|
|
|
/// Edit buffer for the inline rename text input.
|
|
|
|
|
|
style_rename_buf: String,
|
2026-06-10 15:38:32 +03:00
|
|
|
|
/// Active style-manager transaction. Edits mutate the document live for an
|
|
|
|
|
|
/// in-dialog preview but only persist on Apply; closing without Apply
|
|
|
|
|
|
/// restores this snapshot. `None` when no style manager is staging.
|
|
|
|
|
|
style_stage: Option<style_ops::StyleStage>,
|
feat(styles): unify the five style managers behind a shared frame
The text/dimension/table/multileader/multiline managers were hand-copied,
so their list CRUD kept drifting and breaking: Dimension's New created
nothing (#73), new text styles never reached the ribbon dropdown (#74),
and several were added with a NULL handle (dropped on DWG save, #67).
Collapse the duplicated parts into three shared pieces and leave only the
per-manager property editor bespoke:
- app/style_ops.rs: one StyleKind-dispatched CRUD layer (new / copy /
delete / rename) handling handle allocation, ribbon sync, Table re-key
and name-reference rewrites in a single place.
- ui/style_manager.rs: the window scaffold — uniform toolbar (New / Copy /
Delete on the left, Set Current + Apply on the right), style list and
chrome. Each manager now only builds its editor.
- ui/style_list.rs: the list row — single click selects, double click
starts an inline rename.
New capabilities, now consistent across all five:
- Copy duplicates the selected style.
- Double-click a name to rename it inline.
- Right side is always Set Current + Apply (added Table set-current;
MLine apply is a documented placeholder until its editor is editable).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 15:03:34 +03:00
|
|
|
|
|
2026-04-06 15:01:25 +03:00
|
|
|
|
// ── TextStyle Font Browser ────────────────────────────────────────────
|
|
|
|
|
|
textstyle_selected: String,
|
|
|
|
|
|
/// Edit buffer for font file name.
|
|
|
|
|
|
textstyle_font: String,
|
|
|
|
|
|
/// Edit buffer for width factor.
|
|
|
|
|
|
textstyle_width: String,
|
|
|
|
|
|
/// Edit buffer for oblique angle (degrees).
|
|
|
|
|
|
textstyle_oblique: String,
|
2026-06-03 17:43:12 +03:00
|
|
|
|
/// Edit buffer for fixed text height (0 = variable).
|
|
|
|
|
|
textstyle_height: String,
|
|
|
|
|
|
/// Edit buffer for big-font file name.
|
|
|
|
|
|
textstyle_bigfont: String,
|
|
|
|
|
|
/// Edit buffer for TrueType font name.
|
|
|
|
|
|
textstyle_ttf: String,
|
2026-04-06 15:01:25 +03:00
|
|
|
|
|
2026-04-08 18:31:09 +03:00
|
|
|
|
// ── Color Scheme ──────────────────────────────────────────────────────
|
|
|
|
|
|
active_theme: Theme,
|
|
|
|
|
|
|
2026-04-08 18:34:10 +03:00
|
|
|
|
// ── Keyboard Shortcut Editor ──────────────────────────────────────────
|
|
|
|
|
|
/// User-defined function-key overrides: "F3" → command string.
|
2026-06-05 00:35:53 +03:00
|
|
|
|
shortcut_overrides: rustc_hash::FxHashMap<String, String>,
|
2026-04-08 18:34:10 +03:00
|
|
|
|
|
feat(alias): centralize command aliases in user-editable ocad.pgp (#286/#288)
Command aliases (L→LINE, CC→COPYCLIP) were scattered across dispatch match
arms and CommandRegistration name arrays. Move them all into a single
user-editable file:
- assets/ocad.pgp: shipped default alias table (ALIAS,*COMMAND format),
embedded and copied to config/ocad.pgp on first launch; only that user
copy is read after. AA→AREA default closes #286.
- resolve_alias() rewrites the leading command token at the top of
dispatch_command_inner, so aliases resolve for every path (families,
plugins, Repeat menu). Args after the first space are untouched.
- Autocomplete hides alias keys and, when the whole input is an alias,
surfaces its target command as the top (highlighted) suggestion so the
highlight matches what Enter runs (AA→AREA, L→LINE).
- ALIASEDIT opens a new editor modal (add/edit/delete rows) with an Apply
button; Apply persists to ocad.pgp, closing discards unapplied edits.
- Strip alias tokens from all 8 dispatch match-arm files and 71 module
CommandRegistration arrays, keeping canonical/synonym/distinct-command
names. ocad.pgp is now the sole source: deleting or remapping an alias
there takes effect. CUI stays on the keyboard-shortcut panel.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 12:39:22 +03:00
|
|
|
|
// ── Command Aliases ───────────────────────────────────────────────────
|
|
|
|
|
|
/// Command-line aliases: uppercase abbreviation → uppercase command
|
|
|
|
|
|
/// ("L" → "LINE"). Loaded from `ocad.pgp` at boot and consulted before
|
|
|
|
|
|
/// every dispatch (`resolve_alias`); user-editable via ALIASEDIT. See
|
|
|
|
|
|
/// [`alias`].
|
|
|
|
|
|
command_aliases: rustc_hash::FxHashMap<String, String>,
|
|
|
|
|
|
/// Working buffer for the ALIASEDIT modal: `(alias, command)` rows being
|
|
|
|
|
|
/// edited. Seeded from `command_aliases` on open, committed back on close.
|
|
|
|
|
|
alias_editor_rows: Vec<(String, String)>,
|
|
|
|
|
|
|
2026-04-08 18:28:30 +03:00
|
|
|
|
// ── Layout Manager Panel ──────────────────────────────────────────────
|
|
|
|
|
|
layout_manager_selected: String,
|
|
|
|
|
|
layout_manager_rename_buf: String,
|
|
|
|
|
|
|
2026-07-13 10:39:14 +03:00
|
|
|
|
// ── Annotation-scale Manager ──────────────────────────────────────────
|
|
|
|
|
|
scale_manager_selected: String,
|
|
|
|
|
|
scale_manager_paper_buf: String,
|
|
|
|
|
|
scale_manager_drawing_buf: String,
|
2026-07-13 14:37:33 +03:00
|
|
|
|
/// The scale row being renamed inline (double-click), + its edit buffer.
|
|
|
|
|
|
scale_rename: Option<String>,
|
|
|
|
|
|
scale_rename_buf: String,
|
2026-07-13 22:38:29 +03:00
|
|
|
|
/// The entity the Annotation Object Scale dialog is editing (its per-object
|
|
|
|
|
|
/// annotation-scale membership).
|
|
|
|
|
|
anno_object_scale_target: Option<acadrust::types::Handle>,
|
2026-07-13 12:14:40 +03:00
|
|
|
|
/// Open transaction for the scale manager — restored if the window closes
|
|
|
|
|
|
/// without Apply, mirroring the style managers' staging.
|
|
|
|
|
|
scale_stage: Option<crate::app::style_ops::ScaleStage>,
|
2026-07-13 10:39:14 +03:00
|
|
|
|
|
2026-04-08 18:19:48 +03:00
|
|
|
|
// ── Plot Style Panel ──────────────────────────────────────────────────
|
|
|
|
|
|
/// Selected ACI index in the panel (1-255).
|
|
|
|
|
|
plotstyle_panel_aci: u8,
|
|
|
|
|
|
/// Edit buffers for the selected entry.
|
|
|
|
|
|
ps_color_buf: String,
|
|
|
|
|
|
ps_lineweight_buf: String,
|
|
|
|
|
|
ps_screening_buf: String,
|
|
|
|
|
|
|
2026-05-13 09:59:07 +03:00
|
|
|
|
// ── File-open progress ────────────────────────────────────────────────
|
|
|
|
|
|
/// `Some` while a CAD file is loading — drives the modal overlay.
|
|
|
|
|
|
/// Cleared when the load finishes, errors, or the user cancels.
|
|
|
|
|
|
pub(super) opening: Option<OpenProgress>,
|
|
|
|
|
|
|
2026-05-01 11:31:11 +03:00
|
|
|
|
// ── Unsaved-changes dialog ────────────────────────────────────────────
|
|
|
|
|
|
/// Set when the user tries to close a tab or quit while there are unsaved changes.
|
|
|
|
|
|
pending_close: Option<PendingClose>,
|
|
|
|
|
|
/// OS window for the unsaved-changes confirmation dialog.
|
|
|
|
|
|
|
2026-05-01 16:12:32 +03:00
|
|
|
|
// ── Custom Save-As dialog ─────────────────────────────────────────────
|
2026-05-01 16:20:38 +03:00
|
|
|
|
/// OS window for the custom Save As dialog.
|
2026-05-01 16:12:32 +03:00
|
|
|
|
/// Currently selected format string, e.g. "DWG 2013".
|
|
|
|
|
|
save_dialog_format: String,
|
2026-07-11 09:12:36 +03:00
|
|
|
|
/// Editable filename (without path), e.g. "drawing.dwg" — seeds the native
|
|
|
|
|
|
/// OS save dialog's default name (native) or the download name (web).
|
2026-05-01 16:12:32 +03:00
|
|
|
|
save_dialog_filename: String,
|
|
|
|
|
|
/// True when triggered from the unsaved-changes flow.
|
|
|
|
|
|
save_dialog_for_unsaved: bool,
|
|
|
|
|
|
|
2026-04-06 14:25:13 +03:00
|
|
|
|
// ── DimStyle Dialog ───────────────────────────────────────────────────
|
|
|
|
|
|
/// Name of the style currently shown in the dialog.
|
|
|
|
|
|
dimstyle_selected: String,
|
2026-06-14 10:42:39 +03:00
|
|
|
|
/// Which colour field currently has its expanded palette open (if any).
|
|
|
|
|
|
ds_color_open: Option<DsField>,
|
2026-04-06 14:25:13 +03:00
|
|
|
|
/// Active tab: 0=Lines, 1=Arrows, 2=Text, 3=Scale/Units, 4=Tolerances.
|
|
|
|
|
|
dimstyle_tab: u8,
|
|
|
|
|
|
// Edit buffers (strings while typing):
|
2026-05-12 10:36:15 +03:00
|
|
|
|
ds_dimdle: String,
|
|
|
|
|
|
ds_dimdli: String,
|
|
|
|
|
|
ds_dimgap: String,
|
|
|
|
|
|
ds_dimexe: String,
|
|
|
|
|
|
ds_dimexo: String,
|
|
|
|
|
|
ds_dimsd1: bool,
|
|
|
|
|
|
ds_dimsd2: bool,
|
|
|
|
|
|
ds_dimse1: bool,
|
|
|
|
|
|
ds_dimse2: bool,
|
|
|
|
|
|
ds_dimasz: String,
|
|
|
|
|
|
ds_dimcen: String,
|
|
|
|
|
|
ds_dimtsz: String,
|
|
|
|
|
|
ds_dimtxt: String,
|
|
|
|
|
|
ds_dimtxsty: String,
|
|
|
|
|
|
ds_dimtad: String,
|
|
|
|
|
|
ds_dimtih: bool,
|
|
|
|
|
|
ds_dimtoh: bool,
|
|
|
|
|
|
ds_dimscale: String,
|
|
|
|
|
|
ds_dimlfac: String,
|
|
|
|
|
|
ds_dimlunit: String,
|
|
|
|
|
|
ds_dimdec: String,
|
|
|
|
|
|
ds_dimpost: String,
|
|
|
|
|
|
ds_dimtol: bool,
|
|
|
|
|
|
ds_dimlim: bool,
|
|
|
|
|
|
ds_dimtp: String,
|
|
|
|
|
|
ds_dimtm: String,
|
|
|
|
|
|
ds_dimtdec: String,
|
|
|
|
|
|
ds_dimtfac: String,
|
2026-06-03 17:49:38 +03:00
|
|
|
|
ds_annotative: bool,
|
feat(dimstyle): expand Dimension Style editor to the full field set
Adds the remaining ~40 DIMSTYLE variables across the tabs (and a new
Alternate Units tab): dim/ext line colors+lineweights, fixed-length ext
lines, separate arrow blocks, arc symbol, jog angle, text color/justify/
vertical pos/fill/direction, fit options (DIMATFIT/DIMTIX/DIMSOXD/DIMTMOVE/
DIMUPT/DIMTOFL/DIMFIT), decimal separator, rounding, zero suppression,
fraction + angular units, alternate units, and tolerance vert-just/zero
suppression. Only the arrowhead-block / extension-line-linetype Handle
references are left (they need a block/linetype picker).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 20:09:08 +03:00
|
|
|
|
// Lines (colors / lineweights / fixed-length extension)
|
|
|
|
|
|
ds_dimclrd: String,
|
|
|
|
|
|
ds_dimlwd: String,
|
|
|
|
|
|
ds_dimclre: String,
|
|
|
|
|
|
ds_dimlwe: String,
|
|
|
|
|
|
ds_dimfxl: String,
|
|
|
|
|
|
ds_dimfxlon: bool,
|
|
|
|
|
|
// Symbols & Arrows
|
|
|
|
|
|
ds_dimsah: bool,
|
|
|
|
|
|
ds_dimarcsym: String,
|
|
|
|
|
|
ds_dimjogang: String,
|
|
|
|
|
|
// Text
|
|
|
|
|
|
ds_dimclrt: String,
|
|
|
|
|
|
ds_dimjust: String,
|
|
|
|
|
|
ds_dimtvp: String,
|
|
|
|
|
|
ds_dimtfill: String,
|
|
|
|
|
|
ds_dimtfillclr: String,
|
|
|
|
|
|
ds_dimtxtdirection: bool,
|
|
|
|
|
|
// Fit
|
|
|
|
|
|
ds_dimatfit: String,
|
|
|
|
|
|
ds_dimtix: bool,
|
|
|
|
|
|
ds_dimsoxd: bool,
|
|
|
|
|
|
ds_dimtmove: String,
|
|
|
|
|
|
ds_dimupt: bool,
|
|
|
|
|
|
ds_dimtofl: bool,
|
|
|
|
|
|
ds_dimfit: String,
|
|
|
|
|
|
// Primary units
|
|
|
|
|
|
ds_dimdsep: String,
|
|
|
|
|
|
ds_dimrnd: String,
|
|
|
|
|
|
ds_dimzin: String,
|
|
|
|
|
|
ds_dimfrac: String,
|
|
|
|
|
|
ds_dimaunit: String,
|
|
|
|
|
|
ds_dimadec: String,
|
feat(styles): complete MLeader field coverage + Dimension DIMUNIT
MLeader Style editor now exposes every MultiLeaderStyle field: line
weight, line type / arrowhead block / text style / block-content blocks
(handle dropdowns from the document's tables), the four text attachments
+ attachment direction, the full block-content group (color, connection,
rotation, X/Y/Z scale, enable scale/rotation), leader & multileader draw
order, align space, and description.
Dimension Style editor adds the last uncovered DIMUNIT field.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 21:04:22 +03:00
|
|
|
|
ds_dimunit: String,
|
feat(dimstyle): expand Dimension Style editor to the full field set
Adds the remaining ~40 DIMSTYLE variables across the tabs (and a new
Alternate Units tab): dim/ext line colors+lineweights, fixed-length ext
lines, separate arrow blocks, arc symbol, jog angle, text color/justify/
vertical pos/fill/direction, fit options (DIMATFIT/DIMTIX/DIMSOXD/DIMTMOVE/
DIMUPT/DIMTOFL/DIMFIT), decimal separator, rounding, zero suppression,
fraction + angular units, alternate units, and tolerance vert-just/zero
suppression. Only the arrowhead-block / extension-line-linetype Handle
references are left (they need a block/linetype picker).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 20:09:08 +03:00
|
|
|
|
ds_dimazin: String,
|
|
|
|
|
|
// Alternate units
|
|
|
|
|
|
ds_dimalt: bool,
|
|
|
|
|
|
ds_dimaltf: String,
|
|
|
|
|
|
ds_dimaltd: String,
|
|
|
|
|
|
ds_dimaltu: String,
|
|
|
|
|
|
ds_dimalttd: String,
|
|
|
|
|
|
ds_dimaltrnd: String,
|
|
|
|
|
|
ds_dimapost: String,
|
|
|
|
|
|
ds_dimaltz: String,
|
|
|
|
|
|
ds_dimalttz: String,
|
|
|
|
|
|
// Tolerances (extra)
|
|
|
|
|
|
ds_dimtolj: String,
|
|
|
|
|
|
ds_dimtzin: String,
|
2026-04-06 14:25:13 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-01 11:31:11 +03:00
|
|
|
|
/// What triggered the "unsaved changes" dialog.
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
|
pub(super) enum PendingClose {
|
|
|
|
|
|
/// User tried to close the tab at this index.
|
|
|
|
|
|
Tab(usize),
|
|
|
|
|
|
/// User tried to quit the application.
|
|
|
|
|
|
Quit,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-14 11:14:14 +03:00
|
|
|
|
/// Where a colour chosen in the standalone palette window should be applied.
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
|
pub enum ColorPickTarget {
|
|
|
|
|
|
DimStyle(DsField),
|
|
|
|
|
|
MLeader(&'static str),
|
|
|
|
|
|
Table(u8, &'static str),
|
2026-06-14 14:22:24 +03:00
|
|
|
|
/// Selected entities' colour (left properties panel).
|
|
|
|
|
|
Properties,
|
|
|
|
|
|
/// Current creation colour (ribbon).
|
|
|
|
|
|
Ribbon,
|
|
|
|
|
|
/// A layer's colour, by panel row index.
|
|
|
|
|
|
Layer(usize),
|
feat(mtext): structured editor with selection, copy, and visible bold
Rewrite the in-app MTEXT editor to run on acadrust's structured
MTextDocument model end-to-end: load via parse_mtext, edit a flat Cell
model, serialize via to_mtext_string. Retires the hand-rolled format-code
string handling, the parallel visible_spans parser, the composed_value
prefix, and the Edit-raw-code tab.
Selection and copy:
- double-click selects the word, triple-click selects all (click-counting)
- Ctrl+A selects all text
- Ctrl+C copies the selected text to the clipboard
Formatting:
- per-selection Bold/Italic/Underline/Overline/Strike/Case/Alignment
- Bold renders as a visibly thicker SDF pen, keyed (family, char, bold)
through the whole text pipeline; the resolved style font name is stamped
so the \f...|b1; code survives serialization
- per-selection font and color changes
- editor color dropdown reuses the Properties panel color selector
Global toolbar defaults (font/color/oblique/width/tracking) fold onto the
document as real span properties instead of a leading code prefix; a
trailing empty paragraph is preserved so Enter at the end keeps its line.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 20:18:50 +03:00
|
|
|
|
/// The MText editor's selection (or global) colour.
|
|
|
|
|
|
MText,
|
2026-06-14 11:14:14 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-18 14:34:32 +03:00
|
|
|
|
/// Table records the clipboard entities depend on, snapshotted from the source
|
|
|
|
|
|
/// drawing at copy time. On paste into a drawing that lacks any of them, the
|
|
|
|
|
|
/// missing records are recreated (with a fresh handle) so the entities keep
|
|
|
|
|
|
/// their layer / linetype / style instead of dangling. See #129.
|
|
|
|
|
|
#[derive(Default, Clone)]
|
|
|
|
|
|
pub struct ClipboardDeps {
|
|
|
|
|
|
pub layers: Vec<acadrust::tables::Layer>,
|
|
|
|
|
|
pub linetypes: Vec<acadrust::tables::LineType>,
|
|
|
|
|
|
pub text_styles: Vec<acadrust::tables::TextStyle>,
|
|
|
|
|
|
pub dim_styles: Vec<acadrust::tables::DimStyle>,
|
2026-06-20 21:23:59 +03:00
|
|
|
|
/// Block definitions the copied INSERTs reference (transitively),
|
|
|
|
|
|
/// snapshotted from the source drawing. Recreated on paste so a block
|
|
|
|
|
|
/// reference doesn't render empty in a drawing that lacks the
|
|
|
|
|
|
/// definition. (#135)
|
|
|
|
|
|
pub blocks: Vec<BlockDef>,
|
2026-07-07 15:53:21 +02:00
|
|
|
|
/// Baked `*D` blocks referenced by copied dimensions, snapshotted from the
|
|
|
|
|
|
/// source drawing. On paste each pasted dimension gets its own transformed
|
|
|
|
|
|
/// copy of its block (its geometry is baked in WCS, so without this the
|
|
|
|
|
|
/// paste renders at the source location — or, cross-drawing, not at all).
|
|
|
|
|
|
/// See #290 (mirrors the in-drawing copy's #161 fix).
|
|
|
|
|
|
pub dim_blocks: Vec<BlockDef>,
|
2026-06-24 09:33:55 +03:00
|
|
|
|
/// Extension-dictionary object subtrees hanging off the copied entities
|
|
|
|
|
|
/// (XCLIP spatial filters, attached XRecords, …). Each entity's whole
|
|
|
|
|
|
/// `xdictionary` graph is snapshotted so a cross-drawing paste recreates it
|
|
|
|
|
|
/// — without this a pasted clipped block loses its clip and renders whole.
|
|
|
|
|
|
pub ext_objects: Vec<ClipExtObjects>,
|
2026-06-20 21:23:59 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// A captured block definition: its base point and the entities it owns
|
|
|
|
|
|
/// (in block-local coordinates), minus the structural Block/BlockEnd
|
|
|
|
|
|
/// markers which are rebuilt on paste.
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
|
|
pub struct BlockDef {
|
|
|
|
|
|
pub name: String,
|
|
|
|
|
|
pub base_point: acadrust::types::Vector3,
|
|
|
|
|
|
pub entities: Vec<acadrust::EntityType>,
|
2026-06-18 14:34:32 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-24 09:33:55 +03:00
|
|
|
|
/// The extension-dictionary object graph captured for one copied entity.
|
|
|
|
|
|
/// `objects` holds every object reachable from the entity's `xdictionary`
|
|
|
|
|
|
/// (dictionaries + their leaf objects), keyed by their source handles; `root`
|
|
|
|
|
|
/// is the xdictionary handle. On paste the whole set is cloned into the target
|
|
|
|
|
|
/// document with fresh handles and the references are remapped.
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
|
|
pub struct ClipExtObjects {
|
|
|
|
|
|
pub entity_index: usize,
|
|
|
|
|
|
pub src_entity_handle: acadrust::Handle,
|
|
|
|
|
|
pub root: acadrust::Handle,
|
|
|
|
|
|
pub objects: Vec<(acadrust::Handle, acadrust::objects::ObjectType)>,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-18 14:34:32 +03:00
|
|
|
|
impl ClipboardDeps {
|
|
|
|
|
|
/// Snapshot the records `entities` reference that exist in `doc`.
|
|
|
|
|
|
pub fn capture(doc: &acadrust::CadDocument, entities: &[acadrust::EntityType]) -> Self {
|
|
|
|
|
|
use acadrust::EntityType;
|
|
|
|
|
|
use std::collections::BTreeSet;
|
|
|
|
|
|
let (mut layers, mut ltypes, mut tstyles, mut dstyles) = (
|
|
|
|
|
|
BTreeSet::new(),
|
|
|
|
|
|
BTreeSet::new(),
|
|
|
|
|
|
BTreeSet::new(),
|
|
|
|
|
|
BTreeSet::new(),
|
|
|
|
|
|
);
|
|
|
|
|
|
let is_special = |n: &str| {
|
feat(commands): expand standard CAD command coverage
Integrate a large batch of standard CAD commands and fix several
name/behaviour mismatches, with no product references in code.
3D solids (reusing the truck B-rep pipeline):
- SLICE, INTERFERE, PRESSPULL, POLYSOLID, PYRAMID, SECTION
- 3DROTATE, 3DMIRROR, 3DALIGN (cached-solid transforms)
Annotation / text:
- ARCTEXT, SPLINEFIT (Catmull-Rom to Bezier), DIMJOGGED, OBJECTSCALE
(XData markers honoured by the existing geometry-gen / tessellator)
Blocks / misc:
- NCOPY (block-content extraction via explode_entity)
- OPTIONS, SYNCPVIEWPORTS, THICKEN, plus earlier toggles, aliases,
system variables, and layer/style/view commands
POINTCLOUD/RECAP and UNDERLAYLAYERS/UOSNAP are intentionally left out:
they require a proprietary binary / PDF-layer parser, a GPU point
renderer, and real test files.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 08:23:58 +03:00
|
|
|
|
n.is_empty() || n.eq_ignore_ascii_case("ByLayer") || n.eq_ignore_ascii_case("ByBlock")
|
2026-06-18 14:34:32 +03:00
|
|
|
|
};
|
2026-06-21 02:12:58 +03:00
|
|
|
|
// Scan the copied entities AND the entities inside every captured
|
|
|
|
|
|
// block definition, so a block-internal object's layer / linetype /
|
|
|
|
|
|
// style is recreated too — not just the top-level selection's.
|
|
|
|
|
|
let blocks = Self::capture_blocks(doc, entities);
|
|
|
|
|
|
let block_entities = blocks.iter().flat_map(|d| d.entities.iter());
|
|
|
|
|
|
for e in entities.iter().chain(block_entities) {
|
2026-06-18 14:34:32 +03:00
|
|
|
|
let c = e.common();
|
|
|
|
|
|
if !c.layer.is_empty() {
|
|
|
|
|
|
layers.insert(c.layer.clone());
|
|
|
|
|
|
}
|
|
|
|
|
|
if !is_special(&c.linetype) && !c.linetype.eq_ignore_ascii_case("Continuous") {
|
|
|
|
|
|
ltypes.insert(c.linetype.clone());
|
|
|
|
|
|
}
|
|
|
|
|
|
match e {
|
|
|
|
|
|
EntityType::Text(t) if !t.style.is_empty() => {
|
|
|
|
|
|
tstyles.insert(t.style.clone());
|
|
|
|
|
|
}
|
|
|
|
|
|
EntityType::MText(t) if !t.style.is_empty() => {
|
|
|
|
|
|
tstyles.insert(t.style.clone());
|
|
|
|
|
|
}
|
|
|
|
|
|
EntityType::AttributeEntity(a) if !a.text_style.is_empty() => {
|
|
|
|
|
|
tstyles.insert(a.text_style.clone());
|
|
|
|
|
|
}
|
|
|
|
|
|
EntityType::AttributeDefinition(a) if !a.text_style.is_empty() => {
|
|
|
|
|
|
tstyles.insert(a.text_style.clone());
|
|
|
|
|
|
}
|
|
|
|
|
|
EntityType::Dimension(d) if !d.base().style_name.is_empty() => {
|
|
|
|
|
|
dstyles.insert(d.base().style_name.clone());
|
|
|
|
|
|
}
|
|
|
|
|
|
EntityType::Leader(l) if !l.dimension_style.is_empty() => {
|
|
|
|
|
|
dstyles.insert(l.dimension_style.clone());
|
|
|
|
|
|
}
|
|
|
|
|
|
_ => {}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-06-24 09:33:55 +03:00
|
|
|
|
// Extension-dictionary subtree per entity (XCLIP filters etc.).
|
|
|
|
|
|
let mut ext_objects = Vec::new();
|
|
|
|
|
|
for (entity_index, e) in entities.iter().enumerate() {
|
|
|
|
|
|
let c = e.common();
|
|
|
|
|
|
if let Some(root) = c.xdictionary_handle {
|
|
|
|
|
|
if root.is_null() {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
let objects = Self::collect_ext_subtree(doc, root);
|
|
|
|
|
|
if !objects.is_empty() {
|
|
|
|
|
|
ext_objects.push(ClipExtObjects {
|
|
|
|
|
|
entity_index,
|
|
|
|
|
|
src_entity_handle: c.handle,
|
|
|
|
|
|
root,
|
|
|
|
|
|
objects,
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-07 15:53:21 +02:00
|
|
|
|
// Baked `*D` blocks for copied dimensions (top-level and inside captured
|
|
|
|
|
|
// blocks), so a pasted dimension can be given its own transformed block
|
|
|
|
|
|
// instead of aliasing the source's — whose geometry is baked in WCS at
|
|
|
|
|
|
// the source location. (#290)
|
|
|
|
|
|
let mut dim_block_names: BTreeSet<String> = BTreeSet::new();
|
|
|
|
|
|
for e in entities
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
.chain(blocks.iter().flat_map(|d| d.entities.iter()))
|
|
|
|
|
|
{
|
|
|
|
|
|
if let EntityType::Dimension(d) = e {
|
|
|
|
|
|
let bn = d.base().block_name.clone();
|
|
|
|
|
|
if !bn.trim().is_empty() {
|
|
|
|
|
|
dim_block_names.insert(bn);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
let dim_blocks: Vec<BlockDef> = dim_block_names
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
.filter_map(|n| Self::snapshot_block(doc, n))
|
|
|
|
|
|
.collect();
|
|
|
|
|
|
|
2026-06-18 14:34:32 +03:00
|
|
|
|
ClipboardDeps {
|
feat(commands): expand standard CAD command coverage
Integrate a large batch of standard CAD commands and fix several
name/behaviour mismatches, with no product references in code.
3D solids (reusing the truck B-rep pipeline):
- SLICE, INTERFERE, PRESSPULL, POLYSOLID, PYRAMID, SECTION
- 3DROTATE, 3DMIRROR, 3DALIGN (cached-solid transforms)
Annotation / text:
- ARCTEXT, SPLINEFIT (Catmull-Rom to Bezier), DIMJOGGED, OBJECTSCALE
(XData markers honoured by the existing geometry-gen / tessellator)
Blocks / misc:
- NCOPY (block-content extraction via explode_entity)
- OPTIONS, SYNCPVIEWPORTS, THICKEN, plus earlier toggles, aliases,
system variables, and layer/style/view commands
POINTCLOUD/RECAP and UNDERLAYLAYERS/UOSNAP are intentionally left out:
they require a proprietary binary / PDF-layer parser, a GPU point
renderer, and real test files.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 08:23:58 +03:00
|
|
|
|
layers: layers
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
.filter_map(|n| doc.layers.get(n).cloned())
|
|
|
|
|
|
.collect(),
|
|
|
|
|
|
linetypes: ltypes
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
.filter_map(|n| doc.line_types.get(n).cloned())
|
|
|
|
|
|
.collect(),
|
|
|
|
|
|
text_styles: tstyles
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
.filter_map(|n| doc.text_styles.get(n).cloned())
|
|
|
|
|
|
.collect(),
|
|
|
|
|
|
dim_styles: dstyles
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
.filter_map(|n| doc.dim_styles.get(n).cloned())
|
|
|
|
|
|
.collect(),
|
2026-06-21 02:12:58 +03:00
|
|
|
|
blocks,
|
2026-07-07 15:53:21 +02:00
|
|
|
|
dim_blocks,
|
2026-06-24 09:33:55 +03:00
|
|
|
|
ext_objects,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Breadth-first collect of every object reachable from extension-dictionary
|
|
|
|
|
|
/// `root` (dictionary entries, nested xdictionaries, dictionary defaults),
|
|
|
|
|
|
/// returned as `(source_handle, object)` pairs. Cycle-safe.
|
|
|
|
|
|
fn collect_ext_subtree(
|
|
|
|
|
|
doc: &acadrust::CadDocument,
|
|
|
|
|
|
root: acadrust::Handle,
|
|
|
|
|
|
) -> Vec<(acadrust::Handle, acadrust::objects::ObjectType)> {
|
|
|
|
|
|
use acadrust::objects::ObjectType;
|
|
|
|
|
|
use rustc_hash::FxHashSet;
|
|
|
|
|
|
let mut seen: FxHashSet<acadrust::Handle> = FxHashSet::default();
|
|
|
|
|
|
let mut queue = vec![root];
|
|
|
|
|
|
let mut out = Vec::new();
|
|
|
|
|
|
while let Some(h) = queue.pop() {
|
|
|
|
|
|
if h.is_null() || !seen.insert(h) {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
let Some(obj) = doc.objects.get(&h) else {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
};
|
|
|
|
|
|
// Enqueue children referenced by this object.
|
|
|
|
|
|
match obj {
|
|
|
|
|
|
ObjectType::Dictionary(d) => {
|
|
|
|
|
|
queue.extend(d.entries.iter().map(|(_, ch)| *ch));
|
|
|
|
|
|
if let Some(x) = d.xdictionary_handle {
|
|
|
|
|
|
queue.push(x);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
ObjectType::DictionaryWithDefault(d) => {
|
|
|
|
|
|
queue.extend(d.entries.iter().map(|(_, ch)| *ch));
|
|
|
|
|
|
queue.push(d.default_handle);
|
|
|
|
|
|
}
|
|
|
|
|
|
_ => {}
|
|
|
|
|
|
}
|
|
|
|
|
|
out.push((h, obj.clone()));
|
2026-06-20 21:23:59 +03:00
|
|
|
|
}
|
2026-06-24 09:33:55 +03:00
|
|
|
|
out
|
2026-06-20 21:23:59 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Snapshot every block definition the `entities` reference through an
|
|
|
|
|
|
/// INSERT, walking nested INSERTs transitively. Model/paper space and
|
|
|
|
|
|
/// xref blocks are skipped — those aren't portable definitions.
|
feat(commands): expand standard CAD command coverage
Integrate a large batch of standard CAD commands and fix several
name/behaviour mismatches, with no product references in code.
3D solids (reusing the truck B-rep pipeline):
- SLICE, INTERFERE, PRESSPULL, POLYSOLID, PYRAMID, SECTION
- 3DROTATE, 3DMIRROR, 3DALIGN (cached-solid transforms)
Annotation / text:
- ARCTEXT, SPLINEFIT (Catmull-Rom to Bezier), DIMJOGGED, OBJECTSCALE
(XData markers honoured by the existing geometry-gen / tessellator)
Blocks / misc:
- NCOPY (block-content extraction via explode_entity)
- OPTIONS, SYNCPVIEWPORTS, THICKEN, plus earlier toggles, aliases,
system variables, and layer/style/view commands
POINTCLOUD/RECAP and UNDERLAYLAYERS/UOSNAP are intentionally left out:
they require a proprietary binary / PDF-layer parser, a GPU point
renderer, and real test files.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 08:23:58 +03:00
|
|
|
|
fn capture_blocks(
|
|
|
|
|
|
doc: &acadrust::CadDocument,
|
|
|
|
|
|
entities: &[acadrust::EntityType],
|
|
|
|
|
|
) -> Vec<BlockDef> {
|
2026-06-20 21:23:59 +03:00
|
|
|
|
use acadrust::EntityType;
|
|
|
|
|
|
use rustc_hash::FxHashSet;
|
|
|
|
|
|
let mut seen: FxHashSet<String> = FxHashSet::default();
|
|
|
|
|
|
let mut queue: Vec<String> = Vec::new();
|
|
|
|
|
|
for e in entities {
|
|
|
|
|
|
if let EntityType::Insert(ins) = e {
|
|
|
|
|
|
if seen.insert(ins.block_name.clone()) {
|
|
|
|
|
|
queue.push(ins.block_name.clone());
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
let mut defs = Vec::new();
|
|
|
|
|
|
while let Some(name) = queue.pop() {
|
2026-07-07 15:53:21 +02:00
|
|
|
|
let Some(def) = Self::snapshot_block(doc, &name) else {
|
2026-06-20 21:23:59 +03:00
|
|
|
|
continue;
|
|
|
|
|
|
};
|
2026-07-07 15:53:21 +02:00
|
|
|
|
// Follow nested INSERTs so their definitions are captured too.
|
|
|
|
|
|
for e in &def.entities {
|
2026-06-20 21:23:59 +03:00
|
|
|
|
if let EntityType::Insert(ins) = e {
|
|
|
|
|
|
if seen.insert(ins.block_name.clone()) {
|
|
|
|
|
|
queue.push(ins.block_name.clone());
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-07-07 15:53:21 +02:00
|
|
|
|
defs.push(def);
|
2026-06-18 14:34:32 +03:00
|
|
|
|
}
|
2026-06-20 21:23:59 +03:00
|
|
|
|
defs
|
2026-06-18 14:34:32 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-07 15:53:21 +02:00
|
|
|
|
/// Snapshot one block definition as a portable `BlockDef`: its base point
|
|
|
|
|
|
/// and owned entities, minus the structural Block/BlockEnd markers. Returns
|
|
|
|
|
|
/// None for model/paper space and xref blocks (not portable definitions).
|
|
|
|
|
|
fn snapshot_block(doc: &acadrust::CadDocument, name: &str) -> Option<BlockDef> {
|
|
|
|
|
|
use acadrust::EntityType;
|
|
|
|
|
|
let br = doc.block_records.get(name)?;
|
|
|
|
|
|
if name.starts_with("*Model_Space")
|
|
|
|
|
|
|| name.starts_with("*Paper_Space")
|
|
|
|
|
|
|| br.flags.is_xref
|
|
|
|
|
|
{
|
|
|
|
|
|
return None;
|
|
|
|
|
|
}
|
|
|
|
|
|
let base_point = match doc.get_entity(br.block_entity_handle) {
|
|
|
|
|
|
Some(EntityType::Block(b)) => b.base_point,
|
|
|
|
|
|
_ => acadrust::types::Vector3::ZERO,
|
|
|
|
|
|
};
|
|
|
|
|
|
let mut owned = Vec::new();
|
|
|
|
|
|
for &eh in &br.entity_handles {
|
|
|
|
|
|
let Some(e) = doc.get_entity(eh) else {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
};
|
|
|
|
|
|
if matches!(e, EntityType::Block(_) | EntityType::BlockEnd(_)) {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
owned.push(e.clone());
|
|
|
|
|
|
}
|
|
|
|
|
|
Some(BlockDef {
|
|
|
|
|
|
name: name.to_string(),
|
|
|
|
|
|
base_point,
|
|
|
|
|
|
entities: owned,
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-18 14:34:32 +03:00
|
|
|
|
pub fn is_empty(&self) -> bool {
|
|
|
|
|
|
self.layers.is_empty()
|
|
|
|
|
|
&& self.linetypes.is_empty()
|
|
|
|
|
|
&& self.text_styles.is_empty()
|
|
|
|
|
|
&& self.dim_styles.is_empty()
|
2026-06-20 21:23:59 +03:00
|
|
|
|
&& self.blocks.is_empty()
|
2026-06-18 14:34:32 +03:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-16 19:07:25 +03:00
|
|
|
|
/// Which in-canvas modal dialog is currently open (Plan B). At most one shows
|
|
|
|
|
|
/// at a time; dialog-specific data lives in its own fields. Closed via the
|
|
|
|
|
|
/// modal's ✕ (`Message::CloseModal`).
|
|
|
|
|
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
|
|
|
|
|
pub enum ModalKind {
|
|
|
|
|
|
About,
|
|
|
|
|
|
Shortcuts,
|
|
|
|
|
|
PluginManager,
|
|
|
|
|
|
UpdateNotice,
|
2026-06-16 23:22:54 +03:00
|
|
|
|
Layers,
|
feat(plot): unified plot/print dialog, retire Page Setup
Replace the old Page Setup panel with a single OCS-styled plot/print
dialog (dropdowns for printer, paper, orientation, rotation, plot area,
scale, quality, shaded plot; checkboxes for plot options). PLOT / PRINT
/ PAGESETUP all open it.
- Printer output via lp: -d printer, -n copies, -o ColorModel=Gray,
print-quality, Resolution; Windows printto verb; list_printers via
lpstat. Preview opens a temp PDF in the OS viewer (never a save
dialog). PDF export reuses the tested pipeline.
- Window pick returns to the new dialog (was the old Page Setup) and the
clipped window plot drives PDF / printer / preview.
- Remember the user's print preferences across sessions in
config/plot.txt (printer, copies, quality, output options, area,
scale); drawing-specific fields (offset/center/rotation) seed from the
layout. Default plot area Window, scale Fit.
- Delete src/ui/window/page_setup.rs and its ModalKind / Message /
buffer state; fold on_page_setup_commit into apply_plot_page_settings.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 10:27:54 +03:00
|
|
|
|
Plot,
|
2026-06-16 23:22:54 +03:00
|
|
|
|
LayoutManager,
|
|
|
|
|
|
Plotstyle,
|
2026-06-16 23:29:50 +03:00
|
|
|
|
TextStyle,
|
|
|
|
|
|
TableStyle,
|
|
|
|
|
|
MlStyle,
|
|
|
|
|
|
MLeaderStyle,
|
|
|
|
|
|
DimStyle,
|
2026-06-16 23:59:38 +03:00
|
|
|
|
Unsaved,
|
|
|
|
|
|
SaveDialog,
|
2026-06-29 11:47:37 +03:00
|
|
|
|
AecDropWarning,
|
2026-06-22 02:03:34 +03:00
|
|
|
|
#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
|
2026-06-16 23:59:38 +03:00
|
|
|
|
AssocPrompt,
|
2026-06-17 11:13:12 +03:00
|
|
|
|
PointStyle,
|
2026-07-01 08:05:25 +03:00
|
|
|
|
AttributeEditor,
|
2026-07-01 17:23:32 +03:00
|
|
|
|
LayerDeleteWarning,
|
feat(alias): centralize command aliases in user-editable ocad.pgp (#286/#288)
Command aliases (L→LINE, CC→COPYCLIP) were scattered across dispatch match
arms and CommandRegistration name arrays. Move them all into a single
user-editable file:
- assets/ocad.pgp: shipped default alias table (ALIAS,*COMMAND format),
embedded and copied to config/ocad.pgp on first launch; only that user
copy is read after. AA→AREA default closes #286.
- resolve_alias() rewrites the leading command token at the top of
dispatch_command_inner, so aliases resolve for every path (families,
plugins, Repeat menu). Args after the first space are untouched.
- Autocomplete hides alias keys and, when the whole input is an alias,
surfaces its target command as the top (highlighted) suggestion so the
highlight matches what Enter runs (AA→AREA, L→LINE).
- ALIASEDIT opens a new editor modal (add/edit/delete rows) with an Apply
button; Apply persists to ocad.pgp, closing discards unapplied edits.
- Strip alias tokens from all 8 dispatch match-arm files and 71 module
CommandRegistration arrays, keeping canonical/synonym/distinct-command
names. ocad.pgp is now the sole source: deleting or remapping an alias
there takes effect. CUI stays on the keyboard-shortcut panel.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 12:39:22 +03:00
|
|
|
|
Aliases,
|
2026-07-13 10:39:14 +03:00
|
|
|
|
ScaleManager,
|
2026-07-13 22:38:29 +03:00
|
|
|
|
/// Add / remove the annotation scales a single selected object has a
|
|
|
|
|
|
/// per-object representation for.
|
|
|
|
|
|
AnnoObjectScale,
|
2026-06-16 19:07:25 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-06 14:25:13 +03:00
|
|
|
|
/// Identifies a DimStyle field that can be edited in the dialog.
|
|
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
|
|
|
|
pub enum DsField {
|
2026-05-12 10:36:15 +03:00
|
|
|
|
Dimdle,
|
|
|
|
|
|
Dimdli,
|
|
|
|
|
|
Dimgap,
|
|
|
|
|
|
Dimexe,
|
|
|
|
|
|
Dimexo,
|
|
|
|
|
|
Dimsd1,
|
|
|
|
|
|
Dimsd2,
|
|
|
|
|
|
Dimse1,
|
|
|
|
|
|
Dimse2,
|
|
|
|
|
|
Dimasz,
|
|
|
|
|
|
Dimcen,
|
|
|
|
|
|
Dimtsz,
|
|
|
|
|
|
Dimtxt,
|
|
|
|
|
|
Dimtxsty,
|
|
|
|
|
|
Dimtad,
|
|
|
|
|
|
Dimtih,
|
|
|
|
|
|
Dimtoh,
|
|
|
|
|
|
Dimscale,
|
|
|
|
|
|
Dimlfac,
|
|
|
|
|
|
Dimlunit,
|
|
|
|
|
|
Dimdec,
|
|
|
|
|
|
Dimpost,
|
|
|
|
|
|
Dimtol,
|
|
|
|
|
|
Dimlim,
|
|
|
|
|
|
Dimtp,
|
|
|
|
|
|
Dimtm,
|
|
|
|
|
|
Dimtdec,
|
|
|
|
|
|
Dimtfac,
|
2026-06-03 17:49:38 +03:00
|
|
|
|
Annotative,
|
feat(dimstyle): expand Dimension Style editor to the full field set
Adds the remaining ~40 DIMSTYLE variables across the tabs (and a new
Alternate Units tab): dim/ext line colors+lineweights, fixed-length ext
lines, separate arrow blocks, arc symbol, jog angle, text color/justify/
vertical pos/fill/direction, fit options (DIMATFIT/DIMTIX/DIMSOXD/DIMTMOVE/
DIMUPT/DIMTOFL/DIMFIT), decimal separator, rounding, zero suppression,
fraction + angular units, alternate units, and tolerance vert-just/zero
suppression. Only the arrowhead-block / extension-line-linetype Handle
references are left (they need a block/linetype picker).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 20:09:08 +03:00
|
|
|
|
Dimclrd,
|
|
|
|
|
|
Dimlwd,
|
|
|
|
|
|
Dimclre,
|
|
|
|
|
|
Dimlwe,
|
|
|
|
|
|
Dimfxl,
|
|
|
|
|
|
Dimfxlon,
|
|
|
|
|
|
Dimsah,
|
|
|
|
|
|
Dimarcsym,
|
|
|
|
|
|
Dimjogang,
|
|
|
|
|
|
Dimclrt,
|
|
|
|
|
|
Dimjust,
|
|
|
|
|
|
Dimtvp,
|
|
|
|
|
|
Dimtfill,
|
|
|
|
|
|
Dimtfillclr,
|
|
|
|
|
|
Dimtxtdirection,
|
|
|
|
|
|
Dimatfit,
|
|
|
|
|
|
Dimtix,
|
|
|
|
|
|
Dimsoxd,
|
|
|
|
|
|
Dimtmove,
|
|
|
|
|
|
Dimupt,
|
|
|
|
|
|
Dimtofl,
|
|
|
|
|
|
Dimfit,
|
|
|
|
|
|
Dimdsep,
|
|
|
|
|
|
Dimrnd,
|
|
|
|
|
|
Dimzin,
|
|
|
|
|
|
Dimfrac,
|
|
|
|
|
|
Dimaunit,
|
|
|
|
|
|
Dimadec,
|
feat(styles): complete MLeader field coverage + Dimension DIMUNIT
MLeader Style editor now exposes every MultiLeaderStyle field: line
weight, line type / arrowhead block / text style / block-content blocks
(handle dropdowns from the document's tables), the four text attachments
+ attachment direction, the full block-content group (color, connection,
rotation, X/Y/Z scale, enable scale/rotation), leader & multileader draw
order, align space, and description.
Dimension Style editor adds the last uncovered DIMUNIT field.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 21:04:22 +03:00
|
|
|
|
Dimunit,
|
feat(dimstyle): expand Dimension Style editor to the full field set
Adds the remaining ~40 DIMSTYLE variables across the tabs (and a new
Alternate Units tab): dim/ext line colors+lineweights, fixed-length ext
lines, separate arrow blocks, arc symbol, jog angle, text color/justify/
vertical pos/fill/direction, fit options (DIMATFIT/DIMTIX/DIMSOXD/DIMTMOVE/
DIMUPT/DIMTOFL/DIMFIT), decimal separator, rounding, zero suppression,
fraction + angular units, alternate units, and tolerance vert-just/zero
suppression. Only the arrowhead-block / extension-line-linetype Handle
references are left (they need a block/linetype picker).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 20:09:08 +03:00
|
|
|
|
Dimazin,
|
|
|
|
|
|
Dimalt,
|
|
|
|
|
|
Dimaltf,
|
|
|
|
|
|
Dimaltd,
|
|
|
|
|
|
Dimaltu,
|
|
|
|
|
|
Dimalttd,
|
|
|
|
|
|
Dimaltrnd,
|
|
|
|
|
|
Dimapost,
|
|
|
|
|
|
Dimaltz,
|
|
|
|
|
|
Dimalttz,
|
|
|
|
|
|
Dimtolj,
|
|
|
|
|
|
Dimtzin,
|
2026-03-22 22:44:32 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
|
pub enum Message {
|
|
|
|
|
|
Tick(Instant),
|
feat(web): lazy per-script font loading for international CAD text
The web build has no system-font access, so CAD text in non-Latin scripts
(Cyrillic, Greek, CJK, …) rendered as nothing — build_fallback was a hard
None on wasm (#141). Instead, ship per-script Noto subsets under web/fonts
(one alphabet per file, SIL OFL 1.1) and fetch them lazily over HTTP the
first time a drawing uses that script, then outline glyphs with ttf-parser.
- web_font: Script enum + script_of (char → script font), a per-script
store with lazy fetch, and a pending queue drained by the app loop.
- CJK is split by language (chinese/japanese/korean). Han ideographs share
code points but differ by language, so the shared block is routed by the
document's $DWGCODEPAGE (932→JP, 949→KR, GB/936→CN); kana is always
Japanese, Hangul always Korean. Re-tessellates when the language changes.
- build_fallback (wasm) outlines from the fetched subset; clear_fallback_cache
lets glyphs that missed while a font was in flight reappear on load.
- PollWebFonts subscription (web only) + WebFontLoaded message drive fetches.
- Trunk copy-dir serves web/fonts; nothing is bundled into the native binary
(desktop keeps using system fonts).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 16:13:26 +03:00
|
|
|
|
/// Web: periodic check for per-script fonts a drawing needs but hasn't
|
|
|
|
|
|
/// fetched yet (#141). Native: never emitted.
|
|
|
|
|
|
PollWebFonts,
|
|
|
|
|
|
/// Web: a per-script font finished fetching — `Ok(bytes)` or `Err(reason)`.
|
feat(commands): expand standard CAD command coverage
Integrate a large batch of standard CAD commands and fix several
name/behaviour mismatches, with no product references in code.
3D solids (reusing the truck B-rep pipeline):
- SLICE, INTERFERE, PRESSPULL, POLYSOLID, PYRAMID, SECTION
- 3DROTATE, 3DMIRROR, 3DALIGN (cached-solid transforms)
Annotation / text:
- ARCTEXT, SPLINEFIT (Catmull-Rom to Bezier), DIMJOGGED, OBJECTSCALE
(XData markers honoured by the existing geometry-gen / tessellator)
Blocks / misc:
- NCOPY (block-content extraction via explode_entity)
- OPTIONS, SYNCPVIEWPORTS, THICKEN, plus earlier toggles, aliases,
system variables, and layer/style/view commands
POINTCLOUD/RECAP and UNDERLAYLAYERS/UOSNAP are intentionally left out:
they require a proprietary binary / PDF-layer parser, a GPU point
renderer, and real test files.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 08:23:58 +03:00
|
|
|
|
WebFontLoaded(
|
|
|
|
|
|
crate::scene::text::web_font::Script,
|
|
|
|
|
|
Result<Vec<u8>, String>,
|
|
|
|
|
|
),
|
2026-06-20 17:05:02 +03:00
|
|
|
|
/// Ctrl+V. Routed by `update`: into the open text/MText editor (via an async
|
|
|
|
|
|
/// system-clipboard read, which is the only paste path that works on the
|
|
|
|
|
|
/// web) or, with no editor open, the entity paste command.
|
|
|
|
|
|
PasteShortcut,
|
2026-07-03 16:06:10 +03:00
|
|
|
|
/// Ctrl/Cmd+A — select all layer rows when the Layer Manager is open, or all
|
|
|
|
|
|
/// drawing objects otherwise (#236).
|
|
|
|
|
|
SelectAllShortcut,
|
2026-06-20 17:05:02 +03:00
|
|
|
|
/// System-clipboard text read for the MText editor (`None` = empty/denied).
|
|
|
|
|
|
MTextPasteClip(Option<String>),
|
|
|
|
|
|
/// System-clipboard text read for the single-line TEXT editor.
|
|
|
|
|
|
TextInlinePasteClip(Option<String>),
|
2026-03-22 22:44:32 +03:00
|
|
|
|
OpenFile,
|
2026-05-13 09:59:07 +03:00
|
|
|
|
/// File picker returned. `Some((path, size_in_bytes))` → start loading;
|
|
|
|
|
|
/// `None` → user cancelled the dialog (no overlay shown).
|
|
|
|
|
|
OpenPathPicked(Option<(PathBuf, u64)>),
|
feat(app): add fixed Start tab with welcome page and recent documents
A pinned "Start" tab now lives at index 0. It cannot be closed, opens
on launch, and replaces the model-space viewport with a welcome page —
similar to Thunderbird's first-run screen.
- Boot opens with only the Start tab; Drawing1 is no longer auto-spawned.
- The Properties left panel becomes a Recent Documents rail on the
Start tab. Recents persist to disk
(%APPDATA%/H7CAD/recent.txt on Windows, $XDG_CONFIG_HOME/H7CAD/recent.txt
on Linux, ~/Library/Application Support/H7CAD/recent.txt on macOS), up
to 20 entries, saved on every open and rehydrated at boot.
Each row opens or removes the file in place.
- The Support ribbon group (Donate / Report / About / Changelog) is
retired; the same actions surface on the welcome page instead. The
Donate button is the primary call-to-action — solid #B03020 brand
fill with a soft brand-tinted ambient glow halo behind the action row
(Thunderbird-style coloured glow against the neutral page).
- Drawing-only overlays (mouse-area, viewcube, navbar, dynamic input,
selection markers) are suppressed on the Start tab so its clicks
don't trigger CAD commands. OpenFile's "current_is_empty" check
refuses to replace the Start tab, and TabClose ignores requests on
it.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 16:19:31 +03:00
|
|
|
|
/// Open a path from the Start tab's recent-documents list (skips the
|
|
|
|
|
|
/// file picker; the path is already known).
|
|
|
|
|
|
OpenRecent(PathBuf),
|
2026-06-27 12:21:14 +03:00
|
|
|
|
/// Open a URL in the system browser (start-page intro video, links).
|
|
|
|
|
|
OpenUrl(String),
|
2026-07-05 20:53:11 +03:00
|
|
|
|
/// Select which section a narrow (tabbed) Start page shows.
|
|
|
|
|
|
StartSectionSelect(StartSection),
|
2026-07-05 21:02:05 +03:00
|
|
|
|
/// Expand/collapse the properties panel when it has shrunk to a bar.
|
|
|
|
|
|
TogglePropertiesBar,
|
2026-06-27 14:24:47 +03:00
|
|
|
|
/// Scroll the status-bar layout-tab strip horizontally by `delta` px
|
|
|
|
|
|
/// (negative = left). Driven by the ‹ › arrows next to the tabs.
|
|
|
|
|
|
ScrollLayoutTabs(f32),
|
feat(app): add fixed Start tab with welcome page and recent documents
A pinned "Start" tab now lives at index 0. It cannot be closed, opens
on launch, and replaces the model-space viewport with a welcome page —
similar to Thunderbird's first-run screen.
- Boot opens with only the Start tab; Drawing1 is no longer auto-spawned.
- The Properties left panel becomes a Recent Documents rail on the
Start tab. Recents persist to disk
(%APPDATA%/H7CAD/recent.txt on Windows, $XDG_CONFIG_HOME/H7CAD/recent.txt
on Linux, ~/Library/Application Support/H7CAD/recent.txt on macOS), up
to 20 entries, saved on every open and rehydrated at boot.
Each row opens or removes the file in place.
- The Support ribbon group (Donate / Report / About / Changelog) is
retired; the same actions surface on the welcome page instead. The
Donate button is the primary call-to-action — solid #B03020 brand
fill with a soft brand-tinted ambient glow halo behind the action row
(Thunderbird-style coloured glow against the neutral page).
- Drawing-only overlays (mouse-area, viewcube, navbar, dynamic input,
selection markers) are suppressed on the Start tab so its clicks
don't trigger CAD commands. OpenFile's "current_is_empty" check
refuses to replace the Start tab, and TabClose ignores requests on
it.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 16:19:31 +03:00
|
|
|
|
/// Drop a path from the recent-documents list.
|
|
|
|
|
|
RecentRemove(PathBuf),
|
2026-07-06 01:06:01 +03:00
|
|
|
|
/// Set how many recent documents to keep (persisted).
|
|
|
|
|
|
SetRecentLimit(usize),
|
|
|
|
|
|
/// Live edit of the recent-limit input box (not applied until Enter).
|
|
|
|
|
|
RecentLimitInput(String),
|
2026-05-13 09:59:07 +03:00
|
|
|
|
/// User clicked Cancel on the loading overlay. The parser thread keeps
|
|
|
|
|
|
/// running but its result is discarded.
|
|
|
|
|
|
OpenCancel,
|
2026-05-06 23:04:53 +03:00
|
|
|
|
FileOpened(Result<(String, PathBuf, CadDocument, crate::scene::DerivedCaches), String>),
|
2026-03-22 22:44:32 +03:00
|
|
|
|
SaveFile,
|
|
|
|
|
|
SaveAs,
|
2026-05-01 16:12:32 +03:00
|
|
|
|
// ── Custom Save-As dialog ─────────────────────────────────────────────
|
|
|
|
|
|
SaveDialogFormatChanged(String),
|
|
|
|
|
|
SaveDialogFilenameChanged(String),
|
|
|
|
|
|
SaveDialogConfirm,
|
|
|
|
|
|
SaveDialogCancel,
|
2026-07-11 09:12:36 +03:00
|
|
|
|
/// Destination picked in the native OS save dialog (`None` = cancelled).
|
|
|
|
|
|
SaveDialogPathPicked(Option<std::path::PathBuf>),
|
2026-03-22 22:44:32 +03:00
|
|
|
|
ClearScene,
|
|
|
|
|
|
SetWireframe(bool),
|
2026-05-27 00:08:37 +03:00
|
|
|
|
/// Set the active tab's render mode (one of acadrust's seven visual
|
|
|
|
|
|
/// styles). Replaces the binary `SetWireframe` over time; the older
|
|
|
|
|
|
/// message stays for ribbon/CLI back-compat and forwards.
|
|
|
|
|
|
SetRenderMode(acadrust::entities::ViewportRenderMode),
|
2026-03-22 22:44:32 +03:00
|
|
|
|
/// Switch camera projection: true = Orthographic, false = Perspective.
|
|
|
|
|
|
SetProjection(bool),
|
|
|
|
|
|
/// Select a ribbon module tab by index.
|
|
|
|
|
|
RibbonSelectTab(usize),
|
2026-07-06 01:06:01 +03:00
|
|
|
|
/// Change the ribbon tool-panel density (persisted).
|
|
|
|
|
|
SetRibbonCollapseMode(crate::ui::ribbon::CollapseMode),
|
2026-03-22 22:44:32 +03:00
|
|
|
|
/// A ribbon tool button was clicked — highlights the tool and dispatches its event.
|
|
|
|
|
|
RibbonToolClick {
|
|
|
|
|
|
tool_id: String,
|
|
|
|
|
|
event: ModuleEvent,
|
|
|
|
|
|
},
|
2026-06-17 10:22:08 +03:00
|
|
|
|
/// Result of a plugin-requested file picker (`ModuleEvent::PluginFileDialog`).
|
|
|
|
|
|
/// `path` is `None` when the user cancels. On `Some`, the host dispatches
|
|
|
|
|
|
/// `"<command> <path>"` to the plugins with original case preserved.
|
|
|
|
|
|
PluginFileDialogResult {
|
|
|
|
|
|
command: String,
|
|
|
|
|
|
path: Option<std::path::PathBuf>,
|
|
|
|
|
|
},
|
2026-03-22 22:44:32 +03:00
|
|
|
|
// ── Document tabs ──────────────────────────────────────────────────────
|
|
|
|
|
|
/// Create a new empty document tab.
|
|
|
|
|
|
TabNew,
|
|
|
|
|
|
/// Switch to the given tab index.
|
|
|
|
|
|
TabSwitch(usize),
|
|
|
|
|
|
/// Close the given tab index.
|
|
|
|
|
|
TabClose(usize),
|
2026-05-01 11:31:11 +03:00
|
|
|
|
// ── Unsaved-changes confirmation dialog ───────────────────────────────
|
|
|
|
|
|
/// User clicked "Save" in the unsaved-changes dialog.
|
|
|
|
|
|
UnsavedDialogSave,
|
|
|
|
|
|
/// User clicked "Discard" in the unsaved-changes dialog.
|
|
|
|
|
|
UnsavedDialogDiscard,
|
|
|
|
|
|
/// User clicked "Cancel" in the unsaved-changes dialog.
|
|
|
|
|
|
UnsavedDialogCancel,
|
2026-06-29 11:47:37 +03:00
|
|
|
|
// ── AEC / unsupported-object drop warning (lossy Save-As) ──────────────
|
|
|
|
|
|
/// Save as the source DWG version so the unsupported objects survive.
|
|
|
|
|
|
AecDropSameVersion,
|
|
|
|
|
|
/// Proceed with the chosen target format, dropping the unsupported objects.
|
|
|
|
|
|
AecDropProceed,
|
|
|
|
|
|
/// Go back to the Save dialog from the AEC-drop warning.
|
|
|
|
|
|
AecDropBack,
|
2026-06-29 13:17:40 +03:00
|
|
|
|
/// Periodic autosave tick — write `.sv$` recovery files for dirty tabs.
|
|
|
|
|
|
AutoSave,
|
2026-05-01 11:31:11 +03:00
|
|
|
|
/// Save-as path picked for the unsaved-changes → save → close flow.
|
|
|
|
|
|
UnsavedPickedSavePath(Option<std::path::PathBuf>),
|
2026-03-22 22:44:32 +03:00
|
|
|
|
// ─────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
CommandInput(String),
|
|
|
|
|
|
CommandSubmit,
|
|
|
|
|
|
Command(String),
|
2026-05-26 18:59:51 +03:00
|
|
|
|
/// Append one typed character to the command-line input from the
|
|
|
|
|
|
/// global key-press subscription. Used when the text-input widget
|
|
|
|
|
|
/// itself isn't focused (focus parked on viewport / button / etc.)
|
|
|
|
|
|
/// so typing still routes to the command line.
|
|
|
|
|
|
CommandAppendChar(String),
|
|
|
|
|
|
/// Pop the trailing character off the command-line input — backspace
|
|
|
|
|
|
/// counterpart to `CommandAppendChar`.
|
|
|
|
|
|
CommandBackspace,
|
feat(dyn-input): TAB-editable distance/angle/coordinate fields
Dinamik giriş artık cursor yanında düzenlenebilir kutular gösteriyor.
TAB ile kutular arası geçilir, bir kutuya yazınca o kutu kilitlenir
(değer sabitlenir), diğerleri cursor'ı takip etmeye devam eder, Enter
ile çözülen nokta komuta point-pick olarak iletilir.
- DocumentTab.dyn_fields / dyn_active state; DynComponent + DynFieldEntry
- sync_dyn_fields komutun dyn_field() + base noktasına göre kutu kümesini
türetir: nokta+base → [d, açı], nokta+base yok → [X, Y], radius → [d],
açı → [açı]
- klavye yönlendirme: rakam/./-/+ aktif kutuya, TAB sonraki kutuya,
Enter try_dyn_commit, Backspace kilidi açar
- dyn_capturing iken command-line text alanı on_input'suz bırakılıyor
ki focus'u ve tuşları çalmasın; girişler global key aboneliğinden
geliyor (numpad dahil text alanı üzerinden yakalanıyor)
- çoklu-kutu overlay render: aktif kutu vurgulu, kilitli kutu farklı
kenar rengi
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 14:08:57 +03:00
|
|
|
|
/// TAB pressed: move focus to the next dynamic-input field (wraps).
|
|
|
|
|
|
DynTabNext,
|
2026-05-27 22:33:36 +03:00
|
|
|
|
/// Split the active Model viewport in two. `true` → horizontal divider
|
|
|
|
|
|
/// (top / bottom); `false` → vertical divider (left / right).
|
|
|
|
|
|
SplitModelViewport(bool),
|
2026-06-26 21:15:00 +03:00
|
|
|
|
/// Close the active Model viewport, merging it into a neighbour.
|
|
|
|
|
|
/// Only meaningful when more than one model tile exists.
|
|
|
|
|
|
CloseModelViewport,
|
2026-04-07 00:04:12 +03:00
|
|
|
|
/// Recall previous command in history (↑ arrow key).
|
|
|
|
|
|
CommandHistoryPrev,
|
|
|
|
|
|
/// Recall next command in history (↓ arrow key).
|
|
|
|
|
|
CommandHistoryNext,
|
2026-05-26 22:13:15 +03:00
|
|
|
|
/// Toggle the dropdown listing the full command-line history.
|
|
|
|
|
|
CommandHistoryToggle,
|
2026-07-01 18:17:46 +03:00
|
|
|
|
/// Copy the full command-line history (every line) to the system
|
|
|
|
|
|
/// clipboard as plain text — issue #232, so output can be pasted for
|
|
|
|
|
|
/// debugging instead of screenshotted.
|
|
|
|
|
|
CommandHistoryCopy,
|
|
|
|
|
|
/// Clear every line from the command-line history.
|
|
|
|
|
|
CommandHistoryClear,
|
|
|
|
|
|
/// Text-editor action from the read-only history dropdown. Only
|
|
|
|
|
|
/// non-editing actions (cursor moves, selection, scroll) are applied so
|
|
|
|
|
|
/// the log stays read-only while remaining drag-selectable and copyable.
|
|
|
|
|
|
CommandHistoryEdit(iced::widget::text_editor::Action),
|
2026-05-26 22:45:54 +03:00
|
|
|
|
/// User clicked an autocomplete suggestion — fill the input with
|
|
|
|
|
|
/// the chosen command name and dispatch it.
|
|
|
|
|
|
CommandSuggestionPick(String),
|
feat(command): clickable option buttons in the command line (#304)
Commands now expose their step options as clickable buttons rendered
inline beside the command-line prompt, so options need not be typed.
Adds a CmdOption/options() API to CadCommand: each command returns the
options for its current step; the command line renders them as
uppercase buttons and a click feeds the keyword to the command exactly
as if typed (an empty keyword submits like Enter).
Construction commands become "central" — CIRCLE offers [3P/2P/TTR/TTT/
DIAMETER], ELLIPSE [ARC/AXIS], ARC [SCE/SCA/SEA/SER/CSA/3P], RECTANGLE
[ROTATION/CENTER], POLYGON [CIRCUMSCRIBED/EDGE] — each keyword hands off
to the existing dedicated variant command via CmdResult::Dispatch,
instead of the variants only being reachable as isolated commands.
Migrates the existing bracketed-option prompts (PLINE, PEDIT, SPLINE,
SCALE, FILLET, CHAMFER, HATCH, GRADIENT, SOLID, POINT, RAY, XLINE, TRIM,
EXTEND) to the same button API and strips the option text from the
prompt strings, keeping value/coordinate hints. PEDIT's Width option
now enters a value sub-step so the button acts on click.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 00:16:59 +03:00
|
|
|
|
/// User clicked a command-line option button — feed its keyword to the
|
|
|
|
|
|
/// active command as if typed (empty keyword = Enter). (#304)
|
|
|
|
|
|
CommandOptionPick(String),
|
2026-03-22 22:44:32 +03:00
|
|
|
|
ToggleLayers,
|
|
|
|
|
|
LayerToggleVisible(usize),
|
|
|
|
|
|
LayerToggleLock(usize),
|
|
|
|
|
|
LayerToggleFreeze(usize),
|
2026-06-19 20:39:09 +03:00
|
|
|
|
/// Sort the Layer Manager table by a clicked column header.
|
refactor: group ui/ statusbar and window subdirs
Continue tidying the flat ui/ root:
- ui/statusbar/: statusbar.rs becomes statusbar/mod.rs (its
crate::ui::statusbar::* paths are preserved) and absorbs
statusbar_config.rs + statusbar_menu.rs.
- ui/window/: the standalone manager/dialog windows move here —
about, layout_manager, page_setup, plugin_manager, shortcuts,
layers, update_notice, open_progress. The crate::ui::LayerPanel
re-export is repointed so callers are unchanged.
Call sites repointed to the new paths; no compat re-exports beyond the
existing LayerPanel one. ui/ root drops from 21 to 10 flat files with
five subdirs (ribbon, style, popup, statusbar, window).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 23:52:26 +03:00
|
|
|
|
LayerSort(crate::ui::window::layers::LayerSortCol),
|
2026-04-06 23:41:39 +03:00
|
|
|
|
/// Toggle per-viewport freeze: (layer_index, vp_col_index)
|
|
|
|
|
|
LayerToggleVpFreeze(usize, usize),
|
2026-03-22 22:44:32 +03:00
|
|
|
|
LayerNew,
|
|
|
|
|
|
LayerDelete,
|
2026-07-01 17:23:32 +03:00
|
|
|
|
/// Confirm deleting a non-empty layer (erases its objects too).
|
|
|
|
|
|
LayerDeleteConfirm,
|
2026-03-22 22:44:32 +03:00
|
|
|
|
LayerSetCurrent,
|
|
|
|
|
|
LayerSelect(usize),
|
|
|
|
|
|
LayerRenameStart(usize),
|
|
|
|
|
|
LayerRenameEdit(String),
|
|
|
|
|
|
LayerColorPickerToggle(usize),
|
|
|
|
|
|
LayerColorMorePalette,
|
|
|
|
|
|
LayerColorSet(u8),
|
|
|
|
|
|
LayerLinetypeSet(String),
|
|
|
|
|
|
LayerLineweightSet(LineWeight),
|
|
|
|
|
|
LayerTransparencyEdit(usize, String),
|
|
|
|
|
|
LayerRenameCommit,
|
|
|
|
|
|
CursorMoved(Point),
|
|
|
|
|
|
ViewportClick,
|
|
|
|
|
|
ViewportMove(Point),
|
|
|
|
|
|
ViewportLeftPress,
|
|
|
|
|
|
ViewportLeftRelease,
|
|
|
|
|
|
ViewportRightPress,
|
|
|
|
|
|
ViewportRightRelease,
|
|
|
|
|
|
ViewportMiddlePress,
|
|
|
|
|
|
ViewportMiddleRelease,
|
|
|
|
|
|
ViewportScroll(mouse::ScrollDelta),
|
|
|
|
|
|
ViewportExit,
|
2026-06-27 11:50:11 +03:00
|
|
|
|
// ── Per-pane Model viewport (pane_grid) ───────────────────────────────
|
|
|
|
|
|
/// A pane_grid divider was dragged — resize the split.
|
|
|
|
|
|
PaneResized(iced::widget::pane_grid::ResizeEvent),
|
|
|
|
|
|
/// A pane body was clicked — focus that pane.
|
|
|
|
|
|
PaneClicked(iced::widget::pane_grid::Pane),
|
|
|
|
|
|
/// A pane was drag-and-dropped onto another — swap them.
|
|
|
|
|
|
PaneDragged(iced::widget::pane_grid::DragEvent),
|
|
|
|
|
|
/// Per-pane mouse events. `usize` = the pane's tile index; the `Point` is
|
|
|
|
|
|
/// pane-local (offset to canvas coords + focus in the handler).
|
|
|
|
|
|
PaneMove(usize, Point),
|
|
|
|
|
|
PanePress(usize),
|
|
|
|
|
|
PaneRelease(usize),
|
|
|
|
|
|
PaneRightPress(usize),
|
|
|
|
|
|
PaneRightRelease(usize),
|
|
|
|
|
|
PaneMiddlePress(usize),
|
|
|
|
|
|
PaneMiddleRelease(usize),
|
|
|
|
|
|
PaneScroll(usize, mouse::ScrollDelta),
|
|
|
|
|
|
/// Drag-handle pressed on the active pane's controls bar: arm a pane move —
|
|
|
|
|
|
/// the next pane released over is swapped with the active pane.
|
|
|
|
|
|
PaneMoveStart,
|
2026-03-22 22:44:32 +03:00
|
|
|
|
ViewCubeSnap(CubeRegion),
|
2026-06-27 01:12:45 +03:00
|
|
|
|
/// World-frame view snap from a compass cardinal (N/E/S/W), bypassing the
|
|
|
|
|
|
/// UCS so the compass stays world-aligned.
|
|
|
|
|
|
ViewCubeSnapWorld(CubeRegion),
|
2026-06-25 23:46:43 +03:00
|
|
|
|
/// ViewCube home button → jump to the default isometric view.
|
|
|
|
|
|
ViewCubeHome,
|
|
|
|
|
|
/// ViewCube roll arrow → roll the view 90° (true = clockwise).
|
|
|
|
|
|
ViewCubeRoll(bool),
|
|
|
|
|
|
/// ViewCube nudge triangle → tip / spin the view 90°.
|
|
|
|
|
|
ViewCubeNudge(crate::scene::NudgeDir),
|
|
|
|
|
|
/// WCS/UCS selector under the cube — empty string = World.
|
|
|
|
|
|
SetViewcubeUcs(String),
|
feat(grip): multi-functional hover popup menu
Phase 2 of the grip overhaul: when the cursor dwells on a selected
entity's grip the overlay opens a popup menu with entity-specific
options (Stretch / Add Vertex / Remove Vertex / Convert to
Arc-Line / …). Picking an item dispatches `apply_grip_menu`; the
default `Stretch` entry is a no-op that lets the user click the
grip to start the normal drag.
- `GripMenuItem` + `GripMenuAction` (full vocabulary covering
stretch, add/remove vertex, convert to arc/line, reverse arrows,
text-position variants, hatch parameters, tangent direction).
- `Grippable` trait gains `grip_menu` + `apply_grip_menu` with
trait-default `Stretch`-only impls so every existing entity gets
the popup for free.
- `EntityTypeOps` dispatches both methods across the same entity
list as the existing grip / property machinery.
- `OpenCADStudio` carries `grip_hover` (dwell tracker) +
`grip_popup` (open menu). `update_grip_hover` runs from
`ViewportMove`: hit-tests grips, refreshes the dwell timer, and
opens the popup after 600 ms. Cursor drift past 80 px or a grip
click dismisses it.
- `view.rs` paints the popup as an absolute-positioned column of
buttons at the grip's screen anchor; each button publishes
`Message::GripMenuPick(idx)`.
- `LwPolyline` overrides `Grippable` manually (skips the standard
macro) so its vertex grip exposes Add/Remove Vertex and its
segment-midpoint grip exposes Add Vertex + Convert to Arc/Line.
`apply_grip_menu` performs the edits: vertex insertion at the
chord midpoint inheriting the previous bulge, vertex removal,
and bulge toggle for the arc/line conversion.
2026-05-29 09:30:54 +03:00
|
|
|
|
/// User picked an item in the multi-functional grip popup menu —
|
|
|
|
|
|
/// the index is into `grip_popup.items`.
|
|
|
|
|
|
GripMenuPick(usize),
|
2026-06-11 20:11:19 +03:00
|
|
|
|
/// User picked a dynamic-block visibility state — index into the
|
|
|
|
|
|
/// visibility dropdown's items.
|
|
|
|
|
|
VisibilityPick(usize),
|
2026-05-29 09:38:37 +03:00
|
|
|
|
/// Timer pulse while the cursor is dwelling on a grip; drives the
|
|
|
|
|
|
/// dwell-to-popup transition without requiring further mouse motion.
|
|
|
|
|
|
GripDwellTick,
|
2026-06-20 20:43:33 +03:00
|
|
|
|
/// Timer pulse while a rollover hit-test is queued; fires when the
|
|
|
|
|
|
/// cursor has been still long enough to safely run the pick.
|
|
|
|
|
|
HoverDwellTick,
|
2026-03-22 22:44:32 +03:00
|
|
|
|
WindowResized(f32, f32),
|
|
|
|
|
|
/// Enter pressed globally — finalises the active command (no text-input involvement).
|
|
|
|
|
|
CommandFinalize,
|
2026-05-31 13:14:35 +03:00
|
|
|
|
/// Space pressed globally — a literal space in the MText preview, otherwise
|
|
|
|
|
|
/// finalises like Enter.
|
|
|
|
|
|
CommandSpace,
|
2026-03-22 22:44:32 +03:00
|
|
|
|
/// Escape pressed globally — cancels the active command.
|
|
|
|
|
|
CommandEscape,
|
|
|
|
|
|
/// Toggle the global snap on/off (OSNAP button body click).
|
|
|
|
|
|
ToggleSnapEnabled,
|
|
|
|
|
|
/// Toggle grid-snap on/off — F9 / SNAP status-bar button.
|
|
|
|
|
|
ToggleGridSnap,
|
2026-04-24 01:23:06 +03:00
|
|
|
|
/// Toggle the ViewCube 3D gizmo visibility (NAVVCUBE).
|
|
|
|
|
|
ToggleViewCube,
|
|
|
|
|
|
/// Toggle the Properties panel visibility (PROPERTIES).
|
|
|
|
|
|
ToggleProperties,
|
|
|
|
|
|
/// Toggle the document file tabs at the top (FILETAB).
|
|
|
|
|
|
ToggleFileTabs,
|
|
|
|
|
|
/// Toggle the layout tabs at the bottom (LAYOUTTAB).
|
|
|
|
|
|
ToggleLayoutTabs,
|
2026-03-22 22:44:32 +03:00
|
|
|
|
/// Toggle grid display in the viewport — F7 / GRID status-bar button.
|
|
|
|
|
|
ToggleGrid,
|
|
|
|
|
|
/// Toggle orthogonal drawing constraint — F8 / ORTHO status-bar button.
|
|
|
|
|
|
ToggleOrtho,
|
2026-05-23 07:15:26 +03:00
|
|
|
|
/// Toggle LWDISPLAY header flag — LWT status-bar button.
|
|
|
|
|
|
ToggleLineweightDisplay,
|
2026-04-07 19:30:25 +03:00
|
|
|
|
/// Toggle polar-angle constraint — F10 / POLAR status-bar button.
|
2026-03-22 22:44:32 +03:00
|
|
|
|
TogglePolar,
|
2026-07-09 21:35:47 +03:00
|
|
|
|
/// Set polar tracking angle increment (POLAR picker / right-click cycle).
|
2026-04-07 19:30:25 +03:00
|
|
|
|
SetPolarAngle(f32),
|
2026-07-09 21:35:47 +03:00
|
|
|
|
/// Open/close the polar-tracking angle picker (POLAR pill caret).
|
|
|
|
|
|
TogglePolarPopup,
|
|
|
|
|
|
/// Close the polar-tracking angle picker.
|
|
|
|
|
|
ClosePolarPopup,
|
|
|
|
|
|
/// Live text edit of the polar picker's custom-angle field.
|
|
|
|
|
|
PolarCustomInput(String),
|
|
|
|
|
|
/// Apply the typed custom polar angle (Enter in the picker's field).
|
|
|
|
|
|
SubmitPolarCustom,
|
2026-05-06 17:50:46 +03:00
|
|
|
|
/// Set the model-space annotation scale (CANNOSCALE equivalent).
|
|
|
|
|
|
SetAnnotationScale(f32),
|
|
|
|
|
|
/// Set the active viewport's custom_scale (paper space).
|
|
|
|
|
|
SetViewportScale(f64),
|
|
|
|
|
|
/// Toggle the scale picker popup open/closed.
|
|
|
|
|
|
ToggleScalePopup,
|
|
|
|
|
|
/// Close the scale picker popup.
|
|
|
|
|
|
CloseScalePopup,
|
2026-07-13 10:39:14 +03:00
|
|
|
|
/// Open the annotation-scale manager (from the scale popup's Manage row).
|
|
|
|
|
|
ScaleManagerOpen,
|
2026-07-13 22:38:29 +03:00
|
|
|
|
/// Open the Annotation Object Scale dialog for the current single selection.
|
|
|
|
|
|
AnnoObjectScaleOpen,
|
|
|
|
|
|
/// Toggle whether the dialog's object has a representation for this scale.
|
|
|
|
|
|
AnnoObjectScaleToggle(String),
|
2026-07-13 10:39:14 +03:00
|
|
|
|
/// Select a scale row in the manager (loads it into the editor).
|
|
|
|
|
|
ScaleManagerSelect(String),
|
2026-07-13 14:37:33 +03:00
|
|
|
|
/// Add a new scale to the list (staged) and select it for editing.
|
2026-07-13 10:39:14 +03:00
|
|
|
|
ScaleManagerNew,
|
2026-07-13 14:37:33 +03:00
|
|
|
|
/// Duplicate the selected scale under a unique name (staged).
|
|
|
|
|
|
ScaleManagerCopy,
|
|
|
|
|
|
/// Begin inline rename of a scale row (double-click).
|
|
|
|
|
|
ScaleRenameStart(String),
|
|
|
|
|
|
/// Edit the inline-rename buffer.
|
|
|
|
|
|
ScaleRenameEdit(String),
|
|
|
|
|
|
/// Commit the inline rename.
|
|
|
|
|
|
ScaleRenameCommit,
|
2026-07-13 10:39:14 +03:00
|
|
|
|
/// Delete the selected scale.
|
|
|
|
|
|
ScaleManagerDelete,
|
|
|
|
|
|
/// Set the selected scale as the current annotation scale.
|
|
|
|
|
|
ScaleManagerSetCurrent,
|
|
|
|
|
|
/// Apply the editor fields to the selected scale (rename / re-ratio).
|
|
|
|
|
|
ScaleManagerApply,
|
|
|
|
|
|
/// Annotation-scale manager editor field edits.
|
|
|
|
|
|
ScaleManagerPaperBuf(String),
|
|
|
|
|
|
ScaleManagerDrawingBuf(String),
|
2026-06-27 14:24:47 +03:00
|
|
|
|
/// Toggle the leftmost hamburger's Model/layout list dropdown.
|
|
|
|
|
|
ToggleLayoutList,
|
|
|
|
|
|
/// Close the Model/layout list dropdown.
|
|
|
|
|
|
CloseLayoutList,
|
2026-07-10 15:54:57 +03:00
|
|
|
|
/// Cycle the coordinate readout mode ($COORDS): static → live → polar.
|
|
|
|
|
|
CycleCoordsMode,
|
2026-06-04 19:14:04 +03:00
|
|
|
|
/// Toggle the status-bar customization menu open/closed.
|
|
|
|
|
|
ToggleStatusBarMenu,
|
|
|
|
|
|
/// Close the status-bar customization menu.
|
|
|
|
|
|
CloseStatusBarMenu,
|
|
|
|
|
|
/// Show/hide a single status-bar pill.
|
refactor: group ui/ statusbar and window subdirs
Continue tidying the flat ui/ root:
- ui/statusbar/: statusbar.rs becomes statusbar/mod.rs (its
crate::ui::statusbar::* paths are preserved) and absorbs
statusbar_config.rs + statusbar_menu.rs.
- ui/window/: the standalone manager/dialog windows move here —
about, layout_manager, page_setup, plugin_manager, shortcuts,
layers, update_notice, open_progress. The crate::ui::LayerPanel
re-export is repointed so callers are unchanged.
Call sites repointed to the new paths; no compat re-exports beyond the
existing LayerPanel one. ui/ root drops from 21 to 10 flat files with
five subdirs (ribbon, style, popup, statusbar, window).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 23:52:26 +03:00
|
|
|
|
ToggleStatusPill(crate::ui::statusbar::statusbar_config::StatusPill),
|
2026-06-04 19:14:04 +03:00
|
|
|
|
/// Toggle clean-screen mode (hide ribbon + side panels).
|
|
|
|
|
|
ToggleCleanScreen,
|
2026-06-04 23:25:09 +03:00
|
|
|
|
/// Toggle whether entity transparency is shown on screen.
|
|
|
|
|
|
ToggleTransparencyDisplay,
|
2026-06-04 23:31:36 +03:00
|
|
|
|
/// Toggle the Quick Properties floating panel.
|
|
|
|
|
|
ToggleQuickProperties,
|
2026-06-04 23:44:39 +03:00
|
|
|
|
/// Toggle selection cycling for overlapping objects.
|
|
|
|
|
|
ToggleSelectionCycling,
|
2026-06-04 23:56:24 +03:00
|
|
|
|
/// Add an object from the selection-cycling list box to the selection.
|
|
|
|
|
|
CycleSelect(acadrust::Handle),
|
2026-06-05 00:00:52 +03:00
|
|
|
|
/// Preview (highlight) a cycling-list row's object, or clear with `None`.
|
|
|
|
|
|
CycleHover(Option<acadrust::Handle>),
|
2026-06-05 00:05:15 +03:00
|
|
|
|
/// Cursor left a cycling-list row; clear the preview only if it still
|
|
|
|
|
|
/// points at this row (guards against enter/exit event reordering).
|
|
|
|
|
|
CycleHoverExit(acadrust::Handle),
|
2026-06-04 23:56:24 +03:00
|
|
|
|
/// Dismiss the selection-cycling list box without picking.
|
|
|
|
|
|
CycleCancel,
|
2026-06-04 23:39:48 +03:00
|
|
|
|
/// Toggle the selection-filter type picker open/closed.
|
|
|
|
|
|
ToggleSelectionFilterPopup,
|
|
|
|
|
|
/// Close the selection-filter type picker.
|
|
|
|
|
|
CloseSelectionFilterPopup,
|
|
|
|
|
|
/// Include/exclude an entity type from interactive selection.
|
|
|
|
|
|
ToggleSelectionFilterType(String),
|
2026-07-01 09:47:13 +03:00
|
|
|
|
/// Make every entity type selectable again (clear the filter).
|
|
|
|
|
|
SelectionFilterSelectAll,
|
|
|
|
|
|
/// Exclude every present entity type from selection.
|
|
|
|
|
|
SelectionFilterClearAll,
|
2026-06-04 22:06:42 +03:00
|
|
|
|
/// Toggle the drawing-units picker open/closed.
|
|
|
|
|
|
ToggleUnitsPopup,
|
|
|
|
|
|
/// Close the drawing-units picker.
|
|
|
|
|
|
CloseUnitsPopup,
|
|
|
|
|
|
/// Set the drawing units (INSUNITS) for the active drawing.
|
|
|
|
|
|
SetDrawingUnits(i16),
|
2026-06-04 23:17:21 +03:00
|
|
|
|
/// Toggle the Isolate pill's action menu open/closed.
|
|
|
|
|
|
ToggleIsolatePopup,
|
|
|
|
|
|
/// Close the Isolate action menu.
|
|
|
|
|
|
CloseIsolatePopup,
|
2026-04-08 00:12:09 +03:00
|
|
|
|
/// Toggle dynamic input overlay (F12).
|
|
|
|
|
|
ToggleDynInput,
|
2026-04-08 00:26:54 +03:00
|
|
|
|
/// Toggle object snap tracking (F11).
|
|
|
|
|
|
ToggleOTrack,
|
2026-03-22 22:44:32 +03:00
|
|
|
|
/// Toggle an individual snap mode (from popup row click).
|
|
|
|
|
|
ToggleSnap(crate::snap::SnapType),
|
|
|
|
|
|
/// Open / close the OSNAP popup (▾ arrow click).
|
|
|
|
|
|
ToggleSnapPopup,
|
|
|
|
|
|
/// Close the OSNAP popup (click-catcher outside the panel).
|
|
|
|
|
|
CloseSnapPopup,
|
|
|
|
|
|
/// Enable all snap modes.
|
|
|
|
|
|
SnapSelectAll,
|
|
|
|
|
|
/// Disable all snap modes.
|
|
|
|
|
|
SnapClearAll,
|
|
|
|
|
|
/// Toggle a ribbon dropdown open/closed.
|
|
|
|
|
|
ToggleRibbonDropdown(String),
|
2026-07-05 18:04:55 +03:00
|
|
|
|
/// Toggle a collapsed ribbon panel's flyout open/closed (by panel title).
|
|
|
|
|
|
ToggleRibbonPanel(String),
|
2026-03-22 22:44:32 +03:00
|
|
|
|
/// Close any open ribbon dropdown (click-catcher outside the panel).
|
|
|
|
|
|
CloseRibbonDropdown,
|
|
|
|
|
|
/// User selected a specific item from a ribbon dropdown.
|
|
|
|
|
|
DropdownSelectItem {
|
|
|
|
|
|
dropdown_id: &'static str,
|
|
|
|
|
|
cmd: &'static str,
|
|
|
|
|
|
},
|
|
|
|
|
|
/// Delete key — erase all currently selected entities.
|
|
|
|
|
|
DeleteSelected,
|
|
|
|
|
|
Undo,
|
|
|
|
|
|
Redo,
|
|
|
|
|
|
UndoMany(usize),
|
|
|
|
|
|
RedoMany(usize),
|
|
|
|
|
|
// ── Ribbon ────────────────────────────────────────────────────────────
|
|
|
|
|
|
/// User selected a layer from the layer combobox in the ribbon.
|
|
|
|
|
|
RibbonLayerChanged(String),
|
|
|
|
|
|
/// User changed the active color in the Properties toolbar.
|
|
|
|
|
|
RibbonColorChanged(AcadColor),
|
|
|
|
|
|
/// Toggle the full ACI palette inside the ribbon color picker.
|
|
|
|
|
|
RibbonColorPaletteToggle,
|
|
|
|
|
|
/// User changed the active linetype in the Properties toolbar.
|
|
|
|
|
|
RibbonLinetypeChanged(String),
|
|
|
|
|
|
/// User changed the active lineweight in the Properties toolbar.
|
|
|
|
|
|
RibbonLineweightChanged(LineWeight),
|
2026-04-09 03:38:23 +03:00
|
|
|
|
/// User selected a style from a style combobox in the ribbon.
|
2026-05-12 10:36:15 +03:00
|
|
|
|
RibbonStyleChanged {
|
|
|
|
|
|
key: crate::modules::StyleKey,
|
|
|
|
|
|
name: String,
|
|
|
|
|
|
},
|
2026-03-22 22:44:32 +03:00
|
|
|
|
|
|
|
|
|
|
// ── Properties panel ──────────────────────────────────────────────────
|
|
|
|
|
|
/// User selected a layer from the layer pick_list in the Properties panel.
|
|
|
|
|
|
PropLayerChanged(String),
|
|
|
|
|
|
PropSelectionGroupChanged(crate::ui::properties::SelectionGroup),
|
|
|
|
|
|
/// User picked a color from the Properties color picker.
|
|
|
|
|
|
PropColorChanged(AcadColor),
|
|
|
|
|
|
/// User selected a lineweight from the Properties pick_list.
|
|
|
|
|
|
PropLwChanged(LineWeight),
|
|
|
|
|
|
/// User selected a linetype from the linetype pick_list.
|
|
|
|
|
|
PropLinetypeChanged(String),
|
|
|
|
|
|
/// User toggled a boolean property (e.g. Invisible).
|
|
|
|
|
|
PropBoolToggle(&'static str),
|
2026-07-08 17:53:05 +03:00
|
|
|
|
/// User stepped the Current Vertex selector by ±1 (polyline vertex nav).
|
|
|
|
|
|
PropVertexStep(i8),
|
2026-03-22 22:44:32 +03:00
|
|
|
|
/// User selected a hatch pattern from the pattern pick_list in Properties.
|
|
|
|
|
|
PropHatchPatternChanged(String),
|
|
|
|
|
|
/// User selected a generic choice field in the Properties panel.
|
|
|
|
|
|
PropGeomChoiceChanged {
|
|
|
|
|
|
field: &'static str,
|
|
|
|
|
|
value: String,
|
|
|
|
|
|
},
|
|
|
|
|
|
/// User is typing in an editable geometry field (live buffer update).
|
|
|
|
|
|
PropGeomInput {
|
|
|
|
|
|
field: &'static str,
|
|
|
|
|
|
value: String,
|
|
|
|
|
|
},
|
|
|
|
|
|
/// User committed a geometry/common field edit (Enter pressed).
|
|
|
|
|
|
PropGeomCommit(&'static str),
|
2026-07-01 07:42:50 +03:00
|
|
|
|
/// User is typing in a block-attribute value field (live buffer update),
|
|
|
|
|
|
/// keyed by the attribute tag.
|
|
|
|
|
|
PropAttrInput { tag: String, value: String },
|
|
|
|
|
|
/// User committed a block-attribute value edit (Enter pressed).
|
|
|
|
|
|
PropAttrCommit(String),
|
2026-03-22 22:44:32 +03:00
|
|
|
|
/// Toggle the inline color picker dropdown open/closed.
|
|
|
|
|
|
PropColorPickerToggle,
|
2026-07-07 17:41:16 +03:00
|
|
|
|
/// Toggle the MTEXT background-colour picker dropdown open/closed.
|
|
|
|
|
|
PropBgColorPickerToggle,
|
|
|
|
|
|
/// User picked an MTEXT background fill colour — sets `background_color`
|
|
|
|
|
|
/// and switches the background mode to Fill so it shows immediately.
|
|
|
|
|
|
PropBgColorChanged(AcadColor),
|
2026-07-08 23:35:27 +03:00
|
|
|
|
/// Toggle the generic per-field colour picker (e.g. a hatch gradient colour)
|
|
|
|
|
|
/// identified by its property field name.
|
|
|
|
|
|
PropColorFieldToggle(String),
|
|
|
|
|
|
/// User picked a colour for a generic per-field colour row (hatch gradient
|
|
|
|
|
|
/// `Color 1` / `Color 2`), routed by the field name.
|
|
|
|
|
|
PropColorFieldChanged { field: String, color: AcadColor },
|
2026-07-01 19:47:32 +03:00
|
|
|
|
/// Collapse the inline color picker dropdown. Fired when another
|
|
|
|
|
|
/// properties-panel dropdown (a combo_box) opens, so at most one panel
|
|
|
|
|
|
/// dropdown is open at a time and they can't overlap. (#235)
|
|
|
|
|
|
PropColorPickerClose,
|
2026-03-22 22:44:32 +03:00
|
|
|
|
/// Toggle the full ACI colour palette expansion.
|
|
|
|
|
|
PropColorPaletteToggle,
|
2026-03-31 07:24:16 +03:00
|
|
|
|
/// Enter the model-space editing mode inside the given viewport (MSPACE).
|
|
|
|
|
|
EnterViewport(acadrust::Handle),
|
|
|
|
|
|
/// Exit MSPACE and return to paper-space editing (PSPACE).
|
|
|
|
|
|
ExitViewport,
|
2026-03-31 12:52:07 +03:00
|
|
|
|
/// MS command: enter MSPACE for the first available viewport.
|
|
|
|
|
|
MspaceCommand,
|
|
|
|
|
|
/// PS command: exit MSPACE (PSPACE).
|
|
|
|
|
|
PspaceCommand,
|
2026-03-22 22:44:32 +03:00
|
|
|
|
/// Switch to a named layout ("Model" or paper space layout name).
|
|
|
|
|
|
LayoutSwitch(String),
|
|
|
|
|
|
/// Create a new paper space layout.
|
|
|
|
|
|
LayoutCreate,
|
2026-03-31 07:13:36 +03:00
|
|
|
|
/// Delete the named paper space layout (Model cannot be deleted).
|
|
|
|
|
|
LayoutDelete(String),
|
|
|
|
|
|
/// Begin inline rename for the given layout tab.
|
|
|
|
|
|
LayoutRenameStart(String),
|
|
|
|
|
|
/// Live-update the rename text input buffer.
|
|
|
|
|
|
LayoutRenameEdit(String),
|
|
|
|
|
|
/// Commit the rename (Enter pressed in the text input).
|
|
|
|
|
|
LayoutRenameCommit,
|
|
|
|
|
|
/// Cancel an in-progress rename (Escape).
|
|
|
|
|
|
LayoutRenameCancel,
|
|
|
|
|
|
/// Open the right-click context menu for the given layout tab.
|
|
|
|
|
|
LayoutContextMenu(String),
|
|
|
|
|
|
/// Close the layout context menu.
|
|
|
|
|
|
LayoutContextMenuClose,
|
2026-04-08 18:28:30 +03:00
|
|
|
|
// ── Layout Manager Panel ────────────────────────────────────────────
|
|
|
|
|
|
LayoutManagerOpen,
|
2026-04-09 12:44:31 +03:00
|
|
|
|
#[allow(dead_code)]
|
2026-04-08 18:28:30 +03:00
|
|
|
|
LayoutManagerClose,
|
|
|
|
|
|
LayoutManagerSelect(String),
|
|
|
|
|
|
LayoutManagerRenameBuf(String),
|
|
|
|
|
|
LayoutManagerRenameCommit,
|
|
|
|
|
|
LayoutManagerNew,
|
|
|
|
|
|
LayoutManagerDelete,
|
|
|
|
|
|
LayoutManagerMoveLeft,
|
|
|
|
|
|
LayoutManagerMoveRight,
|
|
|
|
|
|
LayoutManagerSetCurrent,
|
2026-04-08 18:31:09 +03:00
|
|
|
|
/// Switch the UI color scheme.
|
|
|
|
|
|
SetTheme(Theme),
|
2026-04-08 18:34:10 +03:00
|
|
|
|
// ── Keyboard Shortcut Editor ────────────────────────────────────────
|
|
|
|
|
|
ShortcutsPanelOpen,
|
2026-04-09 12:44:31 +03:00
|
|
|
|
#[allow(dead_code)]
|
2026-04-08 18:34:10 +03:00
|
|
|
|
ShortcutsPanelClose,
|
feat(alias): centralize command aliases in user-editable ocad.pgp (#286/#288)
Command aliases (L→LINE, CC→COPYCLIP) were scattered across dispatch match
arms and CommandRegistration name arrays. Move them all into a single
user-editable file:
- assets/ocad.pgp: shipped default alias table (ALIAS,*COMMAND format),
embedded and copied to config/ocad.pgp on first launch; only that user
copy is read after. AA→AREA default closes #286.
- resolve_alias() rewrites the leading command token at the top of
dispatch_command_inner, so aliases resolve for every path (families,
plugins, Repeat menu). Args after the first space are untouched.
- Autocomplete hides alias keys and, when the whole input is an alias,
surfaces its target command as the top (highlighted) suggestion so the
highlight matches what Enter runs (AA→AREA, L→LINE).
- ALIASEDIT opens a new editor modal (add/edit/delete rows) with an Apply
button; Apply persists to ocad.pgp, closing discards unapplied edits.
- Strip alias tokens from all 8 dispatch match-arm files and 71 module
CommandRegistration arrays, keeping canonical/synonym/distinct-command
names. ocad.pgp is now the sole source: deleting or remapping an alias
there takes effect. CUI stays on the keyboard-shortcut panel.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 12:39:22 +03:00
|
|
|
|
// ── Command Alias Editor (ALIASEDIT) ────────────────────────────────
|
|
|
|
|
|
/// Open the command-alias editor modal, seeding rows from the alias table.
|
|
|
|
|
|
AliasEditorOpen,
|
|
|
|
|
|
/// Live edit of the alias or command in row `idx`.
|
|
|
|
|
|
AliasEditorInput {
|
|
|
|
|
|
idx: usize,
|
|
|
|
|
|
field: crate::ui::window::alias_editor::AliasField,
|
|
|
|
|
|
value: String,
|
|
|
|
|
|
},
|
|
|
|
|
|
/// Append a blank alias row.
|
|
|
|
|
|
AliasEditorAdd,
|
|
|
|
|
|
/// Remove alias row `idx`.
|
|
|
|
|
|
AliasEditorRemove(usize),
|
|
|
|
|
|
/// Commit the edited rows to the alias table (Apply button); stays open.
|
|
|
|
|
|
AliasEditorApply,
|
2026-04-22 22:22:33 +03:00
|
|
|
|
// ── About window ────────────────────────────────────────────────────
|
|
|
|
|
|
AboutOpen,
|
2026-06-16 19:07:25 +03:00
|
|
|
|
/// Close whatever in-canvas modal dialog is open (Plan B).
|
|
|
|
|
|
CloseModal,
|
2026-07-01 08:05:25 +03:00
|
|
|
|
// ── Attribute editor dialog ───────────────────────────────────────────
|
|
|
|
|
|
/// Open the attribute editor for an INSERT (double-click / ATTEDIT).
|
|
|
|
|
|
AttrEditorOpen(acadrust::Handle),
|
feat(attedit): enhanced attribute editor with 3 tabs
Grow the attribute editor into a three-tab enhanced editor:
- Attribute: the tag/prompt/value list; click a row to select it, edit
its value below.
- Text Options: the selected attribute's Text Style, Justification,
Height, Rotation, Width Factor, Oblique Angle, Backwards, Upside down.
- Properties: the selected attribute's Layer, Linetype, Color,
Lineweight (the colour list also surfaces a non-standard current
colour so it displays and round-trips).
Applying writes value, text-formatting and common-property edits back to
each attribute positionally (guarded on block name + count), with undo
and a repaint. OK/Cancel are replaced by a single Apply button in a top
toolbar that commits but keeps the dialog open, matching the other modal
windows — the frame ✕ closes and discards un-applied edits. The frame
adopts the shared style-window palette, tab styling and sizing.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 09:06:03 +03:00
|
|
|
|
/// Switch the editor's active tab.
|
|
|
|
|
|
AttrEditorTab(crate::ui::window::attribute_editor::AttrTab),
|
|
|
|
|
|
/// Select the row the Text Options / Properties tabs act on.
|
|
|
|
|
|
AttrEditorSelect(usize),
|
|
|
|
|
|
/// Live edit of the attribute value at row `idx` (Attribute tab).
|
2026-07-01 08:05:25 +03:00
|
|
|
|
AttrEditorInput { idx: usize, value: String },
|
feat(attedit): enhanced attribute editor with 3 tabs
Grow the attribute editor into a three-tab enhanced editor:
- Attribute: the tag/prompt/value list; click a row to select it, edit
its value below.
- Text Options: the selected attribute's Text Style, Justification,
Height, Rotation, Width Factor, Oblique Angle, Backwards, Upside down.
- Properties: the selected attribute's Layer, Linetype, Color,
Lineweight (the colour list also surfaces a non-standard current
colour so it displays and round-trips).
Applying writes value, text-formatting and common-property edits back to
each attribute positionally (guarded on block name + count), with undo
and a repaint. OK/Cancel are replaced by a single Apply button in a top
toolbar that commits but keeps the dialog open, matching the other modal
windows — the frame ✕ closes and discards un-applied edits. The frame
adopts the shared style-window palette, tab styling and sizing.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 09:06:03 +03:00
|
|
|
|
// Text Options — all act on the selected row:
|
|
|
|
|
|
AttrEditorTextStyle(String),
|
|
|
|
|
|
AttrEditorJustify(String),
|
|
|
|
|
|
AttrEditorHeight(String),
|
|
|
|
|
|
AttrEditorRotation(String),
|
|
|
|
|
|
AttrEditorWidth(String),
|
|
|
|
|
|
AttrEditorOblique(String),
|
|
|
|
|
|
AttrEditorBackwards(bool),
|
|
|
|
|
|
AttrEditorUpsideDown(bool),
|
|
|
|
|
|
// Properties — all act on the selected row:
|
|
|
|
|
|
AttrEditorLayer(String),
|
|
|
|
|
|
AttrEditorLinetype(String),
|
|
|
|
|
|
AttrEditorColor(String),
|
|
|
|
|
|
AttrEditorLineweight(acadrust::types::LineWeight),
|
|
|
|
|
|
/// Commit every attribute edit to the block, keeping the dialog open
|
|
|
|
|
|
/// (Apply); the frame ✕ closes and discards any un-applied edits.
|
|
|
|
|
|
AttrEditorApply,
|
2026-06-17 09:59:17 +03:00
|
|
|
|
/// Title-bar pressed: begin dragging the active modal.
|
|
|
|
|
|
ModalGrab,
|
|
|
|
|
|
/// Cursor moved while dragging the modal title bar.
|
|
|
|
|
|
ModalDragMove(Point),
|
|
|
|
|
|
/// Title-bar released: stop dragging.
|
|
|
|
|
|
ModalDragRelease,
|
2026-04-22 22:22:33 +03:00
|
|
|
|
AboutCopyInfo,
|
2026-06-15 01:46:09 +03:00
|
|
|
|
// ── Plugin Manager window ───────────────────────────────────────────
|
|
|
|
|
|
PluginManagerOpen,
|
|
|
|
|
|
#[allow(dead_code)]
|
|
|
|
|
|
PluginManagerClose,
|
2026-06-17 10:15:38 +03:00
|
|
|
|
/// Enable (`true`) or disable (`false`) the plugin with this id.
|
|
|
|
|
|
SetPluginEnabled(String, bool),
|
2026-06-17 15:24:56 +03:00
|
|
|
|
// ── Plugin marketplace (install from a linked repo's releases) ─────────
|
|
|
|
|
|
/// Edit the add-repository text field.
|
|
|
|
|
|
PluginRepoInput(String),
|
|
|
|
|
|
/// Link the repository currently in the text field.
|
|
|
|
|
|
PluginRepoAdd,
|
|
|
|
|
|
/// Unlink a repository.
|
|
|
|
|
|
PluginRepoRemove(String),
|
|
|
|
|
|
/// The curated registry was fetched.
|
|
|
|
|
|
PluginRegistryFetched(Result<Vec<crate::plugin::external::RegistryEntry>, String>),
|
2026-07-02 01:00:22 +03:00
|
|
|
|
/// Patreon supporters fetched at boot for the Start page (name, pledge cents).
|
|
|
|
|
|
PatronsFetched(Result<Vec<(String, i64)>, String>),
|
2026-06-17 15:24:56 +03:00
|
|
|
|
/// Installable release tags fetched for `owner/repo`.
|
|
|
|
|
|
PluginReleasesFetched(String, Result<Vec<String>, String>),
|
|
|
|
|
|
/// Choose a release tag for a repo (`repo`, `tag`).
|
|
|
|
|
|
PluginReleaseSelect(String, String),
|
|
|
|
|
|
/// Install the selected release of `owner/repo`.
|
|
|
|
|
|
PluginInstall(String),
|
|
|
|
|
|
/// Result of an install: the plugin id, or an error message.
|
|
|
|
|
|
PluginInstalled(Result<String, String>),
|
2026-06-17 15:57:44 +03:00
|
|
|
|
/// Delete an installed plugin's folder (effective next restart).
|
|
|
|
|
|
PluginUninstall(String),
|
2026-06-17 11:13:12 +03:00
|
|
|
|
// ── Point Style (DDPTYPE) dialog ──────────────────────────────────────
|
|
|
|
|
|
/// Set the full PDMODE value from a glyph-grid cell.
|
|
|
|
|
|
PointStyleSetMode(i16),
|
|
|
|
|
|
/// Choose size mode: `true` = relative to screen (PDSIZE < 0), `false` =
|
|
|
|
|
|
/// absolute units (PDSIZE > 0).
|
|
|
|
|
|
PointStyleSizeRelative(bool),
|
|
|
|
|
|
/// Edit the point-size text field (magnitude only).
|
|
|
|
|
|
PointStyleSizeInput(String),
|
|
|
|
|
|
/// Commit the point-size text field to the header with the current sign.
|
|
|
|
|
|
PointStyleApplySize,
|
|
|
|
|
|
/// Apply the point-size field and close the dialog (OK button).
|
|
|
|
|
|
PointStyleOk,
|
2026-05-29 20:30:56 +03:00
|
|
|
|
// ── Quick Select / Select Similar ───────────────────────────────────
|
|
|
|
|
|
/// Extend the current selection with every entity in the active
|
|
|
|
|
|
/// layout that matches a selected entity by (type, layer).
|
|
|
|
|
|
SelectSimilar,
|
2026-06-20 21:31:05 +03:00
|
|
|
|
/// Replace the current selection with every other selectable object in
|
|
|
|
|
|
/// the active layout (the complement of what's selected now).
|
|
|
|
|
|
InvertSelection,
|
2026-05-30 21:54:35 +03:00
|
|
|
|
/// Keyboard modifier state changed — tracks whether Shift is held so the
|
|
|
|
|
|
/// pick path can do subtractive (Shift+click) selection.
|
2026-07-01 17:44:19 +03:00
|
|
|
|
SetModifiers { shift: bool, ctrl: bool },
|
feat: Implement in-place MText editor with formatting toolbar and live preview
- Added SVG icons for text formatting options (bold, italic, underline, overline, strike, uppercase, lowercase).
- Introduced a new `MTextEditorState` struct to manage the state of the MText editor.
- Implemented functionality to open the MText editor from the command line and edit existing MText entities.
- Added message handling for various MText editor actions (formatting, height, style, font, justification, alignment, line spacing).
- Created a dedicated preview area for MText rendering within the editor.
- Updated the command structure to support opening the MText editor and handling text input.
- Refactored existing MText command logic to integrate with the new editor.
2026-05-30 23:24:06 +03:00
|
|
|
|
// ── In-place MText editor ───────────────────────────────────────────
|
|
|
|
|
|
/// Text-area edit action from the multi-line editor widget.
|
|
|
|
|
|
MTextEdit(iced::widget::text_editor::Action),
|
|
|
|
|
|
/// Toolbar character-format toggle applied to the selection.
|
|
|
|
|
|
MTextFmt(mtext_editor::MTextFmt),
|
|
|
|
|
|
/// Toolbar height field changed.
|
|
|
|
|
|
MTextHeight(String),
|
|
|
|
|
|
/// Toolbar text-style dropdown changed.
|
|
|
|
|
|
MTextStyle(String),
|
|
|
|
|
|
/// Toolbar font dropdown changed.
|
|
|
|
|
|
MTextFont(String),
|
|
|
|
|
|
/// Toolbar oblique-angle field changed.
|
|
|
|
|
|
MTextOblique(String),
|
|
|
|
|
|
/// Toolbar width-factor field changed.
|
|
|
|
|
|
MTextWidth(String),
|
|
|
|
|
|
/// Toolbar character-spacing field changed.
|
|
|
|
|
|
MTextCharSpace(String),
|
feat(mtext): structured editor with selection, copy, and visible bold
Rewrite the in-app MTEXT editor to run on acadrust's structured
MTextDocument model end-to-end: load via parse_mtext, edit a flat Cell
model, serialize via to_mtext_string. Retires the hand-rolled format-code
string handling, the parallel visible_spans parser, the composed_value
prefix, and the Edit-raw-code tab.
Selection and copy:
- double-click selects the word, triple-click selects all (click-counting)
- Ctrl+A selects all text
- Ctrl+C copies the selected text to the clipboard
Formatting:
- per-selection Bold/Italic/Underline/Overline/Strike/Case/Alignment
- Bold renders as a visibly thicker SDF pen, keyed (family, char, bold)
through the whole text pipeline; the resolved style font name is stamped
so the \f...|b1; code survives serialization
- per-selection font and color changes
- editor color dropdown reuses the Properties panel color selector
Global toolbar defaults (font/color/oblique/width/tracking) fold onto the
document as real span properties instead of a leading code prefix; a
trailing empty paragraph is preserved so Enter at the end keeps its line.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 20:18:50 +03:00
|
|
|
|
/// Toolbar colour picker (same widget as Properties) — applies to the
|
|
|
|
|
|
/// selection, or the whole text when nothing is selected.
|
|
|
|
|
|
MTextColorChanged(AcadColor),
|
|
|
|
|
|
/// Open / close the MText colour picker popup.
|
|
|
|
|
|
MTextColorPickerToggle,
|
feat: Implement in-place MText editor with formatting toolbar and live preview
- Added SVG icons for text formatting options (bold, italic, underline, overline, strike, uppercase, lowercase).
- Introduced a new `MTextEditorState` struct to manage the state of the MText editor.
- Implemented functionality to open the MText editor from the command line and edit existing MText entities.
- Added message handling for various MText editor actions (formatting, height, style, font, justification, alignment, line spacing).
- Created a dedicated preview area for MText rendering within the editor.
- Updated the command structure to support opening the MText editor and handling text input.
- Refactored existing MText command logic to integrate with the new editor.
2026-05-30 23:24:06 +03:00
|
|
|
|
/// Toolbar justification / attachment-point change.
|
|
|
|
|
|
MTextJustify(acadrust::entities::mtext::AttachmentPoint),
|
|
|
|
|
|
/// Toolbar paragraph-alignment change.
|
|
|
|
|
|
MTextAlign(mtext_editor::ParaAlign),
|
|
|
|
|
|
/// Toolbar line-spacing change.
|
|
|
|
|
|
MTextLineSpacing(f32),
|
2026-05-31 01:59:39 +03:00
|
|
|
|
/// Switch the editor body between raw code input (`false`) and the
|
|
|
|
|
|
/// rendered preview (`true`).
|
|
|
|
|
|
MTextShowPreview(bool),
|
2026-05-31 02:33:47 +03:00
|
|
|
|
/// Begin a preview text selection at the given visible-character offset.
|
|
|
|
|
|
MTextSelStart(usize),
|
|
|
|
|
|
/// Extend the preview selection to the given visible-character offset.
|
|
|
|
|
|
MTextSelTo(usize),
|
2026-05-31 10:16:30 +03:00
|
|
|
|
/// Move the preview caret by N visible characters.
|
|
|
|
|
|
MTextCaretMove(i32),
|
2026-05-31 13:14:35 +03:00
|
|
|
|
/// Timer tick toggling the preview caret's blink phase.
|
|
|
|
|
|
MTextCaretBlink,
|
feat: Implement in-place MText editor with formatting toolbar and live preview
- Added SVG icons for text formatting options (bold, italic, underline, overline, strike, uppercase, lowercase).
- Introduced a new `MTextEditorState` struct to manage the state of the MText editor.
- Implemented functionality to open the MText editor from the command line and edit existing MText entities.
- Added message handling for various MText editor actions (formatting, height, style, font, justification, alignment, line spacing).
- Created a dedicated preview area for MText rendering within the editor.
- Updated the command structure to support opening the MText editor and handling text input.
- Refactored existing MText command logic to integrate with the new editor.
2026-05-30 23:24:06 +03:00
|
|
|
|
/// Commit the editor: create or update the MText entity.
|
|
|
|
|
|
MTextOk,
|
|
|
|
|
|
/// Discard the editor without creating / changing the entity.
|
|
|
|
|
|
MTextCancel,
|
2026-05-31 14:44:18 +03:00
|
|
|
|
// ── In-place single-line TEXT editor ────────────────────────────────
|
|
|
|
|
|
/// Text-field input changed.
|
|
|
|
|
|
TextInlineInput(String),
|
|
|
|
|
|
/// Commit the editor: create or update the TEXT entity.
|
|
|
|
|
|
TextInlineOk,
|
feat(draworder): cross-type draw order + context menu
DRAWORDER now affects rendering immediately (bump geometry epoch so the
sort cache rebuilds) and front/back anchor to the block's full effective
sort range instead of only existing table entries, so bring-to-front
actually lands on top. The render fallback sorts table-absent entities by
their own handle.
Draw order now applies across entity types, not just within a type. A
per-entity draw-order rank (SortEntitiesTable override or own handle,
ranked per block) is encoded as a small signed clip-z bias in the wire,
hatch, image, and 3DFACE pipelines. The shared LessEqual depth buffer then
orders fills, lines, rasters and 2D solids against each other regardless of
pass order — without touching model coordinates. 3D meshes keep real
geometric depth; wipeouts stay always-on-top.
Adds a Draw Order submenu to the viewport context menu (Bring to Front,
Send to Back, Bring Above/Send Under Object). The above/under items run an
interactive reference-object pick, then reorder the captured selection.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 21:29:01 +03:00
|
|
|
|
// ── Draw Order context menu ─────────────────────────────────────────
|
|
|
|
|
|
/// Toggle the Draw Order sub-items in the viewport context menu.
|
|
|
|
|
|
DrawOrderSubmenuToggle,
|
|
|
|
|
|
/// Begin an interactive reference-object pick to move the current
|
|
|
|
|
|
/// selection above (`true`) or below (`false`) the picked object.
|
|
|
|
|
|
DrawOrderPickRef(bool),
|
2026-05-29 20:30:56 +03:00
|
|
|
|
/// Open the Quick Select panel. Initialises filters from the current
|
|
|
|
|
|
/// selection's first entity (type + layer) when one is selected.
|
|
|
|
|
|
QSelectOpen,
|
|
|
|
|
|
/// Close the Quick Select panel without applying.
|
|
|
|
|
|
QSelectClose,
|
|
|
|
|
|
/// Type filter — `None` means "any type".
|
|
|
|
|
|
QSelectSetType(Option<String>),
|
feat(qselect): per-property filter with operator + value (#28)
Quick Select used to filter only on Object Type and Layer. The classic
QSELECT model — pick a type, pick one of its properties, compare with a
typed value via a logical operator — is much more general: a Line entity
can now be filtered on Start X, Length, Angle, Delta Y, …, and any
entity on the common Layer / Color / Linetype / Lineweight.
Panel layout: Object type → Property → Operator → Value → Append. The
Property dropdown is type-aware: it always lists the four common
properties; picking a specific Object type appends that type's
`geometry_properties` rows, sampled from the first entity of that type
in the active layout (so `Start X`, `Radius`, `Pattern Angle`, … only
appear when their entity type is selected). Changing the Object type
drops the Property when the new type no longer offers it.
Operators: `= Equals`, `!= Not equal`, `> Greater than`, `< Less than`,
`* Any value`. Eq/Neq do case-insensitive string compare against the
canonical property string (so "ByLayer" matches "bylayer"); Gt/Lt parse
both sides as `f64` and reject anything non-numeric; Any skips the
value test entirely. The Value text field is disabled when no Property
is picked or the operator is Any. Append behaves the same as before.
The new value reader is hand-rolled for the four common properties
(layer name, color formatted as `ByLayer`/`ByBlock`/`<aci>`/`r,g,b`,
linetype name, lineweight formatted as `0.30mm`) and falls back through
`geometry_properties()` for type-specific fields — extracting the
property's value out of the `PropValue` variant so EditText / Choice /
ColorChoice all canonicalise the same way the panel will display them.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 21:09:56 +03:00
|
|
|
|
/// Property to compare. `None` means "no property filter — just type
|
|
|
|
|
|
/// filter applies"; the operator and value fields are ignored in
|
|
|
|
|
|
/// that case.
|
|
|
|
|
|
QSelectSetProperty(Option<QSelectPropertyChoice>),
|
|
|
|
|
|
/// Comparison operator.
|
|
|
|
|
|
QSelectSetOperator(QSelectOp),
|
|
|
|
|
|
/// Compare-against value (free-text input).
|
|
|
|
|
|
QSelectSetValue(String),
|
2026-05-29 20:30:56 +03:00
|
|
|
|
/// Append-to-current-selection toggle.
|
|
|
|
|
|
QSelectSetAppend(bool),
|
|
|
|
|
|
/// Apply the current filter and close the panel.
|
|
|
|
|
|
QSelectApply,
|
2026-05-01 11:31:11 +03:00
|
|
|
|
/// The user clicked the title-bar ✕ (fires before the window closes).
|
|
|
|
|
|
WindowCloseRequested(window::Id),
|
|
|
|
|
|
/// A window was fully closed (fires after `window::close()` is called).
|
2026-03-22 22:44:32 +03:00
|
|
|
|
OsWindowClosed(window::Id),
|
|
|
|
|
|
/// No-op — used as a fallback when a TabEvent has no host mapping.
|
|
|
|
|
|
Noop,
|
2026-05-16 19:55:57 +03:00
|
|
|
|
/// GitHub releases API returned a result. `Some(version)` means a
|
|
|
|
|
|
/// newer release exists; we open the update-notice window.
|
2026-06-26 23:06:40 +03:00
|
|
|
|
UpdateCheckResult(Option<crate::io::update_check::UpdateInfo>),
|
2026-05-16 19:55:57 +03:00
|
|
|
|
/// User dismissed the update-notice window.
|
|
|
|
|
|
UpdateNoticeClose,
|
2026-06-12 07:50:41 +00:00
|
|
|
|
/// First-launch default-association prompt: user accepted — register this
|
|
|
|
|
|
/// app as the default handler for .dwg / .dxf.
|
|
|
|
|
|
AssocPromptYes,
|
|
|
|
|
|
/// First-launch default-association prompt: user declined (or "not now").
|
|
|
|
|
|
AssocPromptNo,
|
|
|
|
|
|
/// Result of the platform default-association call.
|
|
|
|
|
|
AssocResult(Result<String, String>),
|
2026-05-16 19:55:57 +03:00
|
|
|
|
/// User clicked the "Open release page" button — opens the GitHub
|
|
|
|
|
|
/// release URL in the OS default browser and closes the notice.
|
|
|
|
|
|
UpdateNoticeOpenRelease,
|
2026-03-31 08:05:17 +03:00
|
|
|
|
// ── Plot / Export ─────────────────────────────────────────────────────
|
|
|
|
|
|
/// Show the SVG save-file dialog and trigger export.
|
|
|
|
|
|
PlotExport,
|
|
|
|
|
|
/// Callback after the user picks (or cancels) the export path.
|
|
|
|
|
|
PlotExportPath(Option<std::path::PathBuf>),
|
2026-07-05 23:12:52 +02:00
|
|
|
|
/// User picked a paper size for the model-space window plot.
|
|
|
|
|
|
PlotFormat(crate::io::paper_sizes::PaperSize),
|
|
|
|
|
|
/// User picked a sheet orientation for the model-space window plot.
|
|
|
|
|
|
PlotOrientation(crate::io::paper_sizes::Orientation),
|
|
|
|
|
|
/// Export the pending model-space plot window (from PLOTWINDOW) to PDF.
|
|
|
|
|
|
PlotWindowExport,
|
|
|
|
|
|
/// Callback after the user picks (or cancels) the window-export path.
|
|
|
|
|
|
PlotWindowExportPath(Option<std::path::PathBuf>),
|
2026-04-08 22:21:50 +03:00
|
|
|
|
/// Send current layout to the system printer (via lp / lpr).
|
|
|
|
|
|
PrintToPrinter,
|
|
|
|
|
|
/// Callback from the async printer job.
|
|
|
|
|
|
PrintResult(Result<String, String>),
|
feat(plot): unified plot/print dialog, retire Page Setup
Replace the old Page Setup panel with a single OCS-styled plot/print
dialog (dropdowns for printer, paper, orientation, rotation, plot area,
scale, quality, shaded plot; checkboxes for plot options). PLOT / PRINT
/ PAGESETUP all open it.
- Printer output via lp: -d printer, -n copies, -o ColorModel=Gray,
print-quality, Resolution; Windows printto verb; list_printers via
lpstat. Preview opens a temp PDF in the OS viewer (never a save
dialog). PDF export reuses the tested pipeline.
- Window pick returns to the new dialog (was the old Page Setup) and the
clipped window plot drives PDF / printer / preview.
- Remember the user's print preferences across sessions in
config/plot.txt (printer, copies, quality, output options, area,
scale); drawing-specific fields (offset/center/rotation) seed from the
layout. Default plot area Window, scale Fit.
- Delete src/ui/window/page_setup.rs and its ModalKind / Message /
buffer state; fold on_page_setup_commit into apply_plot_page_settings.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 10:27:54 +03:00
|
|
|
|
/// Open the full Plot / Print dialog (seeds state from the layout).
|
|
|
|
|
|
PlotDialogOpen,
|
|
|
|
|
|
/// An edit inside the Plot / Print dialog.
|
|
|
|
|
|
PlotDlg(crate::ui::window::plot::PlotDlgMsg),
|
2026-04-06 14:37:23 +03:00
|
|
|
|
// ── Plot Style Table ─────────────────────────────────────────────────
|
|
|
|
|
|
/// Open file dialog to load a CTB/STB plot style table.
|
|
|
|
|
|
PlotStyleLoad,
|
|
|
|
|
|
/// Callback when the user picks (or cancels) a CTB/STB file.
|
|
|
|
|
|
PlotStyleLoaded(Option<crate::io::plot_style::PlotStyleTable>),
|
|
|
|
|
|
/// Clear the active plot style table.
|
|
|
|
|
|
PlotStyleClear,
|
2026-04-08 18:19:48 +03:00
|
|
|
|
/// Open/close the Plot Style panel.
|
|
|
|
|
|
PlotStylePanelOpen,
|
2026-04-09 12:44:31 +03:00
|
|
|
|
#[allow(dead_code)]
|
2026-04-08 18:19:48 +03:00
|
|
|
|
PlotStylePanelClose,
|
|
|
|
|
|
/// Select an ACI entry in the panel.
|
|
|
|
|
|
PlotStylePanelSelectAci(u8),
|
|
|
|
|
|
/// Edit buffers changed.
|
|
|
|
|
|
PlotStylePanelColorBuf(String),
|
|
|
|
|
|
PlotStylePanelLwBuf(String),
|
|
|
|
|
|
PlotStylePanelScreenBuf(String),
|
|
|
|
|
|
/// Apply current edit buffers to the selected ACI entry.
|
|
|
|
|
|
PlotStylePanelApply,
|
|
|
|
|
|
/// Save the modified table back to disk.
|
|
|
|
|
|
PlotStylePanelSave,
|
|
|
|
|
|
/// Save callback.
|
|
|
|
|
|
PlotStylePanelSavePath(Option<std::path::PathBuf>),
|
2026-04-06 15:01:25 +03:00
|
|
|
|
// ── TextStyle Font Browser ────────────────────────────────────────────
|
|
|
|
|
|
TextStyleDialogOpen,
|
2026-04-09 12:44:31 +03:00
|
|
|
|
#[allow(dead_code)]
|
2026-04-06 15:01:25 +03:00
|
|
|
|
TextStyleDialogClose,
|
|
|
|
|
|
TextStyleDialogSelect(String),
|
|
|
|
|
|
TextStyleDialogSetCurrent,
|
|
|
|
|
|
TextStyleDialogNew,
|
feat(styles): unify the five style managers behind a shared frame
The text/dimension/table/multileader/multiline managers were hand-copied,
so their list CRUD kept drifting and breaking: Dimension's New created
nothing (#73), new text styles never reached the ribbon dropdown (#74),
and several were added with a NULL handle (dropped on DWG save, #67).
Collapse the duplicated parts into three shared pieces and leave only the
per-manager property editor bespoke:
- app/style_ops.rs: one StyleKind-dispatched CRUD layer (new / copy /
delete / rename) handling handle allocation, ribbon sync, Table re-key
and name-reference rewrites in a single place.
- ui/style_manager.rs: the window scaffold — uniform toolbar (New / Copy /
Delete on the left, Set Current + Apply on the right), style list and
chrome. Each manager now only builds its editor.
- ui/style_list.rs: the list row — single click selects, double click
starts an inline rename.
New capabilities, now consistent across all five:
- Copy duplicates the selected style.
- Double-click a name to rename it inline.
- Right side is always Set Current + Apply (added Table set-current;
MLine apply is a documented placeholder until its editor is editable).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 15:03:34 +03:00
|
|
|
|
TextStyleDialogCopy,
|
|
|
|
|
|
// Shared inline-rename messages for every style manager. `StyleKind`
|
|
|
|
|
|
// routes the commit to the right backing store.
|
|
|
|
|
|
StyleRenameStart(StyleKind, String),
|
|
|
|
|
|
StyleRenameEdit(String),
|
|
|
|
|
|
StyleRenameCommit(StyleKind),
|
|
|
|
|
|
StyleRenameCancel,
|
2026-04-06 15:01:25 +03:00
|
|
|
|
TextStyleDialogDelete,
|
|
|
|
|
|
/// Edit a string field (FontFile / Width / Oblique).
|
2026-05-12 10:36:15 +03:00
|
|
|
|
TextStyleEdit {
|
|
|
|
|
|
field: &'static str,
|
|
|
|
|
|
value: String,
|
|
|
|
|
|
},
|
2026-04-06 15:01:25 +03:00
|
|
|
|
/// Commit edits to the selected text style.
|
|
|
|
|
|
TextStyleApply,
|
|
|
|
|
|
/// Select a font from the built-in font list.
|
|
|
|
|
|
TextStyleFontPick(String),
|
2026-06-03 17:43:12 +03:00
|
|
|
|
/// Flip a boolean flag on the selected text style (backward / upside_down /
|
|
|
|
|
|
/// vertical / annotative), applied immediately.
|
|
|
|
|
|
TextStyleToggle(&'static str),
|
2026-04-06 14:57:11 +03:00
|
|
|
|
// ── TableStyle Dialog ─────────────────────────────────────────────────
|
|
|
|
|
|
TableStyleDialogOpen,
|
2026-04-09 12:44:31 +03:00
|
|
|
|
#[allow(dead_code)]
|
2026-04-06 14:57:11 +03:00
|
|
|
|
TableStyleDialogClose,
|
|
|
|
|
|
TableStyleDialogSelect(String),
|
|
|
|
|
|
TableStyleDialogNew,
|
feat(styles): unify the five style managers behind a shared frame
The text/dimension/table/multileader/multiline managers were hand-copied,
so their list CRUD kept drifting and breaking: Dimension's New created
nothing (#73), new text styles never reached the ribbon dropdown (#74),
and several were added with a NULL handle (dropped on DWG save, #67).
Collapse the duplicated parts into three shared pieces and leave only the
per-manager property editor bespoke:
- app/style_ops.rs: one StyleKind-dispatched CRUD layer (new / copy /
delete / rename) handling handle allocation, ribbon sync, Table re-key
and name-reference rewrites in a single place.
- ui/style_manager.rs: the window scaffold — uniform toolbar (New / Copy /
Delete on the left, Set Current + Apply on the right), style list and
chrome. Each manager now only builds its editor.
- ui/style_list.rs: the list row — single click selects, double click
starts an inline rename.
New capabilities, now consistent across all five:
- Copy duplicates the selected style.
- Double-click a name to rename it inline.
- Right side is always Set Current + Apply (added Table set-current;
MLine apply is a documented placeholder until its editor is editable).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 15:03:34 +03:00
|
|
|
|
TableStyleDialogCopy,
|
2026-04-06 14:57:11 +03:00
|
|
|
|
TableStyleDialogDelete,
|
feat(styles): unify the five style managers behind a shared frame
The text/dimension/table/multileader/multiline managers were hand-copied,
so their list CRUD kept drifting and breaking: Dimension's New created
nothing (#73), new text styles never reached the ribbon dropdown (#74),
and several were added with a NULL handle (dropped on DWG save, #67).
Collapse the duplicated parts into three shared pieces and leave only the
per-manager property editor bespoke:
- app/style_ops.rs: one StyleKind-dispatched CRUD layer (new / copy /
delete / rename) handling handle allocation, ribbon sync, Table re-key
and name-reference rewrites in a single place.
- ui/style_manager.rs: the window scaffold — uniform toolbar (New / Copy /
Delete on the left, Set Current + Apply on the right), style list and
chrome. Each manager now only builds its editor.
- ui/style_list.rs: the list row — single click selects, double click
starts an inline rename.
New capabilities, now consistent across all five:
- Copy duplicates the selected style.
- Double-click a name to rename it inline.
- Right side is always Set Current + Apply (added Table set-current;
MLine apply is a documented placeholder until its editor is editable).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 15:03:34 +03:00
|
|
|
|
TableStyleDialogSetCurrent,
|
2026-06-03 17:51:45 +03:00
|
|
|
|
/// Toggle the Annotative flag on the selected table style.
|
|
|
|
|
|
TableStyleToggleAnnotative,
|
2026-06-03 20:13:30 +03:00
|
|
|
|
/// Toggle a boolean flag (title_suppressed / header_suppressed / flow) on
|
|
|
|
|
|
/// the selected table style.
|
|
|
|
|
|
TableStyleToggle(&'static str),
|
|
|
|
|
|
/// Update a general edit buffer (hmargin / vmargin).
|
feat(styles): unify the five style managers behind a shared frame
The text/dimension/table/multileader/multiline managers were hand-copied,
so their list CRUD kept drifting and breaking: Dimension's New created
nothing (#73), new text styles never reached the ribbon dropdown (#74),
and several were added with a NULL handle (dropped on DWG save, #67).
Collapse the duplicated parts into three shared pieces and leave only the
per-manager property editor bespoke:
- app/style_ops.rs: one StyleKind-dispatched CRUD layer (new / copy /
delete / rename) handling handle allocation, ribbon sync, Table re-key
and name-reference rewrites in a single place.
- ui/style_manager.rs: the window scaffold — uniform toolbar (New / Copy /
Delete on the left, Set Current + Apply on the right), style list and
chrome. Each manager now only builds its editor.
- ui/style_list.rs: the list row — single click selects, double click
starts an inline rename.
New capabilities, now consistent across all five:
- Copy duplicates the selected style.
- Double-click a name to rename it inline.
- Right side is always Set Current + Apply (added Table set-current;
MLine apply is a documented placeholder until its editor is editable).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 15:03:34 +03:00
|
|
|
|
TableStyleEdit {
|
|
|
|
|
|
field: &'static str,
|
|
|
|
|
|
value: String,
|
|
|
|
|
|
},
|
2026-06-03 20:13:30 +03:00
|
|
|
|
/// Write the general edit buffers back into the selected table style.
|
|
|
|
|
|
TableStyleApply,
|
2026-06-03 20:28:58 +03:00
|
|
|
|
/// Update a per-cell edit buffer (row 0=Data,1=Header,2=Title).
|
|
|
|
|
|
TableStyleCellEdit {
|
|
|
|
|
|
row: u8,
|
|
|
|
|
|
field: &'static str,
|
|
|
|
|
|
value: String,
|
|
|
|
|
|
},
|
2026-06-14 10:42:39 +03:00
|
|
|
|
/// Toggle the expanded colour palette for a table cell colour field.
|
|
|
|
|
|
TableColorMore(u8, &'static str),
|
2026-06-03 20:28:58 +03:00
|
|
|
|
/// Toggle background fill on a cell style.
|
|
|
|
|
|
TableStyleCellToggleFill(u8),
|
2026-06-03 20:44:37 +03:00
|
|
|
|
/// Set the alignment of a cell style from the dropdown.
|
|
|
|
|
|
TableStyleCellSetAlign {
|
|
|
|
|
|
row: u8,
|
|
|
|
|
|
value: String,
|
|
|
|
|
|
},
|
2026-06-03 20:28:58 +03:00
|
|
|
|
/// Write a cell's edit buffers back into the selected table style.
|
|
|
|
|
|
TableStyleCellApply(u8),
|
2026-06-03 21:09:37 +03:00
|
|
|
|
/// Set the table flow direction from the dropdown.
|
|
|
|
|
|
TableStyleSetFlow(String),
|
|
|
|
|
|
/// Update a per-cell, per-border numeric edit buffer.
|
|
|
|
|
|
TableStyleBorderEdit {
|
|
|
|
|
|
cell: u8,
|
|
|
|
|
|
border: u8,
|
|
|
|
|
|
field: &'static str,
|
|
|
|
|
|
value: String,
|
|
|
|
|
|
},
|
|
|
|
|
|
/// Set a border's line type (Single / Double).
|
|
|
|
|
|
TableStyleBorderSetType {
|
|
|
|
|
|
cell: u8,
|
|
|
|
|
|
border: u8,
|
|
|
|
|
|
value: String,
|
|
|
|
|
|
},
|
|
|
|
|
|
/// Toggle a border's visibility.
|
|
|
|
|
|
TableStyleBorderToggleInvisible {
|
|
|
|
|
|
cell: u8,
|
|
|
|
|
|
border: u8,
|
|
|
|
|
|
},
|
2026-04-06 14:54:05 +03:00
|
|
|
|
// ── MLineStyle Dialog ─────────────────────────────────────────────────
|
|
|
|
|
|
MlStyleDialogOpen,
|
2026-04-09 12:44:31 +03:00
|
|
|
|
#[allow(dead_code)]
|
2026-04-06 14:54:05 +03:00
|
|
|
|
MlStyleDialogClose,
|
|
|
|
|
|
MlStyleDialogSelect(String),
|
|
|
|
|
|
MlStyleDialogSetCurrent,
|
feat(styles): unify the five style managers behind a shared frame
The text/dimension/table/multileader/multiline managers were hand-copied,
so their list CRUD kept drifting and breaking: Dimension's New created
nothing (#73), new text styles never reached the ribbon dropdown (#74),
and several were added with a NULL handle (dropped on DWG save, #67).
Collapse the duplicated parts into three shared pieces and leave only the
per-manager property editor bespoke:
- app/style_ops.rs: one StyleKind-dispatched CRUD layer (new / copy /
delete / rename) handling handle allocation, ribbon sync, Table re-key
and name-reference rewrites in a single place.
- ui/style_manager.rs: the window scaffold — uniform toolbar (New / Copy /
Delete on the left, Set Current + Apply on the right), style list and
chrome. Each manager now only builds its editor.
- ui/style_list.rs: the list row — single click selects, double click
starts an inline rename.
New capabilities, now consistent across all five:
- Copy duplicates the selected style.
- Double-click a name to rename it inline.
- Right side is always Set Current + Apply (added Table set-current;
MLine apply is a documented placeholder until its editor is editable).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 15:03:34 +03:00
|
|
|
|
MlStyleApply,
|
2026-04-06 14:54:05 +03:00
|
|
|
|
MlStyleDialogNew,
|
feat(styles): unify the five style managers behind a shared frame
The text/dimension/table/multileader/multiline managers were hand-copied,
so their list CRUD kept drifting and breaking: Dimension's New created
nothing (#73), new text styles never reached the ribbon dropdown (#74),
and several were added with a NULL handle (dropped on DWG save, #67).
Collapse the duplicated parts into three shared pieces and leave only the
per-manager property editor bespoke:
- app/style_ops.rs: one StyleKind-dispatched CRUD layer (new / copy /
delete / rename) handling handle allocation, ribbon sync, Table re-key
and name-reference rewrites in a single place.
- ui/style_manager.rs: the window scaffold — uniform toolbar (New / Copy /
Delete on the left, Set Current + Apply on the right), style list and
chrome. Each manager now only builds its editor.
- ui/style_list.rs: the list row — single click selects, double click
starts an inline rename.
New capabilities, now consistent across all five:
- Copy duplicates the selected style.
- Double-click a name to rename it inline.
- Right side is always Set Current + Apply (added Table set-current;
MLine apply is a documented placeholder until its editor is editable).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 15:03:34 +03:00
|
|
|
|
MlStyleDialogCopy,
|
2026-04-06 14:54:05 +03:00
|
|
|
|
MlStyleDialogDelete,
|
2026-06-03 20:25:14 +03:00
|
|
|
|
// ── MLeaderStyle Dialog ───────────────────────────────────────────────
|
|
|
|
|
|
MLeaderStyleDialogOpen,
|
|
|
|
|
|
#[allow(dead_code)]
|
|
|
|
|
|
MLeaderStyleDialogClose,
|
|
|
|
|
|
MLeaderStyleDialogSelect(String),
|
|
|
|
|
|
MLeaderStyleDialogSetCurrent,
|
|
|
|
|
|
MLeaderStyleDialogNew,
|
feat(styles): unify the five style managers behind a shared frame
The text/dimension/table/multileader/multiline managers were hand-copied,
so their list CRUD kept drifting and breaking: Dimension's New created
nothing (#73), new text styles never reached the ribbon dropdown (#74),
and several were added with a NULL handle (dropped on DWG save, #67).
Collapse the duplicated parts into three shared pieces and leave only the
per-manager property editor bespoke:
- app/style_ops.rs: one StyleKind-dispatched CRUD layer (new / copy /
delete / rename) handling handle allocation, ribbon sync, Table re-key
and name-reference rewrites in a single place.
- ui/style_manager.rs: the window scaffold — uniform toolbar (New / Copy /
Delete on the left, Set Current + Apply on the right), style list and
chrome. Each manager now only builds its editor.
- ui/style_list.rs: the list row — single click selects, double click
starts an inline rename.
New capabilities, now consistent across all five:
- Copy duplicates the selected style.
- Double-click a name to rename it inline.
- Right side is always Set Current + Apply (added Table set-current;
MLine apply is a documented placeholder until its editor is editable).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 15:03:34 +03:00
|
|
|
|
MLeaderStyleDialogCopy,
|
2026-06-03 20:25:14 +03:00
|
|
|
|
MLeaderStyleDialogDelete,
|
|
|
|
|
|
MLeaderStyleEdit {
|
|
|
|
|
|
field: &'static str,
|
|
|
|
|
|
value: String,
|
|
|
|
|
|
},
|
2026-06-14 10:42:39 +03:00
|
|
|
|
/// Toggle the expanded colour palette for an MLeaderStyle colour field.
|
|
|
|
|
|
MLeaderColorMore(&'static str),
|
2026-06-03 20:25:14 +03:00
|
|
|
|
MLeaderStyleToggle(&'static str),
|
2026-06-03 20:44:37 +03:00
|
|
|
|
MLeaderStyleSetEnum {
|
|
|
|
|
|
field: &'static str,
|
|
|
|
|
|
value: String,
|
|
|
|
|
|
},
|
feat(styles): complete MLeader field coverage + Dimension DIMUNIT
MLeader Style editor now exposes every MultiLeaderStyle field: line
weight, line type / arrowhead block / text style / block-content blocks
(handle dropdowns from the document's tables), the four text attachments
+ attachment direction, the full block-content group (color, connection,
rotation, X/Y/Z scale, enable scale/rotation), leader & multileader draw
order, align space, and description.
Dimension Style editor adds the last uncovered DIMUNIT field.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 21:04:22 +03:00
|
|
|
|
/// Set an Option<Handle> field (linetype / arrowhead / text style / block)
|
|
|
|
|
|
/// from a dropdown of record names ("None" clears it).
|
|
|
|
|
|
MLeaderStyleSetHandle {
|
|
|
|
|
|
field: &'static str,
|
|
|
|
|
|
value: String,
|
|
|
|
|
|
},
|
2026-06-03 20:25:14 +03:00
|
|
|
|
MLeaderStyleApply,
|
2026-04-06 14:25:13 +03:00
|
|
|
|
// ── DimStyle Dialog ───────────────────────────────────────────────────
|
|
|
|
|
|
DimStyleDialogOpen,
|
|
|
|
|
|
DimStyleDialogClose,
|
|
|
|
|
|
/// Apply edits to the selected style.
|
|
|
|
|
|
DimStyleDialogApply,
|
|
|
|
|
|
/// Select a different style in the dialog list.
|
|
|
|
|
|
DimStyleDialogSelect(String),
|
|
|
|
|
|
/// Switch the active tab.
|
|
|
|
|
|
DimStyleDialogTab(u8),
|
|
|
|
|
|
/// Create a new empty style (prompts via command line).
|
|
|
|
|
|
DimStyleDialogNew,
|
feat(styles): unify the five style managers behind a shared frame
The text/dimension/table/multileader/multiline managers were hand-copied,
so their list CRUD kept drifting and breaking: Dimension's New created
nothing (#73), new text styles never reached the ribbon dropdown (#74),
and several were added with a NULL handle (dropped on DWG save, #67).
Collapse the duplicated parts into three shared pieces and leave only the
per-manager property editor bespoke:
- app/style_ops.rs: one StyleKind-dispatched CRUD layer (new / copy /
delete / rename) handling handle allocation, ribbon sync, Table re-key
and name-reference rewrites in a single place.
- ui/style_manager.rs: the window scaffold — uniform toolbar (New / Copy /
Delete on the left, Set Current + Apply on the right), style list and
chrome. Each manager now only builds its editor.
- ui/style_list.rs: the list row — single click selects, double click
starts an inline rename.
New capabilities, now consistent across all five:
- Copy duplicates the selected style.
- Double-click a name to rename it inline.
- Right side is always Set Current + Apply (added Table set-current;
MLine apply is a documented placeholder until its editor is editable).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 15:03:34 +03:00
|
|
|
|
DimStyleDialogCopy,
|
2026-04-06 14:25:13 +03:00
|
|
|
|
/// Set the selected style as the document's current dim style.
|
|
|
|
|
|
DimStyleDialogSetCurrent,
|
|
|
|
|
|
/// Delete the selected style.
|
|
|
|
|
|
DimStyleDialogDelete,
|
|
|
|
|
|
// Field edit messages:
|
|
|
|
|
|
DsEdit(DsField, String),
|
|
|
|
|
|
DsToggle(DsField),
|
2026-06-14 10:42:39 +03:00
|
|
|
|
/// Toggle the expanded colour palette for a DimStyle colour field.
|
|
|
|
|
|
DsColorMore(DsField),
|
2026-06-14 11:14:14 +03:00
|
|
|
|
/// Open the standalone palette window targeting the given field.
|
|
|
|
|
|
OpenColorWindow(ColorPickTarget),
|
2026-06-16 23:42:14 +03:00
|
|
|
|
/// Close the nested colour-picker modal without choosing (Plan B).
|
|
|
|
|
|
CloseColorPicker,
|
2026-06-14 11:14:14 +03:00
|
|
|
|
/// A colour was chosen in the standalone palette window.
|
|
|
|
|
|
ColorWindowPick(acadrust::types::Color),
|
2026-06-03 20:44:37 +03:00
|
|
|
|
/// Set a block/linetype Handle field on the selected dim style from a
|
|
|
|
|
|
/// dropdown of available block-records / linetypes (by name).
|
|
|
|
|
|
DsSetHandle {
|
|
|
|
|
|
field: &'static str,
|
|
|
|
|
|
value: String,
|
|
|
|
|
|
},
|
2026-04-06 23:19:08 +03:00
|
|
|
|
// ── Raster Image ──────────────────────────────────────────────────────
|
|
|
|
|
|
/// Open file-picker dialog for IMAGE command (async).
|
|
|
|
|
|
ImagePick,
|
|
|
|
|
|
/// Result of the image file picker + pixel dimension decode.
|
|
|
|
|
|
ImagePickResult(Result<(std::path::PathBuf, u32, u32), String>),
|
2026-04-08 00:02:38 +03:00
|
|
|
|
// ── XREF ──────────────────────────────────────────────────────────────
|
|
|
|
|
|
/// Open file-picker dialog for XATTACH command (async).
|
|
|
|
|
|
XAttachPick,
|
|
|
|
|
|
/// Result of the XATTACH file picker.
|
|
|
|
|
|
XAttachPickResult(Result<std::path::PathBuf, String>),
|
2026-04-08 00:05:04 +03:00
|
|
|
|
// ── WBLOCK ────────────────────────────────────────────────────────────
|
|
|
|
|
|
/// Trigger the WBLOCK save dialog for `block_name` (or `*` = selection).
|
|
|
|
|
|
WblockSave(String),
|
|
|
|
|
|
/// Result of the WBLOCK save path dialog.
|
|
|
|
|
|
WblockSaveResult(String, Option<std::path::PathBuf>),
|
2026-04-08 00:07:58 +03:00
|
|
|
|
// ── DATAEXTRACTION ────────────────────────────────────────────────────
|
|
|
|
|
|
/// Save the pre-built CSV string to a file chosen by the user.
|
|
|
|
|
|
DataExtractionSave(String),
|
|
|
|
|
|
/// Path chosen (or None = cancelled).
|
|
|
|
|
|
DataExtractionSaveResult(String, Option<std::path::PathBuf>),
|
2026-04-08 00:46:05 +03:00
|
|
|
|
// ── STL export ────────────────────────────────────────────────────────
|
|
|
|
|
|
/// Trigger STL export: collect meshes and show save dialog.
|
|
|
|
|
|
StlExport,
|
|
|
|
|
|
/// Callback after the user picks (or cancels) the STL save path.
|
|
|
|
|
|
StlExportPath(Option<std::path::PathBuf>),
|
2026-04-08 22:23:58 +03:00
|
|
|
|
// ── STEP export ───────────────────────────────────────────────────────
|
|
|
|
|
|
/// Trigger STEP AP203 export: show save dialog.
|
|
|
|
|
|
StepExport,
|
|
|
|
|
|
/// Callback after the user picks (or cancels) the STEP save path.
|
|
|
|
|
|
StepExportPath(Option<std::path::PathBuf>),
|
2026-04-08 12:59:27 +03:00
|
|
|
|
// ── OBJ import ────────────────────────────────────────────────────────
|
|
|
|
|
|
/// Trigger OBJ import: show open-file dialog.
|
|
|
|
|
|
ObjImport,
|
|
|
|
|
|
/// Callback after the user picks (or cancels) the OBJ file path.
|
|
|
|
|
|
ObjImportPath(Option<std::path::PathBuf>),
|
2026-03-22 22:44:32 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-23 17:16:15 +03:00
|
|
|
|
impl OpenCADStudio {
|
2026-03-22 22:44:32 +03:00
|
|
|
|
fn new() -> Self {
|
feat(app): add fixed Start tab with welcome page and recent documents
A pinned "Start" tab now lives at index 0. It cannot be closed, opens
on launch, and replaces the model-space viewport with a welcome page —
similar to Thunderbird's first-run screen.
- Boot opens with only the Start tab; Drawing1 is no longer auto-spawned.
- The Properties left panel becomes a Recent Documents rail on the
Start tab. Recents persist to disk
(%APPDATA%/H7CAD/recent.txt on Windows, $XDG_CONFIG_HOME/H7CAD/recent.txt
on Linux, ~/Library/Application Support/H7CAD/recent.txt on macOS), up
to 20 entries, saved on every open and rehydrated at boot.
Each row opens or removes the file in place.
- The Support ribbon group (Donate / Report / About / Changelog) is
retired; the same actions surface on the welcome page instead. The
Donate button is the primary call-to-action — solid #B03020 brand
fill with a soft brand-tinted ambient glow halo behind the action row
(Thunderbird-style coloured glow against the neutral page).
- Drawing-only overlays (mouse-area, viewcube, navbar, dynamic input,
selection markers) are suppressed on the Start tab so its clicks
don't trigger CAD commands. OpenFile's "current_is_empty" check
refuses to replace the Start tab, and TabClose ignores requests on
it.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 16:19:31 +03:00
|
|
|
|
// Boot with only the Welcome/Start tab. The user creates drawings
|
|
|
|
|
|
// explicitly (File → New); we never auto-spawn Drawing1.
|
|
|
|
|
|
let start_tab = DocumentTab::new_start();
|
2026-03-22 22:44:32 +03:00
|
|
|
|
let mut app = Self {
|
|
|
|
|
|
start: Instant::now(),
|
feat(app): add fixed Start tab with welcome page and recent documents
A pinned "Start" tab now lives at index 0. It cannot be closed, opens
on launch, and replaces the model-space viewport with a welcome page —
similar to Thunderbird's first-run screen.
- Boot opens with only the Start tab; Drawing1 is no longer auto-spawned.
- The Properties left panel becomes a Recent Documents rail on the
Start tab. Recents persist to disk
(%APPDATA%/H7CAD/recent.txt on Windows, $XDG_CONFIG_HOME/H7CAD/recent.txt
on Linux, ~/Library/Application Support/H7CAD/recent.txt on macOS), up
to 20 entries, saved on every open and rehydrated at boot.
Each row opens or removes the file in place.
- The Support ribbon group (Donate / Report / About / Changelog) is
retired; the same actions surface on the welcome page instead. The
Donate button is the primary call-to-action — solid #B03020 brand
fill with a soft brand-tinted ambient glow halo behind the action row
(Thunderbird-style coloured glow against the neutral page).
- Drawing-only overlays (mouse-area, viewcube, navbar, dynamic input,
selection markers) are suppressed on the Start tab so its clicks
don't trigger CAD commands. OpenFile's "current_is_empty" check
refuses to replace the Start tab, and TabClose ignores requests on
it.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 16:19:31 +03:00
|
|
|
|
tabs: vec![start_tab],
|
2026-03-22 22:44:32 +03:00
|
|
|
|
active_tab: 0,
|
feat(app): add fixed Start tab with welcome page and recent documents
A pinned "Start" tab now lives at index 0. It cannot be closed, opens
on launch, and replaces the model-space viewport with a welcome page —
similar to Thunderbird's first-run screen.
- Boot opens with only the Start tab; Drawing1 is no longer auto-spawned.
- The Properties left panel becomes a Recent Documents rail on the
Start tab. Recents persist to disk
(%APPDATA%/H7CAD/recent.txt on Windows, $XDG_CONFIG_HOME/H7CAD/recent.txt
on Linux, ~/Library/Application Support/H7CAD/recent.txt on macOS), up
to 20 entries, saved on every open and rehydrated at boot.
Each row opens or removes the file in place.
- The Support ribbon group (Donate / Report / About / Changelog) is
retired; the same actions surface on the welcome page instead. The
Donate button is the primary call-to-action — solid #B03020 brand
fill with a soft brand-tinted ambient glow halo behind the action row
(Thunderbird-style coloured glow against the neutral page).
- Drawing-only overlays (mouse-area, viewcube, navbar, dynamic input,
selection markers) are suppressed on the Start tab so its clicks
don't trigger CAD commands. OpenFile's "current_is_empty" check
refuses to replace the Start tab, and TabClose ignores requests on
it.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 16:19:31 +03:00
|
|
|
|
tab_counter: 0,
|
2026-03-22 22:44:32 +03:00
|
|
|
|
ribbon: Ribbon::new(),
|
refactor(config): consolidate settings into one grouped settings.json
App preferences were spread across six flat plain-text files (settings.txt,
recent.txt, recent_limit.txt, statusbar.txt, ribbon.txt, plot.txt), each with a
bespoke line parser and no grouping. Replace them with a single grouped JSON,
serialized via serde (enabling the derive feature; serde_json was already a
dependency).
- New app/config.rs: AppConfig { settings, recent, statusbar, ribbon, plot } →
<config>/settings.json (pretty JSON), load()/save().
- Make UserSettings, StatusBarConfig/StatusPill, CollapseMode and
PlotDialogState serde-derived; #[serde(skip)] marks the plot dialog's runtime
fields so only print preferences persist. Their bespoke .txt load/save is
removed.
- App gains current_config/apply_config/save_config: one AppConfig::load at
startup distributes into live state; every settings-change site
(persist_settings_if_changed, recent add/remove/limit, status-bar pill
toggle, ribbon density, plot commit) routes through save_config, which diffs
against the last write to avoid thrashing.
- Clean start: the old flat files are ignored (no migration). ocad.pgp stays a
separate hand-editable file.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 16:41:34 +03:00
|
|
|
|
// Populated from the consolidated config after construction
|
|
|
|
|
|
// (`apply_config`); default empty here.
|
|
|
|
|
|
recent_files: Vec::new(),
|
2026-07-10 23:03:23 +03:00
|
|
|
|
recent_thumbs: std::collections::HashMap::new(),
|
refactor(config): consolidate settings into one grouped settings.json
App preferences were spread across six flat plain-text files (settings.txt,
recent.txt, recent_limit.txt, statusbar.txt, ribbon.txt, plot.txt), each with a
bespoke line parser and no grouping. Replace them with a single grouped JSON,
serialized via serde (enabling the derive feature; serde_json was already a
dependency).
- New app/config.rs: AppConfig { settings, recent, statusbar, ribbon, plot } →
<config>/settings.json (pretty JSON), load()/save().
- Make UserSettings, StatusBarConfig/StatusPill, CollapseMode and
PlotDialogState serde-derived; #[serde(skip)] marks the plot dialog's runtime
fields so only print preferences persist. Their bespoke .txt load/save is
removed.
- App gains current_config/apply_config/save_config: one AppConfig::load at
startup distributes into live state; every settings-change site
(persist_settings_if_changed, recent add/remove/limit, status-bar pill
toggle, ribbon density, plot commit) routes through save_config, which diffs
against the last write to avoid thrashing.
- Clean start: the old flat files are ignored (no migration). ocad.pgp stays a
separate hand-editable file.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 16:41:34 +03:00
|
|
|
|
recent_limit: recent::RECENT_DEFAULT,
|
|
|
|
|
|
recent_limit_input: recent::RECENT_DEFAULT.to_string(),
|
2026-03-22 22:44:32 +03:00
|
|
|
|
command_line: CommandLine::new(),
|
2026-07-02 01:00:22 +03:00
|
|
|
|
patrons: Vec::new(),
|
2026-07-05 20:53:11 +03:00
|
|
|
|
start_section: StartSection::default(),
|
2026-07-05 21:02:05 +03:00
|
|
|
|
props_expanded: false,
|
2026-07-01 18:17:46 +03:00
|
|
|
|
history_content: iced::widget::text_editor::Content::new(),
|
2026-03-22 22:44:32 +03:00
|
|
|
|
status_bar: StatusBar::new(),
|
|
|
|
|
|
cursor_pos: Point::ORIGIN,
|
|
|
|
|
|
vp_size: (1280.0, 720.0),
|
feat(ui): anchor every ribbon dropdown and status-bar popup to its widget, flipping at window edges
All ribbon dropdowns (tool dropdowns, undo/redo history, and the layer /
colour / linetype / lineweight / style combos) and all status-bar popups
(scale, units, OSNAP, isolate, filter, customization menu, layout list)
now open directly at their widget instead of at a layout-estimated
position, and flip to the other side when the panel would cross a window
edge.
Each dropdown/pill widget is wrapped in a transparent PosReport that
records its screen bounds into a thread-local map on draw. The overlays
read those bounds:
- Ribbon: Ribbon::dd_anchor(id, panel_w, win_w) returns
(align_right, h_pad, top); position_ribbon_dropdown places the panel
below the widget, flipping right-aligned near the right edge.
- Status bar: popup builders take (pill_bounds, win_size) and call
popup::position_statusbar_popup / popup_anchor, opening just above the
pill and flipping left/right at the window edge.
The window size is now tracked (win_size, set on WindowResized). The old
layout estimators compute_dropdown_left / compute_layer_combo_left /
compute_prop_combo_left / compute_history_dropdown_left and
osnap_popup_right_offset are deleted.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 18:51:54 +03:00
|
|
|
|
win_size: (1280.0, 720.0),
|
2026-03-22 22:44:32 +03:00
|
|
|
|
snapper: Snapper::default(),
|
|
|
|
|
|
snap_popup_open: false,
|
2026-05-06 17:50:46 +03:00
|
|
|
|
scale_popup_open: false,
|
2026-07-09 21:35:47 +03:00
|
|
|
|
polar_popup_open: false,
|
|
|
|
|
|
polar_custom_input: String::new(),
|
2026-06-04 19:14:04 +03:00
|
|
|
|
statusbar_menu_open: false,
|
2026-06-27 14:24:47 +03:00
|
|
|
|
layout_list_open: false,
|
2026-06-04 22:06:42 +03:00
|
|
|
|
units_popup_open: false,
|
2026-06-04 23:17:21 +03:00
|
|
|
|
isolate_popup_open: false,
|
2026-06-04 23:39:48 +03:00
|
|
|
|
selection_filter_popup_open: false,
|
refactor(config): consolidate settings into one grouped settings.json
App preferences were spread across six flat plain-text files (settings.txt,
recent.txt, recent_limit.txt, statusbar.txt, ribbon.txt, plot.txt), each with a
bespoke line parser and no grouping. Replace them with a single grouped JSON,
serialized via serde (enabling the derive feature; serde_json was already a
dependency).
- New app/config.rs: AppConfig { settings, recent, statusbar, ribbon, plot } →
<config>/settings.json (pretty JSON), load()/save().
- Make UserSettings, StatusBarConfig/StatusPill, CollapseMode and
PlotDialogState serde-derived; #[serde(skip)] marks the plot dialog's runtime
fields so only print preferences persist. Their bespoke .txt load/save is
removed.
- App gains current_config/apply_config/save_config: one AppConfig::load at
startup distributes into live state; every settings-change site
(persist_settings_if_changed, recent add/remove/limit, status-bar pill
toggle, ribbon density, plot commit) routes through save_config, which diffs
against the last write to avoid thrashing.
- Clean start: the old flat files are ignored (no migration). ocad.pgp stays a
separate hand-editable file.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 16:41:34 +03:00
|
|
|
|
statusbar_config: crate::ui::statusbar::statusbar_config::StatusBarConfig::default(),
|
|
|
|
|
|
last_saved_config: None,
|
2026-06-09 23:38:08 +03:00
|
|
|
|
otrack_active: None,
|
2026-06-04 19:14:04 +03:00
|
|
|
|
clean_screen: false,
|
2026-06-04 23:31:36 +03:00
|
|
|
|
quick_properties: false,
|
2026-06-04 23:44:39 +03:00
|
|
|
|
selection_cycling: false,
|
2026-06-05 01:07:17 +03:00
|
|
|
|
perf_hud: false,
|
2026-06-04 23:56:24 +03:00
|
|
|
|
cycle_candidates: None,
|
2026-03-22 22:44:32 +03:00
|
|
|
|
pre_cmd_tangent: None,
|
2026-07-03 16:05:54 +03:00
|
|
|
|
add_selected_restore: None,
|
2026-06-16 09:53:32 +01:00
|
|
|
|
rect_suppressed_ortho: false,
|
2026-03-22 22:44:32 +03:00
|
|
|
|
ortho_mode: false,
|
|
|
|
|
|
polar_mode: false,
|
2026-04-07 19:30:25 +03:00
|
|
|
|
polar_increment_deg: 45.0,
|
2026-03-22 22:44:32 +03:00
|
|
|
|
show_grid: false,
|
2026-04-08 00:12:09 +03:00
|
|
|
|
dyn_input: true,
|
2026-06-18 14:55:08 +01:00
|
|
|
|
texteditmode: false,
|
2026-06-29 12:49:02 +03:00
|
|
|
|
backup_on_save: true,
|
2026-06-29 12:58:23 +03:00
|
|
|
|
file_assoc_enabled: true,
|
2026-06-29 13:17:40 +03:00
|
|
|
|
savetime_min: 10,
|
2026-06-26 10:21:46 -07:00
|
|
|
|
default_bg_color: None,
|
|
|
|
|
|
default_paper_bg_color: None,
|
2026-05-27 22:52:55 +03:00
|
|
|
|
awaiting_vports: false,
|
2026-07-03 12:42:55 +03:00
|
|
|
|
pending_setvar: None,
|
2026-06-27 01:12:45 +03:00
|
|
|
|
ucs_icon_hover: false,
|
|
|
|
|
|
ucs_icon_selected: false,
|
|
|
|
|
|
ucs_grip_drag: None,
|
2026-06-27 11:50:11 +03:00
|
|
|
|
pane_move_from: None,
|
2026-05-29 00:57:37 +03:00
|
|
|
|
dyn_user_reshaped: false,
|
feat(grip): multi-functional hover popup menu
Phase 2 of the grip overhaul: when the cursor dwells on a selected
entity's grip the overlay opens a popup menu with entity-specific
options (Stretch / Add Vertex / Remove Vertex / Convert to
Arc-Line / …). Picking an item dispatches `apply_grip_menu`; the
default `Stretch` entry is a no-op that lets the user click the
grip to start the normal drag.
- `GripMenuItem` + `GripMenuAction` (full vocabulary covering
stretch, add/remove vertex, convert to arc/line, reverse arrows,
text-position variants, hatch parameters, tangent direction).
- `Grippable` trait gains `grip_menu` + `apply_grip_menu` with
trait-default `Stretch`-only impls so every existing entity gets
the popup for free.
- `EntityTypeOps` dispatches both methods across the same entity
list as the existing grip / property machinery.
- `OpenCADStudio` carries `grip_hover` (dwell tracker) +
`grip_popup` (open menu). `update_grip_hover` runs from
`ViewportMove`: hit-tests grips, refreshes the dwell timer, and
opens the popup after 600 ms. Cursor drift past 80 px or a grip
click dismisses it.
- `view.rs` paints the popup as an absolute-positioned column of
buttons at the grip's screen anchor; each button publishes
`Message::GripMenuPick(idx)`.
- `LwPolyline` overrides `Grippable` manually (skips the standard
macro) so its vertex grip exposes Add/Remove Vertex and its
segment-midpoint grip exposes Add Vertex + Convert to Arc/Line.
`apply_grip_menu` performs the edits: vertex insertion at the
chord midpoint inheriting the previous bulge, vertex removal,
and bulge toggle for the arc/line conversion.
2026-05-29 09:30:54 +03:00
|
|
|
|
grip_hover: None,
|
|
|
|
|
|
grip_popup: None,
|
2026-05-29 14:58:35 +03:00
|
|
|
|
grip_pending: None,
|
2026-06-11 20:11:19 +03:00
|
|
|
|
visibility_popup: None,
|
2026-06-03 16:17:29 +03:00
|
|
|
|
grip_add_provisional: None,
|
2026-06-05 10:33:08 +03:00
|
|
|
|
grip_preview_handle: None,
|
2026-06-20 20:43:33 +03:00
|
|
|
|
hover_dwell: None,
|
2026-06-05 10:44:31 +03:00
|
|
|
|
grip_original: None,
|
2026-07-10 10:39:44 +03:00
|
|
|
|
grip_text_verts: Vec::new(),
|
|
|
|
|
|
grip_text_slide: false,
|
2026-05-29 20:30:56 +03:00
|
|
|
|
qselect: None,
|
2026-04-03 19:25:55 +03:00
|
|
|
|
show_ucs_icon: true,
|
2026-06-27 01:12:45 +03:00
|
|
|
|
ucs_icon_at_origin: true,
|
2026-04-24 01:23:06 +03:00
|
|
|
|
show_viewcube: true,
|
2026-07-10 14:58:48 +03:00
|
|
|
|
render_bar_w: std::sync::Arc::new(std::sync::atomic::AtomicU32::new(0)),
|
2026-04-24 01:23:06 +03:00
|
|
|
|
show_properties: true,
|
|
|
|
|
|
show_file_tabs: true,
|
|
|
|
|
|
show_layout_tabs: true,
|
2026-03-22 22:44:32 +03:00
|
|
|
|
last_point: None,
|
|
|
|
|
|
main_window: None,
|
2026-06-14 11:14:14 +03:00
|
|
|
|
color_pick_target: None,
|
2026-06-16 19:07:25 +03:00
|
|
|
|
active_modal: None,
|
2026-06-29 11:47:37 +03:00
|
|
|
|
aec_drop_acknowledged: false,
|
|
|
|
|
|
aec_drop_count: 0,
|
2026-07-01 17:23:32 +03:00
|
|
|
|
layer_delete_pending: None,
|
2026-06-17 09:59:17 +03:00
|
|
|
|
modal_offset: iced::Vector::ZERO,
|
|
|
|
|
|
modal_drag_last: None,
|
|
|
|
|
|
modal_dragging: false,
|
2026-07-01 08:05:25 +03:00
|
|
|
|
attr_editor_handle: None,
|
|
|
|
|
|
attr_editor_block: String::new(),
|
feat(attedit): enhanced attribute editor with 3 tabs
Grow the attribute editor into a three-tab enhanced editor:
- Attribute: the tag/prompt/value list; click a row to select it, edit
its value below.
- Text Options: the selected attribute's Text Style, Justification,
Height, Rotation, Width Factor, Oblique Angle, Backwards, Upside down.
- Properties: the selected attribute's Layer, Linetype, Color,
Lineweight (the colour list also surfaces a non-standard current
colour so it displays and round-trips).
Applying writes value, text-formatting and common-property edits back to
each attribute positionally (guarded on block name + count), with undo
and a repaint. OK/Cancel are replaced by a single Apply button in a top
toolbar that commits but keeps the dialog open, matching the other modal
windows — the frame ✕ closes and discards un-applied edits. The frame
adopts the shared style-window palette, tab styling and sizing.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 09:06:03 +03:00
|
|
|
|
attr_editor_rows: Vec::new(),
|
|
|
|
|
|
attr_editor_tab: crate::ui::window::attribute_editor::AttrTab::Attribute,
|
|
|
|
|
|
attr_editor_selected: 0,
|
2026-06-17 10:15:38 +03:00
|
|
|
|
disabled_plugins: rustc_hash::FxHashSet::default(),
|
2026-06-17 11:43:24 +03:00
|
|
|
|
external_plugins: Vec::new(),
|
2026-06-17 14:48:56 +03:00
|
|
|
|
loaded_plugin_ids: rustc_hash::FxHashSet::default(),
|
2026-06-17 15:24:56 +03:00
|
|
|
|
plugin_registry: Vec::new(),
|
|
|
|
|
|
plugin_repos: Vec::new(),
|
|
|
|
|
|
plugin_repo_input: String::new(),
|
|
|
|
|
|
repo_release_tags: rustc_hash::FxHashMap::default(),
|
|
|
|
|
|
repo_selected_tag: rustc_hash::FxHashMap::default(),
|
|
|
|
|
|
marketplace_status: String::new(),
|
2026-06-17 11:13:12 +03:00
|
|
|
|
point_size_buf: String::new(),
|
|
|
|
|
|
point_size_relative: true,
|
2026-06-12 07:50:41 +00:00
|
|
|
|
default_assoc_prompted: false,
|
2026-06-20 13:59:20 +03:00
|
|
|
|
read_only: false,
|
2026-05-16 19:55:57 +03:00
|
|
|
|
update_notice_version: None,
|
2026-05-22 12:55:37 +03:00
|
|
|
|
update_notice_body: None,
|
2026-03-22 22:44:32 +03:00
|
|
|
|
clipboard: Vec::new(),
|
feat(commands): expand standard CAD command coverage
Integrate a large batch of standard CAD commands and fix several
name/behaviour mismatches, with no product references in code.
3D solids (reusing the truck B-rep pipeline):
- SLICE, INTERFERE, PRESSPULL, POLYSOLID, PYRAMID, SECTION
- 3DROTATE, 3DMIRROR, 3DALIGN (cached-solid transforms)
Annotation / text:
- ARCTEXT, SPLINEFIT (Catmull-Rom to Bezier), DIMJOGGED, OBJECTSCALE
(XData markers honoured by the existing geometry-gen / tessellator)
Blocks / misc:
- NCOPY (block-content extraction via explode_entity)
- OPTIONS, SYNCPVIEWPORTS, THICKEN, plus earlier toggles, aliases,
system variables, and layer/style/view commands
POINTCLOUD/RECAP and UNDERLAYLAYERS/UOSNAP are intentionally left out:
they require a proprietary binary / PDF-layer parser, a GPU point
renderer, and real test files.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 08:23:58 +03:00
|
|
|
|
oops_cache: Vec::new(),
|
2026-07-10 11:16:13 +03:00
|
|
|
|
clipboard_base: glam::DVec3::ZERO,
|
2026-06-18 14:34:32 +03:00
|
|
|
|
clipboard_deps: ClipboardDeps::default(),
|
2026-05-30 21:54:35 +03:00
|
|
|
|
shift_down: false,
|
2026-07-01 17:44:19 +03:00
|
|
|
|
ctrl_down: false,
|
2026-07-08 20:39:40 +03:00
|
|
|
|
cont_anchor: None,
|
feat: Implement in-place MText editor with formatting toolbar and live preview
- Added SVG icons for text formatting options (bold, italic, underline, overline, strike, uppercase, lowercase).
- Introduced a new `MTextEditorState` struct to manage the state of the MText editor.
- Implemented functionality to open the MText editor from the command line and edit existing MText entities.
- Added message handling for various MText editor actions (formatting, height, style, font, justification, alignment, line spacing).
- Created a dedicated preview area for MText rendering within the editor.
- Updated the command structure to support opening the MText editor and handling text input.
- Refactored existing MText command logic to integrate with the new editor.
2026-05-30 23:24:06 +03:00
|
|
|
|
mtext_editor: None,
|
2026-05-31 14:44:18 +03:00
|
|
|
|
text_inline: None,
|
2026-03-31 07:13:36 +03:00
|
|
|
|
layout_context_menu: None,
|
|
|
|
|
|
layout_rename_state: None,
|
2026-03-31 07:24:16 +03:00
|
|
|
|
last_vp_click_time: None,
|
|
|
|
|
|
last_vp_click_pos: None,
|
feat(mtext): structured editor with selection, copy, and visible bold
Rewrite the in-app MTEXT editor to run on acadrust's structured
MTextDocument model end-to-end: load via parse_mtext, edit a flat Cell
model, serialize via to_mtext_string. Retires the hand-rolled format-code
string handling, the parallel visible_spans parser, the composed_value
prefix, and the Edit-raw-code tab.
Selection and copy:
- double-click selects the word, triple-click selects all (click-counting)
- Ctrl+A selects all text
- Ctrl+C copies the selected text to the clipboard
Formatting:
- per-selection Bold/Italic/Underline/Overline/Strike/Case/Alignment
- Bold renders as a visibly thicker SDF pen, keyed (family, char, bold)
through the whole text pipeline; the resolved style font name is stamped
so the \f...|b1; code survives serialization
- per-selection font and color changes
- editor color dropdown reuses the Properties panel color selector
Global toolbar defaults (font/color/oblique/width/tracking) fold onto the
document as real span properties instead of a leading code prefix; a
trailing empty paragraph is preserved so Enter at the end keeps its line.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 20:18:50 +03:00
|
|
|
|
mtext_click_time: None,
|
|
|
|
|
|
mtext_click_off: 0,
|
|
|
|
|
|
mtext_click_count: 0,
|
feat(plot): unified plot/print dialog, retire Page Setup
Replace the old Page Setup panel with a single OCS-styled plot/print
dialog (dropdowns for printer, paper, orientation, rotation, plot area,
scale, quality, shaded plot; checkboxes for plot options). PLOT / PRINT
/ PAGESETUP all open it.
- Printer output via lp: -d printer, -n copies, -o ColorModel=Gray,
print-quality, Resolution; Windows printto verb; list_printers via
lpstat. Preview opens a temp PDF in the OS viewer (never a save
dialog). PDF export reuses the tested pipeline.
- Window pick returns to the new dialog (was the old Page Setup) and the
clipped window plot drives PDF / printer / preview.
- Remember the user's print preferences across sessions in
config/plot.txt (printer, copies, quality, output options, area,
scale); drawing-specific fields (offset/center/rotation) seed from the
layout. Default plot area Window, scale Fit.
- Delete src/ui/window/page_setup.rs and its ModalKind / Message /
buffer state; fold on_page_setup_commit into apply_plot_page_settings.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 10:27:54 +03:00
|
|
|
|
plot_scale: "Fit".to_string(),
|
2026-07-05 23:12:52 +02:00
|
|
|
|
plot_window: None,
|
|
|
|
|
|
plot_format: crate::io::paper_sizes::PaperSize::A4,
|
|
|
|
|
|
plot_orientation: crate::io::paper_sizes::Orientation::Landscape,
|
refactor(config): consolidate settings into one grouped settings.json
App preferences were spread across six flat plain-text files (settings.txt,
recent.txt, recent_limit.txt, statusbar.txt, ribbon.txt, plot.txt), each with a
bespoke line parser and no grouping. Replace them with a single grouped JSON,
serialized via serde (enabling the derive feature; serde_json was already a
dependency).
- New app/config.rs: AppConfig { settings, recent, statusbar, ribbon, plot } →
<config>/settings.json (pretty JSON), load()/save().
- Make UserSettings, StatusBarConfig/StatusPill, CollapseMode and
PlotDialogState serde-derived; #[serde(skip)] marks the plot dialog's runtime
fields so only print preferences persist. Their bespoke .txt load/save is
removed.
- App gains current_config/apply_config/save_config: one AppConfig::load at
startup distributes into live state; every settings-change site
(persist_settings_if_changed, recent add/remove/limit, status-bar pill
toggle, ribbon density, plot commit) routes through save_config, which diffs
against the last write to avoid thrashing.
- Clean start: the old flat files are ignored (no migration). ocad.pgp stays a
separate hand-editable file.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 16:41:34 +03:00
|
|
|
|
plot_dialog: crate::ui::window::plot::PlotDialogState::default(),
|
2026-07-09 12:25:46 +03:00
|
|
|
|
plot_prev: None,
|
2026-05-13 09:59:07 +03:00
|
|
|
|
opening: None,
|
2026-05-01 11:31:11 +03:00
|
|
|
|
pending_close: None,
|
2026-05-01 16:12:32 +03:00
|
|
|
|
save_dialog_format: "DWG 2018".to_string(),
|
|
|
|
|
|
save_dialog_filename: "drawing.dwg".to_string(),
|
|
|
|
|
|
save_dialog_for_unsaved: false,
|
2026-04-06 14:37:23 +03:00
|
|
|
|
// Plot style
|
|
|
|
|
|
active_plot_style: None,
|
2026-04-08 18:31:09 +03:00
|
|
|
|
// Color scheme (default: dark CAD-style)
|
|
|
|
|
|
active_theme: Theme::Dark,
|
2026-04-08 18:34:10 +03:00
|
|
|
|
// Keyboard shortcuts
|
2026-06-05 00:35:53 +03:00
|
|
|
|
shortcut_overrides: rustc_hash::FxHashMap::default(),
|
feat(alias): centralize command aliases in user-editable ocad.pgp (#286/#288)
Command aliases (L→LINE, CC→COPYCLIP) were scattered across dispatch match
arms and CommandRegistration name arrays. Move them all into a single
user-editable file:
- assets/ocad.pgp: shipped default alias table (ALIAS,*COMMAND format),
embedded and copied to config/ocad.pgp on first launch; only that user
copy is read after. AA→AREA default closes #286.
- resolve_alias() rewrites the leading command token at the top of
dispatch_command_inner, so aliases resolve for every path (families,
plugins, Repeat menu). Args after the first space are untouched.
- Autocomplete hides alias keys and, when the whole input is an alias,
surfaces its target command as the top (highlighted) suggestion so the
highlight matches what Enter runs (AA→AREA, L→LINE).
- ALIASEDIT opens a new editor modal (add/edit/delete rows) with an Apply
button; Apply persists to ocad.pgp, closing discards unapplied edits.
- Strip alias tokens from all 8 dispatch match-arm files and 71 module
CommandRegistration arrays, keeping canonical/synonym/distinct-command
names. ocad.pgp is now the sole source: deleting or remapping an alias
there takes effect. CUI stays on the keyboard-shortcut panel.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 12:39:22 +03:00
|
|
|
|
// Command aliases (populated from ocad.pgp just after construction)
|
|
|
|
|
|
command_aliases: rustc_hash::FxHashMap::default(),
|
|
|
|
|
|
alias_editor_rows: Vec::new(),
|
2026-04-08 18:28:30 +03:00
|
|
|
|
// Layout Manager
|
|
|
|
|
|
layout_manager_selected: "Model".to_string(),
|
2026-07-13 10:39:14 +03:00
|
|
|
|
scale_manager_selected: String::new(),
|
|
|
|
|
|
scale_manager_paper_buf: String::new(),
|
|
|
|
|
|
scale_manager_drawing_buf: String::new(),
|
2026-07-13 14:37:33 +03:00
|
|
|
|
scale_rename: None,
|
2026-07-13 22:38:29 +03:00
|
|
|
|
anno_object_scale_target: None,
|
2026-07-13 14:37:33 +03:00
|
|
|
|
scale_rename_buf: String::new(),
|
2026-07-13 12:14:40 +03:00
|
|
|
|
scale_stage: None,
|
2026-04-08 18:28:30 +03:00
|
|
|
|
layout_manager_rename_buf: String::new(),
|
2026-04-08 18:19:48 +03:00
|
|
|
|
plotstyle_panel_aci: 1,
|
|
|
|
|
|
ps_color_buf: String::new(),
|
|
|
|
|
|
ps_lineweight_buf: "255".to_string(),
|
|
|
|
|
|
ps_screening_buf: "100".to_string(),
|
2026-04-06 15:01:25 +03:00
|
|
|
|
// TextStyle font browser
|
feat(styles): unify the five style managers behind a shared frame
The text/dimension/table/multileader/multiline managers were hand-copied,
so their list CRUD kept drifting and breaking: Dimension's New created
nothing (#73), new text styles never reached the ribbon dropdown (#74),
and several were added with a NULL handle (dropped on DWG save, #67).
Collapse the duplicated parts into three shared pieces and leave only the
per-manager property editor bespoke:
- app/style_ops.rs: one StyleKind-dispatched CRUD layer (new / copy /
delete / rename) handling handle allocation, ribbon sync, Table re-key
and name-reference rewrites in a single place.
- ui/style_manager.rs: the window scaffold — uniform toolbar (New / Copy /
Delete on the left, Set Current + Apply on the right), style list and
chrome. Each manager now only builds its editor.
- ui/style_list.rs: the list row — single click selects, double click
starts an inline rename.
New capabilities, now consistent across all five:
- Copy duplicates the selected style.
- Double-click a name to rename it inline.
- Right side is always Set Current + Apply (added Table set-current;
MLine apply is a documented placeholder until its editor is editable).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 15:03:34 +03:00
|
|
|
|
style_rename: None,
|
|
|
|
|
|
style_rename_buf: String::new(),
|
2026-06-10 15:38:32 +03:00
|
|
|
|
style_stage: None,
|
2026-04-06 15:01:25 +03:00
|
|
|
|
textstyle_selected: "Standard".to_string(),
|
|
|
|
|
|
textstyle_font: String::new(),
|
|
|
|
|
|
textstyle_width: "1.0".to_string(),
|
|
|
|
|
|
textstyle_oblique: "0.0".to_string(),
|
2026-06-03 17:43:12 +03:00
|
|
|
|
textstyle_height: "0.0".to_string(),
|
|
|
|
|
|
textstyle_bigfont: String::new(),
|
|
|
|
|
|
textstyle_ttf: String::new(),
|
2026-04-06 14:57:11 +03:00
|
|
|
|
// TableStyle dialog
|
|
|
|
|
|
tablestyle_selected: "Standard".to_string(),
|
2026-06-03 20:13:30 +03:00
|
|
|
|
ts_hmargin: "1.5".to_string(),
|
|
|
|
|
|
ts_vmargin: "1.5".to_string(),
|
2026-06-03 21:09:37 +03:00
|
|
|
|
ts_description: String::new(),
|
2026-06-14 10:42:39 +03:00
|
|
|
|
ts_color_open: None,
|
2026-06-03 21:09:37 +03:00
|
|
|
|
ts_cell_textstyle: Default::default(),
|
|
|
|
|
|
ts_cell_height: Default::default(),
|
|
|
|
|
|
ts_cell_textcolor: Default::default(),
|
|
|
|
|
|
ts_cell_fillcolor: Default::default(),
|
|
|
|
|
|
ts_cell_datatype: Default::default(),
|
|
|
|
|
|
ts_cell_unittype: Default::default(),
|
|
|
|
|
|
ts_cell_format: Default::default(),
|
|
|
|
|
|
ts_border_lw: Default::default(),
|
|
|
|
|
|
ts_border_color: Default::default(),
|
|
|
|
|
|
ts_border_spacing: Default::default(),
|
2026-04-06 14:54:05 +03:00
|
|
|
|
// MLineStyle dialog
|
|
|
|
|
|
mlstyle_selected: "Standard".to_string(),
|
2026-06-03 20:25:14 +03:00
|
|
|
|
// MLeaderStyle dialog
|
|
|
|
|
|
mleaderstyle_selected: "Standard".to_string(),
|
2026-06-14 10:42:39 +03:00
|
|
|
|
mls_color_open: None,
|
2026-06-03 20:25:14 +03:00
|
|
|
|
mls_landing_distance: String::new(),
|
|
|
|
|
|
mls_landing_gap: String::new(),
|
|
|
|
|
|
mls_arrowhead_size: String::new(),
|
|
|
|
|
|
mls_text_height: String::new(),
|
|
|
|
|
|
mls_scale_factor: String::new(),
|
|
|
|
|
|
mls_break_gap: String::new(),
|
|
|
|
|
|
mls_first_seg_angle: String::new(),
|
|
|
|
|
|
mls_second_seg_angle: String::new(),
|
|
|
|
|
|
mls_max_points: String::new(),
|
|
|
|
|
|
mls_default_text: String::new(),
|
|
|
|
|
|
mls_line_color: String::new(),
|
|
|
|
|
|
mls_text_color: String::new(),
|
feat(styles): complete MLeader field coverage + Dimension DIMUNIT
MLeader Style editor now exposes every MultiLeaderStyle field: line
weight, line type / arrowhead block / text style / block-content blocks
(handle dropdowns from the document's tables), the four text attachments
+ attachment direction, the full block-content group (color, connection,
rotation, X/Y/Z scale, enable scale/rotation), leader & multileader draw
order, align space, and description.
Dimension Style editor adds the last uncovered DIMUNIT field.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 21:04:22 +03:00
|
|
|
|
mls_description: String::new(),
|
|
|
|
|
|
mls_line_weight: String::new(),
|
|
|
|
|
|
mls_align_space: String::new(),
|
|
|
|
|
|
mls_block_color: String::new(),
|
|
|
|
|
|
mls_block_rotation: String::new(),
|
|
|
|
|
|
mls_block_scale_x: String::new(),
|
|
|
|
|
|
mls_block_scale_y: String::new(),
|
|
|
|
|
|
mls_block_scale_z: String::new(),
|
2026-04-06 14:25:13 +03:00
|
|
|
|
// DimStyle dialog
|
|
|
|
|
|
dimstyle_selected: "Standard".to_string(),
|
2026-06-14 10:42:39 +03:00
|
|
|
|
ds_color_open: None,
|
2026-04-06 14:25:13 +03:00
|
|
|
|
dimstyle_tab: 0,
|
2026-05-12 10:36:15 +03:00
|
|
|
|
ds_dimdle: "0".to_string(),
|
|
|
|
|
|
ds_dimdli: "3.75".to_string(),
|
|
|
|
|
|
ds_dimgap: "0.625".to_string(),
|
|
|
|
|
|
ds_dimexe: "1.25".to_string(),
|
2026-04-06 14:25:13 +03:00
|
|
|
|
ds_dimexo: "0.625".to_string(),
|
2026-05-12 10:36:15 +03:00
|
|
|
|
ds_dimsd1: false,
|
|
|
|
|
|
ds_dimsd2: false,
|
|
|
|
|
|
ds_dimse1: false,
|
|
|
|
|
|
ds_dimse2: false,
|
|
|
|
|
|
ds_dimasz: "0.18".to_string(),
|
|
|
|
|
|
ds_dimcen: "0.09".to_string(),
|
2026-04-06 14:25:13 +03:00
|
|
|
|
ds_dimtsz: "0".to_string(),
|
2026-05-12 10:36:15 +03:00
|
|
|
|
ds_dimtxt: "0.18".to_string(),
|
|
|
|
|
|
ds_dimtxsty: "Standard".to_string(),
|
2026-04-06 14:25:13 +03:00
|
|
|
|
ds_dimtad: "1".to_string(),
|
2026-05-12 10:36:15 +03:00
|
|
|
|
ds_dimtih: false,
|
|
|
|
|
|
ds_dimtoh: false,
|
|
|
|
|
|
ds_dimscale: "1".to_string(),
|
|
|
|
|
|
ds_dimlfac: "1".to_string(),
|
|
|
|
|
|
ds_dimlunit: "2".to_string(),
|
|
|
|
|
|
ds_dimdec: "2".to_string(),
|
2026-04-06 14:25:13 +03:00
|
|
|
|
ds_dimpost: "<>".to_string(),
|
2026-05-12 10:36:15 +03:00
|
|
|
|
ds_dimtol: false,
|
|
|
|
|
|
ds_dimlim: false,
|
|
|
|
|
|
ds_dimtp: "0".to_string(),
|
|
|
|
|
|
ds_dimtm: "0".to_string(),
|
|
|
|
|
|
ds_dimtdec: "2".to_string(),
|
|
|
|
|
|
ds_dimtfac: "1".to_string(),
|
2026-06-03 17:49:38 +03:00
|
|
|
|
ds_annotative: false,
|
feat(dimstyle): expand Dimension Style editor to the full field set
Adds the remaining ~40 DIMSTYLE variables across the tabs (and a new
Alternate Units tab): dim/ext line colors+lineweights, fixed-length ext
lines, separate arrow blocks, arc symbol, jog angle, text color/justify/
vertical pos/fill/direction, fit options (DIMATFIT/DIMTIX/DIMSOXD/DIMTMOVE/
DIMUPT/DIMTOFL/DIMFIT), decimal separator, rounding, zero suppression,
fraction + angular units, alternate units, and tolerance vert-just/zero
suppression. Only the arrowhead-block / extension-line-linetype Handle
references are left (they need a block/linetype picker).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 20:09:08 +03:00
|
|
|
|
ds_dimclrd: "0".to_string(),
|
|
|
|
|
|
ds_dimlwd: "-2".to_string(),
|
|
|
|
|
|
ds_dimclre: "0".to_string(),
|
|
|
|
|
|
ds_dimlwe: "-2".to_string(),
|
|
|
|
|
|
ds_dimfxl: "1".to_string(),
|
|
|
|
|
|
ds_dimfxlon: false,
|
|
|
|
|
|
ds_dimsah: false,
|
|
|
|
|
|
ds_dimarcsym: "0".to_string(),
|
|
|
|
|
|
ds_dimjogang: "45".to_string(),
|
|
|
|
|
|
ds_dimclrt: "0".to_string(),
|
|
|
|
|
|
ds_dimjust: "0".to_string(),
|
|
|
|
|
|
ds_dimtvp: "0".to_string(),
|
|
|
|
|
|
ds_dimtfill: "0".to_string(),
|
|
|
|
|
|
ds_dimtfillclr: "0".to_string(),
|
|
|
|
|
|
ds_dimtxtdirection: false,
|
|
|
|
|
|
ds_dimatfit: "3".to_string(),
|
|
|
|
|
|
ds_dimtix: false,
|
|
|
|
|
|
ds_dimsoxd: false,
|
|
|
|
|
|
ds_dimtmove: "0".to_string(),
|
|
|
|
|
|
ds_dimupt: false,
|
|
|
|
|
|
ds_dimtofl: false,
|
|
|
|
|
|
ds_dimfit: "3".to_string(),
|
|
|
|
|
|
ds_dimdsep: "46".to_string(),
|
|
|
|
|
|
ds_dimrnd: "0".to_string(),
|
|
|
|
|
|
ds_dimzin: "0".to_string(),
|
|
|
|
|
|
ds_dimfrac: "0".to_string(),
|
|
|
|
|
|
ds_dimaunit: "0".to_string(),
|
|
|
|
|
|
ds_dimadec: "0".to_string(),
|
feat(styles): complete MLeader field coverage + Dimension DIMUNIT
MLeader Style editor now exposes every MultiLeaderStyle field: line
weight, line type / arrowhead block / text style / block-content blocks
(handle dropdowns from the document's tables), the four text attachments
+ attachment direction, the full block-content group (color, connection,
rotation, X/Y/Z scale, enable scale/rotation), leader & multileader draw
order, align space, and description.
Dimension Style editor adds the last uncovered DIMUNIT field.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 21:04:22 +03:00
|
|
|
|
ds_dimunit: "2".to_string(),
|
feat(dimstyle): expand Dimension Style editor to the full field set
Adds the remaining ~40 DIMSTYLE variables across the tabs (and a new
Alternate Units tab): dim/ext line colors+lineweights, fixed-length ext
lines, separate arrow blocks, arc symbol, jog angle, text color/justify/
vertical pos/fill/direction, fit options (DIMATFIT/DIMTIX/DIMSOXD/DIMTMOVE/
DIMUPT/DIMTOFL/DIMFIT), decimal separator, rounding, zero suppression,
fraction + angular units, alternate units, and tolerance vert-just/zero
suppression. Only the arrowhead-block / extension-line-linetype Handle
references are left (they need a block/linetype picker).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 20:09:08 +03:00
|
|
|
|
ds_dimazin: "0".to_string(),
|
|
|
|
|
|
ds_dimalt: false,
|
|
|
|
|
|
ds_dimaltf: "25.4".to_string(),
|
|
|
|
|
|
ds_dimaltd: "2".to_string(),
|
|
|
|
|
|
ds_dimaltu: "2".to_string(),
|
|
|
|
|
|
ds_dimalttd: "2".to_string(),
|
|
|
|
|
|
ds_dimaltrnd: "0".to_string(),
|
|
|
|
|
|
ds_dimapost: String::new(),
|
|
|
|
|
|
ds_dimaltz: "0".to_string(),
|
|
|
|
|
|
ds_dimalttz: "0".to_string(),
|
|
|
|
|
|
ds_dimtolj: "1".to_string(),
|
|
|
|
|
|
ds_dimtzin: "0".to_string(),
|
2026-03-22 22:44:32 +03:00
|
|
|
|
};
|
refactor(config): consolidate settings into one grouped settings.json
App preferences were spread across six flat plain-text files (settings.txt,
recent.txt, recent_limit.txt, statusbar.txt, ribbon.txt, plot.txt), each with a
bespoke line parser and no grouping. Replace them with a single grouped JSON,
serialized via serde (enabling the derive feature; serde_json was already a
dependency).
- New app/config.rs: AppConfig { settings, recent, statusbar, ribbon, plot } →
<config>/settings.json (pretty JSON), load()/save().
- Make UserSettings, StatusBarConfig/StatusPill, CollapseMode and
PlotDialogState serde-derived; #[serde(skip)] marks the plot dialog's runtime
fields so only print preferences persist. Their bespoke .txt load/save is
removed.
- App gains current_config/apply_config/save_config: one AppConfig::load at
startup distributes into live state; every settings-change site
(persist_settings_if_changed, recent add/remove/limit, status-bar pill
toggle, ribbon density, plot commit) routes through save_config, which diffs
against the last write to avoid thrashing.
- Clean start: the old flat files are ignored (no migration). ocad.pgp stays a
separate hand-editable file.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 16:41:34 +03:00
|
|
|
|
// Restore the consolidated user config (settings.json) into live state
|
|
|
|
|
|
// so preferences, recents, status-bar layout, ribbon density and print
|
|
|
|
|
|
// options survive across sessions (issue #68). `last_saved_config` is
|
|
|
|
|
|
// seeded below so the first change — not the boot — triggers a write.
|
|
|
|
|
|
app.apply_config(config::AppConfig::load());
|
feat(alias): centralize command aliases in user-editable ocad.pgp (#286/#288)
Command aliases (L→LINE, CC→COPYCLIP) were scattered across dispatch match
arms and CommandRegistration name arrays. Move them all into a single
user-editable file:
- assets/ocad.pgp: shipped default alias table (ALIAS,*COMMAND format),
embedded and copied to config/ocad.pgp on first launch; only that user
copy is read after. AA→AREA default closes #286.
- resolve_alias() rewrites the leading command token at the top of
dispatch_command_inner, so aliases resolve for every path (families,
plugins, Repeat menu). Args after the first space are untouched.
- Autocomplete hides alias keys and, when the whole input is an alias,
surfaces its target command as the top (highlighted) suggestion so the
highlight matches what Enter runs (AA→AREA, L→LINE).
- ALIASEDIT opens a new editor modal (add/edit/delete rows) with an Apply
button; Apply persists to ocad.pgp, closing discards unapplied edits.
- Strip alias tokens from all 8 dispatch match-arm files and 71 module
CommandRegistration arrays, keeping canonical/synonym/distinct-command
names. ocad.pgp is now the sole source: deleting or remapping an alias
there takes effect. CUI stays on the keyboard-shortcut panel.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 12:39:22 +03:00
|
|
|
|
// Load command aliases from ocad.pgp (writes the shipped default file
|
|
|
|
|
|
// on first launch). The hide-set keeps aliases out of autocomplete while
|
|
|
|
|
|
// their target command still shows.
|
|
|
|
|
|
app.command_aliases = alias::load_aliases();
|
|
|
|
|
|
app.command_line.command_aliases = app.command_aliases.clone();
|
2026-06-17 14:48:56 +03:00
|
|
|
|
// Load external plugin packages from the plugins folder once, then fold
|
|
|
|
|
|
// their ribbon tabs into the ribbon. Skipped under test/wasm.
|
|
|
|
|
|
#[cfg(all(not(target_arch = "wasm32"), not(test)))]
|
|
|
|
|
|
{
|
2026-06-23 22:54:33 +02:00
|
|
|
|
for (id, res) in crate::plugin::external::load_at_startup(&mut app) {
|
2026-06-17 14:48:56 +03:00
|
|
|
|
if let Err(e) = res {
|
|
|
|
|
|
app.command_line
|
|
|
|
|
|
.push_error(&format!("Plugin '{id}' failed to load: {e}"));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
feat(commands): expand standard CAD command coverage
Integrate a large batch of standard CAD commands and fix several
name/behaviour mismatches, with no product references in code.
3D solids (reusing the truck B-rep pipeline):
- SLICE, INTERFERE, PRESSPULL, POLYSOLID, PYRAMID, SECTION
- 3DROTATE, 3DMIRROR, 3DALIGN (cached-solid transforms)
Annotation / text:
- ARCTEXT, SPLINEFIT (Catmull-Rom to Bezier), DIMJOGGED, OBJECTSCALE
(XData markers honoured by the existing geometry-gen / tessellator)
Blocks / misc:
- NCOPY (block-content extraction via explode_entity)
- OPTIONS, SYNCPVIEWPORTS, THICKEN, plus earlier toggles, aliases,
system variables, and layer/style/view commands
POINTCLOUD/RECAP and UNDERLAYLAYERS/UOSNAP are intentionally left out:
they require a proprietary binary / PDF-layer parser, a GPU point
renderer, and real test files.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 08:23:58 +03:00
|
|
|
|
app.loaded_plugin_ids = crate::plugin::external::loaded_ids().into_iter().collect();
|
2026-06-17 14:48:56 +03:00
|
|
|
|
app.rebuild_ribbon_modules();
|
|
|
|
|
|
}
|
refactor(config): consolidate settings into one grouped settings.json
App preferences were spread across six flat plain-text files (settings.txt,
recent.txt, recent_limit.txt, statusbar.txt, ribbon.txt, plot.txt), each with a
bespoke line parser and no grouping. Replace them with a single grouped JSON,
serialized via serde (enabling the derive feature; serde_json was already a
dependency).
- New app/config.rs: AppConfig { settings, recent, statusbar, ribbon, plot } →
<config>/settings.json (pretty JSON), load()/save().
- Make UserSettings, StatusBarConfig/StatusPill, CollapseMode and
PlotDialogState serde-derived; #[serde(skip)] marks the plot dialog's runtime
fields so only print preferences persist. Their bespoke .txt load/save is
removed.
- App gains current_config/apply_config/save_config: one AppConfig::load at
startup distributes into live state; every settings-change site
(persist_settings_if_changed, recent add/remove/limit, status-bar pill
toggle, ribbon density, plot commit) routes through save_config, which diffs
against the last write to avoid thrashing.
- Clean start: the old flat files are ignored (no migration). ocad.pgp stays a
separate hand-editable file.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 16:41:34 +03:00
|
|
|
|
app.last_saved_config = Some(app.current_config());
|
2026-03-22 22:44:32 +03:00
|
|
|
|
app.sync_ribbon_layers();
|
|
|
|
|
|
app
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-09 13:52:15 -04:00
|
|
|
|
#[cfg(test)]
|
|
|
|
|
|
pub(crate) fn new_for_test() -> Self {
|
|
|
|
|
|
Self::new()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-23 22:54:33 +02:00
|
|
|
|
/// Install `cmd` as the active interactive command for tab `tab`.
|
|
|
|
|
|
pub(crate) fn set_active_command(
|
|
|
|
|
|
&mut self,
|
|
|
|
|
|
tab: usize,
|
|
|
|
|
|
cmd: Box<dyn crate::command::CadCommand>,
|
|
|
|
|
|
) {
|
|
|
|
|
|
if let Some(t) = self.tabs.get_mut(tab) {
|
|
|
|
|
|
t.active_cmd = Some(cmd);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Push an error message from the plugin runtime to the command line.
|
|
|
|
|
|
pub(crate) fn push_plugin_error(&mut self, msg: &str) {
|
|
|
|
|
|
self.command_line.push_error(msg);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-09 13:52:15 -04:00
|
|
|
|
#[cfg(test)]
|
|
|
|
|
|
pub(crate) fn command_history_info(&self) -> Vec<String> {
|
|
|
|
|
|
use crate::ui::command_line::EntryKind;
|
|
|
|
|
|
self.command_line
|
|
|
|
|
|
.history
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
.filter(|e| e.kind == EntryKind::Info)
|
|
|
|
|
|
.map(|e| e.text.clone())
|
|
|
|
|
|
.collect()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-22 22:44:32 +03:00
|
|
|
|
/// Boot function for `iced::daemon`: returns initial state plus a task that
|
2026-06-20 14:27:51 +03:00
|
|
|
|
/// opens the primary application window. Native only — the web build uses
|
|
|
|
|
|
/// [`Self::boot_web`].
|
|
|
|
|
|
#[cfg(not(target_arch = "wasm32"))]
|
2026-03-22 22:44:32 +03:00
|
|
|
|
fn boot() -> (Self, Task<Message>) {
|
|
|
|
|
|
use helpers::build_window_icon;
|
2026-06-29 13:06:33 +03:00
|
|
|
|
// File association is no longer re-registered on every launch. It is set
|
|
|
|
|
|
// up once via the first-launch prompt below (when the user hasn't been
|
|
|
|
|
|
// asked yet) and afterwards managed entirely by the FILEASSOC command.
|
2026-03-22 22:44:32 +03:00
|
|
|
|
let state = Self::new();
|
|
|
|
|
|
let (id, open_task) = window::open(window::Settings {
|
|
|
|
|
|
maximized: true,
|
|
|
|
|
|
icon: window::icon::from_rgba(build_window_icon(), 32, 32).ok(),
|
2026-05-01 11:31:11 +03:00
|
|
|
|
exit_on_close_request: false,
|
2026-03-22 22:44:32 +03:00
|
|
|
|
..Default::default()
|
|
|
|
|
|
});
|
|
|
|
|
|
let mut s = state;
|
|
|
|
|
|
s.main_window = Some(id);
|
2026-05-16 19:55:57 +03:00
|
|
|
|
let open_main = open_task.map(|_| Message::Noop);
|
|
|
|
|
|
let check_update = Task::perform(
|
2026-06-26 23:06:40 +03:00
|
|
|
|
crate::io::update_check::check_for_update(),
|
2026-05-16 19:55:57 +03:00
|
|
|
|
Message::UpdateCheckResult,
|
|
|
|
|
|
);
|
2026-05-26 18:59:51 +03:00
|
|
|
|
let focus_cmd = s.focus_cmd_input();
|
2026-06-20 13:59:20 +03:00
|
|
|
|
// Startup configuration from the command line (see `cli`). A file
|
|
|
|
|
|
// argument — also how the OS file association launches us when a .dwg
|
|
|
|
|
|
// is double-clicked — opens via `OpenRecent`, which existence-checks
|
|
|
|
|
|
// the path and reports a clean error if it is bogus. `--new` opens a
|
|
|
|
|
|
// fresh drawing tab instead of the welcome screen. `--read-only`
|
|
|
|
|
|
// disables saving. `--script` queues command lines to run once up.
|
|
|
|
|
|
let cfg = crate::cli::gui_config();
|
|
|
|
|
|
s.read_only = cfg.read_only;
|
|
|
|
|
|
let cli_open: Task<Message> = if let Some(p) = cfg.file {
|
|
|
|
|
|
Task::done(Message::OpenRecent(p))
|
|
|
|
|
|
} else if cfg.new {
|
|
|
|
|
|
Task::done(Message::TabNew)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Task::none()
|
|
|
|
|
|
};
|
|
|
|
|
|
// Startup command script: each line dispatched as if typed at the
|
|
|
|
|
|
// command line, in order, after any file open is requested.
|
|
|
|
|
|
let script: Task<Message> = if cfg.script_lines.is_empty() {
|
|
|
|
|
|
Task::none()
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Task::batch(
|
|
|
|
|
|
cfg.script_lines
|
|
|
|
|
|
.into_iter()
|
|
|
|
|
|
.map(|line| Task::done(Message::Command(line))),
|
|
|
|
|
|
)
|
|
|
|
|
|
};
|
2026-06-12 07:50:41 +00:00
|
|
|
|
// One-time prompt offering to make Open CAD Studio the default app for
|
|
|
|
|
|
// .dwg / .dxf. Shown only on the first launch that hasn't answered it
|
|
|
|
|
|
// yet; the flag is persisted so we never ask twice.
|
2026-06-16 23:59:38 +03:00
|
|
|
|
let assoc_prompt: Task<Message> = Task::none();
|
|
|
|
|
|
if !s.default_assoc_prompted {
|
|
|
|
|
|
s.active_modal = Some(ModalKind::AssocPrompt);
|
|
|
|
|
|
}
|
2026-07-02 01:00:22 +03:00
|
|
|
|
// Fetch the Patreon supporters list once at boot for the Start page.
|
|
|
|
|
|
#[cfg(not(target_arch = "wasm32"))]
|
|
|
|
|
|
let patrons_fetch = Task::perform(
|
|
|
|
|
|
async { crate::patreon::fetch_patrons() },
|
|
|
|
|
|
Message::PatronsFetched,
|
|
|
|
|
|
);
|
|
|
|
|
|
#[cfg(target_arch = "wasm32")]
|
|
|
|
|
|
let patrons_fetch = Task::none();
|
feat(packaging): open files from argv, ship Windows MSI installer (#36)
Cross-platform prerequisite for OS-level file associations: the boot
task now consumes `std::env::args_os().nth(1)` as a path and dispatches
`Message::OpenRecent` for it. Flag-style args (starting with `-`) are
ignored, and `OpenRecent` already does the file-existence check, so a
bogus path lands as a clean command-line error instead of a panic.
Linux: the AppImage's desktop entry adds `image/vnd.dwg` alongside the
existing `image/vnd.dxf` MIME type and grows a `%f` placeholder in
`Exec=` so the launcher forwards the picked file path. Once the
AppImage is integrated with the system (xdg-mime / AppImageLauncher),
double-clicking a .dwg / .dxf opens it in Open CAD Studio.
Windows: new `packaging/windows/main.wxs` defines an MSI installer —
per-machine install to `Program Files\Open CAD Studio`, Start Menu
shortcut, `MajorUpgrade` so newer MSIs replace older ones, and a
single ProgID `OpenCADStudio.Drawing` that owns both `.dwg` and
`.dxf` and launches the exe with the file as `argv[1]`. The icon is
pulled from the SVG logo, converted to multi-resolution ICO at build
time with the runner's pre-installed ImageMagick. CI now also runs
`candle` / `light` (WiX Toolset 3, pre-installed on `windows-latest`)
and uploads both the bare `.exe` (portable) and `.msi` (installer)
to the release.
macOS Info.plist already declares the DWG / DXF UTIs; combined with
the argv-handling change above, double-clicking a drawing in Finder
will open it in Open CAD Studio without further packaging changes.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 23:34:14 +03:00
|
|
|
|
(
|
|
|
|
|
|
s,
|
2026-06-20 13:59:20 +03:00
|
|
|
|
Task::batch([
|
|
|
|
|
|
open_main,
|
|
|
|
|
|
check_update,
|
|
|
|
|
|
focus_cmd,
|
|
|
|
|
|
cli_open,
|
|
|
|
|
|
script,
|
|
|
|
|
|
assoc_prompt,
|
2026-07-02 01:00:22 +03:00
|
|
|
|
patrons_fetch,
|
2026-06-20 13:59:20 +03:00
|
|
|
|
]),
|
feat(packaging): open files from argv, ship Windows MSI installer (#36)
Cross-platform prerequisite for OS-level file associations: the boot
task now consumes `std::env::args_os().nth(1)` as a path and dispatches
`Message::OpenRecent` for it. Flag-style args (starting with `-`) are
ignored, and `OpenRecent` already does the file-existence check, so a
bogus path lands as a clean command-line error instead of a panic.
Linux: the AppImage's desktop entry adds `image/vnd.dwg` alongside the
existing `image/vnd.dxf` MIME type and grows a `%f` placeholder in
`Exec=` so the launcher forwards the picked file path. Once the
AppImage is integrated with the system (xdg-mime / AppImageLauncher),
double-clicking a .dwg / .dxf opens it in Open CAD Studio.
Windows: new `packaging/windows/main.wxs` defines an MSI installer —
per-machine install to `Program Files\Open CAD Studio`, Start Menu
shortcut, `MajorUpgrade` so newer MSIs replace older ones, and a
single ProgID `OpenCADStudio.Drawing` that owns both `.dwg` and
`.dxf` and launches the exe with the file as `argv[1]`. The icon is
pulled from the SVG logo, converted to multi-resolution ICO at build
time with the runner's pre-installed ImageMagick. CI now also runs
`candle` / `light` (WiX Toolset 3, pre-installed on `windows-latest`)
and uploads both the bare `.exe` (portable) and `.msi` (installer)
to the release.
macOS Info.plist already declares the DWG / DXF UTIs; combined with
the argv-handling change above, double-clicking a drawing in Finder
will open it in Open CAD Studio without further packaging changes.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 23:34:14 +03:00
|
|
|
|
)
|
2026-03-22 22:44:32 +03:00
|
|
|
|
}
|
2026-06-16 13:16:35 +03:00
|
|
|
|
|
|
|
|
|
|
/// Single-window boot for the web build: no OS-window creation (the browser
|
|
|
|
|
|
/// canvas is the only window), no file-association registration or CLI file
|
|
|
|
|
|
/// open. Secondary manager windows are unavailable on the web for now.
|
|
|
|
|
|
#[cfg(target_arch = "wasm32")]
|
|
|
|
|
|
fn boot_web() -> (Self, Task<Message>) {
|
2026-06-22 02:03:34 +03:00
|
|
|
|
#[cfg_attr(target_arch = "wasm32", allow(unused_mut))]
|
2026-06-16 13:16:35 +03:00
|
|
|
|
let mut s = Self::new();
|
|
|
|
|
|
let focus = s.focus_cmd_input();
|
2026-07-02 01:25:24 +03:00
|
|
|
|
// Web can't reach the Patreon API directly (CORS); fetch the CI-built
|
|
|
|
|
|
// supporters.json served on the same origin instead.
|
|
|
|
|
|
let patrons = Task::perform(
|
|
|
|
|
|
crate::patreon::fetch_patrons_web(),
|
|
|
|
|
|
Message::PatronsFetched,
|
|
|
|
|
|
);
|
|
|
|
|
|
(s, Task::batch([focus, patrons]))
|
2026-06-16 13:16:35 +03:00
|
|
|
|
}
|
2026-03-22 22:44:32 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
use std::path::PathBuf;
|
|
|
|
|
|
|
2026-06-16 13:16:35 +03:00
|
|
|
|
#[cfg(not(target_arch = "wasm32"))]
|
2026-03-22 22:44:32 +03:00
|
|
|
|
pub fn run() -> iced::Result {
|
feat(styles): unify the five style managers behind a shared frame
The text/dimension/table/multileader/multiline managers were hand-copied,
so their list CRUD kept drifting and breaking: Dimension's New created
nothing (#73), new text styles never reached the ribbon dropdown (#74),
and several were added with a NULL handle (dropped on DWG save, #67).
Collapse the duplicated parts into three shared pieces and leave only the
per-manager property editor bespoke:
- app/style_ops.rs: one StyleKind-dispatched CRUD layer (new / copy /
delete / rename) handling handle allocation, ribbon sync, Table re-key
and name-reference rewrites in a single place.
- ui/style_manager.rs: the window scaffold — uniform toolbar (New / Copy /
Delete on the left, Set Current + Apply on the right), style list and
chrome. Each manager now only builds its editor.
- ui/style_list.rs: the list row — single click selects, double click
starts an inline rename.
New capabilities, now consistent across all five:
- Copy duplicates the selected style.
- Double-click a name to rename it inline.
- Right side is always Set Current + Apply (added Table set-current;
MLine apply is a documented placeholder until its editor is editable).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 15:03:34 +03:00
|
|
|
|
iced::daemon(
|
|
|
|
|
|
OpenCADStudio::boot,
|
|
|
|
|
|
OpenCADStudio::update,
|
|
|
|
|
|
OpenCADStudio::view,
|
|
|
|
|
|
)
|
|
|
|
|
|
.subscription(OpenCADStudio::subscription)
|
|
|
|
|
|
.title(|state: &OpenCADStudio, window_id: window::Id| {
|
2026-06-17 00:09:04 +03:00
|
|
|
|
let _ = window_id; // all dialogs are in-canvas modals now
|
feat(styles): unify the five style managers behind a shared frame
The text/dimension/table/multileader/multiline managers were hand-copied,
so their list CRUD kept drifting and breaking: Dimension's New created
nothing (#73), new text styles never reached the ribbon dropdown (#74),
and several were added with a NULL handle (dropped on DWG save, #67).
Collapse the duplicated parts into three shared pieces and leave only the
per-manager property editor bespoke:
- app/style_ops.rs: one StyleKind-dispatched CRUD layer (new / copy /
delete / rename) handling handle allocation, ribbon sync, Table re-key
and name-reference rewrites in a single place.
- ui/style_manager.rs: the window scaffold — uniform toolbar (New / Copy /
Delete on the left, Set Current + Apply on the right), style list and
chrome. Each manager now only builds its editor.
- ui/style_list.rs: the list row — single click selects, double click
starts an inline rename.
New capabilities, now consistent across all five:
- Copy duplicates the selected style.
- Double-click a name to rename it inline.
- Right side is always Set Current + Apply (added Table set-current;
MLine apply is a documented placeholder until its editor is editable).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 15:03:34 +03:00
|
|
|
|
if let Some(tab) = state.tabs.get(state.active_tab) {
|
|
|
|
|
|
let dot = if tab.dirty { "● " } else { "" };
|
|
|
|
|
|
let name = tab.tab_display_name();
|
|
|
|
|
|
format!("{}Open CAD Studio — {}", dot, name)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
"Open CAD Studio".to_string()
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
.theme(|state: &OpenCADStudio, _| state.active_theme.clone())
|
|
|
|
|
|
.run()
|
2026-03-22 22:44:32 +03:00
|
|
|
|
}
|
2026-06-16 13:16:35 +03:00
|
|
|
|
|
2026-06-24 12:10:20 +02:00
|
|
|
|
impl Drop for OpenCADStudio {
|
|
|
|
|
|
fn drop(&mut self) {
|
|
|
|
|
|
// Kill plugin runner processes as soon as the application state is
|
|
|
|
|
|
// dropped, instead of waiting for the thread-local manager destructor.
|
|
|
|
|
|
// This makes host shutdown deterministic and fast on every exit path.
|
|
|
|
|
|
#[cfg(not(target_arch = "wasm32"))]
|
|
|
|
|
|
crate::plugin::external::shutdown_plugins();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-16 13:16:35 +03:00
|
|
|
|
/// Single-window entry for the web (wasm) build. Uses `iced::application`
|
|
|
|
|
|
/// instead of `iced::daemon`: the browser canvas is the only window, so the
|
|
|
|
|
|
/// main-window view is rendered directly and the manager/dialog windows are
|
|
|
|
|
|
/// unavailable for now (see issue #45). Native keeps the multi-window `run`.
|
|
|
|
|
|
#[cfg(target_arch = "wasm32")]
|
|
|
|
|
|
pub fn run_web() -> iced::Result {
|
|
|
|
|
|
iced::application(
|
|
|
|
|
|
OpenCADStudio::boot_web,
|
|
|
|
|
|
OpenCADStudio::update,
|
|
|
|
|
|
OpenCADStudio::view_main,
|
|
|
|
|
|
)
|
|
|
|
|
|
.subscription(OpenCADStudio::subscription)
|
|
|
|
|
|
.title(|_state: &OpenCADStudio| "Open CAD Studio".to_string())
|
|
|
|
|
|
.theme(|state: &OpenCADStudio| state.active_theme.clone())
|
|
|
|
|
|
.run()
|
|
|
|
|
|
}
|