Clean up Graphic Attributes palette

- Revert formatting, import/module reordering and unrelated rewrites
  outside the feature (command.rs, style.rs, paper.rs, view/mod.rs,
  properties.rs, dialog.rs, ValuePromptCommand); the new SVG icons stay.
- Move the palette logic out of dialog.rs into update/graphic_attributes.rs.
- Replace 14 loose app fields and ~50 Message variants by
  GraphicAttributesState (one Menu enum for the open drop-down) and a
  nested GraphicAttributesMsg / GradientMsg, like BlockPaletteMsg.
- Share the selection -> line/fill handles -> unlocked edit path, reuse
  valid_block_name, the ribbon lineweight handler and fill_handles, and
  drop the unused GradientColorToggle, color_open and LineLinetypeScaleInput.
- Linetype scale accepts any positive value (e.g. ISO pen widths 0.13,
  0.18, 0.25, 0.35) instead of rounding to 0.1 with a 0.1 minimum.
- Closing open boundaries and creating the fill is one undo step.
- The close prompt also answers typed Y/N + Enter, a bare Enter (Yes)
  and Escape (cancel), not only the option buttons.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01VgkJN8DEojuPTumAXLHotu
This commit is contained in:
Claude committed 2026-10-07 16:45:39 +00:00
1 parent 3e45331de1
commit 3c859c6b0e
17 files changed
+3478 -3992

No files matched your search

+8 -85
View File
@@ -12,18 +12,20 @@ mod command_driver;
pub(crate) mod commands;
pub(crate) mod dim_viewport;
#[cfg(test)]
mod viewport_dimension_tests;
#[cfg(test)]
mod dimension_preview_tests;
mod document;
mod drafting_settings;
pub(crate) mod expr_eval;
mod options_session;
mod find_replace;
pub(crate) mod helpers;
mod history;
mod layers;
mod model_ops;
mod mtext_editor;
mod navigation;
mod options_session;
mod mtext_editor;
#[cfg(not(target_arch = "wasm32"))]
pub mod plugin_host;
mod presspull_ops;
@@ -38,8 +40,6 @@ mod text_inline;
mod tolerance_dialog;
mod update;
mod view;
#[cfg(test)]
mod viewport_dimension_tests;
mod visibility;
pub use style_ops::StyleKind;
@@ -768,24 +768,9 @@ pub(super) struct OpenCADStudio {
render_mode_preview: Option<acadrust::entities::ViewportRenderMode>,
/// Whether the Properties panel is shown on the left (PROPERTIES).
show_properties: bool,
/// Whether the Graphic Attributes palette is shown (GRAPHICATTRIBUTES).
show_graphic_attributes: bool,
graphic_attributes_header_menu_open: bool,
graphic_attribute_menu_open: bool,
line_color_menu_open: bool,
line_linetype_menu_open: bool,
line_lineweight_menu_open: bool,
line_transparency_menu_open: bool,
graphic_hatch_pattern_search: String,
graphic_hatch_pattern_focus: usize,
solid_fill_color_menu_open: bool,
fill_transparency_menu_open: bool,
gradient_editor: Option<crate::ui::window::gradient_editor::GradientEditorState>,
hatch_editor_handles: Option<Vec<acadrust::Handle>>,
pending_graphic_fill_close: Option<(
crate::ui::window::graphic_attributes::GraphicAttribute,
Vec<acadrust::Handle>,
Vec<acadrust::Handle>,
)>,
graphic_attributes: crate::ui::window::graphic_attributes::GraphicAttributesState,
/// Docked Insert Block panel visibility.
pub(crate) show_block_palette: bool,
/// Docked External References panel visibility (EXTERNALREFERENCES).
@@ -2067,57 +2052,7 @@ pub enum ArrowKey {
#[derive(Debug, Clone)]
pub enum Message {
ToggleGraphicAttributesHeaderMenu,
CloseGraphicAttributesHeaderMenu,
GraphicAttributesSetAllByLayer,
GraphicAttributesSetAllByBlock,
GraphicAttributesRemoveReferences,
GraphicAttributesCreateLayer,
ToggleGraphicAttributeDropdown,
CloseGraphicAttributeDropdown,
GraphicAttributeChanged(crate::ui::window::graphic_attributes::GraphicAttribute),
ToggleLineColorDropdown,
CloseLineColorDropdown,
LineColorChanged(AcadColor),
ToggleLineLinetypeMenu,
CloseLineLinetypeMenu,
LineLinetypeChanged(String),
ToggleLineLineweightMenu,
CloseLineLineweightMenu,
LineLineweightChanged(LineWeight),
LineLinetypeScaleChanged(f64),
LineLinetypeScaleInput(String),
ToggleLineTransparencyDropdown,
CloseLineTransparencyDropdown,
LineTransparencyChanged(u8),
LineTransparencyByLayer,
LineTransparencyByBlock,
ToggleSolidFillColorDropdown,
CloseSolidFillColorDropdown,
SolidFillColorChanged(AcadColor),
ToggleFillTransparencyDropdown,
CloseFillTransparencyDropdown,
FillTransparencyChanged(u8),
FillTransparencyByLayer,
FillTransparencyByBlock,
HatchEditorOpen,
HatchEditorClose,
GraphicHatchPatternSearchChanged(String),
GraphicHatchPatternFocus(usize),
GraphicHatchPatternChanged(String),
GraphicHatchPatternConfirm,
GradientEditorOpen,
GradientColorModeChanged(crate::ui::window::gradient_editor::GradientColorMode),
GradientColorToggle(u8),
GradientColorChanged(u8, AcadColor),
GradientShadeTintChanged(f32),
GradientTypeChanged(crate::scene::model::hatch_model::GradientKind),
GradientInvertToggled,
GradientAngleChanged(String),
GradientAngleReset,
GradientCenteredChanged(bool),
GradientApply,
GradientCancel,
GraphicAttributes(crate::ui::window::graphic_attributes::GraphicAttributesMsg),
SpaceMouseWake,
SpaceMouseFrame(iced::time::Instant),
SpaceMouseFocus(iced::window::Id, bool),
@@ -4170,19 +4105,7 @@ impl OpenCADStudio {
render_mode_preview: None,
show_properties: true,
show_graphic_attributes: false,
graphic_attributes_header_menu_open: false,
graphic_attribute_menu_open: false,
line_color_menu_open: false,
line_linetype_menu_open: false,
line_lineweight_menu_open: false,
line_transparency_menu_open: false,
graphic_hatch_pattern_search: String::new(),
graphic_hatch_pattern_focus: 0,
solid_fill_color_menu_open: false,
fill_transparency_menu_open: false,
gradient_editor: None,
hatch_editor_handles: None,
pending_graphic_fill_close: None,
graphic_attributes: Default::default(),
show_block_palette: false,
show_external_references: false,
show_browser: false,
+369 -311
View File
File diff suppressed because it is too large. Load diff
+165 -1853
View File
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+75 -503
View File
@@ -95,6 +95,7 @@ mod context_menu;
mod dialog;
mod dynamic;
mod file;
mod graphic_attributes;
mod page_setup_import;
mod style;
pub(in crate::app) mod util;
@@ -331,7 +332,8 @@ impl OpenCADStudio {
{
// Esc backs out of a pending ALIASEDIT draft first, mirroring
// the shortcut editor's capture cancel; the next Esc closes.
if self.active_modal == Some(super::ModalKind::Aliases) && self.alias_pending_add {
if self.active_modal == Some(super::ModalKind::Aliases) && self.alias_pending_add
{
return self.update(Message::AliasEditorDraftCancel);
}
if self.active_modal == Some(super::ModalKind::BlockDefinition) {
@@ -393,12 +395,9 @@ impl OpenCADStudio {
// a mouse move included — without allocating.
#[cfg(not(target_arch = "wasm32"))]
let active_command = |app: &Self| -> Option<(u64, Option<&'static str>)> {
app.tabs.get(app.active_tab).map(|tab| {
(
tab.id,
tab.active_cmd.as_ref().map(|command| command.name()),
)
})
app.tabs
.get(app.active_tab)
.map(|tab| (tab.id, tab.active_cmd.as_ref().map(|command| command.name())))
};
#[cfg(not(target_arch = "wasm32"))]
let command_before = active_command(self);
@@ -416,33 +415,9 @@ impl OpenCADStudio {
}
}
self.refresh_gpu_status();
if self.gradient_editor.as_ref().is_some_and(|editor| {
if self.graphic_attributes.tracks_selection() {
let selected = self.tabs[self.active_tab].scene.selected_handles_in_order();
selected.len() != editor.handles.len()
|| selected
.iter()
.any(|handle| !editor.handles.contains(handle))
}) {
self.gradient_editor = None;
}
if self.hatch_editor_handles.as_ref().is_some_and(|handles| {
let selected = self.tabs[self.active_tab].scene.selected_handles_in_order();
selected.len() != handles.len()
|| selected.iter().any(|handle| !handles.contains(handle))
}) {
self.hatch_editor_handles = None;
}
if self
.pending_graphic_fill_close
.as_ref()
.is_some_and(|(_, _, handles)| {
let selected =
self.tabs[self.active_tab].scene.selected_handles_in_order();
selected.len() != handles.len()
|| selected.iter().any(|handle| !handles.contains(handle))
})
{
self.pending_graphic_fill_close = None;
self.graphic_attributes.retain_selection(&selected);
}
self.show_next_startup_modal();
self.sync_open_command_history();
@@ -457,19 +432,7 @@ impl OpenCADStudio {
.active_cmd
.as_ref()
.map(|c| c.prompt())
.or_else(|| {
self.pending_graphic_fill_close.as_ref().map(|(_, handles, _)| {
if handles.len() == 1 {
"The selected object is open. Close it before creating the fill? [Yes / No]"
.to_string()
} else {
format!(
"{} selected objects are open. Close them before creating the fill? [Yes / No]",
handles.len()
)
}
})
})
.or_else(|| self.graphic_attributes.fill_close_prompt())
.or_else(|| self.pending_pick_label());
self.command_line.set_step_prompt(prompt);
// Mirror the step's clickable options so they render as buttons (#304).
@@ -478,7 +441,7 @@ impl OpenCADStudio {
.as_ref()
.map(|c| c.options())
.or_else(|| {
self.pending_graphic_fill_close.as_ref().map(|_| {
self.graphic_attributes.pending_fill_close.as_ref().map(|_| {
vec![
crate::command::CmdOption::new("Yes", "Y"),
crate::command::CmdOption::new("No", "N"),
@@ -558,8 +521,9 @@ impl OpenCADStudio {
Message::SpaceMouseWake => self.on_spacemouse_wake(),
Message::TrackpadPinch(magnification) => self.on_pinch_zoom(magnification),
Message::SpaceMouseFrame(time) => {
self.spacemouse
.frame(time.saturating_duration_since(self.start).as_secs_f64() * 1000.);
self.spacemouse.frame(
time.saturating_duration_since(self.start).as_secs_f64() * 1000.,
);
Task::none()
}
Message::SpaceMouseFocus(id, focused) => {
@@ -611,8 +575,9 @@ impl OpenCADStudio {
if response["ok"] == false {
let code = response["code"].as_str().unwrap_or("failed");
let detail = response["error"].as_str().unwrap_or("");
self.command_line
.push_error(&format!("automation {op}: {code} — {detail}"));
self.command_line.push_error(&format!(
"automation {op}: {code} — {detail}"
));
} else if matches!(
response["status"].as_str(),
Some("completed" | "cancelled" | "waiting_input")
@@ -1213,23 +1178,19 @@ impl OpenCADStudio {
Task::none()
}
Message::ImageEmbedPick => Task::perform(
crate::io::pick_embedded_image_file(),
Message::ImageEmbedPickResult,
),
Message::ImageEmbedPick => {
Task::perform(crate::io::pick_embedded_image_file(), Message::ImageEmbedPickResult)
}
Message::ImageEmbedPickResult(Ok(image)) => {
use crate::command::CadCommand;
use crate::modules::draw::draw::raster_image::ImageCommand;
self.command_line.push_output(
crate::tf!(
self.command_line.push_output(crate::tf!(
"IMAGEEMBED \"{name}\": {w}×{h} px (embedded)",
name = image.name.as_str(),
w = image.pixel_width,
h = image.pixel_height,
)
.as_ref(),
);
).as_ref());
let cmd = ImageCommand::new_embedded(image);
let i = self.active_tab;
self.command_line.push_info(&cmd.prompt());
@@ -1316,13 +1277,10 @@ impl OpenCADStudio {
.insert(crate::io::font_repo::font_key(name));
}
for (name, path) in &pairs {
self.command_line.push_output(
crate::tf!(
self.command_line.push_output(crate::tf!(
"FONT Downloaded {name} → {path}",
path = path.display()
)
.as_ref(),
);
).as_ref());
}
if !unavailable.is_empty() {
self.command_line.push_info(crate::tf!(
@@ -1338,16 +1296,14 @@ impl OpenCADStudio {
if let Some(path) = path {
return Task::done(Message::OpenExternal(path));
}
self.command_line.push_info(
crate::t!("Save and reopen the drawing to apply the new fonts.")
.as_ref(),
);
self.command_line.push_info(crate::t!(
"Save and reopen the drawing to apply the new fonts."
).as_ref());
}
Err(e) => {
// Keep the prompt open: the user can correct a custom
// source or retry a transient network failure.
self.command_line
.push_error(crate::tf!("Font download failed: {e}").as_ref());
self.command_line.push_error(crate::tf!("Font download failed: {e}").as_ref());
}
}
Task::none()
@@ -2439,13 +2395,8 @@ impl OpenCADStudio {
// finishes the step like Enter. (#304)
self.command_line.input.clear();
self.command_line.close_history();
if self.pending_graphic_fill_close.is_some() {
if kw.eq_ignore_ascii_case("Y") || kw.eq_ignore_ascii_case("YES") {
return self.on_graphic_fill_close_response(true);
}
if kw.eq_ignore_ascii_case("N") || kw.eq_ignore_ascii_case("NO") {
return self.on_graphic_fill_close_response(false);
}
if self.awaiting_fill_close_answer() {
return self.on_graphic_fill_close_input(&kw);
}
if kw.is_empty() {
return self.feed_command(crate::command::StepInput::Enter);
@@ -2543,8 +2494,7 @@ impl OpenCADStudio {
use crate::app::config::DockSide;
use crate::ui::dock::PanelId;
if self.dock.location(PanelId::ExternalReferences).is_none() {
self.dock
.dock(PanelId::ExternalReferences, DockSide::Right, usize::MAX);
self.dock.dock(PanelId::ExternalReferences, DockSide::Right, usize::MAX);
}
self.dock_expanded = Some(PanelId::ExternalReferences);
self.refresh_xref_manager();
@@ -2682,13 +2632,16 @@ impl OpenCADStudio {
};
let new_raw = path.to_string_lossy().into_owned();
self.push_undo_snapshot(i, "XREF-PATH");
match crate::io::xref::set_ref_path(&mut self.tabs[i].scene.document, key, &new_raw)
{
match crate::io::xref::set_ref_path(
&mut self.tabs[i].scene.document,
key,
&new_raw,
) {
Ok(name) => {
self.command_line.push_output(
crate::tf!("XREF: Path set for \"{}\" — Reload to apply.", name)
.as_ref(),
);
self.command_line.push_output(crate::tf!(
"XREF: Path set for \"{}\" — Reload to apply.",
name
).as_ref());
self.post_ref_op(i);
}
Err(msg) => self.command_line.push_error(msg.as_str()),
@@ -2698,8 +2651,7 @@ impl OpenCADStudio {
}
Message::XrefPathPickResult(Err(e)) => {
if e != "Cancelled" {
self.command_line
.push_error(crate::tf!("XREF: {e}").as_ref());
self.command_line.push_error(crate::tf!("XREF: {e}").as_ref());
}
Task::none()
}
@@ -2724,11 +2676,9 @@ impl OpenCADStudio {
if let Some(found) = entry.found_at.clone() {
let is_dwg = found.to_ascii_lowercase().ends_with(".dwg")
|| found.to_ascii_lowercase().ends_with(".dxf");
self.command_line
.push_output(crate::tf!("XOPEN: opening \"{}\".", found).as_ref());
self.command_line.push_output(crate::tf!("XOPEN: opening \"{}\".", found).as_ref());
if is_dwg {
return self
.update(Message::OpenRecent(std::path::PathBuf::from(found)));
return self.update(Message::OpenRecent(std::path::PathBuf::from(found)));
} else {
#[cfg(not(target_arch = "wasm32"))]
let _ = open::that_detached(&found);
@@ -2764,10 +2714,7 @@ impl OpenCADStudio {
self.xref_manager.row_change_path_open = false;
let prefill = if let Some(entry) = self.xref_manager.entries.get(index) {
let saved = &entry.saved_path;
if let Some(parent) = std::path::Path::new(saved)
.parent()
.and_then(|p| p.to_str())
{
if let Some(parent) = std::path::Path::new(saved).parent().and_then(|p| p.to_str()) {
if !parent.is_empty() {
format!("XREF Path Find \"{}\" ", parent)
} else {
@@ -3767,13 +3714,7 @@ impl OpenCADStudio {
}
Message::ViewportLeftPress => {
self.graphic_attribute_menu_open = false;
self.line_color_menu_open = false;
self.line_linetype_menu_open = false;
self.line_lineweight_menu_open = false;
self.line_transparency_menu_open = false;
self.solid_fill_color_menu_open = false;
self.fill_transparency_menu_open = false;
self.graphic_attributes.open_menu = None;
let sweep = self.sync_active_field_if_any();
Task::batch(vec![sweep, self.on_viewport_left_press()])
}
@@ -3781,13 +3722,7 @@ impl OpenCADStudio {
Message::ViewportLeftRelease => self.on_viewport_left_release(),
Message::ViewportRightPress => {
self.graphic_attribute_menu_open = false;
self.line_color_menu_open = false;
self.line_linetype_menu_open = false;
self.line_lineweight_menu_open = false;
self.line_transparency_menu_open = false;
self.solid_fill_color_menu_open = false;
self.fill_transparency_menu_open = false;
self.graphic_attributes.open_menu = None;
let i = self.active_tab;
self.ribbon.close_dropdown();
// Shift+RMB: the one-shot snap override menu at the cursor —
@@ -3890,13 +3825,7 @@ impl OpenCADStudio {
}
Message::ViewportMiddlePress => {
self.graphic_attribute_menu_open = false;
self.line_color_menu_open = false;
self.line_linetype_menu_open = false;
self.line_lineweight_menu_open = false;
self.line_transparency_menu_open = false;
self.solid_fill_color_menu_open = false;
self.fill_transparency_menu_open = false;
self.graphic_attributes.open_menu = None;
self.on_viewport_middle_press()
}
@@ -4231,13 +4160,9 @@ impl OpenCADStudio {
}
Message::ToggleGraphicAttributes => {
self.show_graphic_attributes ^= true;
self.ribbon
.set_graphic_attributes(self.show_graphic_attributes);
self.ribbon.set_graphic_attributes(self.show_graphic_attributes);
if !self.show_graphic_attributes {
self.graphic_attributes_header_menu_open = false;
self.graphic_attribute_menu_open = false;
self.gradient_editor = None;
self.hatch_editor_handles = None;
self.graphic_attributes.close_popups();
}
self.save_config();
Task::none()
@@ -4433,9 +4358,7 @@ impl OpenCADStudio {
continue;
};
let current = (
crate::scene::pe_url_of(entity)
.unwrap_or_default()
.to_owned(),
crate::scene::pe_url_of(entity).unwrap_or_default().to_owned(),
crate::scene::pe_url_description_of(entity)
.unwrap_or_default()
.to_owned(),
@@ -4887,9 +4810,7 @@ impl OpenCADStudio {
self.tabs[i].scene.bump_geometry();
self.refresh_properties();
crate::entities::common::set_unit_context(
crate::entities::common::UnitContext::from_header(
&self.tabs[i].scene.document.header,
),
crate::entities::common::UnitContext::from_header(&self.tabs[i].scene.document.header),
);
Task::none()
}
@@ -5048,14 +4969,12 @@ impl OpenCADStudio {
};
let name = state.name.trim().to_string();
if name.is_empty() {
state.error_message =
Some(crate::t!("Block name cannot be empty.").into_owned());
state.error_message = Some(crate::t!("Block name cannot be empty.").into_owned());
return Task::none();
}
if let Some(_ch) = state.invalid_name_char() {
state.error_message = Some(
crate::t!("Block name cannot contain: \\ / : * ? \" < > | = `")
.into_owned(),
crate::t!("Block name cannot contain: \\ / : * ? \" < > | = `").into_owned(),
);
return Task::none();
}
@@ -5066,21 +4985,13 @@ impl OpenCADStudio {
}
if !state.objects_specify_onscreen && state.selected_handles.is_empty() {
state.error_message = Some(
crate::t!(
"No objects selected. You must select objects to define a block."
)
crate::t!("No objects selected. You must select objects to define a block.")
.into_owned(),
);
return Task::none();
}
let i = self.active_tab;
if self.tabs[i]
.scene
.document
.block_records
.get(&name)
.is_some()
{
if self.tabs[i].scene.document.block_records.get(&name).is_some() {
state.confirm_redefine = Some(name);
return Task::none();
}
@@ -5106,16 +5017,12 @@ impl OpenCADStudio {
let current_path = std::path::Path::new(&state.file_path);
let file_stem = current_path.file_stem().and_then(|s| s.to_str());
let is_default_or_block = file_stem == Some("new_block")
|| state
.existing_blocks
.iter()
.any(|b| Some(b.as_str()) == file_stem);
|| state.existing_blocks.iter().any(|b| Some(b.as_str()) == file_stem);
if is_default_or_block {
let new_file_name = format!("{}.dwg", trimmed);
if let Some(parent) = current_path.parent() {
if !parent.as_os_str().is_empty() {
state.file_path =
parent.join(new_file_name).to_string_lossy().to_string();
state.file_path = parent.join(new_file_name).to_string_lossy().to_string();
} else {
state.file_path = new_file_name;
}
@@ -5236,19 +5143,12 @@ impl OpenCADStudio {
WblockSourceMode::Block => {
let name = state.block_name.trim();
if name.is_empty() {
state.error_message = Some(
crate::t!("Please select or enter a block name.").into_owned(),
);
state.error_message =
Some(crate::t!("Please select or enter a block name.").into_owned());
return Task::none();
}
let i = self.active_tab;
if self.tabs[i]
.scene
.document
.block_records
.get(name)
.is_none()
{
if self.tabs[i].scene.document.block_records.get(name).is_none() {
state.error_message = Some(
crate::tf!("Block \"{}\" does not exist in drawing.", name)
.into_owned(),
@@ -5372,7 +5272,10 @@ impl OpenCADStudio {
}
WblockSourceMode::Objects => {
crate::modules::insert::wblock::extract_entities_to_doc_with_base(
&document, &handles, base_point, unit,
&document,
&handles,
base_point,
unit,
)
.map_err(|e| e.to_string())?
}
@@ -5431,9 +5334,7 @@ impl OpenCADStudio {
self.tabs[i].scene.bump_geometry();
self.refresh_properties();
crate::entities::common::set_unit_context(
crate::entities::common::UnitContext::from_header(
&self.tabs[i].scene.document.header,
),
crate::entities::common::UnitContext::from_header(&self.tabs[i].scene.document.header),
);
let label = crate::modules::draw::units::linear_format_label(code);
self.command_line
@@ -5763,7 +5664,8 @@ impl OpenCADStudio {
Task::none()
}
Message::AutoConstrainReset => {
self.auto_constrain_settings = super::settings::AutoConstrainSettings::default();
self.auto_constrain_settings =
super::settings::AutoConstrainSettings::default();
self.auto_constrain_selected_row = 0;
self.auto_constrain_distance_input =
format!("{}", self.auto_constrain_settings.distance_tolerance);
@@ -5802,7 +5704,8 @@ impl OpenCADStudio {
self.auto_constrain_settings.distance_tolerance = distance;
self.auto_constrain_settings.angle_tolerance_deg = angle;
self.auto_constrain_settings.sanitize();
self.auto_constrain_saved = Some(self.auto_constrain_settings.clone());
self.auto_constrain_saved =
Some(self.auto_constrain_settings.clone());
self.persist_settings_if_changed();
if matches!(action, Message::AutoConstrainOk) {
self.close_active_modal();
@@ -9590,312 +9493,7 @@ impl OpenCADStudio {
Message::PlotDialogOpen => self.on_plot_dialog_open(),
Message::PlotDlg(m) => self.on_plot_dlg(m),
Message::BlockPalette(m) => self.on_block_palette(m),
Message::ToggleGraphicAttributesHeaderMenu => {
self.graphic_attributes_header_menu_open ^= true;
Task::none()
}
Message::CloseGraphicAttributesHeaderMenu => {
self.graphic_attributes_header_menu_open = false;
Task::none()
}
Message::GraphicAttributesSetAllByLayer => self.on_graphic_attributes_set_all(true),
Message::GraphicAttributesSetAllByBlock => self.on_graphic_attributes_set_all(false),
Message::GraphicAttributesRemoveReferences => {
self.on_graphic_attributes_remove_references()
}
Message::GraphicAttributesCreateLayer => {
self.graphic_attributes_header_menu_open = false;
let command = crate::command::FreeTextValuePromptCommand::new(
"GRAPHICATTRIBUTELAYER",
"Enter a name for the new layer:",
);
self.command_line.push_info(&command.prompt());
self.tabs[self.active_tab].active_cmd = Some(Box::new(command));
self.focus_cmd_input()
}
Message::ToggleGraphicAttributeDropdown => {
self.graphic_attribute_menu_open ^= true;
self.line_color_menu_open = false;
self.line_linetype_menu_open = false;
self.line_lineweight_menu_open = false;
self.line_transparency_menu_open = false;
self.solid_fill_color_menu_open = false;
self.fill_transparency_menu_open = false;
Task::none()
}
Message::CloseGraphicAttributeDropdown => {
self.graphic_attribute_menu_open = false;
Task::none()
}
Message::GraphicAttributeChanged(value) => self.on_graphic_attribute(value),
Message::ToggleLineColorDropdown => {
self.line_color_menu_open ^= true;
self.line_linetype_menu_open = false;
self.line_lineweight_menu_open = false;
self.line_transparency_menu_open = false;
self.graphic_attribute_menu_open = false;
self.solid_fill_color_menu_open = false;
self.fill_transparency_menu_open = false;
Task::none()
}
Message::CloseLineColorDropdown => {
self.line_color_menu_open = false;
Task::none()
}
Message::LineColorChanged(color) => self.on_line_color(color),
Message::ToggleLineLinetypeMenu => {
self.line_linetype_menu_open ^= true;
self.line_color_menu_open = false;
self.line_lineweight_menu_open = false;
self.line_transparency_menu_open = false;
self.graphic_attribute_menu_open = false;
self.solid_fill_color_menu_open = false;
self.fill_transparency_menu_open = false;
Task::none()
}
Message::CloseLineLinetypeMenu => {
self.line_linetype_menu_open = false;
Task::none()
}
Message::LineLinetypeChanged(value) => self.on_line_linetype(value),
Message::ToggleLineLineweightMenu => {
self.line_lineweight_menu_open ^= true;
self.line_color_menu_open = false;
self.line_linetype_menu_open = false;
self.line_transparency_menu_open = false;
self.graphic_attribute_menu_open = false;
self.solid_fill_color_menu_open = false;
self.fill_transparency_menu_open = false;
Task::none()
}
Message::CloseLineLineweightMenu => {
self.line_lineweight_menu_open = false;
Task::none()
}
Message::LineLineweightChanged(value) => self.on_line_lineweight(value),
Message::LineLinetypeScaleChanged(value) => self.on_line_linetype_scale(value),
Message::LineLinetypeScaleInput(value) => {
let value = value.trim().replace(',', ".");
match value.parse::<f64>() {
Ok(value) if value.is_finite() && value > 0.0 => {
self.on_line_linetype_scale(value)
}
_ => Task::none(),
}
}
Message::ToggleLineTransparencyDropdown => {
self.line_transparency_menu_open ^= true;
self.line_color_menu_open = false;
self.line_linetype_menu_open = false;
self.line_lineweight_menu_open = false;
self.graphic_attribute_menu_open = false;
self.solid_fill_color_menu_open = false;
self.fill_transparency_menu_open = false;
Task::none()
}
Message::CloseLineTransparencyDropdown => {
self.line_transparency_menu_open = false;
Task::none()
}
Message::LineTransparencyChanged(value) => self.on_line_transparency(
acadrust::types::Transparency::from_percent(f64::from(value) / 100.0),
),
Message::LineTransparencyByLayer => {
self.on_line_transparency(acadrust::types::Transparency::BY_LAYER)
}
Message::LineTransparencyByBlock => {
self.on_line_transparency(acadrust::types::Transparency::BY_BLOCK)
}
Message::ToggleSolidFillColorDropdown => {
self.solid_fill_color_menu_open ^= true;
self.line_color_menu_open = false;
self.line_linetype_menu_open = false;
self.line_lineweight_menu_open = false;
self.line_transparency_menu_open = false;
self.graphic_attribute_menu_open = false;
self.fill_transparency_menu_open = false;
Task::none()
}
Message::CloseSolidFillColorDropdown => {
self.solid_fill_color_menu_open = false;
Task::none()
}
Message::SolidFillColorChanged(color) => self.on_solid_fill_color(color),
Message::ToggleFillTransparencyDropdown => {
self.fill_transparency_menu_open ^= true;
self.line_color_menu_open = false;
self.line_linetype_menu_open = false;
self.line_lineweight_menu_open = false;
self.line_transparency_menu_open = false;
self.graphic_attribute_menu_open = false;
self.solid_fill_color_menu_open = false;
Task::none()
}
Message::CloseFillTransparencyDropdown => {
self.fill_transparency_menu_open = false;
Task::none()
}
Message::FillTransparencyChanged(value) => self.on_fill_transparency(
acadrust::types::Transparency::from_percent(f64::from(value) / 100.0),
),
Message::FillTransparencyByLayer => {
self.on_fill_transparency(acadrust::types::Transparency::BY_LAYER)
}
Message::FillTransparencyByBlock => {
self.on_fill_transparency(acadrust::types::Transparency::BY_BLOCK)
}
Message::HatchEditorOpen => {
self.gradient_editor = None;
let i = self.active_tab;
let selected = self.tabs[i].scene.selected_handles_in_order();
let handles = crate::ui::window::graphic_attributes::fill_handles(
&self.tabs[i].scene.document,
&selected,
);
let current = handles.iter().find_map(|handle| {
let acadrust::EntityType::Hatch(hatch) =
self.tabs[i].scene.document.get_entity(*handle)?
else {
return None;
};
Some(hatch.pattern.name.as_str())
});
self.graphic_hatch_pattern_search.clear();
self.graphic_hatch_pattern_focus = current
.and_then(|current| {
crate::ui::properties::filtered_hatch_patterns("")
.iter()
.position(|entry| entry.name.eq_ignore_ascii_case(current))
})
.unwrap_or(0);
self.hatch_editor_handles = Some(selected);
iced::widget::operation::focus(iced::widget::Id::new(
"graphic-hatch-pattern-search",
))
}
Message::HatchEditorClose => {
self.hatch_editor_handles = None;
self.graphic_hatch_pattern_search.clear();
self.graphic_hatch_pattern_focus = 0;
Task::none()
}
Message::GraphicHatchPatternSearchChanged(search) => {
self.graphic_hatch_pattern_search = search;
self.graphic_hatch_pattern_focus = 0;
Task::none()
}
Message::GraphicHatchPatternFocus(index) => {
let len = crate::ui::properties::filtered_hatch_patterns(
&self.graphic_hatch_pattern_search,
)
.len();
if index < len {
self.graphic_hatch_pattern_focus = index;
}
Task::none()
}
Message::GraphicHatchPatternChanged(name) => {
let i = self.active_tab;
let selected = self.tabs[i].scene.selected_handles_in_order();
let handles = crate::ui::window::graphic_attributes::fill_handles(
&self.tabs[i].scene.document,
&selected,
);
self.hatch_editor_handles = None;
self.graphic_hatch_pattern_search.clear();
self.graphic_hatch_pattern_focus = 0;
self.apply_hatch_pattern(i, &handles, name)
}
Message::GraphicHatchPatternConfirm => {
let name = crate::ui::properties::filtered_hatch_patterns(
&self.graphic_hatch_pattern_search,
)
.get(self.graphic_hatch_pattern_focus)
.map(|entry| entry.name.clone());
if let Some(name) = name {
self.update(Message::GraphicHatchPatternChanged(name))
} else {
Task::none()
}
}
Message::GradientEditorOpen => {
self.hatch_editor_handles = None;
self.open_gradient_editor();
Task::none()
}
Message::GradientColorModeChanged(mode) => {
if let Some(s) = &mut self.gradient_editor {
s.color_mode = mode;
}
Task::none()
}
Message::GradientColorToggle(index) => {
if let Some(s) = &mut self.gradient_editor {
s.color_open = if s.color_open == Some(index) {
None
} else {
Some(index)
};
}
Task::none()
}
Message::GradientColorChanged(index, color) => {
if let Some(s) = &mut self.gradient_editor {
if index == 1 {
s.color_1 = color
} else {
s.color_2 = color
};
s.color_open = None;
}
Task::none()
}
Message::GradientShadeTintChanged(value) => {
if let Some(s) = &mut self.gradient_editor {
s.shade_tint = value.clamp(0.0, 1.0);
}
Task::none()
}
Message::GradientTypeChanged(kind) => {
if let Some(s) = &mut self.gradient_editor {
s.kind = kind;
if kind == crate::scene::model::hatch_model::GradientKind::Linear {
s.inverted = false;
}
}
Task::none()
}
Message::GradientInvertToggled => {
if let Some(s) = &mut self.gradient_editor {
if s.kind != crate::scene::model::hatch_model::GradientKind::Linear {
s.inverted ^= true;
}
}
Task::none()
}
Message::GradientAngleChanged(value) => {
if let Some(s) = &mut self.gradient_editor {
s.angle = value;
}
Task::none()
}
Message::GradientAngleReset => {
if let Some(s) = &mut self.gradient_editor {
s.angle = "0.0°".into();
}
Task::none()
}
Message::GradientCenteredChanged(value) => {
if let Some(s) = &mut self.gradient_editor {
s.centered = value;
}
Task::none()
}
Message::GradientApply => self.apply_gradient_editor(),
Message::GradientCancel => {
self.gradient_editor = None;
Task::none()
}
Message::GraphicAttributes(m) => self.on_graphic_attributes(m),
Message::Dock(m) => self.on_dock(m),
Message::PrintAllOpen => self.on_print_all_open(),
Message::PrintAllToggle(name) => {
@@ -11032,14 +10630,12 @@ impl OpenCADStudio {
Task::none()
}
Message::OpenColorWindow(target, color) => {
match target {
crate::app::ColorPickTarget::GraphicAttributesLine => {
self.line_color_menu_open = false;
}
crate::app::ColorPickTarget::GraphicAttributesSolid => {
self.solid_fill_color_menu_open = false;
}
_ => {}
if matches!(
target,
crate::app::ColorPickTarget::GraphicAttributesLine
| crate::app::ColorPickTarget::GraphicAttributesSolid
) {
self.graphic_attributes.open_menu = None;
}
self.color_pick_target = Some((target, color));
@@ -11233,30 +10829,6 @@ mod prop_pointer_tests {
}
}
#[cfg(test)]
mod graphic_attributes_visibility_tests {
use super::Message;
use crate::app::OpenCADStudio;
#[test]
fn graphic_attributes_starts_hidden_and_toggle_is_persisted() {
let mut app = OpenCADStudio::new_for_test();
app.apply_config(crate::app::config::AppConfig::default());
assert!(!app.show_graphic_attributes);
assert!(!app.ribbon.show_graphic_attributes);
let _ = app.update(Message::ToggleGraphicAttributes);
assert!(app.show_graphic_attributes);
assert!(app.ribbon.show_graphic_attributes);
assert!(app.current_config().show_graphic_attributes);
let _ = app.update(Message::ToggleGraphicAttributes);
assert!(!app.show_graphic_attributes);
assert!(!app.ribbon.show_graphic_attributes);
assert!(!app.current_config().show_graphic_attributes);
}
}
#[cfg(test)]
mod free_text_entry_tests {
//! The free-form text prompts (table cell content, TEXT / MTEXT bodies)
+35 -33
View File
@@ -4,12 +4,14 @@
use super::util::*;
use super::{format_size, VIEWCUBE_HIT_SIZE};
use crate::app::helpers::{
parse_coord, polar_constrain_near, ucs_rotate_vec, ucs_to_wcs, ucs_z_axis, CoordKind,
parse_coord, polar_constrain_near, ucs_rotate_vec, ucs_to_wcs, ucs_z_axis,
CoordKind,
};
use crate::app::{Message, OpenCADStudio, POLY_START_DELAY_MS};
use crate::modules::ModuleEvent;
use crate::scene::model::object::GripApply;
use crate::ui::window::graphic_attributes::GraphicAttributesMsg;
use crate::scene::pick::grip::{find_hit_grip, find_hit_grip_paper, find_hit_grip_rte, GripEdit};
use crate::scene::model::object::GripApply;
use crate::scene::{
self, hover_id, CubeRegion, Scene, VIEWCUBE_DRAW_PX, VIEWCUBE_PAD, VIEWCUBE_PX,
};
@@ -19,6 +21,7 @@ use acadrust::{EntityType as AcadEntityType, Handle};
use iced::time::Instant;
use iced::{mouse, Point, Task};
impl OpenCADStudio {
pub(in crate::app) fn mlstyle_mut(
&mut self,
@@ -105,10 +108,7 @@ impl OpenCADStudio {
}
}
pub(in crate::app) fn tablestyle_mut(
&mut self,
tab: usize,
) -> Option<&mut acadrust::objects::TableStyle> {
pub(in crate::app) fn tablestyle_mut(&mut self, tab: usize) -> Option<&mut acadrust::objects::TableStyle> {
use acadrust::objects::ObjectType;
let name = self.tablestyle_selected.clone();
self.tabs[tab]
@@ -212,10 +212,7 @@ impl OpenCADStudio {
/// Mutable access to the currently selected multileader style.
pub(in crate::app) fn mleaderstyle_mut(
&mut self,
tab: usize,
) -> Option<&mut acadrust::objects::MultiLeaderStyle> {
pub(in crate::app) fn mleaderstyle_mut(&mut self, tab: usize) -> Option<&mut acadrust::objects::MultiLeaderStyle> {
use acadrust::objects::ObjectType;
let name = self.mleaderstyle_selected.clone();
self.tabs[tab]
@@ -823,8 +820,7 @@ pub(super) fn on_text_style_dialog_open(&mut self) -> Task<Message> {
Task::none()
}
pub(super) fn on_mleader_style_edit(
&mut self, field: &'static str, value: String,) -> Task<Message> {
pub(super) fn on_mleader_style_edit(&mut self, field: &'static str, value: String) -> Task<Message> {
self.mls_color_open = None;
match field {
"landing_distance" => self.mls_landing_distance = value,
@@ -851,8 +847,7 @@ pub(super) fn on_text_style_dialog_open(&mut self) -> Task<Message> {
Task::none()
}
pub(super) fn on_mleader_style_set_enum(
&mut self, field: &'static str, value: String,) -> Task<Message> {
pub(super) fn on_mleader_style_set_enum(&mut self, field: &'static str, value: String) -> Task<Message> {
use acadrust::objects::{
BlockContentConnectionType, LeaderContentType, LeaderDrawOrderType,
MultiLeaderDrawOrderType, MultiLeaderPathType, TextAlignmentType,
@@ -872,8 +867,9 @@ pub(super) fn on_text_style_dialog_open(&mut self) -> Task<Message> {
"BottomOfTopLineUnderlineTopLine" => {
TextAttachmentType::BottomOfTopLineUnderlineTopLine
}
"BottomOfTopLineUnderlineAll" =>
TextAttachmentType::BottomOfTopLineUnderlineAll,
"BottomOfTopLineUnderlineAll" => {
TextAttachmentType::BottomOfTopLineUnderlineAll
}
"CenterOfText" => TextAttachmentType::CenterOfText,
"CenterOfTextOverline" => TextAttachmentType::CenterOfTextOverline,
_ => TextAttachmentType::MiddleOfTopLine,
@@ -899,8 +895,9 @@ pub(super) fn on_text_style_dialog_open(&mut self) -> Task<Message> {
}
"text_angle_type" => {
s.text_angle_type = match value.as_str() {
"ParallelToLastLeaderLine" =>
TextAngleType::ParallelToLastLeaderLine,
"ParallelToLastLeaderLine" => {
TextAngleType::ParallelToLastLeaderLine
}
"Optimized" => TextAngleType::Optimized,
_ => TextAngleType::Horizontal,
};
@@ -946,8 +943,7 @@ pub(super) fn on_text_style_dialog_open(&mut self) -> Task<Message> {
Task::none()
}
pub(super) fn on_mleader_style_set_handle(
&mut self, field: &'static str, value: String,) -> Task<Message> {
pub(super) fn on_mleader_style_set_handle(&mut self, field: &'static str, value: String) -> Task<Message> {
let i = self.active_tab;
let doc = &self.tabs[i].scene.document;
let handle: Option<acadrust::types::Handle> = if value == "None"
@@ -1135,36 +1131,42 @@ pub(super) fn on_text_style_dialog_open(&mut self) -> Task<Message> {
let s = crate::ui::color_select::color_to_aci_string(color);
let edit = match self.color_pick_target.take().map(|(target, _)| target) {
Some(crate::app::ColorPickTarget::Gradient(index)) => {
Some(Message::GradientColorChanged(index, color))
Some(Message::GraphicAttributes(GraphicAttributesMsg::Gradient(
crate::ui::window::gradient_editor::GradientMsg::Color(index, color),
)))
}
Some(crate::app::ColorPickTarget::GraphicAttributesLine) => {
Some(Message::LineColorChanged(color))
}
Some(crate::app::ColorPickTarget::GraphicAttributesSolid) => {
Some(Message::SolidFillColorChanged(color))
Some(Message::GraphicAttributes(GraphicAttributesMsg::LineColor(color)))
}
Some(crate::app::ColorPickTarget::GraphicAttributesSolid) => {
Some(Message::GraphicAttributes(GraphicAttributesMsg::FillColor(color)))
}
Some(crate::app::ColorPickTarget::DimStyle(f)) => Some(Message::DsEdit(f, s)),
Some(crate::app::ColorPickTarget::MLeader(f)) => {
Some(Message::MLeaderStyleEdit { field: f, value: s })
}
Some(crate::app::ColorPickTarget::Table(r, f)) =>
Some(crate::app::ColorPickTarget::Table(r, f)) => {
Some(Message::TableStyleCellEdit {
row: r,
field: f,
value: s,
}),
Some(crate::app::ColorPickTarget::Properties) =>
Some(Message::PropColorChanged(color)),
})
}
Some(crate::app::ColorPickTarget::Properties) => {
Some(Message::PropColorChanged(color))
}
Some(crate::app::ColorPickTarget::PropertiesBg) => {
Some(Message::PropBgColorChanged(color))
}
Some(crate::app::ColorPickTarget::PropertiesField(field)) => {
Some(Message::PropColorFieldChanged { field, color })
}
Some(crate::app::ColorPickTarget::MText) =>
Some(Message::MTextColorChanged(color)),
Some(crate::app::ColorPickTarget::Ribbon) =>
Some(Message::RibbonColorChanged(color)),
Some(crate::app::ColorPickTarget::MText) => {
Some(Message::MTextColorChanged(color))
}
Some(crate::app::ColorPickTarget::Ribbon) => {
Some(Message::RibbonColorChanged(color))
}
Some(crate::app::ColorPickTarget::Layer(idx)) => {
self.tabs[self.active_tab].layers.selected = Some(idx);
Some(Message::LayerColorSet(color))
+15 -42
View File
@@ -613,10 +613,7 @@ bg={bg_ms:.1}ms n={view_count}"
let control_polygon = tab.selected_handle.and_then(|handle| {
let spline = match tab.scene.document.get_entity(handle) {
Some(acadrust::EntityType::Spline(spline))
if crate::entities::spline::shows_control_vertices(spline) =>
{
spline
}
if crate::entities::spline::shows_control_vertices(spline) => spline,
_ => return None,
};
if tab
@@ -1112,12 +1109,8 @@ bg={bg_ms:.1}ms n={view_count}"
(None, _) if rectangle_values.is_some() => {
let (width, height) = rectangle_values.unwrap();
match f.role {
crate::command::DynRole::Width => {
crate::entities::common::format_length(width)
}
crate::command::DynRole::Height => {
crate::entities::common::format_length(height)
}
crate::command::DynRole::Width => crate::entities::common::format_length(width),
crate::command::DynRole::Height => crate::entities::common::format_length(height),
_ => String::new(),
}
}
@@ -2750,9 +2743,7 @@ impl OpenCADStudio {
} else {
Subscription::none()
},
self.spacemouse
.subscription()
.map(|_| Message::SpaceMouseWake),
self.spacemouse.subscription().map(|_| Message::SpaceMouseWake),
crate::input::trackpad::subscription().map(Message::TrackpadPinch),
event::listen_with(|event, _, id| match event {
iced::Event::Window(window::Event::Focused) => {
@@ -2940,49 +2931,31 @@ impl OpenCADStudio {
let panel: Element<'_, Message> = match id {
crate::ui::dock::PanelId::Properties => tab.properties.view(width, auto_collapse),
crate::ui::dock::PanelId::GraphicAttributes => {
let draw_depth = tab.scene.draw_depth_map();
crate::ui::window::graphic_attributes::view(
&tab.scene.document,
&self.graphic_attributes,
&tab.scene,
&tab.properties,
&tab.scene
.selected_entities()
.into_iter()
.map(|(h, _)| h)
.collect::<Vec<_>>(),
draw_depth.as_ref(),
width,
auto_collapse,
side,
self.graphic_attributes_header_menu_open,
self.graphic_attribute_menu_open,
self.line_color_menu_open,
self.line_linetype_menu_open,
self.line_lineweight_menu_open,
self.line_transparency_menu_open,
self.solid_fill_color_menu_open,
self.fill_transparency_menu_open,
self.hatch_editor_handles.is_some(),
&self.graphic_hatch_pattern_search,
self.graphic_hatch_pattern_focus,
self.gradient_editor.as_ref(),
matches!(
self.color_pick_target.as_ref().map(|(target, _)| target),
Some(crate::app::ColorPickTarget::Gradient(_))
self.color_pick_target,
Some((crate::app::ColorPickTarget::Gradient(_), _))
),
)
}
crate::ui::dock::PanelId::BlockPalette => {
crate::ui::window::block_palette::view(&self.block_palette, width, auto_collapse)
}
crate::ui::dock::PanelId::ExternalReferences => {
self.xref_manager
.view(width, auto_collapse, tab.xref_missing, &tab.scene.document)
}
crate::ui::dock::PanelId::ExternalReferences => self.xref_manager.view(
width,
auto_collapse,
tab.xref_missing,
&tab.scene.document,
),
crate::ui::dock::PanelId::Browser => crate::ui::window::browser::view(
&tab.scene.document,
tab.sketch_session
.as_ref()
.map(|session| session.name.as_str()),
tab.sketch_session.as_ref().map(|session| session.name.as_str()),
width,
auto_collapse,
),