Commit Graph
239 Commits
Author SHA1 Message Date
Hakan SevenandClaude Opus 4.8 5ce3aaf260 fix: modal dialogs capture the keyboard
Modals blocked the mouse but not the keyboard: keystrokes from the global
key subscription leaked past an open dialog into the command line and
fired as commands once it closed. Guard `update()` so that while a modal
is open, Escape closes it and every other keystroke-derived message
(command-line input/edit, F-key toggles, edit shortcuts, undo/redo) is
swallowed. A modal's own text fields keep working — they emit their own
messages, which are not in the blocked set. (#126)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 14:00:03 +03:00
Hakan SevenandClaude Opus 4.8 aafced76d7 fix(web): honor selected DWG/DXF version when saving
The web save path discarded the chosen format version (`let _ = version`)
and `save_to_bytes` never applied it, so every browser download was
written at the document's default version (AC1032 / DWG 2018) regardless
of the format picked in the Save dialog. (#122)

Thread `version` into `save_to_bytes` and set `doc.version` there, the
byte-buffer counterpart to `save_as_version`. Saving as DWG 2013 now
writes an AC1027 file.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 13:46:55 +03:00
Hakan SevenandClaude Opus 4.8 157412419e feat(plugin): uninstall + clean upgrade/reinstall of external plugins
Add an Uninstall button on installed external plugin cards that deletes
the package folder (effective next restart — the library stays resident
for the session). Reinstalling or upgrading via the marketplace now
clears any stale native library with a different name before writing the
new one, so the loader always picks the freshly installed binary.

Part of the #100 extensibility epic (phase 2).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 15:57:44 +03:00
Hakan SevenandClaude Opus 4.8 020a3a4c84 feat(plugin): marketplace — install plugins from a curated registry
Add a Plugin Manager marketplace that installs external add-ons from a
GitHub repo's Releases. A curated registry (plugins/registry.json in this
repo, extended by PR) lists discoverable plugins; the host fetches it,
shows each entry with a release dropdown, and on install downloads the
asset matching the user's platform plus plugin.toml into the plugins
folder (API-version checked first). Users can also link arbitrary
owner/repo manually. Linked repos persist in settings.

Desktop only (ureq + serde_json); the install path reuses the phase-2
loader. Part of the #100 extensibility epic (phase 2).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 15:24:56 +03:00
Hakan SevenandClaude Opus 4.8 f6219a9609 feat(plugin): discover external plugin packages from the plugins folder
Phase-2 groundwork: scan <config>/OpenCADStudio/plugins/<id>/plugin.toml,
parse the documented keys (no new TOML dep), check for a platform native
library beside it, and gate on the API version. The Plugin Manager gains
an "External (plugins folder)" section showing each package with a
status pill (Ready to load / API incompatible / No library). Discovery
only — actually loading the cdylib is the next step.

Part of the #100 extensibility epic (phase 2).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 11:43:24 +03:00
Hakan SevenandClaude Opus 4.8 96ec672d5b feat(points): point style commands + DDPTYPE dialog, viewport-aware size
Points could only render as a 1px dot — no way to change PDMODE/PDSIZE.

- PDMODE / PDSIZE commands set the header value and rebuild the glyphs.
- DDPTYPE opens a Point Style dialog: a 5x4 grid of rendered glyphs
  (shape x enclosure), a size field, and relative/absolute radios.
- Relative (<=0) PDSIZE now sizes from the current world-per-pixel so the
  marker stays a roughly constant on-screen size across zoom, rebuilding
  on zoom via the existing camera-generation cache key. Absolute (>0)
  scales with the world as before. Switching to absolute with a zero size
  seeds a positive value from the on-screen size so it stays representable.

Part of the #100 extensibility epic (point style surfaced in #106).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 11:13:12 +03:00
Hakan SevenandClaude Opus 4.8 50825b6346 feat(plugin): ModuleEvent::PluginFileDialog for native file import
Add-ons had no way to request a file picker — only the host's own Open
dialog. PluginFileDialog lets a plugin tool ask the host to open a native
picker; on selection the host dispatches "<command> <path>" back to the
plugin with original case preserved (bypassing the command-line
upper-casing that mangles case-sensitive paths on Linux/macOS). The demo
plugin gains an Import tool exercising it.

Part of the #100 extensibility epic (surfaced in #106).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 10:22:08 +03:00
Hakan SevenandClaude Opus 4.8 844069b4ef feat(plugin): enable/disable plugins from the Plugin Manager
The manager was a read-only inventory. Each plugin now has an
Enable/Disable toggle: a disabled plugin keeps its manifest listed but
drops its ribbon tab and command dispatch. The disabled-id set persists
in settings.txt (disabled_plugins=) and the ribbon rebuilds on toggle.

Part of the #100 extensibility epic.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 10:15:38 +03:00
Hakan SevenandClaude Opus 4.8 195415af84 feat(modal): drag dialogs by a title-bar grip, clamped to the window
In-canvas modals were locked to screen-centre. Add a green move-arrow
grip beside the ✕ that drags the dialog; positioning is an offset from
centre applied via asymmetric padding. The drag offset is clamped to the
window so a dialog stops at the edge instead of being squeezed, and the
offset resets when the dialog closes so each opens centred.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 09:59:17 +03:00
Hakan SevenandClaude Opus 4.8 ec3161f7f7 fix(snap): unify snap aperture so OTRACK catch matches OSNAP
Tracking and polar pickup used the full crosshair-arm radius (60 px)
while OSNAP used the smaller ¼ radius, so the vector-intersection lock
grabbed over a much larger area than object snaps. Collapse to a single
osnap_radius_px shared by OSNAP, tracking, polar and extension; the
crosshair keeps following the cursor and the point snaps on contact.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 05:25:11 +03:00
Hakan SevenandClaude Opus 4.8 58e4d696ba feat(otrack): lock onto the intersection of two active vectors
When two tracking vectors were active, clicking placed a free point on
one of them instead of their crossing. otrack_snap now builds the active
rays (OTRACK rays from tracking points, plus POLAR rays from the last
point) and, when the cursor is near the crossing of two vectors from
different origins, locks onto that intersection. Covers OTRACK×OTRACK and
POLAR×OTRACK; single-vector alignment is unchanged when no crossing is
near.

Closes #111
Closes #112

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 05:01:41 +03:00
Hakan SevenandClaude Opus 4.8 ca8d249173 fix(otrack): add 250ms dwell timeout before acquiring a tracking point
A tracking point was created as soon as a snap point was highlighted,
so moving the mouse across the view spawned accidental tracking points.
Acquisition now requires the cursor to rest near the snap point for
250ms (wall-clock) rather than a fixed number of move events.

Closes #110

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 04:59:06 +03:00
Hakan SevenandClaude Opus 4.8 fefae274d4 feat(wasm): web Save/Save As go through the dialog with a direct download
On the web, Save and Save As now open the in-app Save dialog (filename +
format) like the desktop, and the unsaved-changes prompt's Save routes
through it too — instead of an immediate rfd download. Confirm serializes
to bytes and downloads directly via a Blob + hidden `<a download>` click
(a user gesture), so there is no intermediate "click to download" link.

Replaces the rfd-based download_web / WebSaveResult round-trip with a
synchronous sys::download_bytes. Native save is unchanged.

Native + web build; 75 tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 00:52:20 +03:00
Hakan SevenandClaude Opus 4.8 3dd1276b6a refactor(ui): remove dead OS-window machinery after Plan B
With every dialog now an in-canvas modal, the per-dialog
`*_window: Option<window::Id>` fields are never set. Remove all 17
fields and their inits, trim `OsWindowClosed` to just the main window
(exit), drop the dialog branches from the window `title` closure, the
leftover `gain_focus` guards in the open handlers, and the now-unused
`iced::window` import in update.rs. Page-setup apply clears
`active_modal` instead of closing a window.

No behaviour change; native + web build, 75 tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 00:09:04 +03:00
Hakan SevenandClaude Opus 4.8 94b906d95b feat(ui): migrate the last dialogs to modals — fully single-window (Plan B)
Move the unsaved-changes prompt, the Save As browser and the
file-association prompt onto `active_modal` / `modal_content`, reusing
their existing open/close helpers (now just set/clear active_modal). The
daemon `view` no longer dispatches per window — every dialog is an
in-canvas modal, so the app is single-window on native and web alike.

`close_active_modal` handles their ✕: the unsaved prompt cancels the
pending close, the association prompt records that it was answered.

Native + web build; 75 tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 23:59:38 +03:00
Hakan SevenandClaude Opus 4.8 c24c6885d0 fix(ui): modal close discards staged edits and clears the ribbon tool
Closing a dialog as an OS window used to discard a style editor's
un-applied (staged) changes and de-highlight the ribbon tool that
launched it. The in-canvas modal ✕ (CloseModal) lost that, so a Cancel
kept staged edits and left the button stuck active.

Add `close_active_modal`: it runs `style_stage_discard` for the style
editors and deactivates the matching ribbon tool by ModalKind, then
clears `active_modal`. CloseModal and the dialog close handlers route
through it. Apply still commits (re-baselined), so Apply-then-✕ keeps
the change.

Native + web build; 75 tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 23:52:21 +03:00
Hakan SevenandClaude Opus 4.8 28232f1247 feat(ui): migrate style editors + nested colour picker (Plan B)
Move Table Style, MLeader Style and Dimension Style editors off OS
windows onto `active_modal` / `modal_content`, and turn the colour
picker into a nested modal that stacks over whichever dialog requested
it (`color_pick_target.is_some()` drives the overlay; CloseColorPicker
dismisses it). OpenColorWindow/ColorWindowPick no longer open/close an
OS window.

All thirteen former pop-up windows now render as in-canvas modals on
both native and web; only the native-only assoc/unsaved/save dialogs
remain as OS windows. Native + web build; 75 tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 23:42:14 +03:00
Hakan SevenandClaude Opus 4.8 7cd938443e feat(ui): migrate Text Style and Multiline Style to modals (Plan B)
Move the Text Style and Multiline Style editors off OS windows onto the
shared `active_modal` / `modal_content` path. Their open handlers set
`active_modal` (keeping the existing buffer/stage setup); construction
moves from the daemon `view` dispatch into `modal_content`.

The remaining style editors (Table, MLeader, Dimension) still use OS
windows for now — they carry a nested colour picker that needs its own
handling. Native + web build; 75 tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 23:29:50 +03:00
Hakan SevenandClaude Opus 4.8 b21d10f6e8 feat(ui): migrate 4 more dialogs to in-canvas modals (Plan B)
Consolidate all migrated dialogs into a single `modal_content` builder
matched on `active_modal`, rendered by view_main. Move Layers, Page
Setup, Layout Manager and Plot Style off OS windows onto it (their
construction relocated from the daemon `view` dispatch, which now only
handles the not-yet-migrated dialogs). Each keeps a bounded size.

Open/close handlers set `active_modal` instead of opening/closing
windows; ToggleLayers toggles it.

Native + web build; 75 tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 23:22:54 +03:00
Hakan SevenandClaude Opus 4.8 6a3663b3f1 feat(ui): unified modal dispatch; migrate 4 dialogs (Plan B)
Replace the per-dialog open flag with a single `active_modal:
Option<ModalKind>` and one `CloseModal` message. view_main matches it to
render the open dialog as an in-canvas modal.

Migrated off OS windows: About (content-sized), Shortcuts, Plugin
Manager and the Update notice (the latter three keep a bounded size via
a wrapping container, since their lists/scroll need defined bounds).

Native + web build; 75 tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 19:07:25 +03:00
Hakan SevenandClaude Opus 4.8 e6213eafbb feat(ui): shared in-canvas modal overlay; migrate About (Plan B)
Start Plan B — replace the daemon's pop-up OS windows with in-canvas
modal overlays so dialogs work identically on native (one main window)
and web (canvas only).

- New `ui::modal::modal(base, content, on_close)`: dims the view behind
  a backdrop that blocks clicks but does NOT dismiss, frames the content,
  and adds a top-right ✕ as the sole close affordance. Sizes to the
  content (no `Fill`), so dialogs shrink to fit.
- view_main stacks the active modal on top, so both run paths show it.
- Migrate About as the pilot: `about_open` bool + AboutOpen/AboutClose
  instead of an OS window; About's view shrinks to content.

Remaining dialogs migrate to the same pattern next. Native + 75 tests OK.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 18:59:13 +03:00
Hakan SevenandClaude Opus 4.8 9d378a0533 feat(wasm): save files from the browser
Wire up save for the web build:

- `io::save_to_bytes`: serialize a document to a byte buffer (DXF via
  write_to_vec, DWG via write_to_writer over a Cursor).
- `io::download_web`: browser save dialog → write bytes (triggers a
  download).
- Save and Save As on wasm both go through a new `web_save` helper:
  serialize the active tab and offer a download (no persistent path or
  in-app save dialog on the web). Format follows the tab name's
  extension, defaulting to DWG. Result reported via `WebSaveResult`.

Native build + 75 tests unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 18:20:22 +03:00
Hakan Seven 1ab892c635 Merge pull request #105 from KarimJerbi/rectang-suppress-ortho
Temporarily suppress Ortho mode during RECTANG commands
2026-06-16 18:17:18 +03:00
Hakan SevenandClaude Opus 4.8 6996343e84 feat(wasm): open files in the browser
Wire up file open for the web build:

- `io::pick_and_load_web`: browser file picker → read bytes → parse via
  `load_bytes` → build caches, returning the same payload as the native
  loader so it feeds the shared `Message::FileOpened` handler.
- `Message::OpenFile` cfg-dispatches: native picks a path then loads on a
  worker thread; web picks + parses in one async step. The web branch also
  sets `opening` (the handler drops results when no open is in progress).
- Disable the cosmic-text shaping / fallback paths on wasm: the web has no
  system fonts, so cosmic-text panics with "no default font found". LFF
  stroke fonts (embedded) still render; LFF-missing glyphs are skipped.

Native build + 75 tests unchanged. Hatches still don't draw on the web.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 18:08:57 +03:00
Hakan SevenandClaude Opus 4.8 c49ad887b8 fix(wasm): make the web build actually run in the browser
With the wasm bundle building, get it rendering in a browser:

- Enable iced `webgl` so wgpu uses WebGL2 where WebGPU is absent
  (e.g. Firefox by default), and `fira-sans` so UI text has an
  embedded font (the web has no system fonts) — without it every
  label was blank.
- Replace `std::time::Instant` with `iced::time::Instant` (web-safe);
  `Instant::now()` panics ("time not implemented") on wasm and crashed
  at boot via the command line.
- Skip the batched-hatch pipeline on wasm: its bind group needs
  vertex-stage storage buffers, which WebGL2 lacks (validation error
  on "New drawing"). Fields become Option; upload/draw guard on them.
  Hatches don't draw on the web for now; everything else does.
- Fix the trunk feature flag (`data-cargo-no-default-features`) and
  ignore the `dist/` build output.

Native build + 75 tests unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 17:53:56 +03:00
Hakan SevenandClaude Opus 4.8 b2e20c95fe feat(wasm): compile the library for wasm32 (issue #45 groundwork)
First step toward a web build: the library now compiles for
wasm32-unknown-unknown with --no-default-features.

- Add a `solid3d` feature (default on) gating truck-meshalgo /
  truck-shapeops / lzma-sys. These pull vtkio → xz2 → lzma-sys (C),
  which cannot cross-compile to wasm. Without the feature the 3-D
  solid path (tessellation, booleans, ACIS import) becomes no-ops;
  2-D CAD is unaffected. Native builds keep the feature by default.
- Move native-only crates (ureq, open) to non-wasm target deps and
  add cfg shims: update-check is a no-op on wasm, and a new `sys`
  module provides open_url + handle_path (rfd FileHandle → PathBuf;
  synthesizes a name-based path on the web).

Native build + all 75 tests unchanged. Browser runtime wiring
(entry point, trunk, real file I/O) is a follow-up.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 13:01:19 +03:00
Karim Jerbi a431e02378 Temporarily suppress Ortho mode during RECTANG commands 2026-06-16 09:53:32 +01:00
Hakan SevenandClaude Opus 4.8 3666c7fa69 refactor(modules): rename home module to draw, restore first-tab order
The Home tab was already titled "Draw"; rename the module to match:
folder modules/home → modules/draw, HomeModule → DrawModule, id "home"
→ "draw". The inner draw/ command folder is kept (modules::draw::draw).

Update build.rs PRIORITY ("home" → "draw") so the tab returns to the
front of the ribbon; registry.rs regenerates accordingly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 11:49:14 +03:00
Hakan SevenandClaude Opus 4.8 7fe7e0230d refactor(scene): group modules into role-based subfolders
Move the 30 flat modules under src/scene/ into six folders by role:
convert (entity→geometry), text (fonts/shaping), model (render models),
pick (hit-test/selection/grips/xclip), view (camera/transform/render),
cache (block + property caches). pipeline/ is unchanged.

Full path migration — all `scene::X` references become
`scene::<group>::X`, no compatibility re-exports. render.rs items that
were `pub(super)` (parent was scene) become `pub(in crate::scene)` to
keep their original scene-wide visibility.

No behavior change; build and tests are green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 11:33:46 +03:00
Hakan SevenandClaude Opus 4.8 6376080336 fix(properties): close the color dropdown on selection change and viewport click
The Properties color dropdown stayed open after deselecting, reselecting, or
clicking into the viewport. refresh_properties carried color_picker_open across
rebuilds, so a selection change never closed it; and a viewport press starts a
box selection, so it only closed on the second click. Stop preserving the
picker state across rebuilds and dismiss both color dropdowns on the first
viewport press.

Fixes #104.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 04:02:46 +03:00
Hakan SevenandClaude Opus 4.8 1a9844b134 feat(dynamic-input): shared angle reference + sign handling
Make angle display, sign and the polar guide reference part of the
common infrastructure instead of per-command logic:

- DynSpec.ref_point now drives the polar guide's reference direction, so
  the arc and value box can sweep from a command-defined reference (not
  just +X). LINE etc. keep +X by leaving it None.
- `dyn_display_angle_deg` is the single rule for how an angle reads: the
  unsigned magnitude of the short angle, so clockwise (cursor on the CW
  side of the reference) shows positive. Used by the generic value box,
  ROTATE and ARC.
- A typed angle committed via on_text_input is re-signed by the cursor's
  side relative to the reference (`dyn_sign_angle_text`), so commands
  receive an already-signed value and don't each implement it.

ROTATE: drop its own sweep arc (the polar guide draws it now, anchored at
the centre and swept from the reference); the rotation commits in the
dragged direction whether picked, typed or accepted with Enter.

ARC SCA/CSA: reference the start angle so the arc, sign and readout are
all relative to it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 23:03:08 +03:00
Hakan SevenandClaude Opus 4.8 5128d94b2b feat(dynamic-input): ARC specs + ellipse-arc preview fixes
- ARC: per-step DynSpec for the typed-value modes — included-angle (SCA,
  CSA), chord length (SCL, CSL) and radius (SER) show their box and route
  the typed value through on_text_input via a new dyn_commit_as_text()
  hook; the angle box previews the live span. Point/3P/SCE steps stay on
  the legacy polar path.
- ARC + ellipse-arc: derive the previewed sweep direction from the swept
  angle with a ~10° tolerance and advance the reference only on a clear
  move, so small cursor jitter no longer flips the arc. Ellipse uses the
  parametric angle so a flat ellipse isn't sticky.
- Ellipse arc: the start-angle step now shows a line from the centre to
  the cursor (it only picks the start angle) instead of a misleading arc.
- Ellipse arc: use the same minor-axis direction (Z x major) as the
  rendered entity so the committed arc matches the preview instead of
  mirroring across the major axis.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 22:03:04 +03:00
Hakan SevenandClaude Opus 4.8 e31dbcb5c4 feat(dynamic-input): per-step specs for rectangle, polygon and ellipse
Extend the DynSpec coverage and add the guides those commands need:

- Rectangle: two-corner enters width/height as unsigned magnitudes (the
  sign follows the cursor side, like the angle entry), drawn with the
  rectangle as the guide; rotated rectangle's height is a single distance
  measured square to the base edge (dimension line offset off the edge
  with extension lines); centre rectangle shows half-width/half-height.
- Polygon: inscribed/circumscribed vertex step shows radius + rotation.
- Ellipse: minor-axis step (all three modes) measures the half-length
  perpendicular to the major axis and draws that semi-axis from the
  centre; angle steps anchor their arc at the centre.

Adds DynGuide::Perp / PerpDim (perpendicular measure to a reference line)
and DynSpec::ref_point, plus Width/Height role handling (unsigned display,
cursor-signed commit) and diameter value scaling.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 18:46:34 +03:00
Hakan SevenandClaude Opus 4.8 b0ee1c39bd feat(dynamic-input): per-step DynSpec framework + guide overhaul
Introduce a per-command, per-step dynamic-input description so each step
shows the value boxes and guide geometry that make sense for it, instead
of a one-size-fits-all distance/angle pair.

- command: add DynSpec{anchor, fields(role+label), guide}, DynRole
  (X/Y/Z/Distance/Angle/Radius/Diameter/Width/Height/Factor/Count with
  label + value scaling), DynGuide(None/Polar/AxisDelta/Radius/RectSides)
  and DynAnchor. New dyn_spec() trait method defaults to None so commands
  on the legacy dyn_field() path are unchanged.
- overlay: redraw the dynamic-input layer by guide — polar reference line
  + angle arc, single radius line, dotted axis legs, or a cursor row —
  and place each box by its role. Angle reads as the unsigned magnitude
  of the short arc; a typed polar angle takes the sign of the cursor side.
- app: route fields/guide/anchor through the tab; resolve points from the
  step anchor and de-scale typed values by role (typed diameter -> radius).
- circle: CR shows a radius value on one line to the cursor; CD shows the
  diameter (twice the cursor radius). No angle arc on radius steps.
- pline/mline: keep polar dynamic input on vertex steps that also accept
  keyword letters via point_step_accepts_keywords().

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 17:12:08 +03:00
Hakan Seven 18f1688486 Revert "fix(input): honor @ / # coordinate prefixes during dynamic input"
This reverts commit df616482e2.
2026-06-15 13:46:27 +03:00
Hakan SevenandClaude Opus 4.8 df616482e2 fix(input): honor @ / # coordinate prefixes during dynamic input
While DYN fields were showing, a leading @ or # landed in the command
line but the following digits were captured by the focused DYN field,
splitting the value so the prefix never resolved. Treat a leading @ / #
as an escape: route the whole coordinate (digits and separators) to the
command line so the relative (@) / absolute (#) prefix works the same
whether DYN is on or off.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 13:31:25 +03:00
Hakan SevenandClaude Opus 4.8 fbac39462e fix(input): command line coordinates are always absolute
A bare command-line coordinate followed the DYN setting, so with dynamic
input on it was treated as relative to the last point. AutoCAD keeps the
command line absolute regardless of DYN; only the DYN tooltip defaults to
relative. Resolve a bare command-line value as absolute and reserve
relative for the explicit `@` prefix (`#` still forces absolute). The DYN
tooltip path keeps its own relative-by-default behaviour.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 13:27:30 +03:00
Hakan SevenandClaude Opus 4.8 ec865a95aa feat(plugin): add a Plugin Manager window listing installed add-ons
Read-only inventory of the add-ons compiled into the build — name, version,
id, API level, description, command prefixes — built from the extracted
`ocs_plugin_api` manifest. Opened with the `PLUGINS` / `PLUGINMANAGER` command
or the Start-page "Plugins" button.

Adds `installed_manifests()` to the plugin registry and wires the window the
same way as the existing About / Shortcuts windows.

Part of #100 (phase-1 plugin manager UI stub).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 01:46:09 +03:00
Hakan SevenandClaude Opus 4.8 036ac7dd86 fix(command): submit typed command on Enter without focusing the input (#99)
Unfocused keyboard input routes through the global subscription: typed
characters reach the command line, but Enter went to CommandFinalize, which
only forwarded the buffer to CommandSubmit while a command was already active.
At startup (before the window grabs focus) typing a command name and pressing
Enter did nothing until the user clicked into the command line. Forward any
non-empty buffer to CommandSubmit regardless of active-command state.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 00:35:03 +03:00
Hakan SevenandClaude Opus 4.8 f7734b698f feat(ui): use the shared colour selector in properties, ribbon and layers
Replace the bespoke colour pickers with the shared color_selector / color_list
widget so colour selection is identical everywhere:
- left properties panel (selected entities' colour)
- ribbon Home-tab colour dropdown
- layer manager per-layer colour

Each routes its "More…" entry to the standalone Select Color window via a
ColorPickTarget (Properties / Ribbon / Layer). Extracted color_list as the
shared popup content; the colour selector button now fills its container.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 14:22:24 +03:00
Hakan SevenandClaude Opus 4.8 3d050fe1ab feat(ui): move full ACI palette to a separate "Select Color" window (#94)
The colour selector popup now lists only the named colours (ByLayer,
ByBlock, ACI 1-9) plus a "More…" entry. "More…" opens a standalone
"Select Color" window with the full 256-colour grid; picking there applies
to the originating field (dimension/multileader/table style) and closes
the window. Routed via a ColorPickTarget so one window serves every field.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 11:14:14 +03:00
Hakan SevenandClaude Opus 4.8 3a87a18f32 fix(ui): float the colour-selector popup instead of pushing the form (#94)
The colour list/palette was laid out inline, shifting everything below it
down when opened. Wrap it in a small custom widget (Floating) that renders
the button inline and the popup as an iced overlay anchored below it, so it
floats over the UI like a real dropdown. Picking a colour now also closes
the popup (the style editors clear their open-field state on edit).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 11:00:43 +03:00
Hakan SevenandClaude Opus 4.8 8cdb17a6b4 feat(ui): shared colour selector used across all style editors (#94)
Add a reusable color_selector widget (ui/color_select.rs): a main-colour
dropdown plus a "more colours" button that expands the full 255-entry ACI
palette — the same design the properties panel uses. Replace the flat ACI
swatch rows added earlier in the dimension, multileader and table style
editors with it, wired through each editor's existing colour edit message
(the chosen colour is encoded as an ACI string) plus a per-field
palette-open toggle.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 10:42:39 +03:00
Hakan SevenandClaude Opus 4.8 29ccfcfc46 fix(command): accept typed points/Enter in keyword steps when DYN is off (#97)
A command step that takes both a point and option keywords (PLINE,
MLINE) returns true from wants_text_input to receive the keywords, but
CommandSubmit then routed the text only to on_text_input and, on a None
(unhandled) result, re-prompted instead of falling through. So with
dynamic input off, typed coordinates and a bare Enter were swallowed —
points couldn't be added by keyboard and the command couldn't be ended.

Fall through to the existing Enter / coordinate handling when
on_text_input doesn't consume the text. Coordinates (including @ and #
prefixes) and Enter now work in these steps with DYN both on and off;
commands that fully handle their text step still return Some and are
unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 00:59:52 +03:00
Hakan SevenandClaude Opus 4.8 8d6e21ef5f fix(layers): sync header CLAYER when making a layer current (#93)
Making a layer current via the layer manager (LayerSetCurrent) or
LAYMCUR updated only the per-tab active_layer, not the document
header's current_layer_name/handle. The no-selection ribbon refresh
reads the header layer, so after the change an Esc (deselect ->
refresh_properties) snapped the active-layer dropdown back to the
stale header value ("0") even though new entities still used the
correct active layer. Mirror the change into the header in both paths,
matching the ribbon dropdown's own set-current path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 00:16:06 +03:00
Hakan SevenandClaude Opus 4.8 57d3e620ea fix(command): keyword/radius input for ribbon-started modify commands (#90)
Starting FILLET/CHAMFER from the ribbon left the command-line field
unfocused, so its Enter arrived as CommandFinalize and discarded a
typed option keyword ("R"/"D"), cancelling the command. Several
related dynamic-input issues surfaced from the same flow:

- CommandFinalize now forwards a non-empty command-line buffer through
  CommandSubmit, so a typed keyword is handled the same whether the
  command was started from the ribbon or the command line.
- sync_dyn_fields shows no coordinate box during an object-pick step
  (needs_entity_pick / needs_structure_point_pick) — there is no point
  to enter there, so typed keywords reach the command line cleanly.
- apply_cmd_result rebuilds the dynamic-input fields on NeedPoint, so a
  step change (object pick -> radius entry) shows the matching box
  immediately and typed digits land in it instead of the command line.
- Object-pick steps now draw a prompt-only hint pill at the cursor
  (no input box).
- FILLET/CHAMFER remember the first pick across a mid-selection radius
  or distance entry, resuming the second pick instead of restarting
  selection.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 23:58:56 +03:00
Hakan SevenandClaude Opus 4.8 cd4d1c2535 fix(layout): focus inline rename field on RENAME
Renaming a layout tab left keyboard focus on the command line, so typed
characters landed there until the user clicked the tab. Give the inline
rename text_input a stable id and return a focus task when the rename
starts so the user types straight into the field. (issue #86)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 14:18:31 +03:00
Hakan SevenandClaude Opus 4.8 dc26c8128c fix(otrack): reset tracking points after each committed point
OTRACK acquired points only ever cleared when a command ended, so a
multi-point command (LINE, etc.) accumulated tracking points across
every input, swamping the user with alignment options. Clear the
acquired points and live alignment vector each time a point is committed
to the active command, at all five point-accept paths. (issue #85)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 14:15:53 +03:00
Hakan SevenandClaude Opus 4.8 9de2760dee fix(selection): keep active selection on empty box/lasso
An empty box or lasso drag called deselect_all, discarding a selection
the user may have built up over several picks. Drop the empty-clear from
the lasso and two-click box paths so a stray empty drag leaves the
selection untouched; Esc remains the deliberate clear. (issue #83)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 14:07:57 +03:00
Claude a7d85fe148 feat(app): first-launch prompt to set Open CAD Studio as default for .dwg/.dxf
On the first launch that hasn't answered it yet, the app shows a small
dialog offering to become the default handler for .dwg and .dxf files.
The choice is persisted (default_assoc_prompted) so the prompt never
reappears.

The platform plumbing lives in io/file_association.rs:
  * Windows — opens the OS's own per-app default-programs dialog via
    IApplicationAssociationRegistrationUI::LaunchAdvancedAssociationUI,
    passing the "Open CAD Studio" RegisteredApplications name the MSI
    registers. Modern Windows guards the actual default behind a
    UserChoice hash, so this is the supported consent path.
  * Linux — xdg-mime default against the installed .desktop file, whose
    MimeType entries already cover image/vnd.dwg and image/vnd.dxf.
  * macOS — LSSetDefaultRoleHandlerForContentType binds the DWG/DXF UTIs
    declared in Info.plist to the app bundle id.

The work runs on a dedicated thread (the Windows dialog is modal) with
the result delivered back through a oneshot channel and surfaced in the
command line.

https://claude.ai/code/session_01DFVSKgViFaxEjZD2KsTdtt
2026-06-12 07:50:41 +00:00