mod document; mod helpers; mod history; mod properties; mod layers; mod commands; mod cmd_result; mod view; mod update; use document::DocumentTab; use acadrust::types::{Color as AcadColor, LineWeight}; use acadrust::CadDocument; use crate::modules::ModuleEvent; use crate::scene::CubeRegion; use crate::snap::Snapper; use crate::ui::{AppMenu, CommandLine, Ribbon, StatusBar}; use iced::time::Instant; use iced::window; use iced::{mouse, Point, Task, Theme}; pub(super) const POLY_START_DELAY_MS: u128 = 150; pub(super) const VARIES_LABEL: &str = "*VARIES*"; // ── Application state ────────────────────────────────────────────────────── pub(super) struct H7CAD { start: Instant, tabs: Vec, 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, /// Whether Tangent snap was enabled before a tangent-pick command started. pre_cmd_tangent: Option, /// Orthogonal drawing constraint (F8): constrains picks to 0°/90°/180°/270°. ortho_mode: bool, /// Polar tracking (F10): constrains picks to 45° angle increments. polar_mode: bool, /// Show grid lines in the viewport (F7). show_grid: bool, /// Last point committed by a drawing command — used as ortho/polar base. last_point: Option, /// OS window Id for the floating Layer Properties Manager (None when closed). layer_window: Option, /// OS window Id of the primary application window. main_window: Option, /// In-memory clipboard: cloned entities waiting to be pasted. clipboard: Vec, /// Centroid of the clipboard entities (XZ plane, Y-up). clipboard_centroid: glam::Vec3, /// Which layout tab has its context menu open (None = closed). layout_context_menu: Option, /// Inline rename state: (original_name, current_edit_value). layout_rename_state: Option<(String, String)>, /// Timestamp of the previous viewport left-click release (for double-click detection). last_vp_click_time: Option, /// Screen position of the previous viewport left-click release. last_vp_click_pos: Option, /// Page Setup overlay open/closed. page_setup_open: bool, /// 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, } #[derive(Debug, Clone)] pub enum Message { Tick(Instant), OpenFile, FileOpened(Result<(String, PathBuf, CadDocument), String>), SaveFile, SaveAs, PickedSavePath(Option), ClearScene, SetWireframe(bool), /// 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), // ───────────────────────────────────────────────────────────────────── CommandInput(String), CommandSubmit, Command(String), ToggleLayers, LayerToggleVisible(usize), LayerToggleLock(usize), LayerToggleFreeze(usize), 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), WindowResized(f32, f32), /// Enter pressed globally — finalises the active command (no text-input involvement). CommandFinalize, /// 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, /// Toggle grid display in the viewport — F7 / GRID status-bar button. ToggleGrid, /// Toggle orthogonal drawing constraint — F8 / ORTHO status-bar button. ToggleOrtho, /// Toggle polar-angle constraint (45° increments) — F10 / POLAR status-bar button. TogglePolar, /// 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), // ── 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, /// Enter the model-space editing mode inside the given viewport (MSPACE). EnterViewport(acadrust::Handle), /// Exit MSPACE and return to paper-space editing (PSPACE). ExitViewport, /// MS command: enter MSPACE for the first available viewport. MspaceCommand, /// PS command: exit MSPACE (PSPACE). PspaceCommand, /// Switch to a named layout ("Model" or paper space layout name). LayoutSwitch(String), /// Create a new paper space layout. LayoutCreate, /// 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, /// A window was closed by the OS (e.g. the user clicked the title-bar ✕). OsWindowClosed(window::Id), /// No-op — used as a fallback when a TabEvent has no host mapping. Noop, // ── 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), /// 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), } impl H7CAD { fn new() -> Self { let first_tab = DocumentTab::new_drawing(1); let mut app = Self { start: Instant::now(), tabs: vec![first_tab], active_tab: 0, tab_counter: 1, ribbon: Ribbon::new(), app_menu: AppMenu::new(), 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, pre_cmd_tangent: None, ortho_mode: false, polar_mode: false, show_grid: false, last_point: None, layer_window: None, main_window: None, clipboard: Vec::new(), clipboard_centroid: glam::Vec3::ZERO, layout_context_menu: None, layout_rename_state: None, last_vp_click_time: None, last_vp_click_pos: None, page_setup_open: false, page_setup_w: String::new(), page_setup_h: String::new(), }; 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) { 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(), ..Default::default() }); let mut s = state; s.main_window = Some(id); let task = open_task.map(|_| Message::Noop); (s, task) } } use std::path::PathBuf; pub fn run() -> iced::Result { iced::daemon(H7CAD::boot, H7CAD::update, H7CAD::view) .subscription(H7CAD::subscription) .title(|state: &H7CAD, window_id: window::Id| { if Some(window_id) == state.layer_window { "Layer Properties Manager".to_string() } else if let Some(tab) = state.tabs.get(state.active_tab) { let dot = if tab.dirty { "● " } else { "" }; let name = tab.tab_display_name(); format!("{}H7CAD — {}", dot, name) } else { "H7CAD".to_string() } }) .theme(Theme::Dark) .run() }