Commit Graph
52 Commits
Author SHA1 Message Date
Karim Jerbi 9afb634557 chore(git): enforce LF line endings via .gitattributes 2026-08-15 13:12:34 +01:00
Karim Jerbi 41223f09d7 feat(plot): read-only rows use the shared selectable read-only field 2026-08-15 11:59:45 +01:00
Karim Jerbi 8e6b60910c feat(style): align mleader read-only field width with editable fields 2026-08-15 11:59:45 +01:00
Karim Jerbi d5b5f0781f feat(style): dim style read-only fields use the shared read-only field 2026-08-15 11:59:45 +01:00
Karim Jerbi 71d4eb96e5 feat(style): mleader style read-only rows use the shared read-only field 2026-08-15 11:59:45 +01:00
Karim Jerbi 61c3497d7f feat(text-style): read-only rows use the shared selectable read-only field 2026-08-15 11:59:45 +01:00
Karim Jerbi f2c4d389db feat(props): read-only rows use the shared selectable read-only field 2026-08-15 11:59:44 +01:00
Karim Jerbi f2b375ab24 feat(read-only): add shared selectable read-only field helper 2026-08-15 11:59:44 +01:00
Karim Jerbi fbab4c4934 feat(ribbon): Block Palette button reflects panel open state
The Insert tab's Block Palette button now behaves like the View tab toggles
(UCS Icon, View Cube, Ortho, Properties, File Tabs, Layout Tabs): it stays
highlighted while the block palette panel is open and clears when closed.

The Ribbon gains a show_block_palette flag with a set_block_palette setter,
fed into ToggleState and mapped in is_active_tool for the BLOCKPALETTE tool
id. show_block_palette on the app remains the single source of truth; both
mutation paths now mirror it onto the ribbon:

- BLOCKPALETTE / BLOCKSPALETTE command toggles ribbon state alongside the
  panel (src/app/commands/blocks.rs).
- Closing the panel via the dock rail close button clears the ribbon state,
  matching how Close(Properties) already syncs (src/app/update/dialog.rs).
2026-08-14 23:33:45 +01:00
Karim Jerbi 28e5deb222 fix(dock): don't panic clamping panel width when window is minimized 2026-08-14 23:06:53 +01:00
Karim Jerbi df940ad58e fix(dock): size drag preview by visible panels, not stack size
The drag preview divided the edge height by the raw panel count on the target
side. Closed panels keep their stack slot, so a hidden panel (e.g. the block
palette tucked away while Properties stays open) split the ghost and the drop
index as if it were rendering — leaving a half-height preview on that edge.
Size slots from the panels actually rendered on the edge instead, via a shared
visibility helper used by both the drop-index math and the ghost/edge-tint
geometry.
2026-08-14 22:24:57 +01:00
Karim Jerbi 2a0b7805c0 fix(dock): size drag preview slots by the edge viewport height
The dock drag preview computed slot heights and drop indices from the full
window height, but docked panel columns only span the model viewport, so the
preview overlay and insertion point drifted below the actual dock edge. Use the
current viewport height so the ghost and drop index line up with the panels.
2026-08-14 21:21:40 +01:00
Karim Jerbi 9f68034a60 feat(props): select whole value when a property field is clicked
Clicking an editable property value field now selects its entire value instead
of only placing the caret, so typing immediately replaces it. The click is
observed through a global left-press subscription (PropPointerPressed); a focus
sweep reports which widget took focus and PropSyncActive maps it back to the
active property row, then issues a select-all operation only when focus landed
on a different field.

The active row stays highlighted while its text input holds keyboard focus and
clears when focus moves elsewhere.
2026-08-14 21:21:40 +01:00
Karim Jerbi dceb9626ae fix(dock): block palette reflects new blocks without reopening
Creating a block from selected entities now bumps the geometry epoch, so the
block palette's per-update stale check notices the new definition and refreshes
its list in place instead of requiring the user to close and reopen the panel.
2026-08-14 21:21:40 +01:00
Karim Jerbi bd33600ee7 fix(dock): single-panel drag preview is one full-height slot
drop_index previously mapped a pointer position inside a lone full-height panel
to a top/bottom insertion split, offering an impossible drop. Clamp to the
single slot when the edge holds one panel, and cover the multi-slot mapping with
tests.
2026-08-14 21:21:40 +01:00
Karim Jerbi 94667f9512 feat(dock): active and dragging rail tab styling
Style the dock rail tab by its panel state: the expanded (active) panel's tab
gets a primary-colored highlight and border, and a tab whose panel is being
dragged gets a lighter primary tint, so the user can see which panel owns the
edge at a glance.
2026-08-14 21:21:40 +01:00
Karim Jerbi 7bfb617745 feat(dock): stacked edge rendering and drag-to-reorder preview 2026-08-11 23:13:05 +01:00
Karim Jerbi 10a1d89f19 feat(dock): migrate block palette onto general edge-stack dock
Pin/close/resize/drag-between-edges/auto-collapse hover, themed and
persisted, mirroring the properties panel chrome.
2026-08-11 23:13:05 +01:00
Karim Jerbi dce555f5bc feat(config): persist general edge-stack dock layout 2026-08-11 23:13:05 +01:00
Karim Jerbi 20be91ccaa feat(dock): add edge-stack dock data model and slice geometry 2026-08-11 23:13:05 +01:00
Karim Jerbi d9016e7196 Merge pull request #734 from KarimJerbi/feature/redraw-regen
Add REDRAW / REDRAWALL / REGEN / REGENALL commands- #721
2026-08-10 23:59:14 +01:00
Karim Jerbi f3d8904fc7 fix(alias): only advance the migration marker after the merge persists
Advancing ocad.pgp.version (or the web counterpart) could succeed while the
merged alias table write failed, so a later launch skipped migration against a
stale file and permanently lost the new aliases. Gate the version write on the
alias-table write (native fs::write / web set_item) succeeding.
2026-08-10 23:49:43 +01:00
Karim Jerbi 7013aa8cbb fix(alias): migrate new default aliases into existing profiles
Existing installs only got newly-added default aliases when storage was
absent; load_aliases parsed an already-present ocad.pgp or web alias table
verbatim, so R/RA/RE/REA remained unknown to profiles seeded before version 2.

Introduce a versioned default merge: track the default-table version each
profile has seen (wordless ocad.pgp.version marker / localStorage key) and
fold in only the aliases introduced after that version, never overwriting
user-defined mappings or resurrecting deliberately removed aliases. On first
migration the merged table and the advanced marker are persisted so later
launches skip the already-delivered aliases.
2026-08-10 23:39:46 +01:00
Karim Jerbi de1c1798a0 fix(scene): cfg-gate test-only refresh_pending_any to silence dead-code warning 2026-08-10 23:26:27 +01:00
Karim Jerbi 319710f36b test(commands): alias R routes like REDRAW 2026-08-10 23:26:27 +01:00
Karim Jerbi d2e18ead38 feat(aliases): R/RA/RE/REA for REDRAW/REGEN; register verbs 2026-08-10 23:26:26 +01:00
Karim Jerbi 692bb3babf feat(commands): REDRAW/REDRAWALL/REGEN/REGENALL 2026-08-10 23:26:26 +01:00
Karim Jerbi 7ace7fef9e feat(render): per-viewport REDRAW force_rasterize through builders to prepare gate 2026-08-10 23:26:26 +01:00
Karim Jerbi df103575b9 feat(scene): REDRAW refresh request resolved to per-viewport instance ids 2026-08-10 23:26:26 +01:00
Karim Jerbi 8f009d0058 feat(insert): add docked block palette
Add a searchable block palette with previews, file import, collision-safe nested definitions, and dependency/table-record import. Keep palette thumbnails current across drawing switches and block-definition edits.
2026-08-06 11:02:52 +03:00
Karim Jerbi 5299a483da fix(hatch): flag only the outer ring as external when serializing holes
When writing a hatched region with holes to DXF/DWG, every NaN-separated ring was created with BoundaryPath::external(), so consumers treated inner loops as outer islands instead of holes. Only the first (outer) ring should carry the external / outermost flags; hole rings are left unflagged. A picked big-minus-small hatch now persists with the small rectangle as a real hole.

Adds a regression test asserting only the first boundary path is flagged external.
2026-07-13 05:55:54 +01:00
Karim Jerbi 934570862c fix(hatch): pick innermost boundary and detect nested holes
The HATCH/GRADIENT/BOUNDARY "pick inside" mode returned the first outline containing the click point, so the result depended on draw order and any nested boundary was ignored. For a small rectangle inside a big one, clicking inside the small shape would hatch the whole big rectangle (or the small one, depending on which was drawn first), and clicking in the gap between them always hatched the full big rectangle with no hole.

Resolve the click against every enclosing outline and choose the smallest-area one as the fill. Any other outline fully contained by that ring but not containing the click point becomes a hole. The boundary is emitted as NaN-separated rings, which the GPU and CPU rasteriser already fill via even-odd, so pattern, solid, and gradient hatches all render the correct region (the small shape, or the big-minus-small ring) regardless of draw order. DXF export now writes one boundary path per ring so nested hatches persist with real holes.
2026-07-13 01:32:05 +01:00
Karim Jerbi 8ef65f59f8 fix(ribbon): highlight toggle buttons by their actual state
The UCS Icon, Properties, File Tabs, and Layout Tabs buttons were highlighted unconditionally because their ids fell through to an active_tool equality check in is_active_tool, which activate_tool sets on every click and never clears. Each id is now mapped to its corresponding visibility flag (show_ucs_icon, show_properties, show_file_tabs, show_layout_tabs), and those flags are kept in sync from their toggle handlers so the highlight reflects live state. The per-toggle booleans previously threaded through the render path are consolidated into a single ToggleState struct read from one place, so introducing a further toggle no longer requires changing every render-function signature and call site.
2026-07-12 19:53:48 +03:00
Karim Jerbi 4e0bba9f6a fix(viewport): clear ViewCube when toggled off
show_viewcube was excluded from render_signature, so toggling a static (cached) view produced an identical signature — the frame was skipped and the last GPU cube pixels stayed as stale cache. Add vp.show_viewcube to render_signature so the toggle invalidates the cache and re-renders, clearing the cube. The 2D overlay is iced UI and re-laid-out correctly, which is why only it hid.
2026-07-11 00:40:28 +01:00
Karim Jerbi 585316945e fix(ribbon): highlight View Cube button by toggle state
NAVVCUBE is a command toggle, so it never set active_tool and is_active_tool never matched it. Add a show_viewcube flag to the ribbon (mirroring wireframe/ortho_mode) and special-case "NAVVCUBE" in is_active_tool to return the live show_viewcube state; ToggleViewCube now calls ribbon.set_viewcube.
2026-07-11 00:40:22 +01:00
Karim Jerbi 73b05e1dec perf(core): cache ribbon_groups() with OnceLock, avoid per-frame tree allocations
Change CadModule::ribbon_groups() from -> Vec<RibbonGroup> to -> &[RibbonGroup],
backed by a per-module OnceLock cache. The ribbon group tree is static data
(&'static str, Copy enums) that was being fully reconstructed — Vecs, String
clones, enum discriminants — on every call, including all three per-frame
call sites (view(), dropdown_overlay(), style_combo_overlay()).

- 7 built-in modules + 2 plugin templates cache via function-local OnceLock.
  Safe because each is a unit struct with exactly one instance per process
  (plugins run in isolated child processes; see PluginProcess::spawn()).
- SharedCadModule avoids the static pattern entirely, storing groups in an
  instance field (owned.rs).
- IPC runner converts &RibbonGroup -> OwnedRibbonGroup directly via new
  From<&T> impls, avoiding an intermediate clone.
- render_small/render_large and the two make_tool_row closures now borrow
  (&RibbonItem, &[ToolDef]) instead of taking ownership, so the view loop
  doesn't need to clone items to satisfy the old by-value signatures.
- Various match-ergonomics deref fixes (*id, *default, *icon, etc.) from
  the &RibbonItem pattern change.
2026-07-02 21:00:57 +01:00
Karim Jerbi 3d23c45016 chore: disable printpdf default features (unused html deps)
printpdf's default = ["html"] pulled in azul-layout, rust-fontconfig,
kuchiki, xmlparser and their transitive trees (~40+ crates including
two with cdylib targets) — none of which are used by the project's
PDF export (only basic primitives like Mm, Line, Polygon, Rgb).

Also fixes a MinGW linker error (export ordinal too large: 100035)
triggered when ld.bfd tries to build a DLL for printpdf with >65535
exported symbols.
2026-06-29 01:06:28 +03:00
Karim Jerbi 63c13f0f63 refactor(ribbon): extract repeated inline color literals into named constants
Consolidates 21 occurrences of identical Color literals scattered across widgets.rs and mod.rs into 9 named constants (COMBO_BG, COMBO_HOVER_BG, COMBO_OPEN_BG, COMBO_BORDER, COMBO_ACTIVE_BORDER, COMBO_ARROW, SWATCH_BORDER, TIP_BG, HIST_INACTIVE_BG). -85 net lines, zero behaviour change.
2026-06-28 22:59:14 +01:00
Karim Jerbi 1e8a98d2cd fix(ribbon): center section titles within their content width
Section labels (Draw, Modify, Annotation, …) were left-aligned with a small padding. Set align_x(Center) on the parent column so the label container — still sized to its text width — is centred within the column, which itself remains sized by the tool buttons above. Sections keep their natural content-based width, no wasted space.
2026-06-28 22:23:01 +01:00
Karim Jerbi d2cd728313 Merge branch 'main' of https://github.com/KarimJerbi/OpenCADStudio 2026-06-28 19:18:12 +01:00
Karim Jerbi ae2946a8a9 fix(block-cache): emit one greek substitute per source MTEXT entity
Colour-split MTEXT produces one outline wire per inline \C/\c override. At <5px zoom each outline independently emitted the full OBB greek rectangle — N overlapping boxes with last-colour-wins. Track last_lod_obb and skip duplicate substitutes.

Also corrects the fill-only LOD bypass: suppress fill-only wires at <5px instead of sending them unconditionally through emit_wire. The outline sibling's baseline or greek box suffices at sub-pixel zoom.
2026-06-28 19:17:24 +01:00
Karim Jerbi d99b480eae Merge branch 'HakanSeven12:main' into main 2026-06-28 18:52:03 +01:00
Karim Jerbi ca6239a32d chore: mark fill_tris_low unpaired sites with FIXME
dimension.rs (x2), multileader.rs (x3), table.rs (x1) emit fill_tris
without a corresponding fill_tris_low. These will trip the debug_assert_eq!
added in the preceding commit when the entity appears inside a block
definition. Each site is marked with a FIXME referencing block_cache.rs:1397
and the correct fix pattern (split_ds on each vertex).
2026-06-28 18:46:02 +01:00
Karim Jerbi 7da8fbe316 fix(block-cache): preserve per-wire colour and fill precision in cached blocks
Three related bugs in tessellate_sub_local and expand_defn:

1. Wire folding lost per-segment colour. Multi-wire tessellator output
   was concatenated (NaN-separated) into a single LocalWire using the
   first wire's colour. Inline \C/\c MTEXT colour overrides inside block
   definitions rendered in the entity colour instead of the override colour.
   Fix: tessellate_sub_local returns Vec<LocalWire>; each wire from the
   tessellator becomes its own LocalWire with its own colour.

2. fill_tris_low was never forwarded. The double-single precision path
   was silently degraded on cached wires. Fix: fill_tris_low forwarded
   from WireModel through to LocalWire and BatchEntry. debug_assert_eq!
   added at emit site to catch future invariant violations.

3. Fill-only wires hit the text LOD ladder. MTEXT outline/fill pairs
   produce separate LocalWires; the fill-only wire was routed through
   emit_text_baseline/emit_greeked_text which ignore fill_tris (invisible
   double-draw). Fix: is_fill_only field on LocalWire bypasses the LOD
   ladder. Also fixes StyleKey::fill_only being hardcoded false at both
   emit_wire call sites (latent batch misclassification).
2026-06-28 18:45:57 +01:00
Karim Jerbi 06bb6b54ff fix(text): canonicalise TTF filename font refs in MTEXT inline overrides
resolve_font previously returned &str, passing raw stems like "arial" verbatim to Face::resolve. System font lookup requires the canonical family name ("Arial"), so inline \f/\F overrides from block-nested MTEXT were silently unresolvable and fell through to no geometry.

Change resolve_font to return Cow<'a, str>:
- no override -> Borrowed(base), zero allocation
- LFF built-in -> Borrowed(font), zero allocation
- system font resolved -> Owned(canonical), one allocation
- unresolvable -> Borrowed(base), falls back to style font

All 4 call sites updated to &font_name (&Cow<str> derefs to &str).

Adds two unit tests covering the fallback and TTF-stem-to-family-name paths. Adds integration test verifying block-nested MTEXT produces geometry and correctly separates outline/fill wires.
2026-06-28 18:44:38 +01:00
Karim Jerbi 627761b629 fix(text): render TTF glyphs as solid fills, fix font fallback, correct Z-order
Text elements previously rendered as hollow wireframe outlines and sometimes
fell back to the wrong system font due to case-sensitive exact matching. Also,
the viewport background grid drew on top of solid entities.

Case-insensitive and partial-name fallback matching (e.g., mapping "arialn"
to "Arial Narrow") is now added to `sysfont::canonical_family_name`, ensuring
text resolves to the correct installed system font.

TrueType font contours are now triangulated into solid fills (`fill_tris`)
using `lyon_tessellation` in `ttf_glyph.rs`. To prevent the GPU from misclassifying
this 2D text as a 3D mesh (which broke flat shading), text tessellation splits
the output into separate outline and fill `WireModel`s.

The background grid is extracted from `SelectionCanvas` into `GridCanvas` and
moved beneath the 3D viewport to ensure proper layering. Finally, `DepthBiasState`
is enabled on all face/fill pipelines to apply a polygon offset, perfectly
resolving Z-fighting between coplanar wireframe lines and solid faces.
2026-06-25 18:17:01 +01:00
Karim Jerbi 02f70ab0b3 fix(editor): prevent empty text commit from advancing command edit_count
- Propagates commit status out of inline editors so commands know whether a real change occurred.

- Fixes TEXTEDIT's Undo step rolling back unrelated document actions when the user presses OK on empty/cleared existing MText.
2026-06-18 17:55:30 +01:00
Karim Jerbi 8e48bf02a0 fix(annotate): correctly resolve Leader annotations in TEXTEDIT
- Custom recursive resolver ensures Leader entities trace back to their editable Text/MText annotations.

- Bypasses overly strict baseline field checks so TEXTEDIT can natively target and launch the inline editor on Leaders.
2026-06-18 17:49:29 +01:00
Karim Jerbi 9a258f29ce feat: implement TEXTEDIT command and TEXTEDITMODE system variable
- Adds TEXTEDIT command to seamlessly edit annotation objects in-place.
- Matches industry-standard CAD tools' prompt behavior and "Current settings" banner logic.
- Implements the TEXTEDITMODE system variable (0/1) for Multiple vs Single editing modes.
- Enhances command lifecycle with CmdResult::SuspendForTextEdit and post_editor_closed hooks, allowing commands to cleanly pause and resume after external UI editors close.
2026-06-18 15:56:27 +01:00
Karim Jerbi 7fc8c5985b Make TORIENT view-aware and fix DYN angle commit 2026-06-17 17:05:20 +01:00
Karim Jerbi 1e7bd1e9b2 Add TORIENT command 2026-06-17 16:36:45 +01:00
Karim Jerbi a431e02378 Temporarily suppress Ortho mode during RECTANG commands 2026-06-16 09:53:32 +01:00