forked from 1b-agent/opencadstudio-upstream
feat(ui): modernize DSETTINGS dialog with tabbed layout and action controls
- Redesign drafting settings window to landscape 700x480 proportions while preserving interactive resizing - Add 7-tab navigation: Snap and Grid, Polar Tracking, Object Snap, 3D Object Snap, Dynamic Input, Quick Properties, and Selection Cycling - Distribute existing drafting modes, snap options, and isometric tools into their respective tabs - Implement OK, Apply, and Close action buttons with unsaved changes confirmation guard
This commit is contained in:
1 parent
ddeddeca31
commit
a0d784502a
5 files changed
+1024
-118
No files matched your search
@@ -0,0 +1,170 @@
|
||||
use super::OpenCADStudio;
|
||||
use crate::ui::window::drafting_settings::{DraftingSettingsState, DraftingSettingsTab};
|
||||
|
||||
impl DraftingSettingsState {
|
||||
pub(crate) fn from_app(app: &OpenCADStudio) -> Self {
|
||||
Self {
|
||||
active_tab: DraftingSettingsTab::SnapAndGrid,
|
||||
snap_on: app.snapper.grid_snap(),
|
||||
grid_on: app.show_grid,
|
||||
isometric: app.isometric_drafting,
|
||||
iso_plane: app.iso_plane,
|
||||
snap_angle_deg: app.snap_angle_deg,
|
||||
polar_on: app.polar_mode,
|
||||
ortho_on: app.ortho_mode,
|
||||
polar_increment_deg: app.polar_increment_deg,
|
||||
osnap_on: app.snapper.snap_enabled,
|
||||
otrack_on: app.snapper.otrack_enabled,
|
||||
snap_modes: app.snapper.enabled.clone(),
|
||||
osnap3d_on: false,
|
||||
dyn_input_on: app.dyn_input,
|
||||
quick_props_on: app.quick_properties,
|
||||
selection_cycling_on: app.selection_cycling,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl OpenCADStudio {
|
||||
pub(super) fn drafting_settings_dirty(&self) -> bool {
|
||||
match (&self.drafting_settings_state, &self.drafting_settings_saved) {
|
||||
(Some(curr), Some(saved)) => curr.is_dirty(saved),
|
||||
(Some(_), None) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn apply_drafting_settings(&mut self) {
|
||||
if let Some(state) = &self.drafting_settings_state {
|
||||
self.show_grid = state.grid_on;
|
||||
self.snapper.grid_snap_on = state.snap_on;
|
||||
self.isometric_drafting = state.isometric;
|
||||
self.iso_plane = state.iso_plane;
|
||||
self.snap_angle_deg = state.snap_angle_deg;
|
||||
self.polar_mode = state.polar_on;
|
||||
self.ortho_mode = state.ortho_on;
|
||||
self.polar_increment_deg = state.polar_increment_deg;
|
||||
self.snapper.snap_enabled = state.osnap_on;
|
||||
self.snapper.otrack_enabled = state.otrack_on;
|
||||
self.snapper.enabled = state.snap_modes.clone();
|
||||
self.dyn_input = state.dyn_input_on;
|
||||
self.quick_properties = state.quick_props_on;
|
||||
self.selection_cycling = state.selection_cycling_on;
|
||||
self.sync_vport_display(self.active_tab);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_drafting_settings_tab_switching_not_dirty() {
|
||||
let state1 = DraftingSettingsState {
|
||||
active_tab: DraftingSettingsTab::SnapAndGrid,
|
||||
snap_on: false,
|
||||
grid_on: true,
|
||||
isometric: false,
|
||||
iso_plane: crate::app::settings::IsoPlane::Left,
|
||||
snap_angle_deg: 0.0,
|
||||
polar_on: false,
|
||||
ortho_on: false,
|
||||
polar_increment_deg: 15.0,
|
||||
osnap_on: true,
|
||||
otrack_on: false,
|
||||
snap_modes: rustc_hash::FxHashSet::default(),
|
||||
osnap3d_on: false,
|
||||
dyn_input_on: false,
|
||||
quick_props_on: false,
|
||||
selection_cycling_on: false,
|
||||
};
|
||||
let mut state2 = state1.clone();
|
||||
assert!(!state2.is_dirty(&state1));
|
||||
|
||||
// Switching tabs should NOT mark it dirty
|
||||
state2.active_tab = DraftingSettingsTab::ObjectSnap;
|
||||
assert!(!state2.is_dirty(&state1));
|
||||
|
||||
// Toggling a setting should mark it dirty
|
||||
state2.grid_on = false;
|
||||
assert!(state2.is_dirty(&state1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_drafting_settings_dirty_toggles() {
|
||||
let base = DraftingSettingsState {
|
||||
active_tab: DraftingSettingsTab::SnapAndGrid,
|
||||
snap_on: false,
|
||||
grid_on: true,
|
||||
isometric: false,
|
||||
iso_plane: crate::app::settings::IsoPlane::Left,
|
||||
snap_angle_deg: 0.0,
|
||||
polar_on: false,
|
||||
ortho_on: false,
|
||||
polar_increment_deg: 15.0,
|
||||
osnap_on: true,
|
||||
otrack_on: false,
|
||||
snap_modes: rustc_hash::FxHashSet::default(),
|
||||
osnap3d_on: false,
|
||||
dyn_input_on: false,
|
||||
quick_props_on: false,
|
||||
selection_cycling_on: false,
|
||||
};
|
||||
|
||||
let mut modded = base.clone();
|
||||
modded.snap_on = true;
|
||||
assert!(modded.is_dirty(&base));
|
||||
|
||||
let mut modded = base.clone();
|
||||
modded.isometric = true;
|
||||
assert!(modded.is_dirty(&base));
|
||||
|
||||
let mut modded = base.clone();
|
||||
modded.iso_plane = crate::app::settings::IsoPlane::Top;
|
||||
assert!(modded.is_dirty(&base));
|
||||
|
||||
let mut modded = base.clone();
|
||||
modded.snap_angle_deg = 45.0;
|
||||
assert!(modded.is_dirty(&base));
|
||||
|
||||
let mut modded = base.clone();
|
||||
modded.polar_on = true;
|
||||
assert!(modded.is_dirty(&base));
|
||||
|
||||
let mut modded = base.clone();
|
||||
modded.ortho_on = true;
|
||||
assert!(modded.is_dirty(&base));
|
||||
|
||||
let mut modded = base.clone();
|
||||
modded.polar_increment_deg = 30.0;
|
||||
assert!(modded.is_dirty(&base));
|
||||
|
||||
let mut modded = base.clone();
|
||||
modded.osnap_on = false;
|
||||
assert!(modded.is_dirty(&base));
|
||||
|
||||
let mut modded = base.clone();
|
||||
modded.otrack_on = true;
|
||||
assert!(modded.is_dirty(&base));
|
||||
|
||||
let mut modded = base.clone();
|
||||
modded.snap_modes.insert(crate::snap::SnapType::Endpoint);
|
||||
assert!(modded.is_dirty(&base));
|
||||
|
||||
let mut modded = base.clone();
|
||||
modded.osnap3d_on = true;
|
||||
assert!(modded.is_dirty(&base));
|
||||
|
||||
let mut modded = base.clone();
|
||||
modded.dyn_input_on = true;
|
||||
assert!(modded.is_dirty(&base));
|
||||
|
||||
let mut modded = base.clone();
|
||||
modded.quick_props_on = true;
|
||||
assert!(modded.is_dirty(&base));
|
||||
|
||||
let mut modded = base.clone();
|
||||
modded.selection_cycling_on = true;
|
||||
assert!(modded.is_dirty(&base));
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ pub use automation::{export_headless, serve};
|
||||
mod command_driver;
|
||||
pub(crate) mod commands;
|
||||
mod document;
|
||||
mod drafting_settings;
|
||||
pub(crate) mod expr_eval;
|
||||
mod find_replace;
|
||||
pub(crate) mod helpers;
|
||||
@@ -414,6 +415,9 @@ pub(super) struct OpenCADStudio {
|
||||
win_size: (f32, f32),
|
||||
snapper: Snapper,
|
||||
snap_popup_open: bool,
|
||||
drafting_settings_state: Option<crate::ui::window::drafting_settings::DraftingSettingsState>,
|
||||
drafting_settings_saved: Option<crate::ui::window::drafting_settings::DraftingSettingsState>,
|
||||
drafting_settings_close_confirm: bool,
|
||||
scale_popup_open: bool,
|
||||
/// True while the polar-tracking angle picker is open.
|
||||
polar_popup_open: bool,
|
||||
@@ -2609,6 +2613,29 @@ pub enum Message {
|
||||
SnapSelectAll,
|
||||
/// Disable all snap modes.
|
||||
SnapClearAll,
|
||||
// ── Drafting Settings Dialog ──────────────────────────────────────────
|
||||
DraftingSettingsTabChanged(crate::ui::window::drafting_settings::DraftingSettingsTab),
|
||||
DraftingSettingsToggleGrid,
|
||||
DraftingSettingsToggleSnap,
|
||||
DraftingSettingsToggleIsometric,
|
||||
DraftingSettingsSetIsoPlane(crate::app::settings::IsoPlane),
|
||||
DraftingSettingsResetRotation,
|
||||
DraftingSettingsTogglePolar,
|
||||
DraftingSettingsToggleOrtho,
|
||||
DraftingSettingsToggleOsnap,
|
||||
DraftingSettingsToggleOtrack,
|
||||
DraftingSettingsToggleSnapMode(crate::snap::SnapType),
|
||||
DraftingSettingsSnapSelectAll,
|
||||
DraftingSettingsSnapClearAll,
|
||||
DraftingSettingsToggle3dOsnap,
|
||||
DraftingSettingsToggleDynInput,
|
||||
DraftingSettingsToggleQuickProps,
|
||||
DraftingSettingsToggleSelCycling,
|
||||
DraftingSettingsApply,
|
||||
DraftingSettingsOk,
|
||||
DraftingSettingsClose,
|
||||
DraftingSettingsCloseDiscard,
|
||||
DraftingSettingsCloseKeep,
|
||||
/// Toggle a ribbon dropdown open/closed.
|
||||
ToggleRibbonDropdown(String),
|
||||
/// Toggle a collapsed ribbon panel's flyout open/closed (by panel title).
|
||||
@@ -3509,6 +3536,9 @@ impl OpenCADStudio {
|
||||
win_size: (1280.0, 720.0),
|
||||
snapper: Snapper::default(),
|
||||
snap_popup_open: false,
|
||||
drafting_settings_state: None,
|
||||
drafting_settings_saved: None,
|
||||
drafting_settings_close_confirm: false,
|
||||
scale_popup_open: false,
|
||||
polar_popup_open: false,
|
||||
polar_custom_input: String::new(),
|
||||
|
||||
@@ -171,6 +171,9 @@ impl OpenCADStudio {
|
||||
}
|
||||
if self.active_modal == Some(DraftingSettings) {
|
||||
self.snap_popup_open = false;
|
||||
self.drafting_settings_close_confirm = false;
|
||||
self.drafting_settings_state = None;
|
||||
self.drafting_settings_saved = None;
|
||||
}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
if self.active_modal == Some(FileInUse) {
|
||||
@@ -4025,9 +4028,17 @@ impl OpenCADStudio {
|
||||
}
|
||||
Message::ToggleSnapPopup => {
|
||||
if self.active_modal == Some(super::ModalKind::DraftingSettings) {
|
||||
if !self.drafting_settings_close_confirm && self.drafting_settings_dirty() {
|
||||
self.drafting_settings_close_confirm = true;
|
||||
return Task::none();
|
||||
}
|
||||
self.close_active_modal();
|
||||
self.snap_popup_open = false;
|
||||
} else {
|
||||
let st = crate::ui::window::drafting_settings::DraftingSettingsState::from_app(self);
|
||||
self.drafting_settings_saved = Some(st.clone());
|
||||
self.drafting_settings_state = Some(st);
|
||||
self.drafting_settings_close_confirm = false;
|
||||
self.active_modal = Some(super::ModalKind::DraftingSettings);
|
||||
self.snap_popup_open = true;
|
||||
}
|
||||
@@ -4049,6 +4060,147 @@ impl OpenCADStudio {
|
||||
Task::none()
|
||||
}
|
||||
|
||||
// ── Drafting Settings Dialog ──────────────────────────────────
|
||||
Message::DraftingSettingsTabChanged(tab) => {
|
||||
if let Some(state) = &mut self.drafting_settings_state {
|
||||
state.active_tab = tab;
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::DraftingSettingsToggleGrid => {
|
||||
if let Some(state) = &mut self.drafting_settings_state {
|
||||
state.grid_on = !state.grid_on;
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::DraftingSettingsToggleSnap => {
|
||||
if let Some(state) = &mut self.drafting_settings_state {
|
||||
state.snap_on = !state.snap_on;
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::DraftingSettingsToggleIsometric => {
|
||||
if let Some(state) = &mut self.drafting_settings_state {
|
||||
state.isometric = !state.isometric;
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::DraftingSettingsSetIsoPlane(plane) => {
|
||||
if let Some(state) = &mut self.drafting_settings_state {
|
||||
state.isometric = true;
|
||||
state.iso_plane = plane;
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::DraftingSettingsResetRotation => {
|
||||
if let Some(state) = &mut self.drafting_settings_state {
|
||||
state.snap_angle_deg = 0.0;
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::DraftingSettingsTogglePolar => {
|
||||
if let Some(state) = &mut self.drafting_settings_state {
|
||||
state.polar_on = !state.polar_on;
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::DraftingSettingsToggleOrtho => {
|
||||
if let Some(state) = &mut self.drafting_settings_state {
|
||||
state.ortho_on = !state.ortho_on;
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::DraftingSettingsToggleOsnap => {
|
||||
if let Some(state) = &mut self.drafting_settings_state {
|
||||
state.osnap_on = !state.osnap_on;
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::DraftingSettingsToggleOtrack => {
|
||||
if let Some(state) = &mut self.drafting_settings_state {
|
||||
state.otrack_on = !state.otrack_on;
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::DraftingSettingsToggleSnapMode(snap_type) => {
|
||||
if let Some(state) = &mut self.drafting_settings_state {
|
||||
if !state.snap_modes.remove(&snap_type) {
|
||||
state.snap_modes.insert(snap_type);
|
||||
}
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::DraftingSettingsSnapSelectAll => {
|
||||
if let Some(state) = &mut self.drafting_settings_state {
|
||||
for &(snap_type, _, _) in crate::snap::ALL_SNAP_MODES {
|
||||
state.snap_modes.insert(snap_type);
|
||||
}
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::DraftingSettingsSnapClearAll => {
|
||||
if let Some(state) = &mut self.drafting_settings_state {
|
||||
state.snap_modes.clear();
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::DraftingSettingsToggle3dOsnap => {
|
||||
if let Some(state) = &mut self.drafting_settings_state {
|
||||
state.osnap3d_on = !state.osnap3d_on;
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::DraftingSettingsToggleDynInput => {
|
||||
if let Some(state) = &mut self.drafting_settings_state {
|
||||
state.dyn_input_on = !state.dyn_input_on;
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::DraftingSettingsToggleQuickProps => {
|
||||
if let Some(state) = &mut self.drafting_settings_state {
|
||||
state.quick_props_on = !state.quick_props_on;
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::DraftingSettingsToggleSelCycling => {
|
||||
if let Some(state) = &mut self.drafting_settings_state {
|
||||
state.selection_cycling_on = !state.selection_cycling_on;
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::DraftingSettingsApply => {
|
||||
self.apply_drafting_settings();
|
||||
self.drafting_settings_saved = self.drafting_settings_state.clone();
|
||||
self.persist_settings_if_changed();
|
||||
Task::none()
|
||||
}
|
||||
Message::DraftingSettingsOk => {
|
||||
self.apply_drafting_settings();
|
||||
self.drafting_settings_saved = self.drafting_settings_state.clone();
|
||||
self.drafting_settings_close_confirm = false;
|
||||
self.persist_settings_if_changed();
|
||||
self.close_active_modal();
|
||||
Task::none()
|
||||
}
|
||||
Message::DraftingSettingsClose => {
|
||||
if !self.drafting_settings_close_confirm && self.drafting_settings_dirty() {
|
||||
self.drafting_settings_close_confirm = true;
|
||||
return Task::none();
|
||||
}
|
||||
self.drafting_settings_close_confirm = false;
|
||||
self.close_active_modal();
|
||||
Task::none()
|
||||
}
|
||||
Message::DraftingSettingsCloseDiscard => {
|
||||
self.drafting_settings_close_confirm = false;
|
||||
self.close_active_modal();
|
||||
Task::none()
|
||||
}
|
||||
Message::DraftingSettingsCloseKeep => {
|
||||
self.drafting_settings_close_confirm = false;
|
||||
Task::none()
|
||||
}
|
||||
|
||||
// ── Ribbon dropdowns ──────────────────────────────────────────
|
||||
Message::ToggleRibbonDropdown(id) => {
|
||||
if self.tabs[self.active_tab].is_start {
|
||||
@@ -6608,6 +6760,14 @@ impl OpenCADStudio {
|
||||
return Task::none();
|
||||
}
|
||||
self.shortcut_close_confirm = false;
|
||||
if self.active_modal == Some(super::ModalKind::DraftingSettings)
|
||||
&& !self.drafting_settings_close_confirm
|
||||
&& self.drafting_settings_dirty()
|
||||
{
|
||||
self.drafting_settings_close_confirm = true;
|
||||
return Task::none();
|
||||
}
|
||||
self.drafting_settings_close_confirm = false;
|
||||
let resume_open_queue = self.active_modal == Some(super::ModalKind::Recovery);
|
||||
self.close_active_modal();
|
||||
if resume_open_queue {
|
||||
|
||||
+22
-19
@@ -283,25 +283,28 @@ impl OpenCADStudio {
|
||||
)
|
||||
},
|
||||
),
|
||||
super::super::ModalKind::DraftingSettings => sized_flow(
|
||||
ex,
|
||||
520,
|
||||
560,
|
||||
|flow| {
|
||||
crate::ui::window::drafting_settings::view_window(
|
||||
&self.snapper,
|
||||
self.show_grid,
|
||||
self.snapper.grid_snap(),
|
||||
self.ortho_mode,
|
||||
self.polar_mode,
|
||||
self.snapper.otrack_enabled,
|
||||
self.isometric_drafting,
|
||||
self.iso_plane,
|
||||
self.snap_angle_deg,
|
||||
flow,
|
||||
)
|
||||
},
|
||||
),
|
||||
super::super::ModalKind::DraftingSettings => {
|
||||
let state = self.drafting_settings_state.as_ref();
|
||||
let dirty = self.drafting_settings_dirty();
|
||||
let confirm = self.drafting_settings_close_confirm;
|
||||
sized_flow(
|
||||
ex,
|
||||
700,
|
||||
480,
|
||||
|flow| {
|
||||
if let Some(state) = state {
|
||||
crate::ui::window::drafting_settings::view_window(
|
||||
state,
|
||||
dirty,
|
||||
confirm,
|
||||
flow,
|
||||
)
|
||||
} else {
|
||||
iced::widget::Space::new().into()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
super::super::ModalKind::FindReplace => automatic_flow(ex, |flow| {
|
||||
crate::ui::window::find_replace::view_window(
|
||||
&self.find_replace.search,
|
||||
|
||||
@@ -1,22 +1,107 @@
|
||||
use crate::app::settings::IsoPlane;
|
||||
use crate::app::Message;
|
||||
use crate::snap::{Snapper, ALL_SNAP_MODES};
|
||||
use crate::snap::{SnapType, ALL_SNAP_MODES};
|
||||
use iced::widget::{button, checkbox, column, container, row, scrollable, text, Space};
|
||||
use iced::{Element, Fill};
|
||||
use iced::{Background, Border, Element, Fill, Length, Theme};
|
||||
use std::borrow::Cow;
|
||||
|
||||
/// Active tab in the Drafting Settings dialog.
|
||||
#[derive(
|
||||
Clone, Copy, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize,
|
||||
)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum DraftingSettingsTab {
|
||||
#[default]
|
||||
SnapAndGrid,
|
||||
PolarTracking,
|
||||
ObjectSnap,
|
||||
ObjectSnap3D,
|
||||
DynamicInput,
|
||||
QuickProperties,
|
||||
SelectionCycling,
|
||||
}
|
||||
|
||||
/// Working buffer of drafting settings edited in the dialog.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct DraftingSettingsState {
|
||||
pub active_tab: DraftingSettingsTab,
|
||||
// Snap and Grid
|
||||
pub snap_on: bool,
|
||||
pub grid_on: bool,
|
||||
pub isometric: bool,
|
||||
pub iso_plane: IsoPlane,
|
||||
pub snap_angle_deg: f32,
|
||||
// Polar Tracking
|
||||
pub polar_on: bool,
|
||||
pub ortho_on: bool,
|
||||
pub polar_increment_deg: f32,
|
||||
// Object Snap
|
||||
pub osnap_on: bool,
|
||||
pub otrack_on: bool,
|
||||
pub snap_modes: rustc_hash::FxHashSet<SnapType>,
|
||||
// 3D Object Snap
|
||||
pub osnap3d_on: bool,
|
||||
// Dynamic Input
|
||||
pub dyn_input_on: bool,
|
||||
// Quick Properties
|
||||
pub quick_props_on: bool,
|
||||
// Selection Cycling
|
||||
pub selection_cycling_on: bool,
|
||||
}
|
||||
|
||||
impl DraftingSettingsState {
|
||||
/// Check whether settings were modified compared to the saved snapshot.
|
||||
/// Excludes `active_tab` so tab navigation does not mark the dialog dirty.
|
||||
pub fn is_dirty(&self, saved: &Self) -> bool {
|
||||
self.snap_on != saved.snap_on
|
||||
|| self.grid_on != saved.grid_on
|
||||
|| self.isometric != saved.isometric
|
||||
|| self.iso_plane != saved.iso_plane
|
||||
|| (self.snap_angle_deg - saved.snap_angle_deg).abs() > 0.001
|
||||
|| self.polar_on != saved.polar_on
|
||||
|| self.ortho_on != saved.ortho_on
|
||||
|| (self.polar_increment_deg - saved.polar_increment_deg).abs() > 0.001
|
||||
|| self.osnap_on != saved.osnap_on
|
||||
|| self.otrack_on != saved.otrack_on
|
||||
|| self.snap_modes != saved.snap_modes
|
||||
|| self.osnap3d_on != saved.osnap3d_on
|
||||
|| self.dyn_input_on != saved.dyn_input_on
|
||||
|| self.quick_props_on != saved.quick_props_on
|
||||
|| self.selection_cycling_on != saved.selection_cycling_on
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper for grouped sub-panels with a light border and header title.
|
||||
fn group<'a>(title: impl Into<String>, body: impl Into<Element<'a, Message>>) -> Element<'a, Message> {
|
||||
container(
|
||||
column![
|
||||
text(title.into()).size(11).style(|theme: &Theme| text::Style {
|
||||
color: Some(theme.palette().background.strongest.text.scale_alpha(0.75)),
|
||||
}),
|
||||
body.into(),
|
||||
]
|
||||
.spacing(6),
|
||||
)
|
||||
.padding([8, 10])
|
||||
.width(Fill)
|
||||
.style(|theme: &Theme| container::Style {
|
||||
border: Border {
|
||||
width: 1.0,
|
||||
radius: 4.0.into(),
|
||||
color: theme.palette().background.strong.color,
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn view_window<'a>(
|
||||
snapper: &'a Snapper,
|
||||
grid: bool,
|
||||
grid_snap: bool,
|
||||
ortho: bool,
|
||||
polar: bool,
|
||||
otrack: bool,
|
||||
isometric: bool,
|
||||
iso_plane: IsoPlane,
|
||||
snap_angle_deg: f32,
|
||||
state: &'a DraftingSettingsState,
|
||||
dirty: bool,
|
||||
close_confirm: bool,
|
||||
sizing: crate::ui::modal::ModalSizing,
|
||||
) -> Element<'a, Message> {
|
||||
let toggle = |value: bool, label: std::borrow::Cow<'a, str>, message: Message| {
|
||||
let toggle = |value: bool, label: Cow<'a, str>, message: Message| {
|
||||
row![
|
||||
checkbox(value).on_toggle(move |_| message.clone()).size(15),
|
||||
text(label).size(12),
|
||||
@@ -25,111 +110,569 @@ pub fn view_window<'a>(
|
||||
.align_y(iced::Center)
|
||||
};
|
||||
|
||||
let drafting_modes = column![
|
||||
text(crate::t!("Drafting modes")).size(15),
|
||||
Space::new().height(8),
|
||||
toggle(grid, crate::t!("Grid display"), Message::ToggleGrid),
|
||||
toggle(grid_snap, crate::t!("Grid snap"), Message::ToggleGridSnap),
|
||||
toggle(ortho, crate::t!("Ortho"), Message::ToggleOrtho),
|
||||
toggle(polar, crate::t!("Polar tracking"), Message::TogglePolar),
|
||||
toggle(otrack, crate::t!("Object snap tracking"), Message::ToggleOTrack),
|
||||
]
|
||||
.spacing(6);
|
||||
// ── Tab Bar ──────────────────────────────────────────────────────────
|
||||
let tab_button = |tab: DraftingSettingsTab, label: Cow<'a, str>| {
|
||||
let is_active = state.active_tab == tab;
|
||||
button(text(label).size(11.5))
|
||||
.on_press(Message::DraftingSettingsTabChanged(tab))
|
||||
.style(if is_active {
|
||||
button::primary
|
||||
} else {
|
||||
button::secondary
|
||||
})
|
||||
.padding([5, 9])
|
||||
};
|
||||
|
||||
let mut planes = row![].spacing(5);
|
||||
for plane in IsoPlane::ALL {
|
||||
planes = planes.push(
|
||||
button(text(crate::t!(plane.label())).size(11))
|
||||
.on_press(Message::SetIsoPlane(plane))
|
||||
.style(if isometric && iso_plane == plane {
|
||||
button::primary
|
||||
} else {
|
||||
button::secondary
|
||||
})
|
||||
.padding([5, 12]),
|
||||
let tab_bar = row![
|
||||
tab_button(DraftingSettingsTab::SnapAndGrid, crate::t!("Snap and Grid")),
|
||||
tab_button(DraftingSettingsTab::PolarTracking, crate::t!("Polar Tracking")),
|
||||
tab_button(DraftingSettingsTab::ObjectSnap, crate::t!("Object Snap")),
|
||||
tab_button(DraftingSettingsTab::ObjectSnap3D, crate::t!("3D Object Snap")),
|
||||
tab_button(DraftingSettingsTab::DynamicInput, crate::t!("Dynamic Input")),
|
||||
tab_button(DraftingSettingsTab::QuickProperties, crate::t!("Quick Properties")),
|
||||
tab_button(DraftingSettingsTab::SelectionCycling, crate::t!("Selection Cycling")),
|
||||
]
|
||||
.spacing(4)
|
||||
.wrap();
|
||||
|
||||
// ── Tab 1: Snap and Grid ─────────────────────────────────────────────
|
||||
let snap_and_grid_view = {
|
||||
// Left Column: Snap settings
|
||||
let snap_on_toggle = toggle(
|
||||
state.snap_on,
|
||||
crate::t!("Snap On (F9)"),
|
||||
Message::DraftingSettingsToggleSnap,
|
||||
);
|
||||
}
|
||||
let isometric_controls = column![
|
||||
text(crate::t!("Isometric drafting")).size(15),
|
||||
Space::new().height(8),
|
||||
toggle(
|
||||
isometric,
|
||||
crate::t!("Enable isometric drafting"),
|
||||
Message::ToggleIsometricDrafting,
|
||||
),
|
||||
planes,
|
||||
text(crate::t!("F5 cycles Left, Top, and Right.")).size(11),
|
||||
Space::new().height(6),
|
||||
row![
|
||||
text(crate::t!("Rotation: %{angle}°", angle = snap_angle_deg)).size(11),
|
||||
button(text(crate::t!("Reset rotation")).size(10))
|
||||
.on_press(Message::ResetDraftingRotation)
|
||||
.style(button::secondary)
|
||||
.padding([4, 10]),
|
||||
|
||||
let snap_spacing_group = group(
|
||||
crate::t!("Snap spacing"),
|
||||
column![
|
||||
row![
|
||||
text(crate::t!("Snap X spacing:")).size(11).width(120),
|
||||
text("10.00").size(11),
|
||||
]
|
||||
.spacing(8)
|
||||
.align_y(iced::Center),
|
||||
row![
|
||||
text(crate::t!("Snap Y spacing:")).size(11).width(120),
|
||||
text("10.00").size(11),
|
||||
]
|
||||
.spacing(8)
|
||||
.align_y(iced::Center),
|
||||
row![
|
||||
checkbox(true).size(14),
|
||||
text(crate::t!("Equal X and Y spacing")).size(11),
|
||||
]
|
||||
.spacing(6)
|
||||
.align_y(iced::Center),
|
||||
]
|
||||
.spacing(6),
|
||||
);
|
||||
|
||||
let mut iso_planes = row![].spacing(4);
|
||||
for plane in IsoPlane::ALL {
|
||||
iso_planes = iso_planes.push(
|
||||
button(text(crate::t!(plane.label())).size(11))
|
||||
.on_press(Message::DraftingSettingsSetIsoPlane(plane))
|
||||
.style(if state.isometric && state.iso_plane == plane {
|
||||
button::primary
|
||||
} else {
|
||||
button::secondary
|
||||
})
|
||||
.padding([4, 10]),
|
||||
);
|
||||
}
|
||||
|
||||
let snap_type_group = group(
|
||||
crate::t!("Snap type"),
|
||||
column![
|
||||
toggle(
|
||||
state.isometric,
|
||||
crate::t!("Enable isometric drafting"),
|
||||
Message::DraftingSettingsToggleIsometric,
|
||||
),
|
||||
iso_planes,
|
||||
text(crate::t!("F5 cycles Left, Top, and Right.")).size(10.5),
|
||||
Space::new().height(4),
|
||||
row![
|
||||
text(crate::t!("Rotation: %{angle}°", angle = state.snap_angle_deg)).size(11),
|
||||
button(text(crate::t!("Reset rotation")).size(10))
|
||||
.on_press(Message::DraftingSettingsResetRotation)
|
||||
.style(button::secondary)
|
||||
.padding([3, 8]),
|
||||
]
|
||||
.spacing(8)
|
||||
.align_y(iced::Center),
|
||||
]
|
||||
.spacing(6),
|
||||
);
|
||||
|
||||
let left_col = column![snap_on_toggle, snap_spacing_group, snap_type_group]
|
||||
.spacing(10)
|
||||
.width(Fill);
|
||||
|
||||
// Right Column: Grid settings
|
||||
let grid_on_toggle = toggle(
|
||||
state.grid_on,
|
||||
crate::t!("Grid On (F7)"),
|
||||
Message::DraftingSettingsToggleGrid,
|
||||
);
|
||||
|
||||
let grid_spacing_group = group(
|
||||
crate::t!("Grid spacing"),
|
||||
column![
|
||||
row![
|
||||
text(crate::t!("Grid X spacing:")).size(11).width(120),
|
||||
text("10.00").size(11),
|
||||
]
|
||||
.spacing(8)
|
||||
.align_y(iced::Center),
|
||||
row![
|
||||
text(crate::t!("Grid Y spacing:")).size(11).width(120),
|
||||
text("10.00").size(11),
|
||||
]
|
||||
.spacing(8)
|
||||
.align_y(iced::Center),
|
||||
row![
|
||||
text(crate::t!("Major line every:")).size(11).width(120),
|
||||
text("5").size(11),
|
||||
]
|
||||
.spacing(8)
|
||||
.align_y(iced::Center),
|
||||
]
|
||||
.spacing(6),
|
||||
);
|
||||
|
||||
let grid_behavior_group = group(
|
||||
crate::t!("Grid behavior"),
|
||||
column![
|
||||
row![
|
||||
checkbox(true).size(14),
|
||||
text(crate::t!("Adaptive grid")).size(11),
|
||||
]
|
||||
.spacing(6)
|
||||
.align_y(iced::Center),
|
||||
row![
|
||||
checkbox(true).size(14),
|
||||
text(crate::t!("Display grid beyond Limits")).size(11),
|
||||
]
|
||||
.spacing(6)
|
||||
.align_y(iced::Center),
|
||||
]
|
||||
.spacing(6),
|
||||
);
|
||||
|
||||
let right_col = column![grid_on_toggle, grid_spacing_group, grid_behavior_group]
|
||||
.spacing(10)
|
||||
.width(Fill);
|
||||
|
||||
row![left_col, right_col].spacing(16).width(Fill)
|
||||
};
|
||||
|
||||
// ── Tab 2: Polar Tracking ────────────────────────────────────────────
|
||||
let polar_tracking_view = {
|
||||
let polar_on_toggle = toggle(
|
||||
state.polar_on,
|
||||
crate::t!("Polar Tracking On (F10)"),
|
||||
Message::DraftingSettingsTogglePolar,
|
||||
);
|
||||
|
||||
let ortho_on_toggle = toggle(
|
||||
state.ortho_on,
|
||||
crate::t!("Ortho mode (F8)"),
|
||||
Message::DraftingSettingsToggleOrtho,
|
||||
);
|
||||
|
||||
let polar_angle_group = group(
|
||||
crate::t!("Polar Angle Settings"),
|
||||
column![
|
||||
row![
|
||||
text(crate::t!("Increment angle:")).size(11).width(130),
|
||||
text(format!("{:.1}°", state.polar_increment_deg)).size(11),
|
||||
]
|
||||
.spacing(8)
|
||||
.align_y(iced::Center),
|
||||
text(crate::t!(
|
||||
"Polar Tracking guides cursor movement along specified angles. Ortho mode constrains movement to orthogonal axes."
|
||||
))
|
||||
.size(10.5)
|
||||
.style(|theme: &Theme| text::Style {
|
||||
color: Some(theme.palette().background.strongest.text.scale_alpha(0.7)),
|
||||
}),
|
||||
]
|
||||
.spacing(6),
|
||||
);
|
||||
|
||||
column![
|
||||
polar_on_toggle,
|
||||
ortho_on_toggle,
|
||||
Space::new().height(4),
|
||||
polar_angle_group,
|
||||
]
|
||||
.spacing(10)
|
||||
.align_y(iced::Center),
|
||||
]
|
||||
.spacing(7);
|
||||
.width(Fill)
|
||||
};
|
||||
|
||||
let mut snap_modes = column![
|
||||
text(crate::t!("Object snap modes")).size(15),
|
||||
Space::new().height(8),
|
||||
toggle(
|
||||
snapper.snap_enabled,
|
||||
crate::t!("Enable object snap"),
|
||||
Message::ToggleSnapEnabled,
|
||||
),
|
||||
row![
|
||||
button(text(crate::t!("Select All")).size(10))
|
||||
.on_press(Message::SnapSelectAll)
|
||||
// ── Tab 3: Object Snap ───────────────────────────────────────────────
|
||||
let object_snap_view = {
|
||||
let osnap_on_toggle = toggle(
|
||||
state.osnap_on,
|
||||
crate::t!("Object Snap On (F3)"),
|
||||
Message::DraftingSettingsToggleOsnap,
|
||||
);
|
||||
|
||||
let otrack_on_toggle = toggle(
|
||||
state.otrack_on,
|
||||
crate::t!("Object Snap Tracking On (F11)"),
|
||||
Message::DraftingSettingsToggleOtrack,
|
||||
);
|
||||
|
||||
let action_buttons = row![
|
||||
button(text(crate::t!("Select All")).size(10.5))
|
||||
.on_press(Message::DraftingSettingsSnapSelectAll)
|
||||
.style(button::secondary)
|
||||
.padding([4, 10]),
|
||||
button(text(crate::t!("Clear All")).size(10))
|
||||
.on_press(Message::SnapClearAll)
|
||||
button(text(crate::t!("Clear All")).size(10.5))
|
||||
.on_press(Message::DraftingSettingsSnapClearAll)
|
||||
.style(button::secondary)
|
||||
.padding([4, 10]),
|
||||
]
|
||||
.spacing(6),
|
||||
]
|
||||
.spacing(5);
|
||||
for &(snap_type, _, label) in ALL_SNAP_MODES {
|
||||
snap_modes = snap_modes.push(
|
||||
row![
|
||||
checkbox(snapper.is_on(snap_type))
|
||||
.on_toggle(move |_| Message::ToggleSnap(snap_type))
|
||||
.size(14),
|
||||
text(crate::t!(label)).size(11),
|
||||
]
|
||||
.spacing(7)
|
||||
.align_y(iced::Center),
|
||||
);
|
||||
}
|
||||
.spacing(8);
|
||||
|
||||
let close = button(text(crate::tr!("action", "close")).size(12))
|
||||
.on_press(Message::CloseModal)
|
||||
.padding([6, 18])
|
||||
.style(button::secondary);
|
||||
let content = column![
|
||||
drafting_modes,
|
||||
Space::new().height(20),
|
||||
isometric_controls,
|
||||
Space::new().height(20),
|
||||
snap_modes,
|
||||
// Two columns of snap modes:
|
||||
let total = ALL_SNAP_MODES.len();
|
||||
let mid = (total + 1) / 2;
|
||||
let (first_half, second_half) = ALL_SNAP_MODES.split_at(mid);
|
||||
|
||||
let mut col1 = column![].spacing(6).width(Fill);
|
||||
for &(snap_type, _, label) in first_half {
|
||||
let is_checked = state.snap_modes.contains(&snap_type);
|
||||
col1 = col1.push(
|
||||
row![
|
||||
checkbox(is_checked)
|
||||
.on_toggle(move |_| Message::DraftingSettingsToggleSnapMode(snap_type))
|
||||
.size(14),
|
||||
text(crate::t!(label)).size(11),
|
||||
]
|
||||
.spacing(7)
|
||||
.align_y(iced::Center),
|
||||
);
|
||||
}
|
||||
|
||||
let mut col2 = column![].spacing(6).width(Fill);
|
||||
for &(snap_type, _, label) in second_half {
|
||||
let is_checked = state.snap_modes.contains(&snap_type);
|
||||
col2 = col2.push(
|
||||
row![
|
||||
checkbox(is_checked)
|
||||
.on_toggle(move |_| Message::DraftingSettingsToggleSnapMode(snap_type))
|
||||
.size(14),
|
||||
text(crate::t!(label)).size(11),
|
||||
]
|
||||
.spacing(7)
|
||||
.align_y(iced::Center),
|
||||
);
|
||||
}
|
||||
|
||||
let snap_modes_group = group(
|
||||
crate::t!("Object Snap modes"),
|
||||
row![col1, col2].spacing(20).width(Fill),
|
||||
);
|
||||
|
||||
column![
|
||||
row![osnap_on_toggle, Space::new().width(24), otrack_on_toggle]
|
||||
.align_y(iced::Center),
|
||||
action_buttons,
|
||||
Space::new().height(4),
|
||||
snap_modes_group,
|
||||
]
|
||||
.spacing(10)
|
||||
.width(Fill)
|
||||
};
|
||||
|
||||
// ── Tab 4: 3D Object Snap ────────────────────────────────────────────
|
||||
let object_snap_3d_view = {
|
||||
let osnap3d_toggle = toggle(
|
||||
state.osnap3d_on,
|
||||
crate::t!("3D Object Snap On (F4)"),
|
||||
Message::DraftingSettingsToggle3dOsnap,
|
||||
);
|
||||
|
||||
let placeholder_group = group(
|
||||
crate::t!("3D Object Snap modes"),
|
||||
column![
|
||||
row![checkbox(false).size(14), text(crate::t!("Vertex")).size(11)]
|
||||
.spacing(7)
|
||||
.align_y(iced::Center),
|
||||
row![
|
||||
checkbox(false).size(14),
|
||||
text(crate::t!("Midpoint on edge")).size(11),
|
||||
]
|
||||
.spacing(7)
|
||||
.align_y(iced::Center),
|
||||
row![
|
||||
checkbox(false).size(14),
|
||||
text(crate::t!("Center of face")).size(11),
|
||||
]
|
||||
.spacing(7)
|
||||
.align_y(iced::Center),
|
||||
row![checkbox(false).size(14), text(crate::t!("Knot")).size(11)]
|
||||
.spacing(7)
|
||||
.align_y(iced::Center),
|
||||
row![
|
||||
checkbox(false).size(14),
|
||||
text(crate::t!("Perpendicular to face")).size(11),
|
||||
]
|
||||
.spacing(7)
|
||||
.align_y(iced::Center),
|
||||
row![
|
||||
checkbox(false).size(14),
|
||||
text(crate::t!("Nearest to face")).size(11),
|
||||
]
|
||||
.spacing(7)
|
||||
.align_y(iced::Center),
|
||||
]
|
||||
.spacing(6),
|
||||
);
|
||||
|
||||
column![osnap3d_toggle, Space::new().height(4), placeholder_group]
|
||||
.spacing(10)
|
||||
.width(Fill)
|
||||
};
|
||||
|
||||
// ── Tab 5: Dynamic Input ─────────────────────────────────────────────
|
||||
let dynamic_input_view = {
|
||||
let dyn_input_toggle = toggle(
|
||||
state.dyn_input_on,
|
||||
crate::t!("Enable Pointer Input (F12)"),
|
||||
Message::DraftingSettingsToggleDynInput,
|
||||
);
|
||||
|
||||
let pointer_group = group(
|
||||
crate::t!("Pointer Input"),
|
||||
column![
|
||||
row![
|
||||
checkbox(true).size(14),
|
||||
text(crate::t!("Display coordinate input near crosshairs")).size(11),
|
||||
]
|
||||
.spacing(7)
|
||||
.align_y(iced::Center),
|
||||
row![
|
||||
checkbox(true).size(14),
|
||||
text(crate::t!("Enable dimension input fields")).size(11),
|
||||
]
|
||||
.spacing(7)
|
||||
.align_y(iced::Center),
|
||||
]
|
||||
.spacing(6),
|
||||
);
|
||||
|
||||
let prompt_group = group(
|
||||
crate::t!("Dynamic Prompts"),
|
||||
column![
|
||||
row![
|
||||
checkbox(true).size(14),
|
||||
text(crate::t!("Show command prompting and command input near crosshairs"))
|
||||
.size(11),
|
||||
]
|
||||
.spacing(7)
|
||||
.align_y(iced::Center),
|
||||
]
|
||||
.spacing(6),
|
||||
);
|
||||
|
||||
column![
|
||||
dyn_input_toggle,
|
||||
Space::new().height(4),
|
||||
pointer_group,
|
||||
prompt_group,
|
||||
]
|
||||
.spacing(10)
|
||||
.width(Fill)
|
||||
};
|
||||
|
||||
// ── Tab 6: Quick Properties ──────────────────────────────────────────
|
||||
let quick_properties_view = {
|
||||
let quick_props_toggle = toggle(
|
||||
state.quick_props_on,
|
||||
crate::t!("Display Quick Properties palette on selection"),
|
||||
Message::DraftingSettingsToggleQuickProps,
|
||||
);
|
||||
|
||||
let palette_group = group(
|
||||
crate::t!("Palette Location"),
|
||||
column![
|
||||
row![
|
||||
checkbox(true).size(14),
|
||||
text(crate::t!("Cursor-dependent position")).size(11),
|
||||
]
|
||||
.spacing(7)
|
||||
.align_y(iced::Center),
|
||||
row![
|
||||
checkbox(false).size(14),
|
||||
text(crate::t!("Static quadrant location")).size(11),
|
||||
]
|
||||
.spacing(7)
|
||||
.align_y(iced::Center),
|
||||
]
|
||||
.spacing(6),
|
||||
);
|
||||
|
||||
column![
|
||||
quick_props_toggle,
|
||||
Space::new().height(4),
|
||||
palette_group,
|
||||
]
|
||||
.spacing(10)
|
||||
.width(Fill)
|
||||
};
|
||||
|
||||
// ── Tab 7: Selection Cycling ─────────────────────────────────────────
|
||||
let selection_cycling_view = {
|
||||
let sel_cycling_toggle = toggle(
|
||||
state.selection_cycling_on,
|
||||
crate::t!("Allow selection cycling"),
|
||||
Message::DraftingSettingsToggleSelCycling,
|
||||
);
|
||||
|
||||
let cycling_group = group(
|
||||
crate::t!("Display Selection Cycling List Box"),
|
||||
column![
|
||||
row![
|
||||
checkbox(true).size(14),
|
||||
text(crate::t!("Show cycling badge when objects overlap")).size(11),
|
||||
]
|
||||
.spacing(7)
|
||||
.align_y(iced::Center),
|
||||
row![
|
||||
checkbox(true).size(14),
|
||||
text(crate::t!("Display selection candidate list box on click")).size(11),
|
||||
]
|
||||
.spacing(7)
|
||||
.align_y(iced::Center),
|
||||
]
|
||||
.spacing(6),
|
||||
);
|
||||
|
||||
column![
|
||||
sel_cycling_toggle,
|
||||
Space::new().height(4),
|
||||
cycling_group,
|
||||
]
|
||||
.spacing(10)
|
||||
.width(Fill)
|
||||
};
|
||||
|
||||
// ── Active Tab Content ───────────────────────────────────────────────
|
||||
let tab_content: Element<'a, Message> = match state.active_tab {
|
||||
DraftingSettingsTab::SnapAndGrid => snap_and_grid_view.into(),
|
||||
DraftingSettingsTab::PolarTracking => polar_tracking_view.into(),
|
||||
DraftingSettingsTab::ObjectSnap => object_snap_view.into(),
|
||||
DraftingSettingsTab::ObjectSnap3D => object_snap_3d_view.into(),
|
||||
DraftingSettingsTab::DynamicInput => dynamic_input_view.into(),
|
||||
DraftingSettingsTab::QuickProperties => quick_properties_view.into(),
|
||||
DraftingSettingsTab::SelectionCycling => selection_cycling_view.into(),
|
||||
};
|
||||
|
||||
// ── Bottom Action Buttons ────────────────────────────────────────────
|
||||
let ok_button = button(text(crate::t!("OK")).size(12))
|
||||
.on_press(Message::DraftingSettingsOk)
|
||||
.style(button::primary)
|
||||
.padding([6, 18]);
|
||||
|
||||
let apply_button = button(text(crate::t!("Apply")).size(12))
|
||||
.on_press_maybe(if dirty {
|
||||
Some(Message::DraftingSettingsApply)
|
||||
} else {
|
||||
None
|
||||
})
|
||||
.style(if dirty {
|
||||
button::secondary
|
||||
} else {
|
||||
button::text
|
||||
})
|
||||
.padding([6, 18]);
|
||||
|
||||
let close_button = button(text(crate::t!("Close")).size(12))
|
||||
.on_press(Message::DraftingSettingsClose)
|
||||
.style(button::secondary)
|
||||
.padding([6, 18]);
|
||||
|
||||
let action_row = row![
|
||||
Space::new().width(Fill),
|
||||
ok_button,
|
||||
apply_button,
|
||||
close_button,
|
||||
]
|
||||
.width(sizing.width);
|
||||
.spacing(8)
|
||||
.align_y(iced::Center);
|
||||
|
||||
// ── Body Layout ──────────────────────────────────────────────────────
|
||||
let body = column![
|
||||
scrollable(content).spacing(8).height(sizing.height),
|
||||
tab_bar,
|
||||
Space::new().height(10),
|
||||
scrollable(tab_content).spacing(6).height(Fill),
|
||||
Space::new().height(12),
|
||||
row![Space::new().width(Fill), close],
|
||||
action_row,
|
||||
]
|
||||
.width(sizing.width)
|
||||
.height(sizing.height);
|
||||
|
||||
container(body)
|
||||
let main_content = container(body)
|
||||
.style(container::rounded_box)
|
||||
.padding([16, 18])
|
||||
.padding([14, 16])
|
||||
.width(sizing.width)
|
||||
.height(sizing.height)
|
||||
.into()
|
||||
.height(sizing.height);
|
||||
|
||||
// ── Unsaved Changes Guard ────────────────────────────────────────────
|
||||
if !close_confirm {
|
||||
return main_content.into();
|
||||
}
|
||||
|
||||
let shield = iced::widget::mouse_area(
|
||||
container(Space::new())
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme
|
||||
.palette()
|
||||
.background
|
||||
.strongest
|
||||
.color
|
||||
.scale_alpha(0.55),
|
||||
)),
|
||||
..Default::default()
|
||||
}),
|
||||
)
|
||||
.on_press(Message::DraftingSettingsCloseKeep);
|
||||
|
||||
let warning_panel = container(
|
||||
column![
|
||||
text(crate::t!("Unsaved changes will be discarded.")).size(13.5),
|
||||
Space::new().height(12),
|
||||
row![
|
||||
button(text(crate::t!("Discard && close")).size(12))
|
||||
.on_press(Message::DraftingSettingsCloseDiscard)
|
||||
.padding([5, 14])
|
||||
.style(button::danger),
|
||||
button(text(crate::t!("Keep editing")).size(12))
|
||||
.on_press(Message::DraftingSettingsCloseKeep)
|
||||
.padding([5, 14])
|
||||
.style(button::secondary),
|
||||
]
|
||||
.spacing(10),
|
||||
]
|
||||
.spacing(0),
|
||||
)
|
||||
.padding([18, 22])
|
||||
.style(container::rounded_box);
|
||||
|
||||
iced::widget::stack![
|
||||
main_content,
|
||||
shield,
|
||||
container(warning_panel).center_x(Fill).center_y(Fill)
|
||||
]
|
||||
.into()
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user