128 Commits
Author SHA1 Message Date
Hakan SevenandClaude Opus 4.8 26479b6faa fix(grid): independent, clipped grid per viewport
Grid (and grid-snap) state was shared through the global show_grid flag, so
toggling it in model space also turned it on in paper space, and a floating
viewport's grid painted across the whole paper — and beyond the 3-D view when
zoomed in.

- Store grid/snap per view: a model tile in model space, the active floating
  viewport or the layout's sheet viewport in paper space, round-tripped via the
  viewport status flags.
- adopt_view_display on layout switch and viewport enter/exit so the live
  toggle follows the view you are in.
- Render one grid per view (paper_viewport_grid_views): the sheet over the
  paper, each floating viewport clipped to its screen rectangle.
- Clamp the grid clip to the widget so an overflowing viewport rectangle never
  paints grid outside the 3-D view.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-22 00:18:01 +03:00
Hakan SevenandClaude Opus 4.8 d36cc9786f fix(web): render OSNAP menu symbols as SVG instead of font glyphs
The object-snap menu drew each mode's marker as a Unicode geometric glyph
(◻ △ ◯ …). The web build bundles only Fira Sans, which lacks them, so they
rendered as tofu boxes (#138). Add an SVG per snap mode under
assets/icons/osnap, map them via icons::osnap, and draw them with the same
tinted-SVG path the rest of the chrome uses — font-independent on every
platform.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-20 14:27:59 +03:00
Hakan SevenandClaude Opus 4.8 3fc51af41c fix(ribbon): anchor tooltips to the right of their button
Ribbon tooltips were centered below the button, so the cursor resting on
the button covered the tip text. Anchor them to the right instead, clear
of the cursor. (#143)

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-20 11:59:20 +03:00
Hakan SevenandClaude Opus 4.8 36d5891572 fix(layers): freeze toggle in ribbon dropdown + sortable manager columns
Ribbon layer dropdown rows were a single button, so a click on the
visible/freeze/lock icon fell through to the make-active handler instead
of toggling that state. Split the row: each icon is its own toggle
button, only the swatch+label selects the active layer. Toggle handlers
now resync the ribbon so the icon updates while the dropdown stays open.

Layer Manager columns are now sortable: clicking a header sorts by that
column, clicking again flips direction (SVG arrow indicator). Sort is
reapplied on every panel sync so it survives edits, preserves the
selection by name, and keeps the ribbon dropdown order in step.

Fixes #133

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-19 20:39:09 +03:00
Hakan SevenandClaude Opus 4.8 9eb805df23 feat(ui): unified viewport control chip + per-tile grid rendering
Move grid and grid-snap toggles off the status bar (and its customization
menu) into the top-left viewport chip, now a single container holding the
horizontal/vertical split buttons, render-mode picker, and grid/snap
toggles, sized to the render-mode picker. Add grid + magnet (snap) icons.

Render the grid per model tile from each tile's own grid_on, in its own
bounds and camera, instead of a single grid in the active tile. This stops
the grid flickering on/off as the cursor crosses panes or leaves the
viewport, and lets each pane show its own grid independently.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-18 23:29:34 +03:00
Hakan SevenandClaude Opus 4.8 02b0fc4dcf chore: drop brand names and orphaned PICK_HIGHLIGHT colours
Neutralise vendor references in comments to plain behavioural descriptions
(object acquisition / object snap). Remove the PICK_HIGHLIGHT and
PICK_HIGHLIGHT_DIM wire colours: their only consumer was the stripped
storm-sewer add-on, and the retained plugin acquisition pathway draws the
ObjectPick snap glyph and per-command hover previews instead, so the
constants are dead.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-18 21:28:07 +03:00
Hakan SevenandClaude Opus 4.8 5a36494ed7 chore: clear compiler warnings
Remove accidental dead code (unused imports, the superseded paste tool()
helper, a never-read TORIENT handles field, a duplicate let binding, an
unneeded mut) and mark intentional-but-unwired scaffolding with
allow(dead_code): the web-only save_to_bytes, the plugin loadable() probe,
the planned PICK_HIGHLIGHT colours, the DynFieldSpec label, and the layout
ribbon helpers. Native build is now warning-clean apart from the upstream
nom/quick-xml future-incompat notices.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-18 21:18:09 +03:00
Hakan SevenandClaude Opus 4.8 cdd72c62ca fix(dyn): keep angle input box clear of the snap point near 0°
The DYN angle box was placed at full cursor radius along the mid-angle
direction. With a near-zero sweep that direction collapses onto the
cursor ray, planting the box on top of the cursor / snap point and
hiding it. Pull the box back along the ray and offset it to the side
opposite the distance box so the snap point stays visible. (#124)

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-18 20:45:04 +03:00
Hakan SevenandClaude Opus 4.8 bf30e57186 fix(web): force Advanced shaping for dynamic-input canvas text
Dynamic-input value boxes, the prompt pill and the UCS axis labels are
drawn with `frame.fill_text`, whose `canvas::Text` defaults to
`Shaping::Auto`. Auto picks Basic shaping for ASCII-only strings, which
renders on desktop but draws nothing on the web (wgpu/webgl) backend, so
the all-digit distance/coordinate boxes and X/Y/Z labels were blank
(the angle box rendered only because its `°` upgraded it to Advanced).
Typed values landed correctly but were invisible. (#117)

Set `shaping: Advanced` on every canvas fill_text in the overlay.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-18 13:34:03 +03:00
Hakan SevenandClaude Opus 4.8 864dd6cef7 fix(web): render UI icons as SVG instead of font glyphs
UI chrome (dropdown carets, undo/redo, checkmarks, close/menu/osnap,
layer/file-menu icons) and ribbon tool icons were drawn as Unicode
glyphs. Desktop fills missing glyphs via system-font fallback, but the
web build bundles only Fira Sans with no fallback, so they rendered as
empty tofu boxes (issue #116).

Add a shared src/ui/icons.rs (SVG-backed, tintable) plus bundled assets,
and switch every glyph site and IconKind::Glyph tool def to SVG.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-18 12:29:32 +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 <[email protected]>
2026-06-17 15:57:44 +03:00
Hakan SevenandClaude Opus 4.8 eb53e445ab refactor(plugin): drop built-in plugins, external-only
OpenCADStudio no longer bundles plugins — every add-on is an external
cdylib loaded from the plugins folder or installed via the marketplace.

- Remove the demo plugin and the in-tree ocs_example_plugin crate.
- Remove the inventory-based built-in registration path (PluginRegistration,
  all_plugins, installed_manifests); registry now serves core ribbon tabs
  plus loaded external plugins, and try_dispatch routes only to externals.
- Plugin Manager drops the 'compiled into this build' section; the
  enable/disable toggle moves onto loaded external cards.
- Prune the now-dead HostSession/DocumentTab helpers and re-exports.

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

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-17 15:50:52 +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 <[email protected]>
2026-06-17 15:24:56 +03:00
Hakan SevenandClaude Opus 4.8 29c71be101 feat(plugin): load external cdylib plugins at runtime (phase 2)
Move BuiltinPlugin into ocs_plugin_api (host feature) so out-of-tree
crates can implement it, and add export_plugin! to emit the two C
symbols a cdylib exposes: ocs_plugin_api_version (checked first, so an
ABI-incompatible build never runs) and ocs_plugin_register -> boxed
BuiltinPlugin. The host loads every compatible package from the plugins
folder at startup via libloading (desktop only), keeps the library
resident for the session, and merges its ribbon tab + command dispatch
into the same paths as built-ins (honouring enable/disable). The Plugin
Manager shows external packages with a Loaded / incompatible status.

Approach B: the plugin hands back a boxed trait object, assuming a
matching toolchain + ocs_plugin_api version (the version symbol enforces
the latter). crates/ocs_example_plugin is the reference cdylib.

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

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-17 14:48: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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
2026-06-17 09:59:17 +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 <[email protected]>
2026-06-16 18:59:13 +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 <[email protected]>
2026-06-16 17:53:56 +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 <[email protected]>
2026-06-16 11:33:46 +03:00
Hakan SevenandClaude Opus 4.8 cd048165a9 feat(fonts): system TrueType support with shaping and fallback
Add a TrueType/OpenType render path alongside the LFF stroke engine so
users can pick fonts installed on their machine for text and MText.

- Discover system families via fontdb (sysfont) and list them in the
  text-style picker.
- Extract glyph outlines with ttf-parser, flattened into wire strokes
  normalized to the same 9-unit cap-height space as LFF glyphs.
- Shape runs with cosmic-text (ligatures, Arabic joining, kerning, bidi)
  plus automatic font fallback: a glyph missing from the chosen family
  is taken from another installed font.
- Unify both sources behind a single Face type; a shared run tokenizer
  drives the stroke (per-glyph) and shaped (TTF) renderers identically.
- Stroke fonts now fall back to a system TTF (cosmic-text picks it) for
  characters no LFF font covers, replacing the unicode/iso3098 chain.

Also trim the embedded LFF set from 47 to 26, keeping the AutoCAD-SHX
equivalents and dropping LibreCAD-only fonts now covered by TrueType.

LFF builtins keep priority over same-named system fonts so DXF text
renders identically across machines.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-16 11:18:17 +03:00
Hakan SevenandClaude Opus 4.8 eff6c7c730 fix(command-line): uppercase only the command verb, not arguments
submit() uppercased the entire typed line, mangling case-sensitive arguments
(file paths on case-sensitive filesystems, identifiers, plugin command args) on
Linux/macOS. Uppercase only the first token; verbs still match and each
sub-command handler already re-uppercases its own keywords.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-16 04:46:27 +03:00
Hakan SevenandClaude Opus 4.8 25bf371290 fix(menu): close the main menu when opening a recent file
The recent-file buttons dispatched the open command without closing the app
menu, so it stayed open over the loaded drawing. Use CloseAppMenuAndRun so the
menu closes first, matching the other menu entries. Also commits the Cargo.lock
update from dropping iced's debug feature.

Fixes #103.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-16 03:51:45 +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 <[email protected]>
2026-06-15 23:03:08 +03:00
Hakan SevenandClaude Opus 4.8 40a52cea79 fix(dynamic-input): offset the prompt/boxes off the crosshair evenly
The row layout sat flush against the cursor's horizontal axis. Place the
prompt one gap (the same as the horizontal offset) below the axis and the
value boxes a further gap below the prompt, so the block clears the
crosshair symmetrically.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-15 22:23:35 +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 <[email protected]>
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 <[email protected]>
2026-06-15 17:12:08 +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 <[email protected]>
2026-06-15 01:46:09 +03:00
Hakan SevenandClaude Opus 4.8 b7c5103891 feat(ui): show RGB for unnamed palette colours instead of "Index"
Picking a colour from the full palette showed a generic "Index" label.
Add color_display_name: standard names for ACI 1-9 / ByLayer / ByBlock,
"R,G,B" (0-255) for every other indexed or true colour. Used by the shared
colour selector button and the ribbon colour combo.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-14 14:27:35 +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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
2026-06-14 11:00:43 +03:00
Hakan SevenandClaude Opus 4.8 96a5a32735 feat(ui): show colour swatches beside names in the colour selector (#94)
Reworked the shared colour selector from a plain text pick_list into a
dropdown button that opens a list where each named colour (ByLayer,
ByBlock, ACI 1-9) shows its swatch next to the name, with the full ACI
palette grid below. The dimension/multileader/table style editors pick
it up unchanged.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-14 10:47:20 +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 <[email protected]>
2026-06-14 10:42:39 +03:00
Hakan SevenandClaude Opus 4.8 3ccda2f129 feat(style): pick colours from ACI swatches in the style editors (#94)
Dimension, multileader and table style editors took colours as raw ACI
integers in a text box. Replace those with a swatch row (the 9 standard
ACI colours + ByLayer) that highlights the current value, reusing each
editor's existing colour edit message (the chosen index is sent as the
field's string value, so no new plumbing).

Text styles carry no colour field, so none was needed there.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-14 10:15:14 +03:00
Hakan SevenandClaude Opus 4.8 f93bda1800 feat(dimstyle): use named dropdowns for enum style settings (#94)
The dimension style editor exposed every enum setting (DIMTAD, DIMJUST,
DIMLUNIT, DIMTMOVE, DIMATFIT, DIMTFILL, DIMFRAC, DIMAUNIT, DIMAZIN,
DIMALTU, DIMTOLJ, DIMARCSYM, and the zero-suppression family) as a raw
integer text box, forcing the user to type codes like "1". Replace them
with named pick_list dropdowns via an enum_field helper that maps the
stored code to a label and back. Numeric fields (sizes, colours,
decimals) and obsolete flags (DIMUNIT/DIMFIT) keep their text inputs.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-14 09:28:36 +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 <[email protected]>
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 <[email protected]>
2026-06-12 14:18:31 +03: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 <[email protected]>
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 <[email protected]>
2026-06-12 01:46:17 +03:00
Hakan SevenandClaude Opus 4.8 b3ba3a53d1 feat(styles): green ✓ for current style + resolve it from DWG files
- The current-style marker is now a green ✓ in its own fixed-width column
  so names stay aligned; rendered as a separate coloured glyph rather than
  baked into the label string.
- Resolve the current text / dimension / multiline style from the handle
  the DWG header stores into the name the app reads (fix_current_style_names
  on load). DXF already provides these as names ($TEXTSTYLE / $DIMSTYLE /
  $CMLSTYLE); DWG only stores handles, so the marker previously always fell
  back to "Standard". Only overrides when the handle resolves.

Note: table and multileader current styles are not surfaced by the file
reader (no header name/handle exposed), so those markers still default to
the app's active selection until acadrust exposes them.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-10 15:56:13 +03:00
Hakan SevenandClaude Opus 4.8 885bacd0f6 feat(styles): mark the current style with a ✓ in the list
Every style manager now shows a leading ✓ next to the current style
(current text/dim style, active table/multileader/multiline style), with
the other rows padded so names stay aligned. The marker replaces the
multileader/multiline-only ◀ and is wired for all five managers by
threading the current-style name into each view.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-10 15:45:48 +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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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
Michael Flynn 82993bc1db Add demo_plugin and plugin host integration tests
Minimal in-tree add-on (DP_HELLO) validates registry, ribbon merge, and try_dispatch. Five new lib tests; EntryKind gains PartialEq for test helpers.
2026-06-09 13:52:15 -04:00
Michael Flynn e46701456f Add plugin host architecture and Storm Sewer add-on package
Generic plugin runtime (src/plugin/, HostSession, per-tab plugin_state, command router). QGIS-style add-on layout with plugin.toml and single PluginRegistration. Storm Sewer refactored as reference consumer with dispatch.rs, catchments, LandXML, headless tests (43 passing). Core update.rs decoupled from storm_sewer via CadCommand object-pick hooks. Docs: plugin-architecture.md, plugin-template, PR and issue-78 reply drafts.
2026-06-09 13:41:21 -04:00