Commit Graph
227 Commits
Author SHA1 Message Date
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
Hakan SevenandClaude Opus 4.8 71837895b0 feat(ui): right-edge side toolbar; drop the paper-space ribbon tab
Switching to a paper layout used to hijack the ribbon with a contextual
"Layout" tab. Replace that with a reusable right-edge vertical toolbar:
a single centred column of icon buttons floated over the canvas, built
from a flat ToolDef list and dispatching through the existing
RibbonToolClick path (ui::side_toolbar). Paper space now shows Viewport /
Page Setup / Export PDF there and leaves the ribbon on the user's tab;
the contextual layout tab and its three auto-switch hooks are removed.
The toolbar is generic infrastructure for future context action sets.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 02:07:26 +03:00
Hakan SevenandClaude Opus 4.8 b47e1bf706 fix(ribbon): load style/layer dropdowns from the document
The Annotate style dropdowns (text / dimension / multileader / table) and
the layer dropdown were hard-coded to "Standard" / "0" and never synced
from the opened document, so they showed placeholder values even with no
file open and stale values after closing one.

- Start the ribbon's style and layer lists empty instead of with a
  placeholder entry.
- Sync the style dropdowns from the document on file open and on tab
  new/switch/close (the layer sync already ran there).
- Guard sync_ribbon_layers / sync_ribbon_styles / sync_ribbon_from_selection
  for the Start (welcome) tab so all three dropdowns stay empty there.
- When a style family is absent (e.g. no table/multileader styles), fall
  back to empty rather than a non-existent "Standard".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 01:46:17 +03:00
Hakan SevenandClaude Opus 4.8 2519d64907 fix(select): box/lasso adds to the selection (#83)
Box selection is a two-click window (corner, corner): the first empty
click armed the box AND cleared the selection, and the box completion
cleared again before applying its hits — so a second box dropped
everything the first one picked. Stop clearing on the arming click and
make the completion accumulate like individual picks (#47): a plain box
or lasso adds, Shift+box/lasso removes the enclosed entities, an empty
box/lasso (or Esc) clears.

Fixes #83

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 01:22:59 +03:00
Hakan SevenandClaude Opus 4.8 cea84ccb64 fix(edit): correct entity-pick hover previews on offset drawings
The trim/extend hover preview (on_hover_entity) gets the cursor in
offset-relative space but compares it against WCS document entities and
returns WCS preview wires. On a drawing with a non-zero world_offset the
preview highlighted the wrong segment and the wires were shifted off the
geometry by the offset. Pass the world-offset-corrected click to
on_hover_entity and shift its returned wires back to the offset-relative
frame the renderer uses. Point-creation previews keep the local cursor.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 01:00:34 +03:00
Hakan SevenandClaude Opus 4.8 14d047ce9a fix(edit): add world_offset to entity-pick clicks
The pick point handed to commands is in offset-relative (local) space,
matching the camera and point-creation commands. Entity-pick, tangent
and structure-pick handlers (TRIM / EXTEND / FILLET / OFFSET, …) instead
compare the click against WCS document entities, so on a drawing with a
non-zero world_offset (UTM-scale coordinates) the click landed ~1e6
units off the geometry — TRIM cut the wrong side or reported the click
as off the segment. Add world_offset back to the pick (model space only)
before passing it to those handlers, matching what the grip path already
does. Point-creation commands keep the local point unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 00:53:20 +03:00
Hakan SevenandClaude Opus 4.8 9b1bd011ca feat(blocks): switch dynamic-block visibility states from a lookup grip
A block reference with a visibility parameter now shows a triangular
lookup grip at the parameter point. Clicking it opens a dropdown of the
named states (current one marked); picking a state toggles which of the
anonymous block's members are visible and rebuilds, so the reference
shows the chosen profile instead of being frozen on the saved state.

The state list is data-driven (owned strings from the file) and applying
it mutates other entities (the anonymous block's members), so it stays
separate from the static GripMenuAction grip-menu: a dedicated
VisibilityPopup and an app-level apply path. The anonymous block is a
parallel-ordered clone of its definition, so a member visible in the
definition's state maps to the same index in the inserted block; the
chosen visibility flags round-trip through the DWG writer.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 20:11:19 +03:00
Hakan SevenandClaude Opus 4.8 25715d1b03 fix(grips): compute grip positions in f64 so they sit on the wire
Grip markers drew visibly offset from the entity on large-coordinate
(UTM-scale) drawings: every Grippable impl cast the f64 entity coordinates
to f32 BEFORE the world-offset subtraction, and at 1e7 magnitude an f32
holds ~0.9 drawing units of rounding error. The wires subtract the offset
in f64 first (precision-preserving), so the grips landed up to ~1 unit
away from the geometry they belong to.

GripDef.world is now DVec3: producers fill it straight from the f64
entity data, refresh_selected_grips subtracts the world offset in f64,
and only the small offset-local result is cast to f32 for screen-space
projection (grips_to_screen / find_hit_grip) and drag state.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 00:04:37 +03:00
Hakan SevenandClaude Opus 4.8 e65a7a6d67 feat(styles): seed current table/multileader style from the file
Bump acadrust to pick up the new $CTABLESTYLE / $CMLEADERSTYLE header
variables, then on load seed the ribbon's active table style and the
tab's active multileader style from the document header so the ✓ marks
the style the file actually declares (DXF). Set Current writes the header
field as the round-trip source of truth, and the style-manager staging
snapshot now covers it. sync_ribbon_styles honours the active table style
instead of always falling back to the first one.

DWG still leaves these at "Standard" until acadrust parses them from the
binary header.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 19:05:07 +03:00
Hakan SevenandClaude Opus 4.8 de4eb31003 feat(styles): make style managers transactional — persist only on Apply
A style manager is now a staged transaction. When it opens, a snapshot of
the style tables, style objects and current-style pointers is taken. Every
New / Copy / Delete / Rename / Set Current / property edit mutates the
document live so the dialog previews the result, but the tab is not marked
dirty, nothing is pushed to undo, and the drawing is not rebuilt.

- Apply commits: one undo entry (restoring the pre-edit state), the tab is
  marked dirty, the drawing rebuilds, and the baseline is reset so further
  edits stage afresh.
- Closing the window without Apply discards: the opening snapshot is
  restored, so none of the changes persist.

Side effects (dirty / undo / bump_geometry) were removed from every style
edit handler and routed exclusively through the commit. Multiline's Apply
is a structural-only commit since its editor has no editable properties.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 15:38:32 +03:00
Hakan SevenandClaude Opus 4.8 65a25033bc fix(styles): make double-click inline rename actually fire
The list row wrapped a `button` inside the `mouse_area`. The button
captured the press, so the parent `mouse_area` never saw the second
click and `on_double_click` never fired — double-clicking a style name
did nothing.

Drop the inner button: the row is now a plain `mouse_area` carrying both
`on_press` (select) and `on_double_click` (start rename), with selection
shown via the container background. Focus the rename field on start so
the user can type immediately.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 15:10:09 +03:00
Hakan SevenandClaude Opus 4.8 f2dfd01795 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
Hakan Seven 6a70e99dd5 Merge pull request #80 from mf4633/feature/plugin-host
Add plugin host (Phase 1)
2026-06-10 00:37:37 +03:00