2026-05-12 10:36:15 +03:00
|
|
|
mod cmd_result;
|
|
|
|
|
mod commands;
|
2026-03-22 22:44:32 +03:00
|
|
|
mod document;
|
|
|
|
|
mod helpers;
|
|
|
|
|
mod history;
|
|
|
|
|
mod layers;
|
feat: Implement in-place MText editor with formatting toolbar and live preview
- Added SVG icons for text formatting options (bold, italic, underline, overline, strike, uppercase, lowercase).
- Introduced a new `MTextEditorState` struct to manage the state of the MText editor.
- Implemented functionality to open the MText editor from the command line and edit existing MText entities.
- Added message handling for various MText editor actions (formatting, height, style, font, justification, alignment, line spacing).
- Created a dedicated preview area for MText rendering within the editor.
- Updated the command structure to support opening the MText editor and handling text input.
- Refactored existing MText command logic to integrate with the new editor.
2026-05-30 23:24:06 +03:00
|
|
|
mod mtext_editor;
|
2026-05-31 19:27:06 +03:00
|
|
|
mod model_ops;
|
2026-05-12 10:36:15 +03:00
|
|
|
mod properties;
|
2026-05-31 14:44:18 +03:00
|
|
|
mod text_inline;
|
2026-03-22 22:44:32 +03:00
|
|
|
mod update;
|
2026-05-12 10:36:15 +03:00
|
|
|
mod view;
|
2026-03-22 22:44:32 +03:00
|
|
|
|
|
|
|
|
use document::DocumentTab;
|
|
|
|
|
|
|
|
|
|
use crate::modules::ModuleEvent;
|
2026-05-28 18:42:39 +03:00
|
|
|
use crate::scene::{CubeRegion, TileEdgeOrient};
|
|
|
|
|
|
|
|
|
|
/// In-flight drag of a Model-tile inner divider. `last_applied` is the
|
|
|
|
|
/// most recent normalized coordinate handed to `move_model_tile_edge` —
|
|
|
|
|
/// the next `ViewportMove` uses it as the `old_coord` argument so the
|
|
|
|
|
/// drag follows the same edge across frames even after sub-pixel
|
|
|
|
|
/// adjustments from the clamp inside `move_model_tile_edge`.
|
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
|
pub struct TileDrag {
|
|
|
|
|
pub orient: TileEdgeOrient,
|
|
|
|
|
pub last_applied: f32,
|
|
|
|
|
}
|
feat(grip): multi-functional hover popup menu
Phase 2 of the grip overhaul: when the cursor dwells on a selected
entity's grip the overlay opens a popup menu with entity-specific
options (Stretch / Add Vertex / Remove Vertex / Convert to
Arc-Line / …). Picking an item dispatches `apply_grip_menu`; the
default `Stretch` entry is a no-op that lets the user click the
grip to start the normal drag.
- `GripMenuItem` + `GripMenuAction` (full vocabulary covering
stretch, add/remove vertex, convert to arc/line, reverse arrows,
text-position variants, hatch parameters, tangent direction).
- `Grippable` trait gains `grip_menu` + `apply_grip_menu` with
trait-default `Stretch`-only impls so every existing entity gets
the popup for free.
- `EntityTypeOps` dispatches both methods across the same entity
list as the existing grip / property machinery.
- `OpenCADStudio` carries `grip_hover` (dwell tracker) +
`grip_popup` (open menu). `update_grip_hover` runs from
`ViewportMove`: hit-tests grips, refreshes the dwell timer, and
opens the popup after 600 ms. Cursor drift past 80 px or a grip
click dismisses it.
- `view.rs` paints the popup as an absolute-positioned column of
buttons at the grip's screen anchor; each button publishes
`Message::GripMenuPick(idx)`.
- `LwPolyline` overrides `Grippable` manually (skips the standard
macro) so its vertex grip exposes Add/Remove Vertex and its
segment-midpoint grip exposes Add Vertex + Convert to Arc/Line.
`apply_grip_menu` performs the edits: vertex insertion at the
chord midpoint inheriting the previous bulge, vertex removal,
and bulge toggle for the arc/line conversion.
2026-05-29 09:30:54 +03:00
|
|
|
|
|
|
|
|
/// Cursor dwelling over a grip. When `started.elapsed() >=` the popup
|
|
|
|
|
/// threshold the multi-functional menu opens (`grip_popup`).
|
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
|
pub struct GripHover {
|
|
|
|
|
pub handle: acadrust::Handle,
|
|
|
|
|
pub grip_id: usize,
|
|
|
|
|
pub screen: iced::Point,
|
|
|
|
|
pub started: std::time::Instant,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Open multi-functional-grip popup state.
|
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
|
pub struct GripPopup {
|
|
|
|
|
pub handle: acadrust::Handle,
|
|
|
|
|
pub grip_id: usize,
|
|
|
|
|
pub anchor: iced::Point,
|
|
|
|
|
pub items: Vec<crate::scene::object::GripMenuItem>,
|
|
|
|
|
pub selected: usize,
|
|
|
|
|
}
|
2026-05-29 14:58:35 +03:00
|
|
|
|
|
|
|
|
/// Pending follow-up value for grip-menu actions that need a number
|
|
|
|
|
/// (Lengthen / Radius / Arc Length / Rotate Text). The next number
|
|
|
|
|
/// typed in the command line is parsed and routed into
|
|
|
|
|
/// `apply_grip_menu_value` for `(handle, grip_id, action)`.
|
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
|
pub struct GripPendingValue {
|
|
|
|
|
pub handle: acadrust::Handle,
|
|
|
|
|
pub grip_id: usize,
|
|
|
|
|
pub action: crate::scene::object::GripMenuAction,
|
|
|
|
|
pub label: &'static str,
|
|
|
|
|
}
|
2026-05-29 20:30:56 +03:00
|
|
|
|
feat(qselect): per-property filter with operator + value (#28)
Quick Select used to filter only on Object Type and Layer. The classic
QSELECT model — pick a type, pick one of its properties, compare with a
typed value via a logical operator — is much more general: a Line entity
can now be filtered on Start X, Length, Angle, Delta Y, …, and any
entity on the common Layer / Color / Linetype / Lineweight.
Panel layout: Object type → Property → Operator → Value → Append. The
Property dropdown is type-aware: it always lists the four common
properties; picking a specific Object type appends that type's
`geometry_properties` rows, sampled from the first entity of that type
in the active layout (so `Start X`, `Radius`, `Pattern Angle`, … only
appear when their entity type is selected). Changing the Object type
drops the Property when the new type no longer offers it.
Operators: `= Equals`, `!= Not equal`, `> Greater than`, `< Less than`,
`* Any value`. Eq/Neq do case-insensitive string compare against the
canonical property string (so "ByLayer" matches "bylayer"); Gt/Lt parse
both sides as `f64` and reject anything non-numeric; Any skips the
value test entirely. The Value text field is disabled when no Property
is picked or the operator is Any. Append behaves the same as before.
The new value reader is hand-rolled for the four common properties
(layer name, color formatted as `ByLayer`/`ByBlock`/`<aci>`/`r,g,b`,
linetype name, lineweight formatted as `0.30mm`) and falls back through
`geometry_properties()` for type-specific fields — extracting the
property's value out of the `PropValue` variant so EditText / Choice /
ColorChoice all canonicalise the same way the panel will display them.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 21:09:56 +03:00
|
|
|
/// Operator the Quick Select filter applies between an entity's
|
|
|
|
|
/// property value and the user-typed test value.
|
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
|
|
|
pub enum QSelectOp {
|
|
|
|
|
/// `*Any value` — the entity matches as long as the type filter
|
|
|
|
|
/// passes; the value column is ignored.
|
|
|
|
|
Any,
|
|
|
|
|
Eq,
|
|
|
|
|
Neq,
|
|
|
|
|
Gt,
|
|
|
|
|
Lt,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl std::fmt::Display for QSelectOp {
|
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
|
let s = match self {
|
|
|
|
|
QSelectOp::Any => "* Any value",
|
|
|
|
|
QSelectOp::Eq => "= Equals",
|
|
|
|
|
QSelectOp::Neq => "!= Not equal",
|
|
|
|
|
QSelectOp::Gt => "> Greater than",
|
|
|
|
|
QSelectOp::Lt => "< Less than",
|
|
|
|
|
};
|
|
|
|
|
f.write_str(s)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// One row in the Quick Select Properties pick_list. `field` is the
|
|
|
|
|
/// stable identifier (`"layer"`, `"start_x"`, …) used to look up the
|
|
|
|
|
/// value on each candidate entity; `label` is the human label rendered
|
|
|
|
|
/// in the dropdown. Equality only compares `field` so the pick_list
|
|
|
|
|
/// round-trips selection correctly even when labels are duplicated.
|
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
|
pub struct QSelectPropertyChoice {
|
|
|
|
|
pub field: String,
|
|
|
|
|
pub label: String,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl PartialEq for QSelectPropertyChoice {
|
|
|
|
|
fn eq(&self, other: &Self) -> bool {
|
|
|
|
|
self.field == other.field
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Eq for QSelectPropertyChoice {}
|
|
|
|
|
|
|
|
|
|
impl std::fmt::Display for QSelectPropertyChoice {
|
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
|
f.write_str(&self.label)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Open Quick Select panel state. The filter is one
|
|
|
|
|
/// `(type, property, op, value)` row plus an Append-to-current-selection
|
|
|
|
|
/// toggle, mirroring the classic QSELECT dialog: the panel filters
|
|
|
|
|
/// candidate entities (entire layout) by type, then by the chosen
|
|
|
|
|
/// property compared to the typed value using the operator.
|
2026-05-29 20:30:56 +03:00
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
|
pub struct QSelectState {
|
feat(qselect): per-property filter with operator + value (#28)
Quick Select used to filter only on Object Type and Layer. The classic
QSELECT model — pick a type, pick one of its properties, compare with a
typed value via a logical operator — is much more general: a Line entity
can now be filtered on Start X, Length, Angle, Delta Y, …, and any
entity on the common Layer / Color / Linetype / Lineweight.
Panel layout: Object type → Property → Operator → Value → Append. The
Property dropdown is type-aware: it always lists the four common
properties; picking a specific Object type appends that type's
`geometry_properties` rows, sampled from the first entity of that type
in the active layout (so `Start X`, `Radius`, `Pattern Angle`, … only
appear when their entity type is selected). Changing the Object type
drops the Property when the new type no longer offers it.
Operators: `= Equals`, `!= Not equal`, `> Greater than`, `< Less than`,
`* Any value`. Eq/Neq do case-insensitive string compare against the
canonical property string (so "ByLayer" matches "bylayer"); Gt/Lt parse
both sides as `f64` and reject anything non-numeric; Any skips the
value test entirely. The Value text field is disabled when no Property
is picked or the operator is Any. Append behaves the same as before.
The new value reader is hand-rolled for the four common properties
(layer name, color formatted as `ByLayer`/`ByBlock`/`<aci>`/`r,g,b`,
linetype name, lineweight formatted as `0.30mm`) and falls back through
`geometry_properties()` for type-specific fields — extracting the
property's value out of the `PropValue` variant so EditText / Choice /
ColorChoice all canonicalise the same way the panel will display them.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 21:09:56 +03:00
|
|
|
/// `None` = "(Any type)".
|
2026-05-29 20:30:56 +03:00
|
|
|
pub type_filter: Option<String>,
|
feat(qselect): per-property filter with operator + value (#28)
Quick Select used to filter only on Object Type and Layer. The classic
QSELECT model — pick a type, pick one of its properties, compare with a
typed value via a logical operator — is much more general: a Line entity
can now be filtered on Start X, Length, Angle, Delta Y, …, and any
entity on the common Layer / Color / Linetype / Lineweight.
Panel layout: Object type → Property → Operator → Value → Append. The
Property dropdown is type-aware: it always lists the four common
properties; picking a specific Object type appends that type's
`geometry_properties` rows, sampled from the first entity of that type
in the active layout (so `Start X`, `Radius`, `Pattern Angle`, … only
appear when their entity type is selected). Changing the Object type
drops the Property when the new type no longer offers it.
Operators: `= Equals`, `!= Not equal`, `> Greater than`, `< Less than`,
`* Any value`. Eq/Neq do case-insensitive string compare against the
canonical property string (so "ByLayer" matches "bylayer"); Gt/Lt parse
both sides as `f64` and reject anything non-numeric; Any skips the
value test entirely. The Value text field is disabled when no Property
is picked or the operator is Any. Append behaves the same as before.
The new value reader is hand-rolled for the four common properties
(layer name, color formatted as `ByLayer`/`ByBlock`/`<aci>`/`r,g,b`,
linetype name, lineweight formatted as `0.30mm`) and falls back through
`geometry_properties()` for type-specific fields — extracting the
property's value out of the `PropValue` variant so EditText / Choice /
ColorChoice all canonicalise the same way the panel will display them.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 21:09:56 +03:00
|
|
|
/// `None` = no property filter; the type filter alone applies.
|
|
|
|
|
pub property: Option<QSelectPropertyChoice>,
|
|
|
|
|
pub operator: QSelectOp,
|
|
|
|
|
pub value: String,
|
2026-05-29 20:30:56 +03:00
|
|
|
pub append: bool,
|
|
|
|
|
}
|
2026-03-22 22:44:32 +03:00
|
|
|
use crate::snap::Snapper;
|
|
|
|
|
use crate::ui::{AppMenu, CommandLine, Ribbon, StatusBar};
|
2026-05-12 10:36:15 +03:00
|
|
|
use acadrust::types::{Color as AcadColor, LineWeight};
|
|
|
|
|
use acadrust::CadDocument;
|
2026-03-22 22:44:32 +03:00
|
|
|
|
|
|
|
|
use iced::time::Instant;
|
|
|
|
|
use iced::window;
|
|
|
|
|
use iced::{mouse, Point, Task, Theme};
|
2026-05-13 09:59:07 +03:00
|
|
|
use std::sync::atomic::AtomicU8;
|
|
|
|
|
use std::sync::Arc;
|
2026-03-22 22:44:32 +03:00
|
|
|
|
|
|
|
|
pub(super) const POLY_START_DELAY_MS: u128 = 150;
|
|
|
|
|
pub(super) const VARIES_LABEL: &str = "*VARIES*";
|
|
|
|
|
|
2026-05-13 09:59:07 +03:00
|
|
|
// ── File-open progress ─────────────────────────────────────────────────────
|
|
|
|
|
// Phase encoding for OpenProgress.phase atomic. Updated from the background
|
|
|
|
|
// loader thread, read by the UI overlay on every frame.
|
|
|
|
|
pub const OPEN_PHASE_READING: u8 = 0;
|
|
|
|
|
pub const OPEN_PHASE_PARSING: u8 = 1;
|
|
|
|
|
pub const OPEN_PHASE_CACHING: u8 = 2;
|
|
|
|
|
pub const OPEN_PHASE_FINALIZING: u8 = 3;
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
2026-05-14 11:12:08 +03:00
|
|
|
pub struct OpenProgress {
|
2026-05-13 09:59:07 +03:00
|
|
|
pub name: String,
|
|
|
|
|
pub size_bytes: u64,
|
|
|
|
|
pub phase: Arc<AtomicU8>,
|
|
|
|
|
pub started: Instant,
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-22 22:44:32 +03:00
|
|
|
// ── Application state ──────────────────────────────────────────────────────
|
|
|
|
|
|
2026-05-23 17:16:15 +03:00
|
|
|
pub(super) struct OpenCADStudio {
|
2026-03-22 22:44:32 +03:00
|
|
|
start: Instant,
|
|
|
|
|
tabs: Vec<DocumentTab>,
|
|
|
|
|
active_tab: usize,
|
|
|
|
|
tab_counter: usize,
|
|
|
|
|
ribbon: Ribbon,
|
|
|
|
|
app_menu: AppMenu,
|
|
|
|
|
command_line: CommandLine,
|
|
|
|
|
status_bar: StatusBar,
|
|
|
|
|
cursor_pos: Point,
|
|
|
|
|
vp_size: (f32, f32),
|
|
|
|
|
snapper: Snapper,
|
|
|
|
|
snap_popup_open: bool,
|
2026-05-06 17:50:46 +03:00
|
|
|
scale_popup_open: bool,
|
2026-03-22 22:44:32 +03:00
|
|
|
/// Whether Tangent snap was enabled before a tangent-pick command started.
|
|
|
|
|
pre_cmd_tangent: Option<bool>,
|
|
|
|
|
/// Orthogonal drawing constraint (F8): constrains picks to 0°/90°/180°/270°.
|
|
|
|
|
ortho_mode: bool,
|
2026-04-07 19:30:25 +03:00
|
|
|
/// Polar tracking (F10): constrains picks to configurable angle increments.
|
2026-03-22 22:44:32 +03:00
|
|
|
polar_mode: bool,
|
2026-04-07 19:30:25 +03:00
|
|
|
/// Polar tracking angle increment in degrees (15 / 30 / 45 / 90).
|
|
|
|
|
polar_increment_deg: f32,
|
2026-03-22 22:44:32 +03:00
|
|
|
/// Show grid lines in the viewport (F7).
|
|
|
|
|
show_grid: bool,
|
2026-04-08 00:12:09 +03:00
|
|
|
/// Dynamic input overlay (F12): show coordinate tooltip near cursor.
|
|
|
|
|
dyn_input: bool,
|
2026-05-27 22:52:55 +03:00
|
|
|
/// `true` after a bare `VPORTS` in model space — the next command-line
|
|
|
|
|
/// entry is treated as the tiled-config option (SIngle/2H/2V/4).
|
|
|
|
|
awaiting_vports: bool,
|
2026-05-28 18:42:39 +03:00
|
|
|
/// Active drag of a Model-tile divider. Set on press over an inner
|
|
|
|
|
/// edge, updated on move, cleared on release (which also runs the
|
|
|
|
|
/// collapse pass).
|
|
|
|
|
tile_drag: Option<TileDrag>,
|
2026-05-29 00:57:37 +03:00
|
|
|
/// `true` once the user has reshaped the dynamic-input field set via
|
|
|
|
|
/// the `,` separator during the current command iteration. Tells
|
|
|
|
|
/// `sync_dyn_fields` to preserve the user's chosen shape instead of
|
|
|
|
|
/// reverting to the command-default when `has_base` flips. Cleared
|
|
|
|
|
/// on point commit / command start. See #35.
|
|
|
|
|
dyn_user_reshaped: bool,
|
feat(grip): multi-functional hover popup menu
Phase 2 of the grip overhaul: when the cursor dwells on a selected
entity's grip the overlay opens a popup menu with entity-specific
options (Stretch / Add Vertex / Remove Vertex / Convert to
Arc-Line / …). Picking an item dispatches `apply_grip_menu`; the
default `Stretch` entry is a no-op that lets the user click the
grip to start the normal drag.
- `GripMenuItem` + `GripMenuAction` (full vocabulary covering
stretch, add/remove vertex, convert to arc/line, reverse arrows,
text-position variants, hatch parameters, tangent direction).
- `Grippable` trait gains `grip_menu` + `apply_grip_menu` with
trait-default `Stretch`-only impls so every existing entity gets
the popup for free.
- `EntityTypeOps` dispatches both methods across the same entity
list as the existing grip / property machinery.
- `OpenCADStudio` carries `grip_hover` (dwell tracker) +
`grip_popup` (open menu). `update_grip_hover` runs from
`ViewportMove`: hit-tests grips, refreshes the dwell timer, and
opens the popup after 600 ms. Cursor drift past 80 px or a grip
click dismisses it.
- `view.rs` paints the popup as an absolute-positioned column of
buttons at the grip's screen anchor; each button publishes
`Message::GripMenuPick(idx)`.
- `LwPolyline` overrides `Grippable` manually (skips the standard
macro) so its vertex grip exposes Add/Remove Vertex and its
segment-midpoint grip exposes Add Vertex + Convert to Arc/Line.
`apply_grip_menu` performs the edits: vertex insertion at the
chord midpoint inheriting the previous bulge, vertex removal,
and bulge toggle for the arc/line conversion.
2026-05-29 09:30:54 +03:00
|
|
|
/// Grip the cursor is currently dwelling on. Set when the cursor
|
|
|
|
|
/// stops within `GRIP_THRESHOLD_PX` of a grip; cleared when it
|
|
|
|
|
/// drifts away. The instant lets `ViewportMove` detect when the
|
|
|
|
|
/// dwell crosses the popup-open threshold.
|
|
|
|
|
grip_hover: Option<GripHover>,
|
|
|
|
|
/// Open multi-functional grip popup. Persists across mouse moves
|
|
|
|
|
/// until dismissed (click outside, ESC, cursor leaves the grip).
|
|
|
|
|
grip_popup: Option<GripPopup>,
|
2026-05-29 14:58:35 +03:00
|
|
|
grip_pending: Option<GripPendingValue>,
|
2026-06-03 16:17:29 +03:00
|
|
|
/// A leader line just added via the "Add Leader" grip menu whose arrow is
|
|
|
|
|
/// being placed (follows the cursor). `(entity handle, new-arrow grip id)`.
|
|
|
|
|
/// Esc before the placement click removes it again.
|
|
|
|
|
grip_add_provisional: Option<(acadrust::Handle, usize)>,
|
2026-05-29 20:30:56 +03:00
|
|
|
/// Open Quick Select panel state. `None` = panel closed. Filters are
|
|
|
|
|
/// applied via `Message::QSelectApply`; the panel is dismissed on
|
|
|
|
|
/// Apply / Cancel / Esc / outside-click.
|
|
|
|
|
qselect: Option<QSelectState>,
|
2026-04-03 19:25:55 +03:00
|
|
|
/// Show the UCS icon in the bottom-left corner of model space (UCSICON).
|
|
|
|
|
show_ucs_icon: bool,
|
2026-04-24 01:23:06 +03:00
|
|
|
/// Whether the ViewCube 3D gizmo is visible in model space (NAVVCUBE).
|
|
|
|
|
show_viewcube: bool,
|
|
|
|
|
/// Whether the Properties panel is shown on the left (PROPERTIES).
|
|
|
|
|
show_properties: bool,
|
|
|
|
|
/// Whether the document file tabs are shown at the top (FILETAB).
|
|
|
|
|
show_file_tabs: bool,
|
|
|
|
|
/// Whether the layout/paper-space tabs are shown at the bottom (LAYOUTTAB).
|
|
|
|
|
show_layout_tabs: bool,
|
2026-03-22 22:44:32 +03:00
|
|
|
/// Last point committed by a drawing command — used as ortho/polar base.
|
|
|
|
|
last_point: Option<glam::Vec3>,
|
|
|
|
|
/// OS window Id for the floating Layer Properties Manager (None when closed).
|
|
|
|
|
layer_window: Option<window::Id>,
|
|
|
|
|
/// OS window Id of the primary application window.
|
|
|
|
|
main_window: Option<window::Id>,
|
2026-04-08 22:36:28 +03:00
|
|
|
// ── Floating panel windows ────────────────────────────────────────────
|
2026-05-12 10:36:15 +03:00
|
|
|
page_setup_window: Option<window::Id>,
|
|
|
|
|
textstyle_window: Option<window::Id>,
|
|
|
|
|
tablestyle_window: Option<window::Id>,
|
|
|
|
|
mlstyle_window: Option<window::Id>,
|
|
|
|
|
layout_manager_window: Option<window::Id>,
|
|
|
|
|
plotstyle_window: Option<window::Id>,
|
|
|
|
|
dimstyle_window: Option<window::Id>,
|
|
|
|
|
shortcuts_window: Option<window::Id>,
|
|
|
|
|
about_window: Option<window::Id>,
|
2026-05-16 19:55:57 +03:00
|
|
|
/// New-release notification window — opened on startup when the
|
|
|
|
|
/// GitHub releases API reports a newer version than this build.
|
|
|
|
|
update_notice_window: Option<window::Id>,
|
|
|
|
|
/// Tag of the latest available release (without the leading "v"),
|
|
|
|
|
/// e.g. `"0.3.0"`. `None` when up-to-date or check hasn't returned.
|
|
|
|
|
update_notice_version: Option<String>,
|
2026-05-22 12:55:37 +03:00
|
|
|
/// Release-notes body for the version above (GitHub release "body"
|
|
|
|
|
/// markdown, as returned by the API). May be empty when the release
|
|
|
|
|
/// shipped without notes.
|
|
|
|
|
update_notice_body: Option<String>,
|
2026-03-22 22:44:32 +03:00
|
|
|
/// In-memory clipboard: cloned entities waiting to be pasted.
|
|
|
|
|
clipboard: Vec<acadrust::EntityType>,
|
|
|
|
|
/// Centroid of the clipboard entities (XZ plane, Y-up).
|
|
|
|
|
clipboard_centroid: glam::Vec3,
|
2026-05-30 21:54:35 +03:00
|
|
|
/// True while the Shift key is held — drives subtractive pick (Shift+click
|
|
|
|
|
/// removes the picked entity from the selection). Tracked from keyboard
|
|
|
|
|
/// modifier-change events since mouse click messages carry no modifiers.
|
|
|
|
|
shift_down: bool,
|
feat: Implement in-place MText editor with formatting toolbar and live preview
- Added SVG icons for text formatting options (bold, italic, underline, overline, strike, uppercase, lowercase).
- Introduced a new `MTextEditorState` struct to manage the state of the MText editor.
- Implemented functionality to open the MText editor from the command line and edit existing MText entities.
- Added message handling for various MText editor actions (formatting, height, style, font, justification, alignment, line spacing).
- Created a dedicated preview area for MText rendering within the editor.
- Updated the command structure to support opening the MText editor and handling text input.
- Refactored existing MText command logic to integrate with the new editor.
2026-05-30 23:24:06 +03:00
|
|
|
/// Open in-place MText editor (toolbar + text area + live preview), if any.
|
|
|
|
|
mtext_editor: Option<mtext_editor::MTextEditorState>,
|
2026-05-31 14:44:18 +03:00
|
|
|
/// Open in-place single-line TEXT editor (plain text-entry box), if any.
|
|
|
|
|
text_inline: Option<text_inline::TextInlineState>,
|
2026-03-31 07:13:36 +03:00
|
|
|
/// Which layout tab has its context menu open (None = closed).
|
|
|
|
|
layout_context_menu: Option<String>,
|
|
|
|
|
/// Inline rename state: (original_name, current_edit_value).
|
|
|
|
|
layout_rename_state: Option<(String, String)>,
|
2026-03-31 07:24:16 +03:00
|
|
|
/// Timestamp of the previous viewport left-click release (for double-click detection).
|
|
|
|
|
last_vp_click_time: Option<Instant>,
|
|
|
|
|
/// Screen position of the previous viewport left-click release.
|
|
|
|
|
last_vp_click_pos: Option<Point>,
|
2026-04-08 22:36:28 +03:00
|
|
|
// page_setup_open: moved to page_setup_window: Option<window::Id>
|
2026-03-31 08:05:17 +03:00
|
|
|
/// Editable paper width buffer for the Page Setup panel (string while typing).
|
|
|
|
|
page_setup_w: String,
|
|
|
|
|
/// Editable paper height buffer for the Page Setup panel (string while typing).
|
|
|
|
|
page_setup_h: String,
|
2026-04-01 00:12:07 +03:00
|
|
|
/// Plot area type: "Layout" | "Extents".
|
|
|
|
|
page_setup_plot_area: String,
|
|
|
|
|
/// Center the drawing on the page when exporting.
|
|
|
|
|
page_setup_center: bool,
|
|
|
|
|
/// Plot offset X in mm (applied after optional centering).
|
|
|
|
|
page_setup_offset_x: String,
|
|
|
|
|
/// Plot offset Y in mm.
|
|
|
|
|
page_setup_offset_y: String,
|
|
|
|
|
/// Plot rotation in degrees: "0" | "90" | "180" | "270".
|
|
|
|
|
page_setup_rotation: String,
|
Feat: ROADMAP Phase 1-5 batch — trim/extend, plot scale, viewport UX, mline caps, table alignment
- TRIM/EXTEND: Ray and XLine entities supported as cutting boundaries and trim targets;
trim results in Ray→Line/Ray, XLine→Ray/Ray or Line; hover preview added
- Plot scale override: Fit/1:1/1:2/1:5/1:10/1:20/1:50/1:100/2:1 buttons added to Page Setup
panel; PageSetupCommit applies scale to PlotSettings
- Viewport UX: locked viewport gets orange border; selected viewport gets white border
- Named view → viewport assignment: Choice dropdown in properties panel copies
view_target/direction/height to the viewport entity
- MLine caps: perpendicular start/end cap lines added for open (non-closed) MLines
- Wipeout polygon grips: boundary vertices become editable grips when clipping_enabled+Polygonal;
world→pixel back-projection on drag
- Table cell alignment: CellStyle.alignment (1-9) drives left/center/right + top/middle/bottom
text positioning; cxf::measure_text() helper added
- New commands/features: DIMCONTINUE (DCO), DIMBASELINE (DBA), TOLERANCE (TOL), TABLE,
ATTDEF, ATTEDIT, ATTDISP, BREAKATPOINT (BAP), PLOTWINDOW (PW), MLINE (ML);
DIMSTYLE/DIMCURRENT/CLAYER/LTSCALE/CELTSCALE/SCALETEXT/XDATA/UCSICON/REGEN/DRAWORDER extended
- PLINE arc mode: A/L/CLOSE text input, bulge computation, arc preview
- Warning cleanup: removed unused imports and unreachable patterns
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 15:31:05 +03:00
|
|
|
/// Plot scale: "Fit" | "1:1" | "1:2" | "1:4" | "1:5" | "1:10" | "1:20" | "1:50" | "1:100" | "2:1".
|
|
|
|
|
page_setup_scale: String,
|
2026-04-06 14:25:13 +03:00
|
|
|
|
2026-04-06 14:37:23 +03:00
|
|
|
// ── Plot Style Table ──────────────────────────────────────────────────
|
|
|
|
|
/// Currently loaded CTB/STB table (None = no override).
|
|
|
|
|
active_plot_style: Option<crate::io::plot_style::PlotStyleTable>,
|
|
|
|
|
|
2026-04-06 14:54:05 +03:00
|
|
|
// ── MLineStyle Dialog ─────────────────────────────────────────────────
|
|
|
|
|
mlstyle_selected: String,
|
|
|
|
|
|
2026-04-06 14:57:11 +03:00
|
|
|
// ── TableStyle Dialog ─────────────────────────────────────────────────
|
|
|
|
|
tablestyle_selected: String,
|
|
|
|
|
|
2026-04-06 15:01:25 +03:00
|
|
|
// ── TextStyle Font Browser ────────────────────────────────────────────
|
|
|
|
|
textstyle_selected: String,
|
|
|
|
|
/// Edit buffer for font file name.
|
|
|
|
|
textstyle_font: String,
|
|
|
|
|
/// Edit buffer for width factor.
|
|
|
|
|
textstyle_width: String,
|
|
|
|
|
/// Edit buffer for oblique angle (degrees).
|
|
|
|
|
textstyle_oblique: String,
|
2026-06-03 17:43:12 +03:00
|
|
|
/// Edit buffer for fixed text height (0 = variable).
|
|
|
|
|
textstyle_height: String,
|
|
|
|
|
/// Edit buffer for big-font file name.
|
|
|
|
|
textstyle_bigfont: String,
|
|
|
|
|
/// Edit buffer for TrueType font name.
|
|
|
|
|
textstyle_ttf: String,
|
2026-04-06 15:01:25 +03:00
|
|
|
|
2026-04-08 18:31:09 +03:00
|
|
|
// ── Color Scheme ──────────────────────────────────────────────────────
|
|
|
|
|
active_theme: Theme,
|
|
|
|
|
|
2026-04-08 18:34:10 +03:00
|
|
|
// ── Keyboard Shortcut Editor ──────────────────────────────────────────
|
|
|
|
|
/// User-defined function-key overrides: "F3" → command string.
|
|
|
|
|
shortcut_overrides: std::collections::HashMap<String, String>,
|
|
|
|
|
|
2026-04-08 18:28:30 +03:00
|
|
|
// ── Layout Manager Panel ──────────────────────────────────────────────
|
|
|
|
|
layout_manager_selected: String,
|
|
|
|
|
layout_manager_rename_buf: String,
|
|
|
|
|
|
2026-04-08 18:19:48 +03:00
|
|
|
// ── Plot Style Panel ──────────────────────────────────────────────────
|
|
|
|
|
/// Selected ACI index in the panel (1-255).
|
|
|
|
|
plotstyle_panel_aci: u8,
|
|
|
|
|
/// Edit buffers for the selected entry.
|
|
|
|
|
ps_color_buf: String,
|
|
|
|
|
ps_lineweight_buf: String,
|
|
|
|
|
ps_screening_buf: String,
|
|
|
|
|
|
2026-05-13 09:59:07 +03:00
|
|
|
// ── File-open progress ────────────────────────────────────────────────
|
|
|
|
|
/// `Some` while a CAD file is loading — drives the modal overlay.
|
|
|
|
|
/// Cleared when the load finishes, errors, or the user cancels.
|
|
|
|
|
pub(super) opening: Option<OpenProgress>,
|
|
|
|
|
|
2026-05-01 11:31:11 +03:00
|
|
|
// ── Unsaved-changes dialog ────────────────────────────────────────────
|
|
|
|
|
/// Set when the user tries to close a tab or quit while there are unsaved changes.
|
|
|
|
|
pending_close: Option<PendingClose>,
|
|
|
|
|
/// OS window for the unsaved-changes confirmation dialog.
|
|
|
|
|
unsaved_dialog_window: Option<window::Id>,
|
|
|
|
|
|
2026-05-01 16:12:32 +03:00
|
|
|
// ── Custom Save-As dialog ─────────────────────────────────────────────
|
2026-05-01 16:20:38 +03:00
|
|
|
/// OS window for the custom Save As dialog.
|
2026-05-01 16:12:32 +03:00
|
|
|
save_dialog_window: Option<window::Id>,
|
|
|
|
|
/// Currently selected format string, e.g. "DWG 2013".
|
|
|
|
|
save_dialog_format: String,
|
|
|
|
|
/// Editable filename (without path), e.g. "drawing.dwg".
|
|
|
|
|
save_dialog_filename: String,
|
2026-05-01 16:20:38 +03:00
|
|
|
/// Currently browsed folder (PathBuf for reliable fs ops).
|
|
|
|
|
save_dialog_folder: std::path::PathBuf,
|
|
|
|
|
/// Cached directory listing: (display_name, is_dir, full_path).
|
|
|
|
|
save_dialog_entries: Vec<(String, bool, std::path::PathBuf)>,
|
2026-05-01 16:12:32 +03:00
|
|
|
/// True when triggered from the unsaved-changes flow.
|
|
|
|
|
save_dialog_for_unsaved: bool,
|
|
|
|
|
|
2026-04-06 14:25:13 +03:00
|
|
|
// ── DimStyle Dialog ───────────────────────────────────────────────────
|
|
|
|
|
/// Name of the style currently shown in the dialog.
|
|
|
|
|
dimstyle_selected: String,
|
|
|
|
|
/// Active tab: 0=Lines, 1=Arrows, 2=Text, 3=Scale/Units, 4=Tolerances.
|
|
|
|
|
dimstyle_tab: u8,
|
|
|
|
|
// Edit buffers (strings while typing):
|
2026-05-12 10:36:15 +03:00
|
|
|
ds_dimdle: String,
|
|
|
|
|
ds_dimdli: String,
|
|
|
|
|
ds_dimgap: String,
|
|
|
|
|
ds_dimexe: String,
|
|
|
|
|
ds_dimexo: String,
|
|
|
|
|
ds_dimsd1: bool,
|
|
|
|
|
ds_dimsd2: bool,
|
|
|
|
|
ds_dimse1: bool,
|
|
|
|
|
ds_dimse2: bool,
|
|
|
|
|
ds_dimasz: String,
|
|
|
|
|
ds_dimcen: String,
|
|
|
|
|
ds_dimtsz: String,
|
|
|
|
|
ds_dimtxt: String,
|
|
|
|
|
ds_dimtxsty: String,
|
|
|
|
|
ds_dimtad: String,
|
|
|
|
|
ds_dimtih: bool,
|
|
|
|
|
ds_dimtoh: bool,
|
|
|
|
|
ds_dimscale: String,
|
|
|
|
|
ds_dimlfac: String,
|
|
|
|
|
ds_dimlunit: String,
|
|
|
|
|
ds_dimdec: String,
|
|
|
|
|
ds_dimpost: String,
|
|
|
|
|
ds_dimtol: bool,
|
|
|
|
|
ds_dimlim: bool,
|
|
|
|
|
ds_dimtp: String,
|
|
|
|
|
ds_dimtm: String,
|
|
|
|
|
ds_dimtdec: String,
|
|
|
|
|
ds_dimtfac: String,
|
2026-06-03 17:49:38 +03:00
|
|
|
ds_annotative: bool,
|
feat(dimstyle): expand Dimension Style editor to the full field set
Adds the remaining ~40 DIMSTYLE variables across the tabs (and a new
Alternate Units tab): dim/ext line colors+lineweights, fixed-length ext
lines, separate arrow blocks, arc symbol, jog angle, text color/justify/
vertical pos/fill/direction, fit options (DIMATFIT/DIMTIX/DIMSOXD/DIMTMOVE/
DIMUPT/DIMTOFL/DIMFIT), decimal separator, rounding, zero suppression,
fraction + angular units, alternate units, and tolerance vert-just/zero
suppression. Only the arrowhead-block / extension-line-linetype Handle
references are left (they need a block/linetype picker).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 20:09:08 +03:00
|
|
|
// Lines (colors / lineweights / fixed-length extension)
|
|
|
|
|
ds_dimclrd: String,
|
|
|
|
|
ds_dimlwd: String,
|
|
|
|
|
ds_dimclre: String,
|
|
|
|
|
ds_dimlwe: String,
|
|
|
|
|
ds_dimfxl: String,
|
|
|
|
|
ds_dimfxlon: bool,
|
|
|
|
|
// Symbols & Arrows
|
|
|
|
|
ds_dimsah: bool,
|
|
|
|
|
ds_dimarcsym: String,
|
|
|
|
|
ds_dimjogang: String,
|
|
|
|
|
// Text
|
|
|
|
|
ds_dimclrt: String,
|
|
|
|
|
ds_dimjust: String,
|
|
|
|
|
ds_dimtvp: String,
|
|
|
|
|
ds_dimtfill: String,
|
|
|
|
|
ds_dimtfillclr: String,
|
|
|
|
|
ds_dimtxtdirection: bool,
|
|
|
|
|
// Fit
|
|
|
|
|
ds_dimatfit: String,
|
|
|
|
|
ds_dimtix: bool,
|
|
|
|
|
ds_dimsoxd: bool,
|
|
|
|
|
ds_dimtmove: String,
|
|
|
|
|
ds_dimupt: bool,
|
|
|
|
|
ds_dimtofl: bool,
|
|
|
|
|
ds_dimfit: String,
|
|
|
|
|
// Primary units
|
|
|
|
|
ds_dimdsep: String,
|
|
|
|
|
ds_dimrnd: String,
|
|
|
|
|
ds_dimzin: String,
|
|
|
|
|
ds_dimfrac: String,
|
|
|
|
|
ds_dimaunit: String,
|
|
|
|
|
ds_dimadec: String,
|
|
|
|
|
ds_dimazin: String,
|
|
|
|
|
// Alternate units
|
|
|
|
|
ds_dimalt: bool,
|
|
|
|
|
ds_dimaltf: String,
|
|
|
|
|
ds_dimaltd: String,
|
|
|
|
|
ds_dimaltu: String,
|
|
|
|
|
ds_dimalttd: String,
|
|
|
|
|
ds_dimaltrnd: String,
|
|
|
|
|
ds_dimapost: String,
|
|
|
|
|
ds_dimaltz: String,
|
|
|
|
|
ds_dimalttz: String,
|
|
|
|
|
// Tolerances (extra)
|
|
|
|
|
ds_dimtolj: String,
|
|
|
|
|
ds_dimtzin: String,
|
2026-04-06 14:25:13 +03:00
|
|
|
}
|
|
|
|
|
|
2026-05-01 11:31:11 +03:00
|
|
|
/// What triggered the "unsaved changes" dialog.
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
pub(super) enum PendingClose {
|
|
|
|
|
/// User tried to close the tab at this index.
|
|
|
|
|
Tab(usize),
|
|
|
|
|
/// User tried to quit the application.
|
|
|
|
|
Quit,
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-06 14:25:13 +03:00
|
|
|
/// Identifies a DimStyle field that can be edited in the dialog.
|
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
|
|
|
pub enum DsField {
|
2026-05-12 10:36:15 +03:00
|
|
|
Dimdle,
|
|
|
|
|
Dimdli,
|
|
|
|
|
Dimgap,
|
|
|
|
|
Dimexe,
|
|
|
|
|
Dimexo,
|
|
|
|
|
Dimsd1,
|
|
|
|
|
Dimsd2,
|
|
|
|
|
Dimse1,
|
|
|
|
|
Dimse2,
|
|
|
|
|
Dimasz,
|
|
|
|
|
Dimcen,
|
|
|
|
|
Dimtsz,
|
|
|
|
|
Dimtxt,
|
|
|
|
|
Dimtxsty,
|
|
|
|
|
Dimtad,
|
|
|
|
|
Dimtih,
|
|
|
|
|
Dimtoh,
|
|
|
|
|
Dimscale,
|
|
|
|
|
Dimlfac,
|
|
|
|
|
Dimlunit,
|
|
|
|
|
Dimdec,
|
|
|
|
|
Dimpost,
|
|
|
|
|
Dimtol,
|
|
|
|
|
Dimlim,
|
|
|
|
|
Dimtp,
|
|
|
|
|
Dimtm,
|
|
|
|
|
Dimtdec,
|
|
|
|
|
Dimtfac,
|
2026-06-03 17:49:38 +03:00
|
|
|
Annotative,
|
feat(dimstyle): expand Dimension Style editor to the full field set
Adds the remaining ~40 DIMSTYLE variables across the tabs (and a new
Alternate Units tab): dim/ext line colors+lineweights, fixed-length ext
lines, separate arrow blocks, arc symbol, jog angle, text color/justify/
vertical pos/fill/direction, fit options (DIMATFIT/DIMTIX/DIMSOXD/DIMTMOVE/
DIMUPT/DIMTOFL/DIMFIT), decimal separator, rounding, zero suppression,
fraction + angular units, alternate units, and tolerance vert-just/zero
suppression. Only the arrowhead-block / extension-line-linetype Handle
references are left (they need a block/linetype picker).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 20:09:08 +03:00
|
|
|
Dimclrd,
|
|
|
|
|
Dimlwd,
|
|
|
|
|
Dimclre,
|
|
|
|
|
Dimlwe,
|
|
|
|
|
Dimfxl,
|
|
|
|
|
Dimfxlon,
|
|
|
|
|
Dimsah,
|
|
|
|
|
Dimarcsym,
|
|
|
|
|
Dimjogang,
|
|
|
|
|
Dimclrt,
|
|
|
|
|
Dimjust,
|
|
|
|
|
Dimtvp,
|
|
|
|
|
Dimtfill,
|
|
|
|
|
Dimtfillclr,
|
|
|
|
|
Dimtxtdirection,
|
|
|
|
|
Dimatfit,
|
|
|
|
|
Dimtix,
|
|
|
|
|
Dimsoxd,
|
|
|
|
|
Dimtmove,
|
|
|
|
|
Dimupt,
|
|
|
|
|
Dimtofl,
|
|
|
|
|
Dimfit,
|
|
|
|
|
Dimdsep,
|
|
|
|
|
Dimrnd,
|
|
|
|
|
Dimzin,
|
|
|
|
|
Dimfrac,
|
|
|
|
|
Dimaunit,
|
|
|
|
|
Dimadec,
|
|
|
|
|
Dimazin,
|
|
|
|
|
Dimalt,
|
|
|
|
|
Dimaltf,
|
|
|
|
|
Dimaltd,
|
|
|
|
|
Dimaltu,
|
|
|
|
|
Dimalttd,
|
|
|
|
|
Dimaltrnd,
|
|
|
|
|
Dimapost,
|
|
|
|
|
Dimaltz,
|
|
|
|
|
Dimalttz,
|
|
|
|
|
Dimtolj,
|
|
|
|
|
Dimtzin,
|
2026-03-22 22:44:32 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
pub enum Message {
|
|
|
|
|
Tick(Instant),
|
|
|
|
|
OpenFile,
|
2026-05-13 09:59:07 +03:00
|
|
|
/// File picker returned. `Some((path, size_in_bytes))` → start loading;
|
|
|
|
|
/// `None` → user cancelled the dialog (no overlay shown).
|
|
|
|
|
OpenPathPicked(Option<(PathBuf, u64)>),
|
feat(app): add fixed Start tab with welcome page and recent documents
A pinned "Start" tab now lives at index 0. It cannot be closed, opens
on launch, and replaces the model-space viewport with a welcome page —
similar to Thunderbird's first-run screen.
- Boot opens with only the Start tab; Drawing1 is no longer auto-spawned.
- The Properties left panel becomes a Recent Documents rail on the
Start tab. Recents persist to disk
(%APPDATA%/H7CAD/recent.txt on Windows, $XDG_CONFIG_HOME/H7CAD/recent.txt
on Linux, ~/Library/Application Support/H7CAD/recent.txt on macOS), up
to 20 entries, saved on every open and rehydrated at boot.
Each row opens or removes the file in place.
- The Support ribbon group (Donate / Report / About / Changelog) is
retired; the same actions surface on the welcome page instead. The
Donate button is the primary call-to-action — solid #B03020 brand
fill with a soft brand-tinted ambient glow halo behind the action row
(Thunderbird-style coloured glow against the neutral page).
- Drawing-only overlays (mouse-area, viewcube, navbar, dynamic input,
selection markers) are suppressed on the Start tab so its clicks
don't trigger CAD commands. OpenFile's "current_is_empty" check
refuses to replace the Start tab, and TabClose ignores requests on
it.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 16:19:31 +03:00
|
|
|
/// Open a path from the Start tab's recent-documents list (skips the
|
|
|
|
|
/// file picker; the path is already known).
|
|
|
|
|
OpenRecent(PathBuf),
|
|
|
|
|
/// Drop a path from the recent-documents list.
|
|
|
|
|
RecentRemove(PathBuf),
|
2026-05-13 09:59:07 +03:00
|
|
|
/// User clicked Cancel on the loading overlay. The parser thread keeps
|
|
|
|
|
/// running but its result is discarded.
|
|
|
|
|
OpenCancel,
|
2026-05-06 23:04:53 +03:00
|
|
|
FileOpened(Result<(String, PathBuf, CadDocument, crate::scene::DerivedCaches), String>),
|
2026-03-22 22:44:32 +03:00
|
|
|
SaveFile,
|
|
|
|
|
SaveAs,
|
2026-05-01 16:12:32 +03:00
|
|
|
// ── Custom Save-As dialog ─────────────────────────────────────────────
|
|
|
|
|
SaveDialogFormatChanged(String),
|
|
|
|
|
SaveDialogFilenameChanged(String),
|
2026-05-01 16:20:38 +03:00
|
|
|
/// Navigate the file-chooser to the given directory.
|
|
|
|
|
SaveDialogNavigate(std::path::PathBuf),
|
|
|
|
|
/// User clicked a file entry (fill filename) or directory entry (navigate).
|
|
|
|
|
SaveDialogEntryClicked(std::path::PathBuf, bool),
|
2026-05-01 16:12:32 +03:00
|
|
|
SaveDialogConfirm,
|
|
|
|
|
SaveDialogCancel,
|
2026-03-22 22:44:32 +03:00
|
|
|
ClearScene,
|
|
|
|
|
SetWireframe(bool),
|
2026-05-27 00:08:37 +03:00
|
|
|
/// Set the active tab's render mode (one of acadrust's seven visual
|
|
|
|
|
/// styles). Replaces the binary `SetWireframe` over time; the older
|
|
|
|
|
/// message stays for ribbon/CLI back-compat and forwards.
|
|
|
|
|
SetRenderMode(acadrust::entities::ViewportRenderMode),
|
2026-03-22 22:44:32 +03:00
|
|
|
/// Switch camera projection: true = Orthographic, false = Perspective.
|
|
|
|
|
SetProjection(bool),
|
|
|
|
|
/// Select a ribbon module tab by index.
|
|
|
|
|
RibbonSelectTab(usize),
|
|
|
|
|
/// A ribbon tool button was clicked — highlights the tool and dispatches its event.
|
|
|
|
|
RibbonToolClick {
|
|
|
|
|
tool_id: String,
|
|
|
|
|
event: ModuleEvent,
|
|
|
|
|
},
|
|
|
|
|
// ── Application menu ──────────────────────────────────────────────────
|
|
|
|
|
ToggleAppMenu,
|
|
|
|
|
CloseAppMenu,
|
|
|
|
|
/// Close the menu and immediately dispatch a CAD command.
|
|
|
|
|
CloseAppMenuAndRun(String),
|
|
|
|
|
AppMenuSearch(String),
|
|
|
|
|
// ── Document tabs ──────────────────────────────────────────────────────
|
|
|
|
|
/// Create a new empty document tab.
|
|
|
|
|
TabNew,
|
|
|
|
|
/// Switch to the given tab index.
|
|
|
|
|
TabSwitch(usize),
|
|
|
|
|
/// Close the given tab index.
|
|
|
|
|
TabClose(usize),
|
2026-05-01 11:31:11 +03:00
|
|
|
// ── Unsaved-changes confirmation dialog ───────────────────────────────
|
|
|
|
|
/// User clicked "Save" in the unsaved-changes dialog.
|
|
|
|
|
UnsavedDialogSave,
|
|
|
|
|
/// User clicked "Discard" in the unsaved-changes dialog.
|
|
|
|
|
UnsavedDialogDiscard,
|
|
|
|
|
/// User clicked "Cancel" in the unsaved-changes dialog.
|
|
|
|
|
UnsavedDialogCancel,
|
|
|
|
|
/// Save-as path picked for the unsaved-changes → save → close flow.
|
|
|
|
|
UnsavedPickedSavePath(Option<std::path::PathBuf>),
|
2026-03-22 22:44:32 +03:00
|
|
|
// ─────────────────────────────────────────────────────────────────────
|
|
|
|
|
CommandInput(String),
|
|
|
|
|
CommandSubmit,
|
|
|
|
|
Command(String),
|
2026-05-26 18:59:51 +03:00
|
|
|
/// Append one typed character to the command-line input from the
|
|
|
|
|
/// global key-press subscription. Used when the text-input widget
|
|
|
|
|
/// itself isn't focused (focus parked on viewport / button / etc.)
|
|
|
|
|
/// so typing still routes to the command line.
|
|
|
|
|
CommandAppendChar(String),
|
|
|
|
|
/// Pop the trailing character off the command-line input — backspace
|
|
|
|
|
/// counterpart to `CommandAppendChar`.
|
|
|
|
|
CommandBackspace,
|
feat(dyn-input): TAB-editable distance/angle/coordinate fields
Dinamik giriş artık cursor yanında düzenlenebilir kutular gösteriyor.
TAB ile kutular arası geçilir, bir kutuya yazınca o kutu kilitlenir
(değer sabitlenir), diğerleri cursor'ı takip etmeye devam eder, Enter
ile çözülen nokta komuta point-pick olarak iletilir.
- DocumentTab.dyn_fields / dyn_active state; DynComponent + DynFieldEntry
- sync_dyn_fields komutun dyn_field() + base noktasına göre kutu kümesini
türetir: nokta+base → [d, açı], nokta+base yok → [X, Y], radius → [d],
açı → [açı]
- klavye yönlendirme: rakam/./-/+ aktif kutuya, TAB sonraki kutuya,
Enter try_dyn_commit, Backspace kilidi açar
- dyn_capturing iken command-line text alanı on_input'suz bırakılıyor
ki focus'u ve tuşları çalmasın; girişler global key aboneliğinden
geliyor (numpad dahil text alanı üzerinden yakalanıyor)
- çoklu-kutu overlay render: aktif kutu vurgulu, kilitli kutu farklı
kenar rengi
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 14:08:57 +03:00
|
|
|
/// TAB pressed: move focus to the next dynamic-input field (wraps).
|
|
|
|
|
DynTabNext,
|
2026-05-27 22:33:36 +03:00
|
|
|
/// Split the active Model viewport in two. `true` → horizontal divider
|
|
|
|
|
/// (top / bottom); `false` → vertical divider (left / right).
|
|
|
|
|
SplitModelViewport(bool),
|
2026-04-07 00:04:12 +03:00
|
|
|
/// Recall previous command in history (↑ arrow key).
|
|
|
|
|
CommandHistoryPrev,
|
|
|
|
|
/// Recall next command in history (↓ arrow key).
|
|
|
|
|
CommandHistoryNext,
|
2026-05-26 22:13:15 +03:00
|
|
|
/// Toggle the dropdown listing the full command-line history.
|
|
|
|
|
CommandHistoryToggle,
|
2026-05-26 22:45:54 +03:00
|
|
|
/// User clicked an autocomplete suggestion — fill the input with
|
|
|
|
|
/// the chosen command name and dispatch it.
|
|
|
|
|
CommandSuggestionPick(String),
|
2026-03-22 22:44:32 +03:00
|
|
|
ToggleLayers,
|
|
|
|
|
LayerToggleVisible(usize),
|
|
|
|
|
LayerToggleLock(usize),
|
|
|
|
|
LayerToggleFreeze(usize),
|
2026-04-06 23:41:39 +03:00
|
|
|
/// Toggle per-viewport freeze: (layer_index, vp_col_index)
|
|
|
|
|
LayerToggleVpFreeze(usize, usize),
|
2026-03-22 22:44:32 +03:00
|
|
|
LayerNew,
|
|
|
|
|
LayerDelete,
|
|
|
|
|
LayerSetCurrent,
|
|
|
|
|
LayerSelect(usize),
|
|
|
|
|
LayerRenameStart(usize),
|
|
|
|
|
LayerRenameEdit(String),
|
|
|
|
|
LayerColorPickerToggle(usize),
|
|
|
|
|
LayerColorMorePalette,
|
|
|
|
|
LayerColorSet(u8),
|
|
|
|
|
LayerLinetypeSet(String),
|
|
|
|
|
LayerLineweightSet(LineWeight),
|
|
|
|
|
LayerTransparencyEdit(usize, String),
|
|
|
|
|
LayerRenameCommit,
|
|
|
|
|
CursorMoved(Point),
|
|
|
|
|
ViewportClick,
|
|
|
|
|
ViewportMove(Point),
|
|
|
|
|
ViewportLeftPress,
|
|
|
|
|
ViewportLeftRelease,
|
|
|
|
|
ViewportRightPress,
|
|
|
|
|
ViewportRightRelease,
|
|
|
|
|
ViewportMiddlePress,
|
|
|
|
|
ViewportMiddleRelease,
|
|
|
|
|
ViewportScroll(mouse::ScrollDelta),
|
|
|
|
|
ViewportExit,
|
|
|
|
|
ViewCubeSnap(CubeRegion),
|
feat(grip): multi-functional hover popup menu
Phase 2 of the grip overhaul: when the cursor dwells on a selected
entity's grip the overlay opens a popup menu with entity-specific
options (Stretch / Add Vertex / Remove Vertex / Convert to
Arc-Line / …). Picking an item dispatches `apply_grip_menu`; the
default `Stretch` entry is a no-op that lets the user click the
grip to start the normal drag.
- `GripMenuItem` + `GripMenuAction` (full vocabulary covering
stretch, add/remove vertex, convert to arc/line, reverse arrows,
text-position variants, hatch parameters, tangent direction).
- `Grippable` trait gains `grip_menu` + `apply_grip_menu` with
trait-default `Stretch`-only impls so every existing entity gets
the popup for free.
- `EntityTypeOps` dispatches both methods across the same entity
list as the existing grip / property machinery.
- `OpenCADStudio` carries `grip_hover` (dwell tracker) +
`grip_popup` (open menu). `update_grip_hover` runs from
`ViewportMove`: hit-tests grips, refreshes the dwell timer, and
opens the popup after 600 ms. Cursor drift past 80 px or a grip
click dismisses it.
- `view.rs` paints the popup as an absolute-positioned column of
buttons at the grip's screen anchor; each button publishes
`Message::GripMenuPick(idx)`.
- `LwPolyline` overrides `Grippable` manually (skips the standard
macro) so its vertex grip exposes Add/Remove Vertex and its
segment-midpoint grip exposes Add Vertex + Convert to Arc/Line.
`apply_grip_menu` performs the edits: vertex insertion at the
chord midpoint inheriting the previous bulge, vertex removal,
and bulge toggle for the arc/line conversion.
2026-05-29 09:30:54 +03:00
|
|
|
/// User picked an item in the multi-functional grip popup menu —
|
|
|
|
|
/// the index is into `grip_popup.items`.
|
|
|
|
|
GripMenuPick(usize),
|
2026-05-29 09:38:37 +03:00
|
|
|
/// Timer pulse while the cursor is dwelling on a grip; drives the
|
|
|
|
|
/// dwell-to-popup transition without requiring further mouse motion.
|
|
|
|
|
GripDwellTick,
|
2026-03-22 22:44:32 +03:00
|
|
|
WindowResized(f32, f32),
|
|
|
|
|
/// Enter pressed globally — finalises the active command (no text-input involvement).
|
|
|
|
|
CommandFinalize,
|
2026-05-31 13:14:35 +03:00
|
|
|
/// Space pressed globally — a literal space in the MText preview, otherwise
|
|
|
|
|
/// finalises like Enter.
|
|
|
|
|
CommandSpace,
|
2026-03-22 22:44:32 +03:00
|
|
|
/// Escape pressed globally — cancels the active command.
|
|
|
|
|
CommandEscape,
|
|
|
|
|
/// Toggle the global snap on/off (OSNAP button body click).
|
|
|
|
|
ToggleSnapEnabled,
|
|
|
|
|
/// Toggle grid-snap on/off — F9 / SNAP status-bar button.
|
|
|
|
|
ToggleGridSnap,
|
2026-04-24 01:23:06 +03:00
|
|
|
/// Toggle the ViewCube 3D gizmo visibility (NAVVCUBE).
|
|
|
|
|
ToggleViewCube,
|
|
|
|
|
/// Toggle the Properties panel visibility (PROPERTIES).
|
|
|
|
|
ToggleProperties,
|
|
|
|
|
/// Toggle the document file tabs at the top (FILETAB).
|
|
|
|
|
ToggleFileTabs,
|
|
|
|
|
/// Toggle the layout tabs at the bottom (LAYOUTTAB).
|
|
|
|
|
ToggleLayoutTabs,
|
2026-03-22 22:44:32 +03:00
|
|
|
/// Toggle grid display in the viewport — F7 / GRID status-bar button.
|
|
|
|
|
ToggleGrid,
|
|
|
|
|
/// Toggle orthogonal drawing constraint — F8 / ORTHO status-bar button.
|
|
|
|
|
ToggleOrtho,
|
2026-05-23 07:15:26 +03:00
|
|
|
/// Toggle LWDISPLAY header flag — LWT status-bar button.
|
|
|
|
|
ToggleLineweightDisplay,
|
2026-04-07 19:30:25 +03:00
|
|
|
/// Toggle polar-angle constraint — F10 / POLAR status-bar button.
|
2026-03-22 22:44:32 +03:00
|
|
|
TogglePolar,
|
2026-04-07 19:30:25 +03:00
|
|
|
/// Set polar tracking angle increment (right-click POLAR button).
|
|
|
|
|
SetPolarAngle(f32),
|
2026-05-06 17:50:46 +03:00
|
|
|
/// Set the model-space annotation scale (CANNOSCALE equivalent).
|
|
|
|
|
SetAnnotationScale(f32),
|
|
|
|
|
/// Set the active viewport's custom_scale (paper space).
|
|
|
|
|
SetViewportScale(f64),
|
|
|
|
|
/// Toggle the scale picker popup open/closed.
|
|
|
|
|
ToggleScalePopup,
|
|
|
|
|
/// Close the scale picker popup.
|
|
|
|
|
CloseScalePopup,
|
2026-04-08 00:12:09 +03:00
|
|
|
/// Toggle dynamic input overlay (F12).
|
|
|
|
|
ToggleDynInput,
|
2026-04-08 00:26:54 +03:00
|
|
|
/// Toggle object snap tracking (F11).
|
|
|
|
|
ToggleOTrack,
|
2026-03-22 22:44:32 +03:00
|
|
|
/// Toggle an individual snap mode (from popup row click).
|
|
|
|
|
ToggleSnap(crate::snap::SnapType),
|
|
|
|
|
/// Open / close the OSNAP popup (▾ arrow click).
|
|
|
|
|
ToggleSnapPopup,
|
|
|
|
|
/// Close the OSNAP popup (click-catcher outside the panel).
|
|
|
|
|
CloseSnapPopup,
|
|
|
|
|
/// Enable all snap modes.
|
|
|
|
|
SnapSelectAll,
|
|
|
|
|
/// Disable all snap modes.
|
|
|
|
|
SnapClearAll,
|
|
|
|
|
/// Toggle a ribbon dropdown open/closed.
|
|
|
|
|
ToggleRibbonDropdown(String),
|
|
|
|
|
/// Close any open ribbon dropdown (click-catcher outside the panel).
|
|
|
|
|
CloseRibbonDropdown,
|
|
|
|
|
/// User selected a specific item from a ribbon dropdown.
|
|
|
|
|
DropdownSelectItem {
|
|
|
|
|
dropdown_id: &'static str,
|
|
|
|
|
cmd: &'static str,
|
|
|
|
|
},
|
|
|
|
|
/// Delete key — erase all currently selected entities.
|
|
|
|
|
DeleteSelected,
|
|
|
|
|
Undo,
|
|
|
|
|
Redo,
|
|
|
|
|
UndoMany(usize),
|
|
|
|
|
RedoMany(usize),
|
|
|
|
|
// ── Ribbon ────────────────────────────────────────────────────────────
|
|
|
|
|
/// User selected a layer from the layer combobox in the ribbon.
|
|
|
|
|
RibbonLayerChanged(String),
|
|
|
|
|
/// User changed the active color in the Properties toolbar.
|
|
|
|
|
RibbonColorChanged(AcadColor),
|
|
|
|
|
/// Toggle the full ACI palette inside the ribbon color picker.
|
|
|
|
|
RibbonColorPaletteToggle,
|
|
|
|
|
/// User changed the active linetype in the Properties toolbar.
|
|
|
|
|
RibbonLinetypeChanged(String),
|
|
|
|
|
/// User changed the active lineweight in the Properties toolbar.
|
|
|
|
|
RibbonLineweightChanged(LineWeight),
|
2026-04-09 03:38:23 +03:00
|
|
|
/// User selected a style from a style combobox in the ribbon.
|
2026-05-12 10:36:15 +03:00
|
|
|
RibbonStyleChanged {
|
|
|
|
|
key: crate::modules::StyleKey,
|
|
|
|
|
name: String,
|
|
|
|
|
},
|
2026-03-22 22:44:32 +03:00
|
|
|
|
|
|
|
|
// ── Properties panel ──────────────────────────────────────────────────
|
|
|
|
|
/// User selected a layer from the layer pick_list in the Properties panel.
|
|
|
|
|
PropLayerChanged(String),
|
|
|
|
|
PropSelectionGroupChanged(crate::ui::properties::SelectionGroup),
|
|
|
|
|
/// User picked a color from the Properties color picker.
|
|
|
|
|
PropColorChanged(AcadColor),
|
|
|
|
|
/// User selected a lineweight from the Properties pick_list.
|
|
|
|
|
PropLwChanged(LineWeight),
|
|
|
|
|
/// User selected a linetype from the linetype pick_list.
|
|
|
|
|
PropLinetypeChanged(String),
|
|
|
|
|
/// User toggled a boolean property (e.g. Invisible).
|
|
|
|
|
PropBoolToggle(&'static str),
|
|
|
|
|
/// User selected a hatch pattern from the pattern pick_list in Properties.
|
|
|
|
|
PropHatchPatternChanged(String),
|
|
|
|
|
/// User selected a generic choice field in the Properties panel.
|
|
|
|
|
PropGeomChoiceChanged {
|
|
|
|
|
field: &'static str,
|
|
|
|
|
value: String,
|
|
|
|
|
},
|
|
|
|
|
/// User is typing in an editable geometry field (live buffer update).
|
|
|
|
|
PropGeomInput {
|
|
|
|
|
field: &'static str,
|
|
|
|
|
value: String,
|
|
|
|
|
},
|
|
|
|
|
/// User committed a geometry/common field edit (Enter pressed).
|
|
|
|
|
PropGeomCommit(&'static str),
|
|
|
|
|
/// Toggle the inline color picker dropdown open/closed.
|
|
|
|
|
PropColorPickerToggle,
|
|
|
|
|
/// Toggle the full ACI colour palette expansion.
|
|
|
|
|
PropColorPaletteToggle,
|
2026-03-31 07:24:16 +03:00
|
|
|
/// Enter the model-space editing mode inside the given viewport (MSPACE).
|
|
|
|
|
EnterViewport(acadrust::Handle),
|
|
|
|
|
/// Exit MSPACE and return to paper-space editing (PSPACE).
|
|
|
|
|
ExitViewport,
|
2026-03-31 12:52:07 +03:00
|
|
|
/// MS command: enter MSPACE for the first available viewport.
|
|
|
|
|
MspaceCommand,
|
|
|
|
|
/// PS command: exit MSPACE (PSPACE).
|
|
|
|
|
PspaceCommand,
|
2026-03-22 22:44:32 +03:00
|
|
|
/// Switch to a named layout ("Model" or paper space layout name).
|
|
|
|
|
LayoutSwitch(String),
|
|
|
|
|
/// Create a new paper space layout.
|
|
|
|
|
LayoutCreate,
|
2026-03-31 07:13:36 +03:00
|
|
|
/// Delete the named paper space layout (Model cannot be deleted).
|
|
|
|
|
LayoutDelete(String),
|
|
|
|
|
/// Begin inline rename for the given layout tab.
|
|
|
|
|
LayoutRenameStart(String),
|
|
|
|
|
/// Live-update the rename text input buffer.
|
|
|
|
|
LayoutRenameEdit(String),
|
|
|
|
|
/// Commit the rename (Enter pressed in the text input).
|
|
|
|
|
LayoutRenameCommit,
|
|
|
|
|
/// Cancel an in-progress rename (Escape).
|
|
|
|
|
LayoutRenameCancel,
|
|
|
|
|
/// Open the right-click context menu for the given layout tab.
|
|
|
|
|
LayoutContextMenu(String),
|
|
|
|
|
/// Close the layout context menu.
|
|
|
|
|
LayoutContextMenuClose,
|
2026-04-08 18:28:30 +03:00
|
|
|
// ── Layout Manager Panel ────────────────────────────────────────────
|
|
|
|
|
LayoutManagerOpen,
|
2026-04-09 12:44:31 +03:00
|
|
|
#[allow(dead_code)]
|
2026-04-08 18:28:30 +03:00
|
|
|
LayoutManagerClose,
|
|
|
|
|
LayoutManagerSelect(String),
|
|
|
|
|
LayoutManagerRenameBuf(String),
|
|
|
|
|
LayoutManagerRenameCommit,
|
|
|
|
|
LayoutManagerNew,
|
|
|
|
|
LayoutManagerDelete,
|
|
|
|
|
LayoutManagerMoveLeft,
|
|
|
|
|
LayoutManagerMoveRight,
|
|
|
|
|
LayoutManagerSetCurrent,
|
2026-04-08 18:31:09 +03:00
|
|
|
/// Switch the UI color scheme.
|
|
|
|
|
SetTheme(Theme),
|
2026-04-08 18:34:10 +03:00
|
|
|
// ── Keyboard Shortcut Editor ────────────────────────────────────────
|
|
|
|
|
ShortcutsPanelOpen,
|
2026-04-09 12:44:31 +03:00
|
|
|
#[allow(dead_code)]
|
2026-04-08 18:34:10 +03:00
|
|
|
ShortcutsPanelClose,
|
2026-04-22 22:22:33 +03:00
|
|
|
// ── About window ────────────────────────────────────────────────────
|
|
|
|
|
AboutOpen,
|
|
|
|
|
AboutCopyInfo,
|
2026-05-29 20:30:56 +03:00
|
|
|
// ── Quick Select / Select Similar ───────────────────────────────────
|
|
|
|
|
/// Extend the current selection with every entity in the active
|
|
|
|
|
/// layout that matches a selected entity by (type, layer).
|
|
|
|
|
SelectSimilar,
|
2026-05-30 21:54:35 +03:00
|
|
|
/// Keyboard modifier state changed — tracks whether Shift is held so the
|
|
|
|
|
/// pick path can do subtractive (Shift+click) selection.
|
|
|
|
|
SetShiftDown(bool),
|
feat: Implement in-place MText editor with formatting toolbar and live preview
- Added SVG icons for text formatting options (bold, italic, underline, overline, strike, uppercase, lowercase).
- Introduced a new `MTextEditorState` struct to manage the state of the MText editor.
- Implemented functionality to open the MText editor from the command line and edit existing MText entities.
- Added message handling for various MText editor actions (formatting, height, style, font, justification, alignment, line spacing).
- Created a dedicated preview area for MText rendering within the editor.
- Updated the command structure to support opening the MText editor and handling text input.
- Refactored existing MText command logic to integrate with the new editor.
2026-05-30 23:24:06 +03:00
|
|
|
// ── In-place MText editor ───────────────────────────────────────────
|
|
|
|
|
/// Text-area edit action from the multi-line editor widget.
|
|
|
|
|
MTextEdit(iced::widget::text_editor::Action),
|
|
|
|
|
/// Toolbar character-format toggle applied to the selection.
|
|
|
|
|
MTextFmt(mtext_editor::MTextFmt),
|
|
|
|
|
/// Toolbar height field changed.
|
|
|
|
|
MTextHeight(String),
|
|
|
|
|
/// Toolbar text-style dropdown changed.
|
|
|
|
|
MTextStyle(String),
|
|
|
|
|
/// Toolbar font dropdown changed.
|
|
|
|
|
MTextFont(String),
|
|
|
|
|
/// Toolbar oblique-angle field changed.
|
|
|
|
|
MTextOblique(String),
|
|
|
|
|
/// Toolbar width-factor field changed.
|
|
|
|
|
MTextWidth(String),
|
|
|
|
|
/// Toolbar character-spacing field changed.
|
|
|
|
|
MTextCharSpace(String),
|
|
|
|
|
/// Toolbar colour dropdown — global text colour (ACI index, 256 = ByLayer).
|
|
|
|
|
MTextColor(u16),
|
|
|
|
|
/// Toolbar justification / attachment-point change.
|
|
|
|
|
MTextJustify(acadrust::entities::mtext::AttachmentPoint),
|
|
|
|
|
/// Toolbar paragraph-alignment change.
|
|
|
|
|
MTextAlign(mtext_editor::ParaAlign),
|
|
|
|
|
/// Toolbar line-spacing change.
|
|
|
|
|
MTextLineSpacing(f32),
|
2026-05-31 01:59:39 +03:00
|
|
|
/// Switch the editor body between raw code input (`false`) and the
|
|
|
|
|
/// rendered preview (`true`).
|
|
|
|
|
MTextShowPreview(bool),
|
2026-05-31 02:33:47 +03:00
|
|
|
/// Begin a preview text selection at the given visible-character offset.
|
|
|
|
|
MTextSelStart(usize),
|
|
|
|
|
/// Extend the preview selection to the given visible-character offset.
|
|
|
|
|
MTextSelTo(usize),
|
2026-05-31 10:16:30 +03:00
|
|
|
/// Move the preview caret by N visible characters.
|
|
|
|
|
MTextCaretMove(i32),
|
2026-05-31 13:14:35 +03:00
|
|
|
/// Timer tick toggling the preview caret's blink phase.
|
|
|
|
|
MTextCaretBlink,
|
feat: Implement in-place MText editor with formatting toolbar and live preview
- Added SVG icons for text formatting options (bold, italic, underline, overline, strike, uppercase, lowercase).
- Introduced a new `MTextEditorState` struct to manage the state of the MText editor.
- Implemented functionality to open the MText editor from the command line and edit existing MText entities.
- Added message handling for various MText editor actions (formatting, height, style, font, justification, alignment, line spacing).
- Created a dedicated preview area for MText rendering within the editor.
- Updated the command structure to support opening the MText editor and handling text input.
- Refactored existing MText command logic to integrate with the new editor.
2026-05-30 23:24:06 +03:00
|
|
|
/// Commit the editor: create or update the MText entity.
|
|
|
|
|
MTextOk,
|
|
|
|
|
/// Discard the editor without creating / changing the entity.
|
|
|
|
|
MTextCancel,
|
2026-05-31 14:44:18 +03:00
|
|
|
// ── In-place single-line TEXT editor ────────────────────────────────
|
|
|
|
|
/// Text-field input changed.
|
|
|
|
|
TextInlineInput(String),
|
|
|
|
|
/// Commit the editor: create or update the TEXT entity.
|
|
|
|
|
TextInlineOk,
|
feat(draworder): cross-type draw order + context menu
DRAWORDER now affects rendering immediately (bump geometry epoch so the
sort cache rebuilds) and front/back anchor to the block's full effective
sort range instead of only existing table entries, so bring-to-front
actually lands on top. The render fallback sorts table-absent entities by
their own handle.
Draw order now applies across entity types, not just within a type. A
per-entity draw-order rank (SortEntitiesTable override or own handle,
ranked per block) is encoded as a small signed clip-z bias in the wire,
hatch, image, and 3DFACE pipelines. The shared LessEqual depth buffer then
orders fills, lines, rasters and 2D solids against each other regardless of
pass order — without touching model coordinates. 3D meshes keep real
geometric depth; wipeouts stay always-on-top.
Adds a Draw Order submenu to the viewport context menu (Bring to Front,
Send to Back, Bring Above/Send Under Object). The above/under items run an
interactive reference-object pick, then reorder the captured selection.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 21:29:01 +03:00
|
|
|
// ── Draw Order context menu ─────────────────────────────────────────
|
|
|
|
|
/// Toggle the Draw Order sub-items in the viewport context menu.
|
|
|
|
|
DrawOrderSubmenuToggle,
|
|
|
|
|
/// Begin an interactive reference-object pick to move the current
|
|
|
|
|
/// selection above (`true`) or below (`false`) the picked object.
|
|
|
|
|
DrawOrderPickRef(bool),
|
2026-05-29 20:30:56 +03:00
|
|
|
/// Open the Quick Select panel. Initialises filters from the current
|
|
|
|
|
/// selection's first entity (type + layer) when one is selected.
|
|
|
|
|
QSelectOpen,
|
|
|
|
|
/// Close the Quick Select panel without applying.
|
|
|
|
|
QSelectClose,
|
|
|
|
|
/// Type filter — `None` means "any type".
|
|
|
|
|
QSelectSetType(Option<String>),
|
feat(qselect): per-property filter with operator + value (#28)
Quick Select used to filter only on Object Type and Layer. The classic
QSELECT model — pick a type, pick one of its properties, compare with a
typed value via a logical operator — is much more general: a Line entity
can now be filtered on Start X, Length, Angle, Delta Y, …, and any
entity on the common Layer / Color / Linetype / Lineweight.
Panel layout: Object type → Property → Operator → Value → Append. The
Property dropdown is type-aware: it always lists the four common
properties; picking a specific Object type appends that type's
`geometry_properties` rows, sampled from the first entity of that type
in the active layout (so `Start X`, `Radius`, `Pattern Angle`, … only
appear when their entity type is selected). Changing the Object type
drops the Property when the new type no longer offers it.
Operators: `= Equals`, `!= Not equal`, `> Greater than`, `< Less than`,
`* Any value`. Eq/Neq do case-insensitive string compare against the
canonical property string (so "ByLayer" matches "bylayer"); Gt/Lt parse
both sides as `f64` and reject anything non-numeric; Any skips the
value test entirely. The Value text field is disabled when no Property
is picked or the operator is Any. Append behaves the same as before.
The new value reader is hand-rolled for the four common properties
(layer name, color formatted as `ByLayer`/`ByBlock`/`<aci>`/`r,g,b`,
linetype name, lineweight formatted as `0.30mm`) and falls back through
`geometry_properties()` for type-specific fields — extracting the
property's value out of the `PropValue` variant so EditText / Choice /
ColorChoice all canonicalise the same way the panel will display them.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 21:09:56 +03:00
|
|
|
/// Property to compare. `None` means "no property filter — just type
|
|
|
|
|
/// filter applies"; the operator and value fields are ignored in
|
|
|
|
|
/// that case.
|
|
|
|
|
QSelectSetProperty(Option<QSelectPropertyChoice>),
|
|
|
|
|
/// Comparison operator.
|
|
|
|
|
QSelectSetOperator(QSelectOp),
|
|
|
|
|
/// Compare-against value (free-text input).
|
|
|
|
|
QSelectSetValue(String),
|
2026-05-29 20:30:56 +03:00
|
|
|
/// Append-to-current-selection toggle.
|
|
|
|
|
QSelectSetAppend(bool),
|
|
|
|
|
/// Apply the current filter and close the panel.
|
|
|
|
|
QSelectApply,
|
2026-05-01 11:31:11 +03:00
|
|
|
/// The user clicked the title-bar ✕ (fires before the window closes).
|
|
|
|
|
WindowCloseRequested(window::Id),
|
|
|
|
|
/// A window was fully closed (fires after `window::close()` is called).
|
2026-03-22 22:44:32 +03:00
|
|
|
OsWindowClosed(window::Id),
|
|
|
|
|
/// No-op — used as a fallback when a TabEvent has no host mapping.
|
|
|
|
|
Noop,
|
2026-05-16 19:55:57 +03:00
|
|
|
/// GitHub releases API returned a result. `Some(version)` means a
|
|
|
|
|
/// newer release exists; we open the update-notice window.
|
2026-05-22 12:55:37 +03:00
|
|
|
UpdateCheckResult(Option<crate::update_check::UpdateInfo>),
|
2026-05-16 19:55:57 +03:00
|
|
|
/// User dismissed the update-notice window.
|
|
|
|
|
UpdateNoticeClose,
|
|
|
|
|
/// User clicked the "Open release page" button — opens the GitHub
|
|
|
|
|
/// release URL in the OS default browser and closes the notice.
|
|
|
|
|
UpdateNoticeOpenRelease,
|
2026-03-31 08:05:17 +03:00
|
|
|
// ── Page Setup ────────────────────────────────────────────────────────
|
|
|
|
|
/// Open the Page Setup panel for the current layout.
|
|
|
|
|
PageSetupOpen,
|
|
|
|
|
/// Close (cancel) the Page Setup panel without applying changes.
|
|
|
|
|
PageSetupClose,
|
|
|
|
|
/// Live-edit of the paper width field.
|
|
|
|
|
PageSetupWidthEdit(String),
|
|
|
|
|
/// Live-edit of the paper height field.
|
|
|
|
|
PageSetupHeightEdit(String),
|
2026-04-01 00:12:07 +03:00
|
|
|
/// User selected a paper size preset (e.g. "A4 Portrait").
|
|
|
|
|
PageSetupPreset(String),
|
|
|
|
|
/// User changed the plot area type ("Layout" or "Extents").
|
|
|
|
|
PageSetupPlotArea(String),
|
|
|
|
|
/// Toggle center-on-page.
|
|
|
|
|
PageSetupCenterToggle,
|
|
|
|
|
/// Live-edit of plot offset X.
|
|
|
|
|
PageSetupOffsetXEdit(String),
|
|
|
|
|
/// Live-edit of plot offset Y.
|
|
|
|
|
PageSetupOffsetYEdit(String),
|
|
|
|
|
/// User changed plot rotation.
|
|
|
|
|
PageSetupRotation(String),
|
Feat: ROADMAP Phase 1-5 batch — trim/extend, plot scale, viewport UX, mline caps, table alignment
- TRIM/EXTEND: Ray and XLine entities supported as cutting boundaries and trim targets;
trim results in Ray→Line/Ray, XLine→Ray/Ray or Line; hover preview added
- Plot scale override: Fit/1:1/1:2/1:5/1:10/1:20/1:50/1:100/2:1 buttons added to Page Setup
panel; PageSetupCommit applies scale to PlotSettings
- Viewport UX: locked viewport gets orange border; selected viewport gets white border
- Named view → viewport assignment: Choice dropdown in properties panel copies
view_target/direction/height to the viewport entity
- MLine caps: perpendicular start/end cap lines added for open (non-closed) MLines
- Wipeout polygon grips: boundary vertices become editable grips when clipping_enabled+Polygonal;
world→pixel back-projection on drag
- Table cell alignment: CellStyle.alignment (1-9) drives left/center/right + top/middle/bottom
text positioning; cxf::measure_text() helper added
- New commands/features: DIMCONTINUE (DCO), DIMBASELINE (DBA), TOLERANCE (TOL), TABLE,
ATTDEF, ATTEDIT, ATTDISP, BREAKATPOINT (BAP), PLOTWINDOW (PW), MLINE (ML);
DIMSTYLE/DIMCURRENT/CLAYER/LTSCALE/CELTSCALE/SCALETEXT/XDATA/UCSICON/REGEN/DRAWORDER extended
- PLINE arc mode: A/L/CLOSE text input, bulge computation, arc preview
- Warning cleanup: removed unused imports and unreachable patterns
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 15:31:05 +03:00
|
|
|
PageSetupScale(String),
|
2026-03-31 08:05:17 +03:00
|
|
|
/// Apply the changes entered in Page Setup.
|
|
|
|
|
PageSetupCommit,
|
|
|
|
|
// ── Plot / Export ─────────────────────────────────────────────────────
|
|
|
|
|
/// Show the SVG save-file dialog and trigger export.
|
|
|
|
|
PlotExport,
|
|
|
|
|
/// Callback after the user picks (or cancels) the export path.
|
|
|
|
|
PlotExportPath(Option<std::path::PathBuf>),
|
2026-04-08 22:21:50 +03:00
|
|
|
/// Send current layout to the system printer (via lp / lpr).
|
|
|
|
|
PrintToPrinter,
|
|
|
|
|
/// Callback from the async printer job.
|
|
|
|
|
PrintResult(Result<String, String>),
|
2026-04-06 14:37:23 +03:00
|
|
|
// ── Plot Style Table ─────────────────────────────────────────────────
|
|
|
|
|
/// Open file dialog to load a CTB/STB plot style table.
|
|
|
|
|
PlotStyleLoad,
|
|
|
|
|
/// Callback when the user picks (or cancels) a CTB/STB file.
|
|
|
|
|
PlotStyleLoaded(Option<crate::io::plot_style::PlotStyleTable>),
|
|
|
|
|
/// Clear the active plot style table.
|
|
|
|
|
PlotStyleClear,
|
2026-04-08 18:19:48 +03:00
|
|
|
/// Open/close the Plot Style panel.
|
|
|
|
|
PlotStylePanelOpen,
|
2026-04-09 12:44:31 +03:00
|
|
|
#[allow(dead_code)]
|
2026-04-08 18:19:48 +03:00
|
|
|
PlotStylePanelClose,
|
|
|
|
|
/// Select an ACI entry in the panel.
|
|
|
|
|
PlotStylePanelSelectAci(u8),
|
|
|
|
|
/// Edit buffers changed.
|
|
|
|
|
PlotStylePanelColorBuf(String),
|
|
|
|
|
PlotStylePanelLwBuf(String),
|
|
|
|
|
PlotStylePanelScreenBuf(String),
|
|
|
|
|
/// Apply current edit buffers to the selected ACI entry.
|
|
|
|
|
PlotStylePanelApply,
|
|
|
|
|
/// Save the modified table back to disk.
|
|
|
|
|
PlotStylePanelSave,
|
|
|
|
|
/// Save callback.
|
|
|
|
|
PlotStylePanelSavePath(Option<std::path::PathBuf>),
|
2026-04-06 15:01:25 +03:00
|
|
|
// ── TextStyle Font Browser ────────────────────────────────────────────
|
|
|
|
|
TextStyleDialogOpen,
|
2026-04-09 12:44:31 +03:00
|
|
|
#[allow(dead_code)]
|
2026-04-06 15:01:25 +03:00
|
|
|
TextStyleDialogClose,
|
|
|
|
|
TextStyleDialogSelect(String),
|
|
|
|
|
TextStyleDialogSetCurrent,
|
|
|
|
|
TextStyleDialogNew,
|
|
|
|
|
TextStyleDialogDelete,
|
|
|
|
|
/// Edit a string field (FontFile / Width / Oblique).
|
2026-05-12 10:36:15 +03:00
|
|
|
TextStyleEdit {
|
|
|
|
|
field: &'static str,
|
|
|
|
|
value: String,
|
|
|
|
|
},
|
2026-04-06 15:01:25 +03:00
|
|
|
/// Commit edits to the selected text style.
|
|
|
|
|
TextStyleApply,
|
|
|
|
|
/// Select a font from the built-in font list.
|
|
|
|
|
TextStyleFontPick(String),
|
2026-06-03 17:43:12 +03:00
|
|
|
/// Flip a boolean flag on the selected text style (backward / upside_down /
|
|
|
|
|
/// vertical / annotative), applied immediately.
|
|
|
|
|
TextStyleToggle(&'static str),
|
2026-04-06 14:57:11 +03:00
|
|
|
// ── TableStyle Dialog ─────────────────────────────────────────────────
|
|
|
|
|
TableStyleDialogOpen,
|
2026-04-09 12:44:31 +03:00
|
|
|
#[allow(dead_code)]
|
2026-04-06 14:57:11 +03:00
|
|
|
TableStyleDialogClose,
|
|
|
|
|
TableStyleDialogSelect(String),
|
|
|
|
|
TableStyleDialogNew,
|
|
|
|
|
TableStyleDialogDelete,
|
2026-06-03 17:51:45 +03:00
|
|
|
/// Toggle the Annotative flag on the selected table style.
|
|
|
|
|
TableStyleToggleAnnotative,
|
2026-04-06 14:54:05 +03:00
|
|
|
// ── MLineStyle Dialog ─────────────────────────────────────────────────
|
|
|
|
|
MlStyleDialogOpen,
|
2026-04-09 12:44:31 +03:00
|
|
|
#[allow(dead_code)]
|
2026-04-06 14:54:05 +03:00
|
|
|
MlStyleDialogClose,
|
|
|
|
|
MlStyleDialogSelect(String),
|
|
|
|
|
MlStyleDialogSetCurrent,
|
|
|
|
|
MlStyleDialogNew,
|
|
|
|
|
MlStyleDialogDelete,
|
2026-04-06 14:25:13 +03:00
|
|
|
// ── DimStyle Dialog ───────────────────────────────────────────────────
|
|
|
|
|
DimStyleDialogOpen,
|
|
|
|
|
DimStyleDialogClose,
|
|
|
|
|
/// Apply edits to the selected style.
|
|
|
|
|
DimStyleDialogApply,
|
|
|
|
|
/// Select a different style in the dialog list.
|
|
|
|
|
DimStyleDialogSelect(String),
|
|
|
|
|
/// Switch the active tab.
|
|
|
|
|
DimStyleDialogTab(u8),
|
|
|
|
|
/// Create a new empty style (prompts via command line).
|
|
|
|
|
DimStyleDialogNew,
|
|
|
|
|
/// Set the selected style as the document's current dim style.
|
|
|
|
|
DimStyleDialogSetCurrent,
|
|
|
|
|
/// Delete the selected style.
|
|
|
|
|
DimStyleDialogDelete,
|
|
|
|
|
// Field edit messages:
|
|
|
|
|
DsEdit(DsField, String),
|
|
|
|
|
DsToggle(DsField),
|
2026-04-06 23:19:08 +03:00
|
|
|
// ── Raster Image ──────────────────────────────────────────────────────
|
|
|
|
|
/// Open file-picker dialog for IMAGE command (async).
|
|
|
|
|
ImagePick,
|
|
|
|
|
/// Result of the image file picker + pixel dimension decode.
|
|
|
|
|
ImagePickResult(Result<(std::path::PathBuf, u32, u32), String>),
|
2026-04-08 00:02:38 +03:00
|
|
|
// ── XREF ──────────────────────────────────────────────────────────────
|
|
|
|
|
/// Open file-picker dialog for XATTACH command (async).
|
|
|
|
|
XAttachPick,
|
|
|
|
|
/// Result of the XATTACH file picker.
|
|
|
|
|
XAttachPickResult(Result<std::path::PathBuf, String>),
|
2026-04-08 00:05:04 +03:00
|
|
|
// ── WBLOCK ────────────────────────────────────────────────────────────
|
|
|
|
|
/// Trigger the WBLOCK save dialog for `block_name` (or `*` = selection).
|
|
|
|
|
WblockSave(String),
|
|
|
|
|
/// Result of the WBLOCK save path dialog.
|
|
|
|
|
WblockSaveResult(String, Option<std::path::PathBuf>),
|
2026-04-08 00:07:58 +03:00
|
|
|
// ── DATAEXTRACTION ────────────────────────────────────────────────────
|
|
|
|
|
/// Save the pre-built CSV string to a file chosen by the user.
|
|
|
|
|
DataExtractionSave(String),
|
|
|
|
|
/// Path chosen (or None = cancelled).
|
|
|
|
|
DataExtractionSaveResult(String, Option<std::path::PathBuf>),
|
2026-04-08 00:46:05 +03:00
|
|
|
// ── STL export ────────────────────────────────────────────────────────
|
|
|
|
|
/// Trigger STL export: collect meshes and show save dialog.
|
|
|
|
|
StlExport,
|
|
|
|
|
/// Callback after the user picks (or cancels) the STL save path.
|
|
|
|
|
StlExportPath(Option<std::path::PathBuf>),
|
2026-04-08 22:23:58 +03:00
|
|
|
// ── STEP export ───────────────────────────────────────────────────────
|
|
|
|
|
/// Trigger STEP AP203 export: show save dialog.
|
|
|
|
|
StepExport,
|
|
|
|
|
/// Callback after the user picks (or cancels) the STEP save path.
|
|
|
|
|
StepExportPath(Option<std::path::PathBuf>),
|
2026-04-08 12:59:27 +03:00
|
|
|
// ── OBJ import ────────────────────────────────────────────────────────
|
|
|
|
|
/// Trigger OBJ import: show open-file dialog.
|
|
|
|
|
ObjImport,
|
|
|
|
|
/// Callback after the user picks (or cancels) the OBJ file path.
|
|
|
|
|
ObjImportPath(Option<std::path::PathBuf>),
|
2026-03-22 22:44:32 +03:00
|
|
|
}
|
|
|
|
|
|
2026-05-23 17:16:15 +03:00
|
|
|
impl OpenCADStudio {
|
2026-03-22 22:44:32 +03:00
|
|
|
fn new() -> Self {
|
feat(app): add fixed Start tab with welcome page and recent documents
A pinned "Start" tab now lives at index 0. It cannot be closed, opens
on launch, and replaces the model-space viewport with a welcome page —
similar to Thunderbird's first-run screen.
- Boot opens with only the Start tab; Drawing1 is no longer auto-spawned.
- The Properties left panel becomes a Recent Documents rail on the
Start tab. Recents persist to disk
(%APPDATA%/H7CAD/recent.txt on Windows, $XDG_CONFIG_HOME/H7CAD/recent.txt
on Linux, ~/Library/Application Support/H7CAD/recent.txt on macOS), up
to 20 entries, saved on every open and rehydrated at boot.
Each row opens or removes the file in place.
- The Support ribbon group (Donate / Report / About / Changelog) is
retired; the same actions surface on the welcome page instead. The
Donate button is the primary call-to-action — solid #B03020 brand
fill with a soft brand-tinted ambient glow halo behind the action row
(Thunderbird-style coloured glow against the neutral page).
- Drawing-only overlays (mouse-area, viewcube, navbar, dynamic input,
selection markers) are suppressed on the Start tab so its clicks
don't trigger CAD commands. OpenFile's "current_is_empty" check
refuses to replace the Start tab, and TabClose ignores requests on
it.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 16:19:31 +03:00
|
|
|
// Boot with only the Welcome/Start tab. The user creates drawings
|
|
|
|
|
// explicitly (File → New); we never auto-spawn Drawing1.
|
|
|
|
|
let start_tab = DocumentTab::new_start();
|
|
|
|
|
let mut app_menu = AppMenu::new();
|
|
|
|
|
// Restore recents from disk so the Start page lists them across runs.
|
|
|
|
|
app_menu.load_persistent_recents();
|
2026-03-22 22:44:32 +03:00
|
|
|
let mut app = Self {
|
|
|
|
|
start: Instant::now(),
|
feat(app): add fixed Start tab with welcome page and recent documents
A pinned "Start" tab now lives at index 0. It cannot be closed, opens
on launch, and replaces the model-space viewport with a welcome page —
similar to Thunderbird's first-run screen.
- Boot opens with only the Start tab; Drawing1 is no longer auto-spawned.
- The Properties left panel becomes a Recent Documents rail on the
Start tab. Recents persist to disk
(%APPDATA%/H7CAD/recent.txt on Windows, $XDG_CONFIG_HOME/H7CAD/recent.txt
on Linux, ~/Library/Application Support/H7CAD/recent.txt on macOS), up
to 20 entries, saved on every open and rehydrated at boot.
Each row opens or removes the file in place.
- The Support ribbon group (Donate / Report / About / Changelog) is
retired; the same actions surface on the welcome page instead. The
Donate button is the primary call-to-action — solid #B03020 brand
fill with a soft brand-tinted ambient glow halo behind the action row
(Thunderbird-style coloured glow against the neutral page).
- Drawing-only overlays (mouse-area, viewcube, navbar, dynamic input,
selection markers) are suppressed on the Start tab so its clicks
don't trigger CAD commands. OpenFile's "current_is_empty" check
refuses to replace the Start tab, and TabClose ignores requests on
it.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 16:19:31 +03:00
|
|
|
tabs: vec![start_tab],
|
2026-03-22 22:44:32 +03:00
|
|
|
active_tab: 0,
|
feat(app): add fixed Start tab with welcome page and recent documents
A pinned "Start" tab now lives at index 0. It cannot be closed, opens
on launch, and replaces the model-space viewport with a welcome page —
similar to Thunderbird's first-run screen.
- Boot opens with only the Start tab; Drawing1 is no longer auto-spawned.
- The Properties left panel becomes a Recent Documents rail on the
Start tab. Recents persist to disk
(%APPDATA%/H7CAD/recent.txt on Windows, $XDG_CONFIG_HOME/H7CAD/recent.txt
on Linux, ~/Library/Application Support/H7CAD/recent.txt on macOS), up
to 20 entries, saved on every open and rehydrated at boot.
Each row opens or removes the file in place.
- The Support ribbon group (Donate / Report / About / Changelog) is
retired; the same actions surface on the welcome page instead. The
Donate button is the primary call-to-action — solid #B03020 brand
fill with a soft brand-tinted ambient glow halo behind the action row
(Thunderbird-style coloured glow against the neutral page).
- Drawing-only overlays (mouse-area, viewcube, navbar, dynamic input,
selection markers) are suppressed on the Start tab so its clicks
don't trigger CAD commands. OpenFile's "current_is_empty" check
refuses to replace the Start tab, and TabClose ignores requests on
it.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 16:19:31 +03:00
|
|
|
tab_counter: 0,
|
2026-03-22 22:44:32 +03:00
|
|
|
ribbon: Ribbon::new(),
|
feat(app): add fixed Start tab with welcome page and recent documents
A pinned "Start" tab now lives at index 0. It cannot be closed, opens
on launch, and replaces the model-space viewport with a welcome page —
similar to Thunderbird's first-run screen.
- Boot opens with only the Start tab; Drawing1 is no longer auto-spawned.
- The Properties left panel becomes a Recent Documents rail on the
Start tab. Recents persist to disk
(%APPDATA%/H7CAD/recent.txt on Windows, $XDG_CONFIG_HOME/H7CAD/recent.txt
on Linux, ~/Library/Application Support/H7CAD/recent.txt on macOS), up
to 20 entries, saved on every open and rehydrated at boot.
Each row opens or removes the file in place.
- The Support ribbon group (Donate / Report / About / Changelog) is
retired; the same actions surface on the welcome page instead. The
Donate button is the primary call-to-action — solid #B03020 brand
fill with a soft brand-tinted ambient glow halo behind the action row
(Thunderbird-style coloured glow against the neutral page).
- Drawing-only overlays (mouse-area, viewcube, navbar, dynamic input,
selection markers) are suppressed on the Start tab so its clicks
don't trigger CAD commands. OpenFile's "current_is_empty" check
refuses to replace the Start tab, and TabClose ignores requests on
it.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 16:19:31 +03:00
|
|
|
app_menu,
|
2026-03-22 22:44:32 +03:00
|
|
|
command_line: CommandLine::new(),
|
|
|
|
|
status_bar: StatusBar::new(),
|
|
|
|
|
cursor_pos: Point::ORIGIN,
|
|
|
|
|
vp_size: (1280.0, 720.0),
|
|
|
|
|
snapper: Snapper::default(),
|
|
|
|
|
snap_popup_open: false,
|
2026-05-06 17:50:46 +03:00
|
|
|
scale_popup_open: false,
|
2026-03-22 22:44:32 +03:00
|
|
|
pre_cmd_tangent: None,
|
|
|
|
|
ortho_mode: false,
|
|
|
|
|
polar_mode: false,
|
2026-04-07 19:30:25 +03:00
|
|
|
polar_increment_deg: 45.0,
|
2026-03-22 22:44:32 +03:00
|
|
|
show_grid: false,
|
2026-04-08 00:12:09 +03:00
|
|
|
dyn_input: true,
|
2026-05-27 22:52:55 +03:00
|
|
|
awaiting_vports: false,
|
2026-05-28 18:42:39 +03:00
|
|
|
tile_drag: None,
|
2026-05-29 00:57:37 +03:00
|
|
|
dyn_user_reshaped: false,
|
feat(grip): multi-functional hover popup menu
Phase 2 of the grip overhaul: when the cursor dwells on a selected
entity's grip the overlay opens a popup menu with entity-specific
options (Stretch / Add Vertex / Remove Vertex / Convert to
Arc-Line / …). Picking an item dispatches `apply_grip_menu`; the
default `Stretch` entry is a no-op that lets the user click the
grip to start the normal drag.
- `GripMenuItem` + `GripMenuAction` (full vocabulary covering
stretch, add/remove vertex, convert to arc/line, reverse arrows,
text-position variants, hatch parameters, tangent direction).
- `Grippable` trait gains `grip_menu` + `apply_grip_menu` with
trait-default `Stretch`-only impls so every existing entity gets
the popup for free.
- `EntityTypeOps` dispatches both methods across the same entity
list as the existing grip / property machinery.
- `OpenCADStudio` carries `grip_hover` (dwell tracker) +
`grip_popup` (open menu). `update_grip_hover` runs from
`ViewportMove`: hit-tests grips, refreshes the dwell timer, and
opens the popup after 600 ms. Cursor drift past 80 px or a grip
click dismisses it.
- `view.rs` paints the popup as an absolute-positioned column of
buttons at the grip's screen anchor; each button publishes
`Message::GripMenuPick(idx)`.
- `LwPolyline` overrides `Grippable` manually (skips the standard
macro) so its vertex grip exposes Add/Remove Vertex and its
segment-midpoint grip exposes Add Vertex + Convert to Arc/Line.
`apply_grip_menu` performs the edits: vertex insertion at the
chord midpoint inheriting the previous bulge, vertex removal,
and bulge toggle for the arc/line conversion.
2026-05-29 09:30:54 +03:00
|
|
|
grip_hover: None,
|
|
|
|
|
grip_popup: None,
|
2026-05-29 14:58:35 +03:00
|
|
|
grip_pending: None,
|
2026-06-03 16:17:29 +03:00
|
|
|
grip_add_provisional: None,
|
2026-05-29 20:30:56 +03:00
|
|
|
qselect: None,
|
2026-04-03 19:25:55 +03:00
|
|
|
show_ucs_icon: true,
|
2026-04-24 01:23:06 +03:00
|
|
|
show_viewcube: true,
|
|
|
|
|
show_properties: true,
|
|
|
|
|
show_file_tabs: true,
|
|
|
|
|
show_layout_tabs: true,
|
2026-03-22 22:44:32 +03:00
|
|
|
last_point: None,
|
|
|
|
|
layer_window: None,
|
|
|
|
|
main_window: None,
|
2026-05-12 10:36:15 +03:00
|
|
|
page_setup_window: None,
|
|
|
|
|
textstyle_window: None,
|
|
|
|
|
tablestyle_window: None,
|
|
|
|
|
mlstyle_window: None,
|
2026-04-08 22:36:28 +03:00
|
|
|
layout_manager_window: None,
|
2026-05-12 10:36:15 +03:00
|
|
|
plotstyle_window: None,
|
|
|
|
|
dimstyle_window: None,
|
|
|
|
|
shortcuts_window: None,
|
|
|
|
|
about_window: None,
|
2026-05-16 19:55:57 +03:00
|
|
|
update_notice_window: None,
|
|
|
|
|
update_notice_version: None,
|
2026-05-22 12:55:37 +03:00
|
|
|
update_notice_body: None,
|
2026-03-22 22:44:32 +03:00
|
|
|
clipboard: Vec::new(),
|
|
|
|
|
clipboard_centroid: glam::Vec3::ZERO,
|
2026-05-30 21:54:35 +03:00
|
|
|
shift_down: false,
|
feat: Implement in-place MText editor with formatting toolbar and live preview
- Added SVG icons for text formatting options (bold, italic, underline, overline, strike, uppercase, lowercase).
- Introduced a new `MTextEditorState` struct to manage the state of the MText editor.
- Implemented functionality to open the MText editor from the command line and edit existing MText entities.
- Added message handling for various MText editor actions (formatting, height, style, font, justification, alignment, line spacing).
- Created a dedicated preview area for MText rendering within the editor.
- Updated the command structure to support opening the MText editor and handling text input.
- Refactored existing MText command logic to integrate with the new editor.
2026-05-30 23:24:06 +03:00
|
|
|
mtext_editor: None,
|
2026-05-31 14:44:18 +03:00
|
|
|
text_inline: None,
|
2026-03-31 07:13:36 +03:00
|
|
|
layout_context_menu: None,
|
|
|
|
|
layout_rename_state: None,
|
2026-03-31 07:24:16 +03:00
|
|
|
last_vp_click_time: None,
|
|
|
|
|
last_vp_click_pos: None,
|
2026-03-31 08:05:17 +03:00
|
|
|
page_setup_w: String::new(),
|
|
|
|
|
page_setup_h: String::new(),
|
2026-04-01 00:12:07 +03:00
|
|
|
page_setup_plot_area: "Layout".to_string(),
|
|
|
|
|
page_setup_center: true,
|
|
|
|
|
page_setup_offset_x: "0.0".to_string(),
|
|
|
|
|
page_setup_offset_y: "0.0".to_string(),
|
|
|
|
|
page_setup_rotation: "0".to_string(),
|
Feat: ROADMAP Phase 1-5 batch — trim/extend, plot scale, viewport UX, mline caps, table alignment
- TRIM/EXTEND: Ray and XLine entities supported as cutting boundaries and trim targets;
trim results in Ray→Line/Ray, XLine→Ray/Ray or Line; hover preview added
- Plot scale override: Fit/1:1/1:2/1:5/1:10/1:20/1:50/1:100/2:1 buttons added to Page Setup
panel; PageSetupCommit applies scale to PlotSettings
- Viewport UX: locked viewport gets orange border; selected viewport gets white border
- Named view → viewport assignment: Choice dropdown in properties panel copies
view_target/direction/height to the viewport entity
- MLine caps: perpendicular start/end cap lines added for open (non-closed) MLines
- Wipeout polygon grips: boundary vertices become editable grips when clipping_enabled+Polygonal;
world→pixel back-projection on drag
- Table cell alignment: CellStyle.alignment (1-9) drives left/center/right + top/middle/bottom
text positioning; cxf::measure_text() helper added
- New commands/features: DIMCONTINUE (DCO), DIMBASELINE (DBA), TOLERANCE (TOL), TABLE,
ATTDEF, ATTEDIT, ATTDISP, BREAKATPOINT (BAP), PLOTWINDOW (PW), MLINE (ML);
DIMSTYLE/DIMCURRENT/CLAYER/LTSCALE/CELTSCALE/SCALETEXT/XDATA/UCSICON/REGEN/DRAWORDER extended
- PLINE arc mode: A/L/CLOSE text input, bulge computation, arc preview
- Warning cleanup: removed unused imports and unreachable patterns
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 15:31:05 +03:00
|
|
|
page_setup_scale: "Fit".to_string(),
|
2026-05-13 09:59:07 +03:00
|
|
|
opening: None,
|
2026-05-01 11:31:11 +03:00
|
|
|
pending_close: None,
|
|
|
|
|
unsaved_dialog_window: None,
|
2026-05-01 16:12:32 +03:00
|
|
|
save_dialog_window: None,
|
|
|
|
|
save_dialog_format: "DWG 2018".to_string(),
|
|
|
|
|
save_dialog_filename: "drawing.dwg".to_string(),
|
|
|
|
|
save_dialog_folder: std::env::var("HOME")
|
|
|
|
|
.or_else(|_| std::env::var("USERPROFILE"))
|
2026-05-01 16:20:38 +03:00
|
|
|
.unwrap_or_else(|_| ".".to_string())
|
|
|
|
|
.into(),
|
|
|
|
|
save_dialog_entries: Vec::new(),
|
2026-05-01 16:12:32 +03:00
|
|
|
save_dialog_for_unsaved: false,
|
2026-04-06 14:37:23 +03:00
|
|
|
// Plot style
|
|
|
|
|
active_plot_style: None,
|
2026-04-08 18:31:09 +03:00
|
|
|
// Color scheme (default: dark CAD-style)
|
|
|
|
|
active_theme: Theme::Dark,
|
2026-04-08 18:34:10 +03:00
|
|
|
// Keyboard shortcuts
|
|
|
|
|
shortcut_overrides: std::collections::HashMap::new(),
|
2026-04-08 18:28:30 +03:00
|
|
|
// Layout Manager
|
|
|
|
|
layout_manager_selected: "Model".to_string(),
|
|
|
|
|
layout_manager_rename_buf: String::new(),
|
2026-04-08 18:19:48 +03:00
|
|
|
plotstyle_panel_aci: 1,
|
|
|
|
|
ps_color_buf: String::new(),
|
|
|
|
|
ps_lineweight_buf: "255".to_string(),
|
|
|
|
|
ps_screening_buf: "100".to_string(),
|
2026-04-06 15:01:25 +03:00
|
|
|
// TextStyle font browser
|
|
|
|
|
textstyle_selected: "Standard".to_string(),
|
|
|
|
|
textstyle_font: String::new(),
|
|
|
|
|
textstyle_width: "1.0".to_string(),
|
|
|
|
|
textstyle_oblique: "0.0".to_string(),
|
2026-06-03 17:43:12 +03:00
|
|
|
textstyle_height: "0.0".to_string(),
|
|
|
|
|
textstyle_bigfont: String::new(),
|
|
|
|
|
textstyle_ttf: String::new(),
|
2026-04-06 14:57:11 +03:00
|
|
|
// TableStyle dialog
|
|
|
|
|
tablestyle_selected: "Standard".to_string(),
|
2026-04-06 14:54:05 +03:00
|
|
|
// MLineStyle dialog
|
|
|
|
|
mlstyle_selected: "Standard".to_string(),
|
2026-04-06 14:25:13 +03:00
|
|
|
// DimStyle dialog
|
|
|
|
|
dimstyle_selected: "Standard".to_string(),
|
|
|
|
|
dimstyle_tab: 0,
|
2026-05-12 10:36:15 +03:00
|
|
|
ds_dimdle: "0".to_string(),
|
|
|
|
|
ds_dimdli: "3.75".to_string(),
|
|
|
|
|
ds_dimgap: "0.625".to_string(),
|
|
|
|
|
ds_dimexe: "1.25".to_string(),
|
2026-04-06 14:25:13 +03:00
|
|
|
ds_dimexo: "0.625".to_string(),
|
2026-05-12 10:36:15 +03:00
|
|
|
ds_dimsd1: false,
|
|
|
|
|
ds_dimsd2: false,
|
|
|
|
|
ds_dimse1: false,
|
|
|
|
|
ds_dimse2: false,
|
|
|
|
|
ds_dimasz: "0.18".to_string(),
|
|
|
|
|
ds_dimcen: "0.09".to_string(),
|
2026-04-06 14:25:13 +03:00
|
|
|
ds_dimtsz: "0".to_string(),
|
2026-05-12 10:36:15 +03:00
|
|
|
ds_dimtxt: "0.18".to_string(),
|
|
|
|
|
ds_dimtxsty: "Standard".to_string(),
|
2026-04-06 14:25:13 +03:00
|
|
|
ds_dimtad: "1".to_string(),
|
2026-05-12 10:36:15 +03:00
|
|
|
ds_dimtih: false,
|
|
|
|
|
ds_dimtoh: false,
|
|
|
|
|
ds_dimscale: "1".to_string(),
|
|
|
|
|
ds_dimlfac: "1".to_string(),
|
|
|
|
|
ds_dimlunit: "2".to_string(),
|
|
|
|
|
ds_dimdec: "2".to_string(),
|
2026-04-06 14:25:13 +03:00
|
|
|
ds_dimpost: "<>".to_string(),
|
2026-05-12 10:36:15 +03:00
|
|
|
ds_dimtol: false,
|
|
|
|
|
ds_dimlim: false,
|
|
|
|
|
ds_dimtp: "0".to_string(),
|
|
|
|
|
ds_dimtm: "0".to_string(),
|
|
|
|
|
ds_dimtdec: "2".to_string(),
|
|
|
|
|
ds_dimtfac: "1".to_string(),
|
2026-06-03 17:49:38 +03:00
|
|
|
ds_annotative: false,
|
feat(dimstyle): expand Dimension Style editor to the full field set
Adds the remaining ~40 DIMSTYLE variables across the tabs (and a new
Alternate Units tab): dim/ext line colors+lineweights, fixed-length ext
lines, separate arrow blocks, arc symbol, jog angle, text color/justify/
vertical pos/fill/direction, fit options (DIMATFIT/DIMTIX/DIMSOXD/DIMTMOVE/
DIMUPT/DIMTOFL/DIMFIT), decimal separator, rounding, zero suppression,
fraction + angular units, alternate units, and tolerance vert-just/zero
suppression. Only the arrowhead-block / extension-line-linetype Handle
references are left (they need a block/linetype picker).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 20:09:08 +03:00
|
|
|
ds_dimclrd: "0".to_string(),
|
|
|
|
|
ds_dimlwd: "-2".to_string(),
|
|
|
|
|
ds_dimclre: "0".to_string(),
|
|
|
|
|
ds_dimlwe: "-2".to_string(),
|
|
|
|
|
ds_dimfxl: "1".to_string(),
|
|
|
|
|
ds_dimfxlon: false,
|
|
|
|
|
ds_dimsah: false,
|
|
|
|
|
ds_dimarcsym: "0".to_string(),
|
|
|
|
|
ds_dimjogang: "45".to_string(),
|
|
|
|
|
ds_dimclrt: "0".to_string(),
|
|
|
|
|
ds_dimjust: "0".to_string(),
|
|
|
|
|
ds_dimtvp: "0".to_string(),
|
|
|
|
|
ds_dimtfill: "0".to_string(),
|
|
|
|
|
ds_dimtfillclr: "0".to_string(),
|
|
|
|
|
ds_dimtxtdirection: false,
|
|
|
|
|
ds_dimatfit: "3".to_string(),
|
|
|
|
|
ds_dimtix: false,
|
|
|
|
|
ds_dimsoxd: false,
|
|
|
|
|
ds_dimtmove: "0".to_string(),
|
|
|
|
|
ds_dimupt: false,
|
|
|
|
|
ds_dimtofl: false,
|
|
|
|
|
ds_dimfit: "3".to_string(),
|
|
|
|
|
ds_dimdsep: "46".to_string(),
|
|
|
|
|
ds_dimrnd: "0".to_string(),
|
|
|
|
|
ds_dimzin: "0".to_string(),
|
|
|
|
|
ds_dimfrac: "0".to_string(),
|
|
|
|
|
ds_dimaunit: "0".to_string(),
|
|
|
|
|
ds_dimadec: "0".to_string(),
|
|
|
|
|
ds_dimazin: "0".to_string(),
|
|
|
|
|
ds_dimalt: false,
|
|
|
|
|
ds_dimaltf: "25.4".to_string(),
|
|
|
|
|
ds_dimaltd: "2".to_string(),
|
|
|
|
|
ds_dimaltu: "2".to_string(),
|
|
|
|
|
ds_dimalttd: "2".to_string(),
|
|
|
|
|
ds_dimaltrnd: "0".to_string(),
|
|
|
|
|
ds_dimapost: String::new(),
|
|
|
|
|
ds_dimaltz: "0".to_string(),
|
|
|
|
|
ds_dimalttz: "0".to_string(),
|
|
|
|
|
ds_dimtolj: "1".to_string(),
|
|
|
|
|
ds_dimtzin: "0".to_string(),
|
2026-03-22 22:44:32 +03:00
|
|
|
};
|
|
|
|
|
app.sync_ribbon_layers();
|
|
|
|
|
app
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Boot function for `iced::daemon`: returns initial state plus a task that
|
|
|
|
|
/// opens the primary application window.
|
|
|
|
|
fn boot() -> (Self, Task<Message>) {
|
|
|
|
|
use helpers::build_window_icon;
|
|
|
|
|
let state = Self::new();
|
|
|
|
|
let (id, open_task) = window::open(window::Settings {
|
|
|
|
|
maximized: true,
|
|
|
|
|
icon: window::icon::from_rgba(build_window_icon(), 32, 32).ok(),
|
2026-05-01 11:31:11 +03:00
|
|
|
exit_on_close_request: false,
|
2026-03-22 22:44:32 +03:00
|
|
|
..Default::default()
|
|
|
|
|
});
|
|
|
|
|
let mut s = state;
|
|
|
|
|
s.main_window = Some(id);
|
2026-05-16 19:55:57 +03:00
|
|
|
let open_main = open_task.map(|_| Message::Noop);
|
|
|
|
|
let check_update = Task::perform(
|
|
|
|
|
crate::update_check::check_for_update(),
|
|
|
|
|
Message::UpdateCheckResult,
|
|
|
|
|
);
|
2026-05-26 18:59:51 +03:00
|
|
|
let focus_cmd = s.focus_cmd_input();
|
feat(packaging): open files from argv, ship Windows MSI installer (#36)
Cross-platform prerequisite for OS-level file associations: the boot
task now consumes `std::env::args_os().nth(1)` as a path and dispatches
`Message::OpenRecent` for it. Flag-style args (starting with `-`) are
ignored, and `OpenRecent` already does the file-existence check, so a
bogus path lands as a clean command-line error instead of a panic.
Linux: the AppImage's desktop entry adds `image/vnd.dwg` alongside the
existing `image/vnd.dxf` MIME type and grows a `%f` placeholder in
`Exec=` so the launcher forwards the picked file path. Once the
AppImage is integrated with the system (xdg-mime / AppImageLauncher),
double-clicking a .dwg / .dxf opens it in Open CAD Studio.
Windows: new `packaging/windows/main.wxs` defines an MSI installer —
per-machine install to `Program Files\Open CAD Studio`, Start Menu
shortcut, `MajorUpgrade` so newer MSIs replace older ones, and a
single ProgID `OpenCADStudio.Drawing` that owns both `.dwg` and
`.dxf` and launches the exe with the file as `argv[1]`. The icon is
pulled from the SVG logo, converted to multi-resolution ICO at build
time with the runner's pre-installed ImageMagick. CI now also runs
`candle` / `light` (WiX Toolset 3, pre-installed on `windows-latest`)
and uploads both the bare `.exe` (portable) and `.msi` (installer)
to the release.
macOS Info.plist already declares the DWG / DXF UTIs; combined with
the argv-handling change above, double-clicking a drawing in Finder
will open it in Open CAD Studio without further packaging changes.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 23:34:14 +03:00
|
|
|
// Open a file path passed on the command line. Used by the
|
|
|
|
|
// OS file association: double-clicking a .dwg in the file
|
|
|
|
|
// explorer launches the binary with the file path as the
|
|
|
|
|
// first positional argument. Unknown / flag-style args are
|
|
|
|
|
// ignored. `Message::OpenRecent` does the existence check
|
|
|
|
|
// and reports a clean error if the path is bogus.
|
|
|
|
|
let cli_open: Task<Message> = std::env::args_os()
|
|
|
|
|
.nth(1)
|
|
|
|
|
.map(PathBuf::from)
|
|
|
|
|
.filter(|p| !p.as_os_str().to_string_lossy().starts_with('-'))
|
|
|
|
|
.map(|p| Task::done(Message::OpenRecent(p)))
|
|
|
|
|
.unwrap_or_else(Task::none);
|
|
|
|
|
(
|
|
|
|
|
s,
|
|
|
|
|
Task::batch([open_main, check_update, focus_cmd, cli_open]),
|
|
|
|
|
)
|
2026-03-22 22:44:32 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
use std::path::PathBuf;
|
|
|
|
|
|
|
|
|
|
pub fn run() -> iced::Result {
|
2026-05-23 17:16:15 +03:00
|
|
|
iced::daemon(OpenCADStudio::boot, OpenCADStudio::update, OpenCADStudio::view)
|
|
|
|
|
.subscription(OpenCADStudio::subscription)
|
|
|
|
|
.title(|state: &OpenCADStudio, window_id: window::Id| {
|
2026-05-12 10:36:15 +03:00
|
|
|
if Some(window_id) == state.layer_window {
|
|
|
|
|
return "Layer Properties Manager".into();
|
|
|
|
|
}
|
|
|
|
|
if Some(window_id) == state.page_setup_window {
|
|
|
|
|
return "Page Setup".into();
|
|
|
|
|
}
|
|
|
|
|
if Some(window_id) == state.textstyle_window {
|
|
|
|
|
return "Text Style".into();
|
|
|
|
|
}
|
|
|
|
|
if Some(window_id) == state.tablestyle_window {
|
|
|
|
|
return "Table Style".into();
|
|
|
|
|
}
|
|
|
|
|
if Some(window_id) == state.mlstyle_window {
|
|
|
|
|
return "Multiline Style".into();
|
|
|
|
|
}
|
|
|
|
|
if Some(window_id) == state.layout_manager_window {
|
|
|
|
|
return "Layout Manager".into();
|
|
|
|
|
}
|
|
|
|
|
if Some(window_id) == state.plotstyle_window {
|
|
|
|
|
return "Plot Style Table Editor".into();
|
|
|
|
|
}
|
|
|
|
|
if Some(window_id) == state.dimstyle_window {
|
|
|
|
|
return "Dimension Style Manager".into();
|
|
|
|
|
}
|
|
|
|
|
if Some(window_id) == state.shortcuts_window {
|
|
|
|
|
return "Keyboard Shortcuts".into();
|
|
|
|
|
}
|
|
|
|
|
if Some(window_id) == state.about_window {
|
2026-05-23 17:16:15 +03:00
|
|
|
return "About Open CAD Studio".into();
|
2026-05-12 10:36:15 +03:00
|
|
|
}
|
2026-05-16 19:55:57 +03:00
|
|
|
if Some(window_id) == state.update_notice_window {
|
|
|
|
|
return "Update Available".into();
|
|
|
|
|
}
|
2026-05-12 10:36:15 +03:00
|
|
|
if Some(window_id) == state.unsaved_dialog_window {
|
|
|
|
|
return "Unsaved Changes".into();
|
|
|
|
|
}
|
|
|
|
|
if Some(window_id) == state.save_dialog_window {
|
|
|
|
|
return "Save As".into();
|
|
|
|
|
}
|
2026-04-08 22:36:28 +03:00
|
|
|
if let Some(tab) = state.tabs.get(state.active_tab) {
|
2026-03-22 22:44:32 +03:00
|
|
|
let dot = if tab.dirty { "● " } else { "" };
|
|
|
|
|
let name = tab.tab_display_name();
|
2026-05-23 17:16:15 +03:00
|
|
|
format!("{}Open CAD Studio — {}", dot, name)
|
2026-03-22 22:44:32 +03:00
|
|
|
} else {
|
2026-05-23 17:16:15 +03:00
|
|
|
"Open CAD Studio".to_string()
|
2026-03-22 22:44:32 +03:00
|
|
|
}
|
|
|
|
|
})
|
2026-05-23 17:16:15 +03:00
|
|
|
.theme(|state: &OpenCADStudio, _| state.active_theme.clone())
|
2026-03-22 22:44:32 +03:00
|
|
|
.run()
|
|
|
|
|
}
|