feat: view tab widget toggle commands and unified SVG icon color scheme

Integrate NAVVCUBE, NAVBAR, PROPERTIES, FILETAB, LAYOUTTAB, TOOLPALETTES
and SHEETSET commands to toggle UI widgets at runtime. Add 15 new SVG icons
for view module tools previously using glyph fallbacks. Restyle all SVG
icons to use the established two-color scheme: #e0e0e0 for passive geometry,
#4cc9f0 for active/accent elements.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
This commit is contained in:
Hakan SevenandClaude Sonnet 4.6 committed 2026-04-24 01:23:06 +03:00
1 parent 6a0125e4d6
commit 1ec53a4730
81 files changed
+533 -597

No files matched your search

+35
View File
@@ -3706,6 +3706,41 @@ impl H7CAD {
}
}
// ── NAVVCUBE — toggle ViewCube visibility ────────────────────────────
"NAVVCUBE" => {
return Task::done(Message::ToggleViewCube);
}
// ── NAVBAR — toggle navigation toolbar visibility ────────────────────
"NAVBAR" => {
return Task::done(Message::ToggleNavbar);
}
// ── PROPERTIES — toggle Properties panel visibility ──────────────────
"PROPERTIES" | "PR" | "PROPS" => {
return Task::done(Message::ToggleProperties);
}
// ── FILETAB — toggle file/document tabs ──────────────────────────────
"FILETAB" => {
return Task::done(Message::ToggleFileTabs);
}
// ── LAYOUTTAB — toggle layout/paper-space tabs ───────────────────────
"LAYOUTTAB" => {
return Task::done(Message::ToggleLayoutTabs);
}
// ── TOOLPALETTES — not yet implemented ───────────────────────────────
"TOOLPALETTES" | "TP" => {
self.command_line.push_info("TOOLPALETTES: Tool Palettes not yet implemented.");
}
// ── SHEETSET — not yet implemented ───────────────────────────────────
"SHEETSET" | "SSM" => {
self.command_line.push_info("SHEETSET: Sheet Set Manager not yet implemented.");
}
// ── XDATA — read/write extended entity data ──────────────────────────
// XDATA LIST — show all xdata records on selected entities
// XDATA SET <app> <str> — append a string xdata value for <app>
+25
View File
@@ -53,6 +53,16 @@ pub(super) struct H7CAD {
dyn_input: bool,
/// Show the UCS icon in the bottom-left corner of model space (UCSICON).
show_ucs_icon: bool,
/// Whether the ViewCube 3D gizmo is visible in model space (NAVVCUBE).
show_viewcube: bool,
/// Whether the navigation toolbar is shown in the viewport (NAVBAR).
show_navbar: 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,
/// 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).
@@ -248,6 +258,16 @@ pub enum Message {
ToggleSnapEnabled,
/// Toggle grid-snap on/off — F9 / SNAP status-bar button.
ToggleGridSnap,
/// Toggle the ViewCube 3D gizmo visibility (NAVVCUBE).
ToggleViewCube,
/// Toggle the navigation toolbar visibility (NAVBAR).
ToggleNavbar,
/// 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,
/// Toggle grid display in the viewport — F7 / GRID status-bar button.
ToggleGrid,
/// Toggle orthogonal drawing constraint — F8 / ORTHO status-bar button.
@@ -545,6 +565,11 @@ impl H7CAD {
show_grid: false,
dyn_input: true,
show_ucs_icon: true,
show_viewcube: true,
show_navbar: true,
show_properties: true,
show_file_tabs: true,
show_layout_tabs: true,
last_point: None,
layer_window: None,
main_window: None,
+5
View File
@@ -1878,6 +1878,11 @@ impl H7CAD {
Task::none()
}
Message::ToggleDynInput => { self.dyn_input ^= true; Task::none() }
Message::ToggleViewCube => { self.show_viewcube ^= true; Task::none() }
Message::ToggleNavbar => { self.show_navbar ^= true; Task::none() }
Message::ToggleProperties => { self.show_properties ^= true; Task::none() }
Message::ToggleFileTabs => { self.show_file_tabs ^= true; Task::none() }
Message::ToggleLayoutTabs => { self.show_layout_tabs ^= true; Task::none() }
Message::ToggleOTrack => {
self.snapper.otrack_enabled ^= true;
if !self.snapper.otrack_enabled {
+53 -42
View File
@@ -189,14 +189,9 @@ impl H7CAD {
vec![]
};
overlay::selection_overlay(sel, snap_info, grips, grid, ucs_icon, ost_points, tab.last_cursor_screen, !is_paper)
overlay::selection_overlay(sel, snap_info, grips, grid, ucs_icon, ost_points, tab.last_cursor_screen, !is_paper && self.show_viewcube)
};
let nav = container(overlay::nav_toolbar())
.align_right(Fill)
.align_top(Fill)
.padding(iced::Padding { top: 148.0, right: 8.0, bottom: 0.0, left: 0.0 });
let info = container(overlay::info_bar(
if is_paper { &tab.scene.current_layout } else { "Custom View" },
&tab.visual_style,
@@ -216,21 +211,6 @@ impl H7CAD {
.on_scroll(Message::ViewportScroll)
.on_exit(Message::ViewportExit);
let cube_click = mouse_area(container(
iced::widget::Space::new()
.width(iced::Length::Fixed(VIEWCUBE_HIT_SIZE))
.height(iced::Length::Fixed(VIEWCUBE_HIT_SIZE)),
))
.on_move(Message::CursorMoved)
.on_press(Message::ViewportClick);
let cube_click = container(cube_click)
.align_right(Fill)
.align_top(Fill)
.padding(iced::Padding { top: VIEWCUBE_PAD, right: VIEWCUBE_PAD, bottom: 0.0, left: 0.0 })
.width(Fill)
.height(Fill);
let bg_color = if is_paper {
// Desk color — matches the DESK constant in paper_canvas.rs.
Color { r: 0.22, g: 0.24, b: 0.28, a: 1.0 }
@@ -270,36 +250,67 @@ impl H7CAD {
container(info).width(Fill).height(Fill),
selection_overlay,
viewport_mouse,
nav,
cube_click,
]
.width(Fill)
.height(Fill);
if self.show_navbar {
let nav = container(overlay::nav_toolbar())
.align_right(Fill)
.align_top(Fill)
.padding(iced::Padding { top: 148.0, right: 8.0, bottom: 0.0, left: 0.0 });
viewport_stack = viewport_stack.push(nav);
}
if self.show_viewcube && !is_paper {
let cube_click: Element<'_, Message> = container(
mouse_area(container(
iced::widget::Space::new()
.width(iced::Length::Fixed(VIEWCUBE_HIT_SIZE))
.height(iced::Length::Fixed(VIEWCUBE_HIT_SIZE)),
))
.on_move(Message::CursorMoved)
.on_press(Message::ViewportClick),
)
.align_right(Fill)
.align_top(Fill)
.padding(iced::Padding { top: VIEWCUBE_PAD, right: VIEWCUBE_PAD, bottom: 0.0, left: 0.0 })
.width(Fill)
.height(Fill)
.into();
viewport_stack = viewport_stack.push(cube_click);
}
if let Some(dyn_ol) = dyn_input_overlay {
viewport_stack = viewport_stack.push(dyn_ol);
}
let properties_el: Element<'_, Message> = if self.show_properties {
tab.properties.view()
} else {
Space::new().into()
};
let center_stack = iced::widget::stack![
row![tab.properties.view(), viewport_stack]
row![properties_el, viewport_stack]
.width(Fill)
.height(Fill),
]
.width(Fill)
.height(Fill);
let tab_bar = doc_tab_bar(&self.tabs, self.active_tab);
let main_ui = container(
column![
self.ribbon.view(
is_paper,
self.tabs[self.active_tab].history.undo_stack.len(),
self.tabs[self.active_tab].history.redo_stack.len(),
),
tab_bar,
center_stack,
self.command_line.view(),
self.status_bar.view(
let main_ui = container({
let mut col = column![self.ribbon.view(
is_paper,
self.tabs[self.active_tab].history.undo_stack.len(),
self.tabs[self.active_tab].history.redo_stack.len(),
)];
if self.show_file_tabs {
col = col.push(doc_tab_bar(&self.tabs, self.active_tab));
}
col.push(center_stack)
.push(self.command_line.view())
.push(self.status_bar.view(
&self.snapper,
self.snap_popup_open,
self.ortho_mode,
@@ -314,11 +325,11 @@ impl H7CAD {
tab.scene.first_viewport_scale(),
tab.scene.viewport_count(),
tab.scene.active_viewport.is_some(),
)
]
.width(Fill)
.height(Fill),
)
self.show_layout_tabs,
))
.width(Fill)
.height(Fill)
})
.style(|_: &Theme| container::Style {
background: Some(Background::Color(Color { r: 0.11, g: 0.11, b: 0.11, a: 1.0 })),
..Default::default()
+2 -6
View File
@@ -1,9 +1,5 @@
use crate::modules::{IconKind, ModuleEvent, ToolDef};
pub const ICON: IconKind = IconKind::Svg(include_bytes!("../../../assets/icons/hidden.svg"));
pub fn tool() -> ToolDef {
ToolDef {
id: "HIDDENLINE",
label: "Hidden",
icon: IconKind::Glyph("◫"),
event: ModuleEvent::Command("HIDDENLINE".to_string()),
}
ToolDef { id: "HIDDENLINE", label: "Hidden", icon: ICON, event: ModuleEvent::Command("HIDDENLINE".to_string()) }
}
+2 -6
View File
@@ -1,9 +1,5 @@
use crate::modules::{IconKind, ModuleEvent, ToolDef};
pub const ICON: IconKind = IconKind::Svg(include_bytes!("../../../assets/icons/orbit.svg"));
pub fn tool() -> ToolDef {
ToolDef {
id: "3DORBIT",
label: "3D Orbit",
icon: IconKind::Glyph("⟳"),
event: ModuleEvent::Command("3DORBIT".to_string()),
}
ToolDef { id: "3DORBIT", label: "3D Orbit", icon: ICON, event: ModuleEvent::Command("3DORBIT".to_string()) }
}
+2 -6
View File
@@ -1,9 +1,5 @@
use crate::modules::{IconKind, ModuleEvent, ToolDef};
pub const ICON: IconKind = IconKind::Svg(include_bytes!("../../../assets/icons/ortho.svg"));
pub fn tool() -> ToolDef {
ToolDef {
id: "ORTHO",
label: "Ortho",
icon: IconKind::Glyph("⊡"),
event: ModuleEvent::Command("ORTHO".into()),
}
ToolDef { id: "ORTHO", label: "Ortho", icon: ICON, event: ModuleEvent::Command("ORTHO".into()) }
}
+2 -6
View File
@@ -1,9 +1,5 @@
use crate::modules::{IconKind, ModuleEvent, ToolDef};
pub const ICON: IconKind = IconKind::Svg(include_bytes!("../../../assets/icons/pan.svg"));
pub fn tool() -> ToolDef {
ToolDef {
id: "PAN",
label: "Pan",
icon: IconKind::Glyph("✥"),
event: ModuleEvent::Command("PAN".to_string()),
}
ToolDef { id: "PAN", label: "Pan", icon: ICON, event: ModuleEvent::Command("PAN".to_string()) }
}
+2 -6
View File
@@ -1,9 +1,5 @@
use crate::modules::{IconKind, ModuleEvent, ToolDef};
pub const ICON: IconKind = IconKind::Svg(include_bytes!("../../../assets/icons/persp.svg"));
pub fn tool() -> ToolDef {
ToolDef {
id: "PERSP",
label: "Persp",
icon: IconKind::Glyph("⟁"),
event: ModuleEvent::Command("PERSP".into()),
}
ToolDef { id: "PERSP", label: "Persp", icon: ICON, event: ModuleEvent::Command("PERSP".into()) }
}
+2 -6
View File
@@ -2,11 +2,7 @@
// id "SOLID" is special-cased in ribbon.rs for active-state highlighting.
use crate::modules::{IconKind, ModuleEvent, ToolDef};
pub const ICON: IconKind = IconKind::Svg(include_bytes!("../../../assets/icons/solid.svg"));
pub fn tool() -> ToolDef {
ToolDef {
id: "SOLID",
label: "Solid",
icon: IconKind::Glyph("■"),
event: ModuleEvent::SetWireframe(false),
}
ToolDef { id: "SOLID", label: "Solid", icon: ICON, event: ModuleEvent::SetWireframe(false) }
}
+2 -6
View File
@@ -1,9 +1,5 @@
use crate::modules::{IconKind, ModuleEvent, ToolDef};
pub const ICON: IconKind = IconKind::Svg(include_bytes!("../../../assets/icons/view_front.svg"));
pub fn tool() -> ToolDef {
ToolDef {
id: "VIEW_FRONT",
label: "Front",
icon: IconKind::Glyph("⊥"),
event: ModuleEvent::Command("VIEW FRONT".to_string()),
}
ToolDef { id: "VIEW_FRONT", label: "Front", icon: ICON, event: ModuleEvent::Command("VIEW FRONT".to_string()) }
}
+2 -6
View File
@@ -1,9 +1,5 @@
use crate::modules::{IconKind, ModuleEvent, ToolDef};
pub const ICON: IconKind = IconKind::Svg(include_bytes!("../../../assets/icons/view_iso.svg"));
pub fn tool() -> ToolDef {
ToolDef {
id: "VIEW_ISO",
label: "Iso",
icon: IconKind::Glyph("◈"),
event: ModuleEvent::Command("VIEW ISO".to_string()),
}
ToolDef { id: "VIEW_ISO", label: "Iso", icon: ICON, event: ModuleEvent::Command("VIEW ISO".to_string()) }
}
+2 -6
View File
@@ -1,9 +1,5 @@
use crate::modules::{IconKind, ModuleEvent, ToolDef};
pub const ICON: IconKind = IconKind::Svg(include_bytes!("../../../assets/icons/view_right.svg"));
pub fn tool() -> ToolDef {
ToolDef {
id: "VIEW_RIGHT",
label: "Right",
icon: IconKind::Glyph("⊣"),
event: ModuleEvent::Command("VIEW RIGHT".to_string()),
}
ToolDef { id: "VIEW_RIGHT", label: "Right", icon: ICON, event: ModuleEvent::Command("VIEW RIGHT".to_string()) }
}
+2 -6
View File
@@ -1,9 +1,5 @@
use crate::modules::{IconKind, ModuleEvent, ToolDef};
pub const ICON: IconKind = IconKind::Svg(include_bytes!("../../../assets/icons/view_top.svg"));
pub fn tool() -> ToolDef {
ToolDef {
id: "VIEW_TOP",
label: "Top",
icon: IconKind::Glyph("⊤"),
event: ModuleEvent::Command("VIEW TOP".to_string()),
}
ToolDef { id: "VIEW_TOP", label: "Top", icon: ICON, event: ModuleEvent::Command("VIEW TOP".to_string()) }
}
+2 -6
View File
@@ -2,11 +2,7 @@
// id "WIREFRAME" is special-cased in ribbon.rs for active-state highlighting.
use crate::modules::{IconKind, ModuleEvent, ToolDef};
pub const ICON: IconKind = IconKind::Svg(include_bytes!("../../../assets/icons/wireframe.svg"));
pub fn tool() -> ToolDef {
ToolDef {
id: "WIREFRAME",
label: "Wireframe",
icon: IconKind::Glyph("□"),
event: ModuleEvent::SetWireframe(true),
}
ToolDef { id: "WIREFRAME", label: "Wireframe", icon: ICON, event: ModuleEvent::SetWireframe(true) }
}
+2 -6
View File
@@ -1,9 +1,5 @@
use crate::modules::{IconKind, ModuleEvent, ToolDef};
pub const ICON: IconKind = IconKind::Svg(include_bytes!("../../../assets/icons/xray.svg"));
pub fn tool() -> ToolDef {
ToolDef {
id: "XRAY",
label: "X-Ray",
icon: IconKind::Glyph("◈"),
event: ModuleEvent::Command("XRAY".to_string()),
}
ToolDef { id: "XRAY", label: "X-Ray", icon: ICON, event: ModuleEvent::Command("XRAY".to_string()) }
}
+2 -6
View File
@@ -1,9 +1,5 @@
use crate::modules::{IconKind, ModuleEvent, ToolDef};
pub const ICON: IconKind = IconKind::Svg(include_bytes!("../../../assets/icons/zoom_ext.svg"));
pub fn tool() -> ToolDef {
ToolDef {
id: "ZOOM_EXT",
label: "Zoom Ext",
icon: IconKind::Glyph("⊡"),
event: ModuleEvent::Command("ZOOM EXTENTS".to_string()),
}
ToolDef { id: "ZOOM_EXT", label: "Zoom\nExtents", icon: ICON, event: ModuleEvent::Command("ZOOM EXTENTS".to_string()) }
}
+2 -6
View File
@@ -1,9 +1,5 @@
use crate::modules::{IconKind, ModuleEvent, ToolDef};
pub const ICON: IconKind = IconKind::Svg(include_bytes!("../../../assets/icons/zoom_in.svg"));
pub fn tool() -> ToolDef {
ToolDef {
id: "ZOOM_IN",
label: "Zoom In",
icon: IconKind::Glyph("🔍"),
event: ModuleEvent::Command("ZOOM IN".to_string()),
}
ToolDef { id: "ZOOM_IN", label: "Zoom In", icon: ICON, event: ModuleEvent::Command("ZOOM IN".to_string()) }
}
+2 -6
View File
@@ -1,9 +1,5 @@
use crate::modules::{IconKind, ModuleEvent, ToolDef};
pub const ICON: IconKind = IconKind::Svg(include_bytes!("../../../assets/icons/zoom_out.svg"));
pub fn tool() -> ToolDef {
ToolDef {
id: "ZOOM_OUT",
label: "Zoom Out",
icon: IconKind::Glyph("🔎"),
event: ModuleEvent::Command("ZOOM OUT".to_string()),
}
ToolDef { id: "ZOOM_OUT", label: "Zoom Out", icon: ICON, event: ModuleEvent::Command("ZOOM OUT".to_string()) }
}
+11 -7
View File
@@ -40,6 +40,8 @@ impl StatusBar {
viewport_count: usize,
// True when the user is editing inside a paper-space viewport (MSPACE).
in_mspace: bool,
// Whether the layout tabs (Model/Paper) are visible (LAYOUTTAB).
show_layout_tabs: bool,
) -> Element<'a, Message> {
let menu_btn = button(text("≡").size(14).color(ICON_COLOR))
.on_press(Message::Command("MENU".into()))
@@ -107,14 +109,16 @@ impl StatusBar {
let mut bar = Row::new().align_y(iced::Center).spacing(0);
bar = bar.push(menu_btn);
for name in layouts {
let is_active = name == current_layout;
let renaming = rename_state
.filter(|(orig, _)| *orig == name)
.map(|(_, edit)| edit.as_str());
bar = bar.push(space_tab(name, is_active, renaming));
if show_layout_tabs {
for name in layouts {
let is_active = name == current_layout;
let renaming = rename_state
.filter(|(orig, _)| *orig == name)
.map(|(_, edit)| edit.as_str());
bar = bar.push(space_tab(name, is_active, renaming));
}
bar = bar.push(add_btn);
}
bar = bar.push(add_btn);
bar = bar.push(iced::widget::Space::new().width(Length::Fill));
bar = bar.push(right_status);