forked from 1b-agent/opencadstudio-upstream
feat(model-space): theme-aware model space canvas and CAD selection/grip sysvars
Model space appearance now follows the active UI theme instead of a hardcoded dark background. Canvas mode is user-selectable (Match Theme / Classic CAD Dark / Custom RGB), with matching controls for the paper sheet and desk surround backgrounds and grid opacity. Implements industry-standard CAD system variables, all settable via command name, SETVAR, and the Options dialog: - COLORTHEME (0/1 dark/light UI scheme, preserves custom palettes) - COLORSCHEME (interactive named-theme selection) - BACKGROUND (THEME/CLASSIC/DEFAULT/DESK presets, RGB triplets) - SELECTIONAREA, SELECTIONAREAOPACITY (translucent marquee fill) - WINDOWSAREACOLOR/WINDOWAREACOLOR, CROSSINGAREACOLOR (ACI or theme) - SELECTIONEFFECT, SELECTIONEFFECTCOLOR (GPU highlight pass, driven by selection_generation invalidation — no retessellation) - SELECTIONPREVIEW (bit 0 = idle rollover, bit 1 = in-command hover) - GRIPSIZE, GRIPCOLOR, GRIPHOT, GRIPHOVER (grip overlay styling)
This commit is contained in:
1 parent
89b1a22850
commit
688785a6cf
18 files changed
+1949
-316
No files matched your search
+429
-44
@@ -27,6 +27,7 @@ impl<T> fmt::Display for Labelled<T> {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn view_window<'a>(
|
||||
default_save_format: &'a str,
|
||||
file_assoc_enabled: bool,
|
||||
@@ -40,6 +41,10 @@ pub fn view_window<'a>(
|
||||
crosshair_color: Option<[u8; 3]>,
|
||||
crosshair_color_input: &'a str,
|
||||
lineweight_display_scale: i32,
|
||||
model_space: &'a crate::app::config::ModelSpaceThemeConfig,
|
||||
model_bg_input: &'a str,
|
||||
paper_bg_input: &'a str,
|
||||
desk_bg_input: &'a str,
|
||||
sizing: crate::ui::modal::ModalSizing,
|
||||
) -> Element<'a, Message> {
|
||||
let selected_format = crate::io::SAVE_FORMAT_OPTIONS
|
||||
@@ -204,7 +209,144 @@ pub fn view_window<'a>(
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let display = column![
|
||||
let model_bg_rgb = model_space.custom_bg.unwrap_or(crate::app::config::CLASSIC_CAD_DARK_BG);
|
||||
let model_bg_swatch = container(Space::new())
|
||||
.width(28)
|
||||
.height(22)
|
||||
.style(move |theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(iced::Color::from_rgb8(
|
||||
model_bg_rgb[0],
|
||||
model_bg_rgb[1],
|
||||
model_bg_rgb[2],
|
||||
))),
|
||||
border: Border {
|
||||
color: theme.palette().background.strong.color,
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let paper_bg_rgb = model_space.custom_paper_bg.unwrap_or(crate::app::config::DEFAULT_PAPER_BG);
|
||||
let paper_bg_swatch = container(Space::new())
|
||||
.width(28)
|
||||
.height(22)
|
||||
.style(move |theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(iced::Color::from_rgb8(
|
||||
paper_bg_rgb[0],
|
||||
paper_bg_rgb[1],
|
||||
paper_bg_rgb[2],
|
||||
))),
|
||||
border: Border {
|
||||
color: theme.palette().background.strong.color,
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let desk_bg_rgb = model_space.custom_desk_bg.unwrap_or(crate::app::config::DEFAULT_DESK_BG);
|
||||
let desk_bg_swatch = container(Space::new())
|
||||
.width(28)
|
||||
.height(22)
|
||||
.style(move |theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(iced::Color::from_rgb8(
|
||||
desk_bg_rgb[0],
|
||||
desk_bg_rgb[1],
|
||||
desk_bg_rgb[2],
|
||||
))),
|
||||
border: Border {
|
||||
color: theme.palette().background.strong.color,
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let mode_options = crate::app::config::ModelSpaceMode::ALL
|
||||
.into_iter()
|
||||
.map(|value| Labelled {
|
||||
label: crate::t!(value.label()).into_owned(),
|
||||
value,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let selected_mode = mode_options
|
||||
.iter()
|
||||
.find(|choice| choice.value == model_space.mode)
|
||||
.cloned();
|
||||
|
||||
let color_choice_options = [
|
||||
(0u8, "0: Theme Default"),
|
||||
(1, "1: Red"),
|
||||
(2, "2: Yellow"),
|
||||
(3, "3: Green"),
|
||||
(4, "4: Cyan"),
|
||||
(5, "5: Blue"),
|
||||
(6, "6: Magenta"),
|
||||
(7, "7: White/Black"),
|
||||
]
|
||||
.into_iter()
|
||||
.map(|(value, label)| Labelled {
|
||||
value,
|
||||
label: label.to_string(),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let selected_window_color = color_choice_options
|
||||
.iter()
|
||||
.find(|c| c.value == model_space.selection_window_color)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| Labelled {
|
||||
value: model_space.selection_window_color,
|
||||
label: format!("ACI {}", model_space.selection_window_color),
|
||||
});
|
||||
|
||||
let selected_crossing_color = color_choice_options
|
||||
.iter()
|
||||
.find(|c| c.value == model_space.selection_crossing_color)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| Labelled {
|
||||
value: model_space.selection_crossing_color,
|
||||
label: format!("ACI {}", model_space.selection_crossing_color),
|
||||
});
|
||||
|
||||
let selected_highlight_color = color_choice_options
|
||||
.iter()
|
||||
.find(|c| c.value == model_space.selection_highlight_color)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| Labelled {
|
||||
value: model_space.selection_highlight_color,
|
||||
label: format!("ACI {}", model_space.selection_highlight_color),
|
||||
});
|
||||
|
||||
let selected_grip_color = color_choice_options
|
||||
.iter()
|
||||
.find(|c| c.value == model_space.grip_color)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| Labelled {
|
||||
value: model_space.grip_color,
|
||||
label: format!("ACI {}", model_space.grip_color),
|
||||
});
|
||||
|
||||
let selected_grip_hot = color_choice_options
|
||||
.iter()
|
||||
.find(|c| c.value == model_space.grip_hot)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| Labelled {
|
||||
value: model_space.grip_hot,
|
||||
label: format!("ACI {}", model_space.grip_hot),
|
||||
});
|
||||
|
||||
let selected_grip_hover = color_choice_options
|
||||
.iter()
|
||||
.find(|c| c.value == model_space.grip_hover)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| Labelled {
|
||||
value: model_space.grip_hover,
|
||||
label: format!("ACI {}", model_space.grip_hover),
|
||||
});
|
||||
|
||||
let mut display = column![
|
||||
text(crate::tr!("options", "theme-section")).size(15),
|
||||
Space::new().height(10),
|
||||
row![
|
||||
@@ -226,71 +368,176 @@ pub fn view_window<'a>(
|
||||
Space::new().height(12),
|
||||
color_controls,
|
||||
Space::new().height(24),
|
||||
text(crate::t!("Crosshair")).size(15),
|
||||
Space::new().height(10),
|
||||
row![
|
||||
text(crate::t!("Crosshair size")).size(12).width(140),
|
||||
slider(1..=100, cursor_size.clamp(1, 100), Message::CursorSizeChanged)
|
||||
.step(1)
|
||||
.width(Fill),
|
||||
text(format!("{}%", cursor_size.clamp(1, 100)))
|
||||
.size(11)
|
||||
.width(44),
|
||||
text(crate::t!("Model Space Appearance")).size(15),
|
||||
Space::new().width(Fill),
|
||||
button(text(crate::t!("Restore Defaults")).size(11))
|
||||
.on_press(Message::RestoreModelSpaceDisplayDefaults)
|
||||
.padding([4, 10])
|
||||
.style(button::secondary),
|
||||
]
|
||||
.spacing(10)
|
||||
.align_y(iced::Center),
|
||||
Space::new().height(10),
|
||||
row![
|
||||
text(crate::t!("Cursor type")).size(12).width(140),
|
||||
text(crate::t!("Canvas mode")).size(12).width(140),
|
||||
iced::widget::pick_list(
|
||||
selected_cursor,
|
||||
cursor_options,
|
||||
selected_mode,
|
||||
mode_options,
|
||||
|choice| choice.label.clone(),
|
||||
)
|
||||
.on_select(|choice| Message::CursorTypeChanged(choice.value))
|
||||
.on_select(|choice| Message::ModelSpaceModeChanged(choice.value))
|
||||
.width(Fill),
|
||||
]
|
||||
.spacing(10)
|
||||
.align_y(iced::Center),
|
||||
Space::new().height(10),
|
||||
];
|
||||
|
||||
if model_space.mode == crate::app::config::ModelSpaceMode::Custom {
|
||||
display = display.push(Space::new().height(10)).push(
|
||||
row![
|
||||
text(crate::t!("Model background")).size(12).width(140),
|
||||
model_bg_swatch,
|
||||
text_input("#RRGGBB", model_bg_input)
|
||||
.on_input(Message::ModelSpaceBgChanged)
|
||||
.width(150),
|
||||
]
|
||||
.spacing(10)
|
||||
.align_y(iced::Center),
|
||||
);
|
||||
}
|
||||
|
||||
display = display.push(Space::new().height(10)).push(
|
||||
row![
|
||||
text(crate::t!("Crosshair color")).size(12).width(140),
|
||||
crosshair_swatch,
|
||||
text_input(crate::t!("#RRGGBB or blank").as_ref(), crosshair_color_input)
|
||||
.on_input(Message::CrosshairColorChanged)
|
||||
text(crate::t!("Paper background")).size(12).width(140),
|
||||
paper_bg_swatch,
|
||||
text_input("#RRGGBB", paper_bg_input)
|
||||
.on_input(Message::PaperSpaceBgChanged)
|
||||
.width(150),
|
||||
]
|
||||
.spacing(10)
|
||||
.align_y(iced::Center),
|
||||
Space::new().height(6),
|
||||
text(crate::t!("Leave the color blank to keep automatic viewport contrast."))
|
||||
.size(11)
|
||||
.width(sizing.width),
|
||||
Space::new().height(24),
|
||||
text(crate::t!("Lineweight")).size(15),
|
||||
Space::new().height(10),
|
||||
);
|
||||
|
||||
display = display.push(Space::new().height(10)).push(
|
||||
row![
|
||||
text(crate::t!("Model display scale")).size(12).width(140),
|
||||
slider(
|
||||
25..=200,
|
||||
lineweight_display_scale.clamp(25, 200),
|
||||
Message::LineweightDisplayScaleChanged,
|
||||
)
|
||||
.step(1)
|
||||
.width(Fill),
|
||||
text(format!("{}%", lineweight_display_scale.clamp(25, 200)))
|
||||
.size(11)
|
||||
.width(44),
|
||||
text(crate::t!("Desk surround")).size(12).width(140),
|
||||
desk_bg_swatch,
|
||||
text_input("#RRGGBB", desk_bg_input)
|
||||
.on_input(Message::DeskSpaceBgChanged)
|
||||
.width(150),
|
||||
]
|
||||
.spacing(10)
|
||||
.align_y(iced::Center),
|
||||
Space::new().height(6),
|
||||
text(crate::t!("Changes the on-screen width in Model without affecting plotted output."))
|
||||
);
|
||||
|
||||
display = display
|
||||
.push(Space::new().height(10))
|
||||
.push(
|
||||
row![
|
||||
text(crate::t!("Grid opacity")).size(12).width(140),
|
||||
slider(5..=100, model_space.grid_opacity.clamp(5, 100), Message::GridOpacityChanged)
|
||||
.step(1)
|
||||
.width(Fill),
|
||||
text(format!("{}%", model_space.grid_opacity.clamp(5, 100)))
|
||||
.size(11)
|
||||
.width(44),
|
||||
]
|
||||
.spacing(10)
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.push(Space::new().height(6))
|
||||
.push(
|
||||
text(match model_space.mode {
|
||||
crate::app::config::ModelSpaceMode::MatchTheme => {
|
||||
crate::t!("Canvas background, grid, and default line colors automatically adapt to the active theme.")
|
||||
}
|
||||
crate::app::config::ModelSpaceMode::ClassicDark => {
|
||||
crate::t!("Model space canvas remains locked to classic CAD dark charcoal (#212830).")
|
||||
}
|
||||
crate::app::config::ModelSpaceMode::Custom => {
|
||||
crate::t!("Custom background colors set above are applied to Model and Paper space.")
|
||||
}
|
||||
})
|
||||
.size(11)
|
||||
.width(sizing.width),
|
||||
]
|
||||
.spacing(0)
|
||||
.width(sizing.width);
|
||||
)
|
||||
.push(Space::new().height(24))
|
||||
.push(text(crate::t!("Crosshair")).size(15))
|
||||
.push(Space::new().height(10))
|
||||
.push(
|
||||
row![
|
||||
text(crate::t!("Crosshair size")).size(12).width(140),
|
||||
slider(1..=100, cursor_size.clamp(1, 100), Message::CursorSizeChanged)
|
||||
.step(1)
|
||||
.width(Fill),
|
||||
text(format!("{}%", cursor_size.clamp(1, 100)))
|
||||
.size(11)
|
||||
.width(44),
|
||||
]
|
||||
.spacing(10)
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.push(Space::new().height(10))
|
||||
.push(
|
||||
row![
|
||||
text(crate::t!("Cursor type")).size(12).width(140),
|
||||
iced::widget::pick_list(
|
||||
selected_cursor,
|
||||
cursor_options,
|
||||
|choice| choice.label.clone(),
|
||||
)
|
||||
.on_select(|choice| Message::CursorTypeChanged(choice.value))
|
||||
.width(Fill),
|
||||
]
|
||||
.spacing(10)
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.push(Space::new().height(10))
|
||||
.push(
|
||||
row![
|
||||
text(crate::t!("Crosshair color")).size(12).width(140),
|
||||
crosshair_swatch,
|
||||
text_input(crate::t!("#RRGGBB or blank").as_ref(), crosshair_color_input)
|
||||
.on_input(Message::CrosshairColorChanged)
|
||||
.width(150),
|
||||
]
|
||||
.spacing(10)
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.push(Space::new().height(6))
|
||||
.push(
|
||||
text(crate::t!("Leave the color blank to keep automatic viewport contrast."))
|
||||
.size(11)
|
||||
.width(sizing.width),
|
||||
)
|
||||
.push(Space::new().height(24))
|
||||
.push(text(crate::t!("Lineweight")).size(15))
|
||||
.push(Space::new().height(10))
|
||||
.push(
|
||||
row![
|
||||
text(crate::t!("Model display scale")).size(12).width(140),
|
||||
slider(
|
||||
25..=200,
|
||||
lineweight_display_scale.clamp(25, 200),
|
||||
Message::LineweightDisplayScaleChanged,
|
||||
)
|
||||
.step(1)
|
||||
.width(Fill),
|
||||
text(format!("{}%", lineweight_display_scale.clamp(25, 200)))
|
||||
.size(11)
|
||||
.width(44),
|
||||
]
|
||||
.spacing(10)
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.push(Space::new().height(6))
|
||||
.push(
|
||||
text(crate::t!("Changes the on-screen width in Model without affecting plotted output."))
|
||||
.size(11)
|
||||
.width(sizing.width),
|
||||
);
|
||||
|
||||
let display_element = display.spacing(0).width(sizing.width);
|
||||
|
||||
let selection = column![
|
||||
text(crate::t!("Selection")).size(15),
|
||||
@@ -310,13 +557,151 @@ pub fn view_window<'a>(
|
||||
))
|
||||
.size(11)
|
||||
.width(sizing.width),
|
||||
Space::new().height(24),
|
||||
row![
|
||||
text(crate::t!("Visual Effect Settings")).size(15),
|
||||
Space::new().width(Fill),
|
||||
button(text(crate::t!("Restore Defaults")).size(11))
|
||||
.on_press(Message::RestoreSelectionVisualDefaults)
|
||||
.padding([4, 10])
|
||||
.style(button::secondary),
|
||||
]
|
||||
.align_y(iced::Center),
|
||||
Space::new().height(10),
|
||||
row![
|
||||
iced::widget::checkbox(model_space.selection_area)
|
||||
.on_toggle(Message::SelectionAreaToggled)
|
||||
.size(15),
|
||||
text(crate::t!("Indicate selection area with transparent fill (SELECTIONAREA)"))
|
||||
.size(12),
|
||||
]
|
||||
.spacing(8)
|
||||
.align_y(iced::Center),
|
||||
Space::new().height(10),
|
||||
row![
|
||||
text(crate::t!("Selection area opacity")).size(12).width(140),
|
||||
slider(
|
||||
0..=100,
|
||||
model_space.selection_opacity.clamp(0, 100),
|
||||
Message::SelectionOpacityChanged,
|
||||
)
|
||||
.step(1)
|
||||
.width(Fill),
|
||||
text(format!("{}%", model_space.selection_opacity.clamp(0, 100)))
|
||||
.size(11)
|
||||
.width(44),
|
||||
]
|
||||
.spacing(10)
|
||||
.align_y(iced::Center),
|
||||
Space::new().height(6),
|
||||
text(crate::t!(
|
||||
"Transparency of the window and crossing selection areas (SELECTIONAREAOPACITY)."
|
||||
))
|
||||
.size(11)
|
||||
.width(sizing.width),
|
||||
Space::new().height(10),
|
||||
row![
|
||||
text(crate::t!("Window selection color")).size(12).width(140),
|
||||
iced::widget::pick_list(
|
||||
Some(selected_window_color),
|
||||
color_choice_options.clone(),
|
||||
|choice| choice.label.clone(),
|
||||
)
|
||||
.on_select(|choice| Message::SelectionWindowColorChanged(choice.value))
|
||||
.width(Fill),
|
||||
]
|
||||
.spacing(10)
|
||||
.align_y(iced::Center),
|
||||
Space::new().height(10),
|
||||
row![
|
||||
text(crate::t!("Crossing selection color")).size(12).width(140),
|
||||
iced::widget::pick_list(
|
||||
Some(selected_crossing_color),
|
||||
color_choice_options.clone(),
|
||||
|choice| choice.label.clone(),
|
||||
)
|
||||
.on_select(|choice| Message::SelectionCrossingColorChanged(choice.value))
|
||||
.width(Fill),
|
||||
]
|
||||
.spacing(10)
|
||||
.align_y(iced::Center),
|
||||
Space::new().height(10),
|
||||
row![
|
||||
text(crate::t!("Selection highlight color")).size(12).width(140),
|
||||
iced::widget::pick_list(
|
||||
Some(selected_highlight_color),
|
||||
color_choice_options.clone(),
|
||||
|choice| choice.label.clone(),
|
||||
)
|
||||
.on_select(|choice| Message::SelectionHighlightColorChanged(choice.value))
|
||||
.width(Fill),
|
||||
]
|
||||
.spacing(10)
|
||||
.align_y(iced::Center),
|
||||
Space::new().height(24),
|
||||
text(crate::t!("Grip Settings")).size(15),
|
||||
Space::new().height(10),
|
||||
row![
|
||||
text(crate::t!("Grip size")).size(12).width(140),
|
||||
slider(
|
||||
1..=25,
|
||||
model_space.grip_size.clamp(1, 25),
|
||||
Message::GripSizeChanged,
|
||||
)
|
||||
.step(1)
|
||||
.width(Fill),
|
||||
text(format!("{} px", model_space.grip_size.clamp(1, 25)))
|
||||
.size(11)
|
||||
.width(44),
|
||||
]
|
||||
.spacing(10)
|
||||
.align_y(iced::Center),
|
||||
Space::new().height(10),
|
||||
row![
|
||||
text(crate::t!("Unselected grip color")).size(12).width(140),
|
||||
iced::widget::pick_list(
|
||||
Some(selected_grip_color),
|
||||
color_choice_options.clone(),
|
||||
|choice| choice.label.clone(),
|
||||
)
|
||||
.on_select(|choice| Message::GripColorChanged(choice.value))
|
||||
.width(Fill),
|
||||
]
|
||||
.spacing(10)
|
||||
.align_y(iced::Center),
|
||||
Space::new().height(10),
|
||||
row![
|
||||
text(crate::t!("Selected/hot grip color")).size(12).width(140),
|
||||
iced::widget::pick_list(
|
||||
Some(selected_grip_hot),
|
||||
color_choice_options.clone(),
|
||||
|choice| choice.label.clone(),
|
||||
)
|
||||
.on_select(|choice| Message::GripHotChanged(choice.value))
|
||||
.width(Fill),
|
||||
]
|
||||
.spacing(10)
|
||||
.align_y(iced::Center),
|
||||
Space::new().height(10),
|
||||
row![
|
||||
text(crate::t!("Hover grip color")).size(12).width(140),
|
||||
iced::widget::pick_list(
|
||||
Some(selected_grip_hover),
|
||||
color_choice_options,
|
||||
|choice| choice.label.clone(),
|
||||
)
|
||||
.on_select(|choice| Message::GripHoverChanged(choice.value))
|
||||
.width(Fill),
|
||||
]
|
||||
.spacing(10)
|
||||
.align_y(iced::Center),
|
||||
]
|
||||
.spacing(0)
|
||||
.width(sizing.width);
|
||||
|
||||
let content: Element<'a, Message> = match active_tab {
|
||||
OptionsTab::General => general.into(),
|
||||
OptionsTab::Display => display.into(),
|
||||
OptionsTab::Display => display_element.into(),
|
||||
OptionsTab::Selection => selection.into(),
|
||||
};
|
||||
|
||||
|
||||
Reference in new issue
Block a user