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 <noreply@anthropic.com>
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 commands;
pub(crate) mod dim_viewport; pub(crate) mod dim_viewport;
#[cfg(test)] #[cfg(test)]
mod viewport_dimension_tests;
#[cfg(test)]
mod dimension_preview_tests; mod dimension_preview_tests;
mod document; mod document;
mod drafting_settings; mod drafting_settings;
pub(crate) mod expr_eval; pub(crate) mod expr_eval;
mod options_session;
mod find_replace; mod find_replace;
pub(crate) mod helpers; pub(crate) mod helpers;
mod history; mod history;
mod layers; mod layers;
mod model_ops; mod model_ops;
mod mtext_editor;
mod navigation; mod navigation;
mod options_session; mod mtext_editor;
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
pub mod plugin_host; pub mod plugin_host;
mod presspull_ops; mod presspull_ops;
@@ -38,8 +40,6 @@ mod text_inline;
mod tolerance_dialog; mod tolerance_dialog;
mod update; mod update;
mod view; mod view;
#[cfg(test)]
mod viewport_dimension_tests;
mod visibility; mod visibility;
pub use style_ops::StyleKind; pub use style_ops::StyleKind;
@@ -768,24 +768,9 @@ pub(super) struct OpenCADStudio {
render_mode_preview: Option<acadrust::entities::ViewportRenderMode>, render_mode_preview: Option<acadrust::entities::ViewportRenderMode>,
/// Whether the Properties panel is shown on the left (PROPERTIES). /// Whether the Properties panel is shown on the left (PROPERTIES).
show_properties: bool, show_properties: bool,
/// Whether the Graphic Attributes palette is shown (GRAPHICATTRIBUTES).
show_graphic_attributes: bool, show_graphic_attributes: bool,
graphic_attributes_header_menu_open: bool, graphic_attributes: crate::ui::window::graphic_attributes::GraphicAttributesState,
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>,
)>,
/// Docked Insert Block panel visibility. /// Docked Insert Block panel visibility.
pub(crate) show_block_palette: bool, pub(crate) show_block_palette: bool,
/// Docked External References panel visibility (EXTERNALREFERENCES). /// Docked External References panel visibility (EXTERNALREFERENCES).
@@ -2067,57 +2052,7 @@ pub enum ArrowKey {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum Message { pub enum Message {
ToggleGraphicAttributesHeaderMenu, GraphicAttributes(crate::ui::window::graphic_attributes::GraphicAttributesMsg),
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,
SpaceMouseWake, SpaceMouseWake,
SpaceMouseFrame(iced::time::Instant), SpaceMouseFrame(iced::time::Instant),
SpaceMouseFocus(iced::window::Id, bool), SpaceMouseFocus(iced::window::Id, bool),
@@ -4170,19 +4105,7 @@ impl OpenCADStudio {
render_mode_preview: None, render_mode_preview: None,
show_properties: true, show_properties: true,
show_graphic_attributes: false, show_graphic_attributes: false,
graphic_attributes_header_menu_open: false, graphic_attributes: Default::default(),
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,
show_block_palette: false, show_block_palette: false,
show_external_references: false, show_external_references: false,
show_browser: 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 dialog;
mod dynamic; mod dynamic;
mod file; mod file;
mod graphic_attributes;
mod page_setup_import; mod page_setup_import;
mod style; mod style;
pub(in crate::app) mod util; pub(in crate::app) mod util;
@@ -331,7 +332,8 @@ impl OpenCADStudio {
{ {
// Esc backs out of a pending ALIASEDIT draft first, mirroring // Esc backs out of a pending ALIASEDIT draft first, mirroring
// the shortcut editor's capture cancel; the next Esc closes. // 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); return self.update(Message::AliasEditorDraftCancel);
} }
if self.active_modal == Some(super::ModalKind::BlockDefinition) { if self.active_modal == Some(super::ModalKind::BlockDefinition) {
@@ -393,12 +395,9 @@ impl OpenCADStudio {
// a mouse move included — without allocating. // a mouse move included — without allocating.
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
let active_command = |app: &Self| -> Option<(u64, Option<&'static str>)> { let active_command = |app: &Self| -> Option<(u64, Option<&'static str>)> {
app.tabs.get(app.active_tab).map(|tab| { app.tabs
( .get(app.active_tab)
tab.id, .map(|tab| (tab.id, tab.active_cmd.as_ref().map(|command| command.name())))
tab.active_cmd.as_ref().map(|command| command.name()),
)
})
}; };
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
let command_before = active_command(self); let command_before = active_command(self);
@@ -416,33 +415,9 @@ impl OpenCADStudio {
} }
} }
self.refresh_gpu_status(); 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(); let selected = self.tabs[self.active_tab].scene.selected_handles_in_order();
selected.len() != editor.handles.len() self.graphic_attributes.retain_selection(&selected);
|| 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.show_next_startup_modal(); self.show_next_startup_modal();
self.sync_open_command_history(); self.sync_open_command_history();
@@ -457,19 +432,7 @@ impl OpenCADStudio {
.active_cmd .active_cmd
.as_ref() .as_ref()
.map(|c| c.prompt()) .map(|c| c.prompt())
.or_else(|| { .or_else(|| self.graphic_attributes.fill_close_prompt())
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.pending_pick_label()); .or_else(|| self.pending_pick_label());
self.command_line.set_step_prompt(prompt); self.command_line.set_step_prompt(prompt);
// Mirror the step's clickable options so they render as buttons (#304). // Mirror the step's clickable options so they render as buttons (#304).
@@ -478,7 +441,7 @@ impl OpenCADStudio {
.as_ref() .as_ref()
.map(|c| c.options()) .map(|c| c.options())
.or_else(|| { .or_else(|| {
self.pending_graphic_fill_close.as_ref().map(|_| { self.graphic_attributes.pending_fill_close.as_ref().map(|_| {
vec![ vec![
crate::command::CmdOption::new("Yes", "Y"), crate::command::CmdOption::new("Yes", "Y"),
crate::command::CmdOption::new("No", "N"), crate::command::CmdOption::new("No", "N"),
@@ -558,8 +521,9 @@ impl OpenCADStudio {
Message::SpaceMouseWake => self.on_spacemouse_wake(), Message::SpaceMouseWake => self.on_spacemouse_wake(),
Message::TrackpadPinch(magnification) => self.on_pinch_zoom(magnification), Message::TrackpadPinch(magnification) => self.on_pinch_zoom(magnification),
Message::SpaceMouseFrame(time) => { Message::SpaceMouseFrame(time) => {
self.spacemouse self.spacemouse.frame(
.frame(time.saturating_duration_since(self.start).as_secs_f64() * 1000.); time.saturating_duration_since(self.start).as_secs_f64() * 1000.,
);
Task::none() Task::none()
} }
Message::SpaceMouseFocus(id, focused) => { Message::SpaceMouseFocus(id, focused) => {
@@ -611,8 +575,9 @@ impl OpenCADStudio {
if response["ok"] == false { if response["ok"] == false {
let code = response["code"].as_str().unwrap_or("failed"); let code = response["code"].as_str().unwrap_or("failed");
let detail = response["error"].as_str().unwrap_or(""); let detail = response["error"].as_str().unwrap_or("");
self.command_line self.command_line.push_error(&format!(
.push_error(&format!("automation {op}: {code} — {detail}")); "automation {op}: {code} — {detail}"
));
} else if matches!( } else if matches!(
response["status"].as_str(), response["status"].as_str(),
Some("completed" | "cancelled" | "waiting_input") Some("completed" | "cancelled" | "waiting_input")
@@ -1213,23 +1178,19 @@ impl OpenCADStudio {
Task::none() Task::none()
} }
Message::ImageEmbedPick => Task::perform( Message::ImageEmbedPick => {
crate::io::pick_embedded_image_file(), Task::perform(crate::io::pick_embedded_image_file(), Message::ImageEmbedPickResult)
Message::ImageEmbedPickResult, }
),
Message::ImageEmbedPickResult(Ok(image)) => { Message::ImageEmbedPickResult(Ok(image)) => {
use crate::command::CadCommand; use crate::command::CadCommand;
use crate::modules::draw::draw::raster_image::ImageCommand; use crate::modules::draw::draw::raster_image::ImageCommand;
self.command_line.push_output( self.command_line.push_output(crate::tf!(
crate::tf!(
"IMAGEEMBED \"{name}\": {w}×{h} px (embedded)", "IMAGEEMBED \"{name}\": {w}×{h} px (embedded)",
name = image.name.as_str(), name = image.name.as_str(),
w = image.pixel_width, w = image.pixel_width,
h = image.pixel_height, h = image.pixel_height,
) ).as_ref());
.as_ref(),
);
let cmd = ImageCommand::new_embedded(image); let cmd = ImageCommand::new_embedded(image);
let i = self.active_tab; let i = self.active_tab;
self.command_line.push_info(&cmd.prompt()); self.command_line.push_info(&cmd.prompt());
@@ -1316,13 +1277,10 @@ impl OpenCADStudio {
.insert(crate::io::font_repo::font_key(name)); .insert(crate::io::font_repo::font_key(name));
} }
for (name, path) in &pairs { for (name, path) in &pairs {
self.command_line.push_output( self.command_line.push_output(crate::tf!(
crate::tf!(
"FONT Downloaded {name} → {path}", "FONT Downloaded {name} → {path}",
path = path.display() path = path.display()
) ).as_ref());
.as_ref(),
);
} }
if !unavailable.is_empty() { if !unavailable.is_empty() {
self.command_line.push_info(crate::tf!( self.command_line.push_info(crate::tf!(
@@ -1338,16 +1296,14 @@ impl OpenCADStudio {
if let Some(path) = path { if let Some(path) = path {
return Task::done(Message::OpenExternal(path)); return Task::done(Message::OpenExternal(path));
} }
self.command_line.push_info( self.command_line.push_info(crate::t!(
crate::t!("Save and reopen the drawing to apply the new fonts.") "Save and reopen the drawing to apply the new fonts."
.as_ref(), ).as_ref());
);
} }
Err(e) => { Err(e) => {
// Keep the prompt open: the user can correct a custom // Keep the prompt open: the user can correct a custom
// source or retry a transient network failure. // source or retry a transient network failure.
self.command_line self.command_line.push_error(crate::tf!("Font download failed: {e}").as_ref());
.push_error(crate::tf!("Font download failed: {e}").as_ref());
} }
} }
Task::none() Task::none()
@@ -2439,13 +2395,8 @@ impl OpenCADStudio {
// finishes the step like Enter. (#304) // finishes the step like Enter. (#304)
self.command_line.input.clear(); self.command_line.input.clear();
self.command_line.close_history(); self.command_line.close_history();
if self.pending_graphic_fill_close.is_some() { if self.awaiting_fill_close_answer() {
if kw.eq_ignore_ascii_case("Y") || kw.eq_ignore_ascii_case("YES") { return self.on_graphic_fill_close_input(&kw);
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 kw.is_empty() { if kw.is_empty() {
return self.feed_command(crate::command::StepInput::Enter); return self.feed_command(crate::command::StepInput::Enter);
@@ -2543,8 +2494,7 @@ impl OpenCADStudio {
use crate::app::config::DockSide; use crate::app::config::DockSide;
use crate::ui::dock::PanelId; use crate::ui::dock::PanelId;
if self.dock.location(PanelId::ExternalReferences).is_none() { if self.dock.location(PanelId::ExternalReferences).is_none() {
self.dock self.dock.dock(PanelId::ExternalReferences, DockSide::Right, usize::MAX);
.dock(PanelId::ExternalReferences, DockSide::Right, usize::MAX);
} }
self.dock_expanded = Some(PanelId::ExternalReferences); self.dock_expanded = Some(PanelId::ExternalReferences);
self.refresh_xref_manager(); self.refresh_xref_manager();
@@ -2682,13 +2632,16 @@ impl OpenCADStudio {
}; };
let new_raw = path.to_string_lossy().into_owned(); let new_raw = path.to_string_lossy().into_owned();
self.push_undo_snapshot(i, "XREF-PATH"); 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) => { Ok(name) => {
self.command_line.push_output( self.command_line.push_output(crate::tf!(
crate::tf!("XREF: Path set for \"{}\" — Reload to apply.", name) "XREF: Path set for \"{}\" — Reload to apply.",
.as_ref(), name
); ).as_ref());
self.post_ref_op(i); self.post_ref_op(i);
} }
Err(msg) => self.command_line.push_error(msg.as_str()), Err(msg) => self.command_line.push_error(msg.as_str()),
@@ -2698,8 +2651,7 @@ impl OpenCADStudio {
} }
Message::XrefPathPickResult(Err(e)) => { Message::XrefPathPickResult(Err(e)) => {
if e != "Cancelled" { if e != "Cancelled" {
self.command_line self.command_line.push_error(crate::tf!("XREF: {e}").as_ref());
.push_error(crate::tf!("XREF: {e}").as_ref());
} }
Task::none() Task::none()
} }
@@ -2724,11 +2676,9 @@ impl OpenCADStudio {
if let Some(found) = entry.found_at.clone() { if let Some(found) = entry.found_at.clone() {
let is_dwg = found.to_ascii_lowercase().ends_with(".dwg") let is_dwg = found.to_ascii_lowercase().ends_with(".dwg")
|| found.to_ascii_lowercase().ends_with(".dxf"); || found.to_ascii_lowercase().ends_with(".dxf");
self.command_line self.command_line.push_output(crate::tf!("XOPEN: opening \"{}\".", found).as_ref());
.push_output(crate::tf!("XOPEN: opening \"{}\".", found).as_ref());
if is_dwg { if is_dwg {
return self return self.update(Message::OpenRecent(std::path::PathBuf::from(found)));
.update(Message::OpenRecent(std::path::PathBuf::from(found)));
} else { } else {
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
let _ = open::that_detached(&found); let _ = open::that_detached(&found);
@@ -2764,10 +2714,7 @@ impl OpenCADStudio {
self.xref_manager.row_change_path_open = false; self.xref_manager.row_change_path_open = false;
let prefill = if let Some(entry) = self.xref_manager.entries.get(index) { let prefill = if let Some(entry) = self.xref_manager.entries.get(index) {
let saved = &entry.saved_path; let saved = &entry.saved_path;
if let Some(parent) = std::path::Path::new(saved) if let Some(parent) = std::path::Path::new(saved).parent().and_then(|p| p.to_str()) {
.parent()
.and_then(|p| p.to_str())
{
if !parent.is_empty() { if !parent.is_empty() {
format!("XREF Path Find \"{}\" ", parent) format!("XREF Path Find \"{}\" ", parent)
} else { } else {
@@ -3767,13 +3714,7 @@ impl OpenCADStudio {
} }
Message::ViewportLeftPress => { Message::ViewportLeftPress => {
self.graphic_attribute_menu_open = false; self.graphic_attributes.open_menu = None;
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;
let sweep = self.sync_active_field_if_any(); let sweep = self.sync_active_field_if_any();
Task::batch(vec![sweep, self.on_viewport_left_press()]) Task::batch(vec![sweep, self.on_viewport_left_press()])
} }
@@ -3781,13 +3722,7 @@ impl OpenCADStudio {
Message::ViewportLeftRelease => self.on_viewport_left_release(), Message::ViewportLeftRelease => self.on_viewport_left_release(),
Message::ViewportRightPress => { Message::ViewportRightPress => {
self.graphic_attribute_menu_open = false; self.graphic_attributes.open_menu = None;
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;
let i = self.active_tab; let i = self.active_tab;
self.ribbon.close_dropdown(); self.ribbon.close_dropdown();
// Shift+RMB: the one-shot snap override menu at the cursor — // Shift+RMB: the one-shot snap override menu at the cursor —
@@ -3890,13 +3825,7 @@ impl OpenCADStudio {
} }
Message::ViewportMiddlePress => { Message::ViewportMiddlePress => {
self.graphic_attribute_menu_open = false; self.graphic_attributes.open_menu = None;
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.on_viewport_middle_press() self.on_viewport_middle_press()
} }
@@ -4231,13 +4160,9 @@ impl OpenCADStudio {
} }
Message::ToggleGraphicAttributes => { Message::ToggleGraphicAttributes => {
self.show_graphic_attributes ^= true; self.show_graphic_attributes ^= true;
self.ribbon self.ribbon.set_graphic_attributes(self.show_graphic_attributes);
.set_graphic_attributes(self.show_graphic_attributes);
if !self.show_graphic_attributes { if !self.show_graphic_attributes {
self.graphic_attributes_header_menu_open = false; self.graphic_attributes.close_popups();
self.graphic_attribute_menu_open = false;
self.gradient_editor = None;
self.hatch_editor_handles = None;
} }
self.save_config(); self.save_config();
Task::none() Task::none()
@@ -4433,9 +4358,7 @@ impl OpenCADStudio {
continue; continue;
}; };
let current = ( let current = (
crate::scene::pe_url_of(entity) crate::scene::pe_url_of(entity).unwrap_or_default().to_owned(),
.unwrap_or_default()
.to_owned(),
crate::scene::pe_url_description_of(entity) crate::scene::pe_url_description_of(entity)
.unwrap_or_default() .unwrap_or_default()
.to_owned(), .to_owned(),
@@ -4887,9 +4810,7 @@ impl OpenCADStudio {
self.tabs[i].scene.bump_geometry(); self.tabs[i].scene.bump_geometry();
self.refresh_properties(); self.refresh_properties();
crate::entities::common::set_unit_context( crate::entities::common::set_unit_context(
crate::entities::common::UnitContext::from_header( crate::entities::common::UnitContext::from_header(&self.tabs[i].scene.document.header),
&self.tabs[i].scene.document.header,
),
); );
Task::none() Task::none()
} }
@@ -5048,14 +4969,12 @@ impl OpenCADStudio {
}; };
let name = state.name.trim().to_string(); let name = state.name.trim().to_string();
if name.is_empty() { if name.is_empty() {
state.error_message = state.error_message = Some(crate::t!("Block name cannot be empty.").into_owned());
Some(crate::t!("Block name cannot be empty.").into_owned());
return Task::none(); return Task::none();
} }
if let Some(_ch) = state.invalid_name_char() { if let Some(_ch) = state.invalid_name_char() {
state.error_message = Some( state.error_message = Some(
crate::t!("Block name cannot contain: \\ / : * ? \" < > | = `") crate::t!("Block name cannot contain: \\ / : * ? \" < > | = `").into_owned(),
.into_owned(),
); );
return Task::none(); return Task::none();
} }
@@ -5066,21 +4985,13 @@ impl OpenCADStudio {
} }
if !state.objects_specify_onscreen && state.selected_handles.is_empty() { if !state.objects_specify_onscreen && state.selected_handles.is_empty() {
state.error_message = Some( state.error_message = Some(
crate::t!( crate::t!("No objects selected. You must select objects to define a block.")
"No objects selected. You must select objects to define a block."
)
.into_owned(), .into_owned(),
); );
return Task::none(); return Task::none();
} }
let i = self.active_tab; let i = self.active_tab;
if self.tabs[i] if self.tabs[i].scene.document.block_records.get(&name).is_some() {
.scene
.document
.block_records
.get(&name)
.is_some()
{
state.confirm_redefine = Some(name); state.confirm_redefine = Some(name);
return Task::none(); return Task::none();
} }
@@ -5106,16 +5017,12 @@ impl OpenCADStudio {
let current_path = std::path::Path::new(&state.file_path); let current_path = std::path::Path::new(&state.file_path);
let file_stem = current_path.file_stem().and_then(|s| s.to_str()); let file_stem = current_path.file_stem().and_then(|s| s.to_str());
let is_default_or_block = file_stem == Some("new_block") let is_default_or_block = file_stem == Some("new_block")
|| state || state.existing_blocks.iter().any(|b| Some(b.as_str()) == file_stem);
.existing_blocks
.iter()
.any(|b| Some(b.as_str()) == file_stem);
if is_default_or_block { if is_default_or_block {
let new_file_name = format!("{}.dwg", trimmed); let new_file_name = format!("{}.dwg", trimmed);
if let Some(parent) = current_path.parent() { if let Some(parent) = current_path.parent() {
if !parent.as_os_str().is_empty() { if !parent.as_os_str().is_empty() {
state.file_path = state.file_path = parent.join(new_file_name).to_string_lossy().to_string();
parent.join(new_file_name).to_string_lossy().to_string();
} else { } else {
state.file_path = new_file_name; state.file_path = new_file_name;
} }
@@ -5236,19 +5143,12 @@ impl OpenCADStudio {
WblockSourceMode::Block => { WblockSourceMode::Block => {
let name = state.block_name.trim(); let name = state.block_name.trim();
if name.is_empty() { if name.is_empty() {
state.error_message = Some( state.error_message =
crate::t!("Please select or enter a block name.").into_owned(), Some(crate::t!("Please select or enter a block name.").into_owned());
);
return Task::none(); return Task::none();
} }
let i = self.active_tab; let i = self.active_tab;
if self.tabs[i] if self.tabs[i].scene.document.block_records.get(name).is_none() {
.scene
.document
.block_records
.get(name)
.is_none()
{
state.error_message = Some( state.error_message = Some(
crate::tf!("Block \"{}\" does not exist in drawing.", name) crate::tf!("Block \"{}\" does not exist in drawing.", name)
.into_owned(), .into_owned(),
@@ -5372,7 +5272,10 @@ impl OpenCADStudio {
} }
WblockSourceMode::Objects => { WblockSourceMode::Objects => {
crate::modules::insert::wblock::extract_entities_to_doc_with_base( 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())? .map_err(|e| e.to_string())?
} }
@@ -5431,9 +5334,7 @@ impl OpenCADStudio {
self.tabs[i].scene.bump_geometry(); self.tabs[i].scene.bump_geometry();
self.refresh_properties(); self.refresh_properties();
crate::entities::common::set_unit_context( crate::entities::common::set_unit_context(
crate::entities::common::UnitContext::from_header( crate::entities::common::UnitContext::from_header(&self.tabs[i].scene.document.header),
&self.tabs[i].scene.document.header,
),
); );
let label = crate::modules::draw::units::linear_format_label(code); let label = crate::modules::draw::units::linear_format_label(code);
self.command_line self.command_line
@@ -5763,7 +5664,8 @@ impl OpenCADStudio {
Task::none() Task::none()
} }
Message::AutoConstrainReset => { 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_selected_row = 0;
self.auto_constrain_distance_input = self.auto_constrain_distance_input =
format!("{}", self.auto_constrain_settings.distance_tolerance); format!("{}", self.auto_constrain_settings.distance_tolerance);
@@ -5802,7 +5704,8 @@ impl OpenCADStudio {
self.auto_constrain_settings.distance_tolerance = distance; self.auto_constrain_settings.distance_tolerance = distance;
self.auto_constrain_settings.angle_tolerance_deg = angle; self.auto_constrain_settings.angle_tolerance_deg = angle;
self.auto_constrain_settings.sanitize(); 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(); self.persist_settings_if_changed();
if matches!(action, Message::AutoConstrainOk) { if matches!(action, Message::AutoConstrainOk) {
self.close_active_modal(); self.close_active_modal();
@@ -9590,312 +9493,7 @@ impl OpenCADStudio {
Message::PlotDialogOpen => self.on_plot_dialog_open(), Message::PlotDialogOpen => self.on_plot_dialog_open(),
Message::PlotDlg(m) => self.on_plot_dlg(m), Message::PlotDlg(m) => self.on_plot_dlg(m),
Message::BlockPalette(m) => self.on_block_palette(m), Message::BlockPalette(m) => self.on_block_palette(m),
Message::ToggleGraphicAttributesHeaderMenu => { Message::GraphicAttributes(m) => self.on_graphic_attributes(m),
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::Dock(m) => self.on_dock(m), Message::Dock(m) => self.on_dock(m),
Message::PrintAllOpen => self.on_print_all_open(), Message::PrintAllOpen => self.on_print_all_open(),
Message::PrintAllToggle(name) => { Message::PrintAllToggle(name) => {
@@ -11032,14 +10630,12 @@ impl OpenCADStudio {
Task::none() Task::none()
} }
Message::OpenColorWindow(target, color) => { Message::OpenColorWindow(target, color) => {
match target { if matches!(
crate::app::ColorPickTarget::GraphicAttributesLine => { target,
self.line_color_menu_open = false; crate::app::ColorPickTarget::GraphicAttributesLine
} | crate::app::ColorPickTarget::GraphicAttributesSolid
crate::app::ColorPickTarget::GraphicAttributesSolid => { ) {
self.solid_fill_color_menu_open = false; self.graphic_attributes.open_menu = None;
}
_ => {}
} }
self.color_pick_target = Some((target, color)); 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)] #[cfg(test)]
mod free_text_entry_tests { mod free_text_entry_tests {
//! The free-form text prompts (table cell content, TEXT / MTEXT bodies) //! The free-form text prompts (table cell content, TEXT / MTEXT bodies)
+35 -33
View File
@@ -4,12 +4,14 @@
use super::util::*; use super::util::*;
use super::{format_size, VIEWCUBE_HIT_SIZE}; use super::{format_size, VIEWCUBE_HIT_SIZE};
use crate::app::helpers::{ 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::app::{Message, OpenCADStudio, POLY_START_DELAY_MS};
use crate::modules::ModuleEvent; 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::pick::grip::{find_hit_grip, find_hit_grip_paper, find_hit_grip_rte, GripEdit};
use crate::scene::model::object::GripApply;
use crate::scene::{ use crate::scene::{
self, hover_id, CubeRegion, Scene, VIEWCUBE_DRAW_PX, VIEWCUBE_PAD, VIEWCUBE_PX, 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::time::Instant;
use iced::{mouse, Point, Task}; use iced::{mouse, Point, Task};
impl OpenCADStudio { impl OpenCADStudio {
pub(in crate::app) fn mlstyle_mut( pub(in crate::app) fn mlstyle_mut(
&mut self, &mut self,
@@ -105,10 +108,7 @@ impl OpenCADStudio {
} }
} }
pub(in crate::app) fn tablestyle_mut( pub(in crate::app) fn tablestyle_mut(&mut self, tab: usize) -> Option<&mut acadrust::objects::TableStyle> {
&mut self,
tab: usize,
) -> Option<&mut acadrust::objects::TableStyle> {
use acadrust::objects::ObjectType; use acadrust::objects::ObjectType;
let name = self.tablestyle_selected.clone(); let name = self.tablestyle_selected.clone();
self.tabs[tab] self.tabs[tab]
@@ -212,10 +212,7 @@ impl OpenCADStudio {
/// Mutable access to the currently selected multileader style. /// Mutable access to the currently selected multileader style.
pub(in crate::app) fn mleaderstyle_mut( pub(in crate::app) fn mleaderstyle_mut(&mut self, tab: usize) -> Option<&mut acadrust::objects::MultiLeaderStyle> {
&mut self,
tab: usize,
) -> Option<&mut acadrust::objects::MultiLeaderStyle> {
use acadrust::objects::ObjectType; use acadrust::objects::ObjectType;
let name = self.mleaderstyle_selected.clone(); let name = self.mleaderstyle_selected.clone();
self.tabs[tab] self.tabs[tab]
@@ -823,8 +820,7 @@ pub(super) fn on_text_style_dialog_open(&mut self) -> Task<Message> {
Task::none() Task::none()
} }
pub(super) fn on_mleader_style_edit( pub(super) fn on_mleader_style_edit(&mut self, field: &'static str, value: String) -> Task<Message> {
&mut self, field: &'static str, value: String,) -> Task<Message> {
self.mls_color_open = None; self.mls_color_open = None;
match field { match field {
"landing_distance" => self.mls_landing_distance = value, "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() Task::none()
} }
pub(super) fn on_mleader_style_set_enum( pub(super) fn on_mleader_style_set_enum(&mut self, field: &'static str, value: String) -> Task<Message> {
&mut self, field: &'static str, value: String,) -> Task<Message> {
use acadrust::objects::{ use acadrust::objects::{
BlockContentConnectionType, LeaderContentType, LeaderDrawOrderType, BlockContentConnectionType, LeaderContentType, LeaderDrawOrderType,
MultiLeaderDrawOrderType, MultiLeaderPathType, TextAlignmentType, MultiLeaderDrawOrderType, MultiLeaderPathType, TextAlignmentType,
@@ -872,8 +867,9 @@ pub(super) fn on_text_style_dialog_open(&mut self) -> Task<Message> {
"BottomOfTopLineUnderlineTopLine" => { "BottomOfTopLineUnderlineTopLine" => {
TextAttachmentType::BottomOfTopLineUnderlineTopLine TextAttachmentType::BottomOfTopLineUnderlineTopLine
} }
"BottomOfTopLineUnderlineAll" => "BottomOfTopLineUnderlineAll" => {
TextAttachmentType::BottomOfTopLineUnderlineAll, TextAttachmentType::BottomOfTopLineUnderlineAll
}
"CenterOfText" => TextAttachmentType::CenterOfText, "CenterOfText" => TextAttachmentType::CenterOfText,
"CenterOfTextOverline" => TextAttachmentType::CenterOfTextOverline, "CenterOfTextOverline" => TextAttachmentType::CenterOfTextOverline,
_ => TextAttachmentType::MiddleOfTopLine, _ => TextAttachmentType::MiddleOfTopLine,
@@ -899,8 +895,9 @@ pub(super) fn on_text_style_dialog_open(&mut self) -> Task<Message> {
} }
"text_angle_type" => { "text_angle_type" => {
s.text_angle_type = match value.as_str() { s.text_angle_type = match value.as_str() {
"ParallelToLastLeaderLine" => "ParallelToLastLeaderLine" => {
TextAngleType::ParallelToLastLeaderLine, TextAngleType::ParallelToLastLeaderLine
}
"Optimized" => TextAngleType::Optimized, "Optimized" => TextAngleType::Optimized,
_ => TextAngleType::Horizontal, _ => TextAngleType::Horizontal,
}; };
@@ -946,8 +943,7 @@ pub(super) fn on_text_style_dialog_open(&mut self) -> Task<Message> {
Task::none() Task::none()
} }
pub(super) fn on_mleader_style_set_handle( pub(super) fn on_mleader_style_set_handle(&mut self, field: &'static str, value: String) -> Task<Message> {
&mut self, field: &'static str, value: String,) -> Task<Message> {
let i = self.active_tab; let i = self.active_tab;
let doc = &self.tabs[i].scene.document; let doc = &self.tabs[i].scene.document;
let handle: Option<acadrust::types::Handle> = if value == "None" 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 s = crate::ui::color_select::color_to_aci_string(color);
let edit = match self.color_pick_target.take().map(|(target, _)| target) { let edit = match self.color_pick_target.take().map(|(target, _)| target) {
Some(crate::app::ColorPickTarget::Gradient(index)) => { 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(crate::app::ColorPickTarget::GraphicAttributesLine) => {
Some(Message::LineColorChanged(color)) Some(Message::GraphicAttributes(GraphicAttributesMsg::LineColor(color)))
} }
Some(crate::app::ColorPickTarget::GraphicAttributesSolid) => { Some(crate::app::ColorPickTarget::GraphicAttributesSolid) => {
Some(Message::SolidFillColorChanged(color)) Some(Message::GraphicAttributes(GraphicAttributesMsg::FillColor(color)))
} }
Some(crate::app::ColorPickTarget::DimStyle(f)) => Some(Message::DsEdit(f, s)), Some(crate::app::ColorPickTarget::DimStyle(f)) => Some(Message::DsEdit(f, s)),
Some(crate::app::ColorPickTarget::MLeader(f)) => { Some(crate::app::ColorPickTarget::MLeader(f)) => {
Some(Message::MLeaderStyleEdit { field: f, value: s }) Some(Message::MLeaderStyleEdit { field: f, value: s })
} }
Some(crate::app::ColorPickTarget::Table(r, f)) => Some(crate::app::ColorPickTarget::Table(r, f)) => {
Some(Message::TableStyleCellEdit { Some(Message::TableStyleCellEdit {
row: r, row: r,
field: f, field: f,
value: s, 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(crate::app::ColorPickTarget::PropertiesBg) => {
Some(Message::PropBgColorChanged(color)) Some(Message::PropBgColorChanged(color))
} }
Some(crate::app::ColorPickTarget::PropertiesField(field)) => { Some(crate::app::ColorPickTarget::PropertiesField(field)) => {
Some(Message::PropColorFieldChanged { field, color }) Some(Message::PropColorFieldChanged { field, color })
} }
Some(crate::app::ColorPickTarget::MText) => Some(crate::app::ColorPickTarget::MText) => {
Some(Message::MTextColorChanged(color)), Some(Message::MTextColorChanged(color))
Some(crate::app::ColorPickTarget::Ribbon) => }
Some(Message::RibbonColorChanged(color)), Some(crate::app::ColorPickTarget::Ribbon) => {
Some(Message::RibbonColorChanged(color))
}
Some(crate::app::ColorPickTarget::Layer(idx)) => { Some(crate::app::ColorPickTarget::Layer(idx)) => {
self.tabs[self.active_tab].layers.selected = Some(idx); self.tabs[self.active_tab].layers.selected = Some(idx);
Some(Message::LayerColorSet(color)) 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 control_polygon = tab.selected_handle.and_then(|handle| {
let spline = match tab.scene.document.get_entity(handle) { let spline = match tab.scene.document.get_entity(handle) {
Some(acadrust::EntityType::Spline(spline)) Some(acadrust::EntityType::Spline(spline))
if crate::entities::spline::shows_control_vertices(spline) => if crate::entities::spline::shows_control_vertices(spline) => spline,
{
spline
}
_ => return None, _ => return None,
}; };
if tab if tab
@@ -1112,12 +1109,8 @@ bg={bg_ms:.1}ms n={view_count}"
(None, _) if rectangle_values.is_some() => { (None, _) if rectangle_values.is_some() => {
let (width, height) = rectangle_values.unwrap(); let (width, height) = rectangle_values.unwrap();
match f.role { match f.role {
crate::command::DynRole::Width => { crate::command::DynRole::Width => crate::entities::common::format_length(width),
crate::entities::common::format_length(width) crate::command::DynRole::Height => crate::entities::common::format_length(height),
}
crate::command::DynRole::Height => {
crate::entities::common::format_length(height)
}
_ => String::new(), _ => String::new(),
} }
} }
@@ -2750,9 +2743,7 @@ impl OpenCADStudio {
} else { } else {
Subscription::none() Subscription::none()
}, },
self.spacemouse self.spacemouse.subscription().map(|_| Message::SpaceMouseWake),
.subscription()
.map(|_| Message::SpaceMouseWake),
crate::input::trackpad::subscription().map(Message::TrackpadPinch), crate::input::trackpad::subscription().map(Message::TrackpadPinch),
event::listen_with(|event, _, id| match event { event::listen_with(|event, _, id| match event {
iced::Event::Window(window::Event::Focused) => { iced::Event::Window(window::Event::Focused) => {
@@ -2940,49 +2931,31 @@ impl OpenCADStudio {
let panel: Element<'_, Message> = match id { let panel: Element<'_, Message> = match id {
crate::ui::dock::PanelId::Properties => tab.properties.view(width, auto_collapse), crate::ui::dock::PanelId::Properties => tab.properties.view(width, auto_collapse),
crate::ui::dock::PanelId::GraphicAttributes => { crate::ui::dock::PanelId::GraphicAttributes => {
let draw_depth = tab.scene.draw_depth_map();
crate::ui::window::graphic_attributes::view( crate::ui::window::graphic_attributes::view(
&tab.scene.document, &self.graphic_attributes,
&tab.scene,
&tab.properties, &tab.properties,
&tab.scene
.selected_entities()
.into_iter()
.map(|(h, _)| h)
.collect::<Vec<_>>(),
draw_depth.as_ref(),
width, width,
auto_collapse, auto_collapse,
side, 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!( matches!(
self.color_pick_target.as_ref().map(|(target, _)| target), self.color_pick_target,
Some(crate::app::ColorPickTarget::Gradient(_)) Some((crate::app::ColorPickTarget::Gradient(_), _))
), ),
) )
} }
crate::ui::dock::PanelId::BlockPalette => { crate::ui::dock::PanelId::BlockPalette => {
crate::ui::window::block_palette::view(&self.block_palette, width, auto_collapse) crate::ui::window::block_palette::view(&self.block_palette, width, auto_collapse)
} }
crate::ui::dock::PanelId::ExternalReferences => { crate::ui::dock::PanelId::ExternalReferences => self.xref_manager.view(
self.xref_manager width,
.view(width, auto_collapse, tab.xref_missing, &tab.scene.document) auto_collapse,
} tab.xref_missing,
&tab.scene.document,
),
crate::ui::dock::PanelId::Browser => crate::ui::window::browser::view( crate::ui::dock::PanelId::Browser => crate::ui::window::browser::view(
&tab.scene.document, &tab.scene.document,
tab.sketch_session tab.sketch_session.as_ref().map(|session| session.name.as_str()),
.as_ref()
.map(|session| session.name.as_str()),
width, width,
auto_collapse, auto_collapse,
), ),
+9 -22
View File
@@ -308,10 +308,6 @@ pub struct ValuePromptCommand {
prompt: &'static str, prompt: &'static str,
} }
fn dispatch_prompt_value(name: &str, text: &str) -> CmdResult {
CmdResult::Dispatch(format!("{name} {}", text.trim()))
}
impl ValuePromptCommand { impl ValuePromptCommand {
pub fn new(name: &'static str, prompt: &'static str) -> Self { pub fn new(name: &'static str, prompt: &'static str) -> Self {
Self { name, prompt } Self { name, prompt }
@@ -332,7 +328,12 @@ impl CadCommand for ValuePromptCommand {
} }
fn on_text_input(&mut self, text: &str) -> Option<CmdResult> { fn on_text_input(&mut self, text: &str) -> Option<CmdResult> {
Some(dispatch_prompt_value(self.name, text)) let t = text.trim();
if t.is_empty() {
Some(CmdResult::Dispatch(format!("{} ", self.name)))
} else {
Some(CmdResult::Dispatch(format!("{} {t}", self.name)))
}
} }
fn on_point(&mut self, _pt: DVec3) -> CmdResult { fn on_point(&mut self, _pt: DVec3) -> CmdResult {
@@ -342,7 +343,7 @@ impl CadCommand for ValuePromptCommand {
fn on_enter(&mut self) -> CmdResult { fn on_enter(&mut self) -> CmdResult {
// Bare Enter → report the current value via the inline handler. // Bare Enter → report the current value via the inline handler.
dispatch_prompt_value(self.name, "") CmdResult::Dispatch(format!("{} ", self.name))
} }
} }
@@ -374,7 +375,7 @@ impl CadCommand for FreeTextValuePromptCommand {
} }
fn on_text_input(&mut self, text: &str) -> Option<CmdResult> { fn on_text_input(&mut self, text: &str) -> Option<CmdResult> {
Some(dispatch_prompt_value(self.name, text)) Some(CmdResult::Dispatch(format!("{} {}", self.name, text.trim())))
} }
fn on_point(&mut self, _pt: DVec3) -> CmdResult { fn on_point(&mut self, _pt: DVec3) -> CmdResult {
@@ -382,7 +383,7 @@ impl CadCommand for FreeTextValuePromptCommand {
} }
fn on_enter(&mut self) -> CmdResult { fn on_enter(&mut self) -> CmdResult {
dispatch_prompt_value(self.name, "") CmdResult::Dispatch(format!("{} ", self.name))
} }
} }
@@ -3078,20 +3079,6 @@ mod constraint_registry_tests {
mod tests { mod tests {
use super::*; use super::*;
#[test]
fn prompt_dispatch_trims_values_and_preserves_empty_argument() {
let CmdResult::Dispatch(value) = dispatch_prompt_value("TEST", " value with spaces ")
else {
panic!("prompt input should dispatch a command");
};
assert_eq!(value, "TEST value with spaces");
let CmdResult::Dispatch(value) = dispatch_prompt_value("TEST", " ") else {
panic!("empty prompt input should dispatch a command");
};
assert_eq!(value, "TEST ");
}
#[test] #[test]
fn test_mid2point_command() { fn test_mid2point_command() {
let mut cmd = Mid2PointCommand::new(); let mut cmd = Mid2PointCommand::new();
+7 -27
View File
@@ -2004,11 +2004,9 @@ impl Scene {
let range = boundary.len()..boundary.len() + ring.len(); let range = boundary.len()..boundary.len() + ring.len();
boundary.extend(ring); boundary.extend(ring);
local_boundary.extend(local_ring.iter().map(|&[x, y]| [x as f32, y as f32])); local_boundary.extend(local_ring.iter().map(|&[x, y]| [x as f32, y as f32]));
if path if path.edges.iter().any(|edge| {
.edges matches!(edge, acadrust::entities::BoundaryEdge::Spline(_))
.iter() }) {
.any(|edge| matches!(edge, acadrust::entities::BoundaryEdge::Spline(_)))
{
spline_paths.push((path, local_ring, range)); spline_paths.push((path, local_ring, range));
} }
boundary_exterior.push(depth == 0); boundary_exterior.push(depth == 0);
@@ -2057,14 +2055,7 @@ impl Scene {
.colors .colors
.get(i) .get(i)
.and_then(|e| e.color.rgb()) .and_then(|e| e.color.rgb())
.map(|(r, g, b)| { .map(|(r, g, b)| [r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0, color[3]])
[
r as f32 / 255.0,
g as f32 / 255.0,
b as f32 / 255.0,
color[3],
]
})
}; };
gradient_color1 = stop(0); gradient_color1 = stop(0);
let color1 = gradient_color1.unwrap_or(color); let color1 = gradient_color1.unwrap_or(color);
@@ -2221,21 +2212,13 @@ impl Scene {
} }
let project = |point: [f64; 2]| { let project = |point: [f64; 2]| {
let [x, y] = to_xy(point[0], point[1]); let [x, y] = to_xy(point[0], point[1]);
[ [(x - world_origin[0]) as f32 as f64, (y - world_origin[1]) as f32 as f64]
(x - world_origin[0]) as f32 as f64,
(y - world_origin[1]) as f32 as f64,
]
}; };
for (path, ring, range) in spline_paths.into_iter().rev() { for (path, ring, range) in spline_paths.into_iter().rev() {
if range.end > boundary.len() { if range.end > boundary.len() {
continue; continue;
} }
let curves = || { let curves = || path.edges.iter().filter_map(crate::entities::hatch::edge_curve).collect();
path.edges
.iter()
.filter_map(crate::entities::hatch::edge_curve)
.collect()
};
if let Some(refined) = if let Some(refined) =
cadkernel::geom2d::refine_spline_boundary(&ring, curves, &project) cadkernel::geom2d::refine_spline_boundary(&ring, curves, &project)
{ {
@@ -2243,10 +2226,7 @@ impl Scene {
continue; continue;
} }
boundary.splice(range.clone(), refined.iter().map(|p| to_xy(p[0], p[1]))); boundary.splice(range.clone(), refined.iter().map(|p| to_xy(p[0], p[1])));
local_boundary.splice( local_boundary.splice(range, refined.into_iter().map(|[x, y]| [x as f32, y as f32]));
range,
refined.into_iter().map(|[x, y]| [x as f32, y as f32]),
);
} }
} }
+2 -1
View File
@@ -221,7 +221,8 @@ fn valid_samples_are_unchanged_and_invalid_samples_stay_rejected() {
|p| p |p| p
) )
.is_none()); .is_none());
assert!(refine_spline_boundary(&vec![[0.0; 2]; 513], assert!(refine_spline_boundary(
&vec![[0.0; 2]; 513],
|| panic!("size cap ignored"), || panic!("size cap ignored"),
|p| p |p| p
) )
+3 -2
View File
@@ -362,8 +362,9 @@ impl HatchModel {
self.pattern_segments_with_dot_length(Some(0.0)) self.pattern_segments_with_dot_length(Some(0.0))
} }
fn pattern_segments_with_dot_length(&self, fn pattern_segments_with_dot_length(
plot_dot_length: Option<f64> &self,
plot_dot_length: Option<f64>,
) -> Vec<[[f64; 2]; 2]> { ) -> Vec<[[f64; 2]; 2]> {
let HatchPattern::Pattern(families) = &self.pattern else { let HatchPattern::Pattern(families) = &self.pattern else {
return Vec::new(); return Vec::new();
+22 -20
View File
@@ -2,7 +2,8 @@
use super::*; use super::*;
impl Scene { impl Scene {
pub(super) fn paper_viewport_handles(&self pub(super) fn paper_viewport_handles(
&self,
) -> (Handle, Handle, Arc<Vec<Handle>>) { ) -> (Handle, Handle, Arc<Vec<Handle>>) {
{ {
let cache = self.paper_viewport_cache.borrow(); let cache = self.paper_viewport_cache.borrow();
@@ -11,7 +12,7 @@ impl Scene {
return ( return (
cache.layout_block, cache.layout_block,
cache.sheet, cache.sheet,
Arc::clone(&cache.content) Arc::clone(&cache.content),
); );
} }
} }
@@ -122,8 +123,7 @@ impl Scene {
) -> Vec<ViewportInstance> { ) -> Vec<ViewportInstance> {
if self.current_layout == "Model" { if self.current_layout == "Model" {
let tiles = self.model_tiles.borrow(); let tiles = self.model_tiles.borrow();
let active = self.active_model_tile.get() let active = self.active_model_tile.get().min(tiles.len().saturating_sub(1));
.min(tiles.len().saturating_sub(1));
return tiles return tiles
.iter() .iter()
.enumerate() .enumerate()
@@ -298,7 +298,9 @@ impl Scene {
hatches.push(sheet); hatches.push(sheet);
} }
hatches.extend( hatches.extend(
self.paper_canvas_hatches(tint_selected).iter().cloned() self.paper_canvas_hatches(tint_selected)
.iter()
.cloned(),
); );
let hatches = Arc::new(hatches); let hatches = Arc::new(hatches);
let wipeouts = self.paper_canvas_wipeouts(); let wipeouts = self.paper_canvas_wipeouts();
@@ -375,8 +377,9 @@ impl Scene {
/// Physical sheet bounds in canvas pixels. Uses the same forced top-down /// Physical sheet bounds in canvas pixels. Uses the same forced top-down
/// paper transform as the GPU sheet viewport, ignoring stored camera twist. /// paper transform as the GPU sheet viewport, ignoring stored camera twist.
pub fn paper_sheet_screen_rect(&self, pub fn paper_sheet_screen_rect(
canvas_px: (f32, f32) &self,
canvas_px: (f32, f32),
) -> Option<iced::Rectangle> { ) -> Option<iced::Rectangle> {
let ((x0, y0), (x1, y1)) = self.paper_limits()?; let ((x0, y0), (x1, y1)) = self.paper_limits()?;
let (canvas_w, canvas_h) = canvas_px; let (canvas_w, canvas_h) = canvas_px;
@@ -474,13 +477,13 @@ impl Scene {
) )
.is_some() => .is_some() =>
{ {
Self::hatch_model_from_dxf(dxf, model.color).unwrap_or_else(|| model.clone()) Self::hatch_model_from_dxf(dxf, model.color)
.unwrap_or_else(|| model.clone())
} }
_ => model.clone(), _ => model.clone(),
}; };
let style = self.render_style(entity); let style = self.render_style(entity);
if matches!( if matches!(m.pattern, model::hatch_model::HatchPattern::Gradient { .. }) {
m.pattern, model::hatch_model::HatchPattern::Gradient { .. }) {
m.set_alpha(style.0[3]); m.set_alpha(style.0[3]);
} else { } else {
m.color = style.0; m.color = style.0;
@@ -601,15 +604,13 @@ impl Scene {
) )
.is_some() => .is_some() =>
{ {
Self::hatch_model_from_dxf(dxf, model.color).unwrap_or_else(|| model.clone()) Self::hatch_model_from_dxf(dxf, model.color)
.unwrap_or_else(|| model.clone())
} }
_ => model.clone(), _ => model.clone(),
}; };
let style = self.render_style(entity); let style = self.render_style(entity);
if matches!( if matches!(hatch.pattern, model::hatch_model::HatchPattern::Gradient { .. }) {
hatch.pattern,
model::hatch_model::HatchPattern::Gradient { .. }
) {
hatch.set_alpha(style.0[3]); hatch.set_alpha(style.0[3]);
} else { } else {
hatch.color = style.0; hatch.color = style.0;
@@ -722,9 +723,9 @@ impl Scene {
// from a big symbol library) has viewports legitimately aimed at that // from a big symbol library) has viewports legitimately aimed at that
// second cluster; testing only the dense one wrongly auto-fits them onto // second cluster; testing only the dense one wrongly auto-fits them onto
// the library. Fall back to the cluster box when no extents are known. // the library. Fall back to the cluster box when no extents are known.
let full_bounds = self.model_space_extents() let full_bounds = self.model_space_extents().map(|(mn, mx)| {
.map(|(mn, mx)| (mn.x as f64, mn.y as f64, mx.x as f64, mx.y as f64)
(mn.x as f64, mn.y as f64, mx.x as f64, mx.y as f64)); });
// Absolute drawing centre. Geometry now reaches the scene at absolute // Absolute drawing centre. Geometry now reaches the scene at absolute
// (UTM) coordinates — the old code centred the overlap test and the // (UTM) coordinates — the old code centred the overlap test and the
// auto-fit on the origin, which was right only while world_offset // auto-fit on the origin, which was right only while world_offset
@@ -741,8 +742,9 @@ impl Scene {
(self.local_center[0], self.local_center[1]) (self.local_center[0], self.local_center[1])
} else { } else {
self.model_space_extents() self.model_space_extents()
.map(|(mn, mx)| .map(|(mn, mx)| {
(((mn.x + mx.x) * 0.5) as f64, ((mn.y + mx.y) * 0.5) as f64)) (((mn.x + mx.x) * 0.5) as f64, ((mn.y + mx.y) * 0.5) as f64)
})
.unwrap_or((0.0, 0.0)) .unwrap_or((0.0, 0.0))
}; };
+5 -2
View File
@@ -273,7 +273,10 @@ mod tests {
fn default_docks_each_known_panel_on_an_edge() { fn default_docks_each_known_panel_on_an_edge() {
let state = DockState::default(); let state = DockState::default();
assert_eq!(state.location(PanelId::Properties), Some((DockSide::Left, 0))); assert_eq!(state.location(PanelId::Properties), Some((DockSide::Left, 0)));
assert_eq!(state.location(PanelId::GraphicAttributes), Some((DockSide::Left, 1))); assert_eq!(
state.location(PanelId::GraphicAttributes),
Some((DockSide::Left, 1))
);
assert_eq!( assert_eq!(
state.location(PanelId::BlockPalette), state.location(PanelId::BlockPalette),
Some((DockSide::Right, 0)) Some((DockSide::Right, 0))
@@ -373,4 +376,4 @@ mod tests {
assert_eq!(drop_index(10.0, 2, 900.0), 0); assert_eq!(drop_index(10.0, 2, 900.0), 0);
assert_eq!(drop_index(890.0, 2, 900.0), 2); assert_eq!(drop_index(890.0, 2, 900.0), 2);
} }
} }
+69 -70
View File
@@ -10,7 +10,6 @@
use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet}; use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
use std::{fmt, sync::Arc}; use std::{fmt, sync::Arc};
use crate::t;
use crate::ui::ROW_H; use crate::ui::ROW_H;
use acadrust::types::{Color as AcadColor, LineWeight}; use acadrust::types::{Color as AcadColor, LineWeight};
use acadrust::Handle; use acadrust::Handle;
@@ -21,6 +20,7 @@ use iced::widget::{
use iced::{ use iced::{
mouse, Background, Border, Color, Element, Length, Padding, Point, Rectangle, Size, Theme, mouse, Background, Border, Color, Element, Length, Padding, Point, Rectangle, Size, Theme,
}; };
use crate::t;
// ── Row-height-derived constants ───────────────────────────────────────── // ── Row-height-derived constants ─────────────────────────────────────────
const FONT_SZ: f32 = ROW_H * 0.42; // ≈11 px const FONT_SZ: f32 = ROW_H * 0.42; // ≈11 px
@@ -205,7 +205,7 @@ fn hatch_preview_scale(pattern: &crate::scene::model::hatch_model::HatchPattern)
} }
} }
pub(crate) fn hatch_pattern_preview( fn hatch_pattern_preview(
pattern: crate::scene::model::hatch_model::HatchPattern, pattern: crate::scene::model::hatch_model::HatchPattern,
height: f32, height: f32,
) -> Element<'static, Message> { ) -> Element<'static, Message> {
@@ -213,9 +213,9 @@ pub(crate) fn hatch_pattern_preview(
pattern, pattern,
padding: 4.0, padding: 4.0,
}) })
.width(Length::Fill) .width(Length::Fill)
.height(height) .height(height)
.into() .into()
} }
pub(crate) fn compact_hatch_pattern_preview( pub(crate) fn compact_hatch_pattern_preview(
@@ -237,8 +237,7 @@ fn hatch_pattern_matches(
let query = search.trim(); let query = search.trim();
query.is_empty() query.is_empty()
|| entry.name.to_lowercase().contains(&query.to_lowercase()) || entry.name.to_lowercase().contains(&query.to_lowercase())
|| entry.description.to_lowercase() || entry.description.to_lowercase().contains(&query.to_lowercase())
.contains(&query.to_lowercase())
} }
pub(crate) fn filtered_hatch_patterns( pub(crate) fn filtered_hatch_patterns(
@@ -412,7 +411,7 @@ impl FieldKey {
} }
/// Text-input widget id for one column of a named-parameter row. /// Text-input widget id for one column of a named-parameter row.
pub fn prop_param_field_id(index: usize, field: crate::ui::window::named_parameters::ParamField,) -> iced::widget::Id { pub fn prop_param_field_id(index: usize, field: crate::ui::window::named_parameters::ParamField) -> iced::widget::Id {
use crate::ui::window::named_parameters::ParamField; use crate::ui::window::named_parameters::ParamField;
let suffix = match field { let suffix = match field {
ParamField::Name => "name", ParamField::Name => "name",
@@ -461,7 +460,7 @@ pub fn active_key_focused(
/// its field key in O(1) instead of re-scanning the sections, enabling /// its field key in O(1) instead of re-scanning the sections, enabling
/// select-all-on-focus for both text inputs and edit-choices (e.g. transparency). /// select-all-on-focus for both text inputs and edit-choices (e.g. transparency).
pub fn build_field_key_map( pub fn build_field_key_map(
sections: &[PropSection] sections: &[PropSection],
) -> HashMap<iced::widget::Id, FieldKey> { ) -> HashMap<iced::widget::Id, FieldKey> {
let mut map = HashMap::default(); let mut map = HashMap::default();
for section in sections { for section in sections {
@@ -675,7 +674,7 @@ impl PropertiesPanel {
crate::ui::icons::themed_secondary(crate::ui::icons::PIN, 12.0) crate::ui::icons::themed_secondary(crate::ui::icons::PIN, 12.0)
}; };
let pin = button(pin_icon) let pin = button(pin_icon)
.on_press(Message::Dock(DockMsg::AutoCollapseToggle(PanelId::Properties,))) .on_press(Message::Dock(DockMsg::AutoCollapseToggle(PanelId::Properties)))
.style(move |theme: &Theme, status| { .style(move |theme: &Theme, status| {
let mut style = button::subtle(theme, status); let mut style = button::subtle(theme, status);
if auto_collapse { if auto_collapse {
@@ -700,7 +699,9 @@ impl PropertiesPanel {
let close = tooltip( let close = tooltip(
close, close,
text(t!("Close")).size(10), text(t!("Close")).size(10),
tooltip::Position::Bottom).gap(4); tooltip::Position::Bottom,
)
.gap(4);
let header = mouse_area( let header = mouse_area(
container( container(
@@ -715,7 +716,7 @@ impl PropertiesPanel {
) )
.style(|theme: &Theme| container::Style { .style(|theme: &Theme| container::Style {
background: Some(Background::Color( background: Some(Background::Color(
theme.palette().background.weak.color theme.palette().background.weak.color,
)), )),
..Default::default() ..Default::default()
}) })
@@ -882,7 +883,9 @@ impl PropertiesPanel {
let hdr = container( let hdr = container(
row![ row![
title, title,
toggle,].align_y(iced::Center) toggle,
]
.align_y(iced::Center),
) )
.style(|theme: &Theme| { .style(|theme: &Theme| {
let palette = theme.palette(); let palette = theme.palette();
@@ -946,8 +949,9 @@ impl PropertiesPanel {
label: &'a str, label: &'a str,
) -> Element<'a, Message> { ) -> Element<'a, Message> {
match &prop.value { match &prop.value {
PropValue::ColorChoice(color) => PropValue::ColorChoice(color) => {
self.render_color_row(label, prop.field, *color, None), self.render_color_row(label, prop.field, *color, None)
}
PropValue::NamedColorChoice { color, name } => { PropValue::NamedColorChoice { color, name } => {
self.render_color_row(label, prop.field, *color, Some(name)) self.render_color_row(label, prop.field, *color, Some(name))
} }
@@ -957,8 +961,9 @@ impl PropertiesPanel {
PropValue::FieldLwChoice { field, value } => { PropValue::FieldLwChoice { field, value } => {
self.render_field_lw_row(label, field, *value) self.render_field_lw_row(label, field, *value)
} }
PropValue::FieldLwVaries { field } => PropValue::FieldLwVaries { field } => {
self.render_field_lw_varies_row(label, field), self.render_field_lw_varies_row(label, field)
}
PropValue::LwVaries => self.render_lw_varies_row(label), PropValue::LwVaries => self.render_lw_varies_row(label),
PropValue::LinetypeChoice(lt) => self.render_linetype_row(label, lt), PropValue::LinetypeChoice(lt) => self.render_linetype_row(label, lt),
PropValue::Choice { selected, options } => { PropValue::Choice { selected, options } => {
@@ -980,13 +985,16 @@ impl PropertiesPanel {
PropValue::ReadOnlyWithTooltip { value, tooltip } => { PropValue::ReadOnlyWithTooltip { value, tooltip } => {
render_ro_with_tooltip_row(label, value, tooltip) render_ro_with_tooltip_row(label, value, tooltip)
} }
PropValue::HatchPatternChoice(current) => PropValue::HatchPatternChoice(current) => {
self.render_hatch_pattern_row(label, current), self.render_hatch_pattern_row(label, current)
}
PropValue::AttrText { tag, value } => self.render_attr_row(tag, value), PropValue::AttrText { tag, value } => self.render_attr_row(tag, value),
PropValue::EntityLink { id, handles, conflicting, } => PropValue::EntityLink { id, handles, conflicting } => {
render_entity_link_row(label, *id, handles.clone(), *conflicting), render_entity_link_row(label, *id, handles.clone(), *conflicting)
PropValue::ParamRow { index, name, formula, resolved, } => }
self.render_param_row(*index, name, formula, resolved), PropValue::ParamRow { index, name, formula, resolved } => {
self.render_param_row(*index, name, formula, resolved)
}
PropValue::ParamAddRow => render_param_add_row(), PropValue::ParamAddRow => render_param_add_row(),
PropValue::ParamsVisibilityToggle(value) => render_params_visibility_toggle_row(*value), PropValue::ParamsVisibilityToggle(value) => render_params_visibility_toggle_row(*value),
} }
@@ -1047,7 +1055,7 @@ impl PropertiesPanel {
// (#415). // (#415).
Message::OpenColorWindow( Message::OpenColorWindow(
crate::app::ColorPickTarget::PropertiesBg, crate::app::ColorPickTarget::PropertiesBg,
color color,
), ),
); );
return prop_row_widget(label, selector); return prop_row_widget(label, selector);
@@ -1123,7 +1131,7 @@ impl PropertiesPanel {
Message::PropColorPickerToggle, Message::PropColorPickerToggle,
Message::OpenColorWindow( Message::OpenColorWindow(
crate::app::ColorPickTarget::Properties, crate::app::ColorPickTarget::Properties,
color color,
), ),
); );
prop_row_widget(label, selector) prop_row_widget(label, selector)
@@ -1141,7 +1149,7 @@ impl PropertiesPanel {
Message::PropColorPickerToggle, Message::PropColorPickerToggle,
Message::OpenColorWindow( Message::OpenColorWindow(
crate::app::ColorPickTarget::Properties, crate::app::ColorPickTarget::Properties,
AcadColor::ByLayer AcadColor::ByLayer,
), ),
); );
prop_row_widget(label, selector) prop_row_widget(label, selector)
@@ -1531,28 +1539,24 @@ impl PropertiesPanel {
use crate::ui::window::named_parameters::ParamField; use crate::ui::window::named_parameters::ParamField;
let name_key = FieldKey::Param(index, ParamField::Name); let name_key = FieldKey::Param(index, ParamField::Name);
let name_display = self.edit_buf.get(&name_key) let name_display = self.edit_buf.get(&name_key).map(|s| s.as_str()).unwrap_or(name);
.map(|s| s.as_str())
.unwrap_or(name);
let name_active = self.active_field.as_ref() == Some(&name_key); let name_active = self.active_field.as_ref() == Some(&name_key);
let name_input = text_input("name", name_display) let name_input = text_input("name", name_display)
.id(name_key.widget_id()) .id(name_key.widget_id())
.on_input(move |v| Message::PropParamInput { index, field: ParamField::Name, value: v, }) .on_input(move |v| Message::PropParamInput { index, field: ParamField::Name, value: v })
.on_submit(Message::PropParamCommit { index, field: ParamField::Name, }) .on_submit(Message::PropParamCommit { index, field: ParamField::Name })
.size(FONT_SZ) .size(FONT_SZ)
.style(text_input_style) .style(text_input_style)
.padding([3, 6]) .padding([3, 6])
.width(Length::FillPortion(3)); .width(Length::FillPortion(3));
let formula_key = FieldKey::Param(index, ParamField::Formula); let formula_key = FieldKey::Param(index, ParamField::Formula);
let formula_display = self.edit_buf.get(&formula_key) let formula_display = self.edit_buf.get(&formula_key).map(|s| s.as_str()).unwrap_or(formula);
.map(|s| s.as_str())
.unwrap_or(formula);
let formula_active = self.active_field.as_ref() == Some(&formula_key); let formula_active = self.active_field.as_ref() == Some(&formula_key);
let formula_input = text_input("formula", formula_display) let formula_input = text_input("formula", formula_display)
.id(formula_key.widget_id()) .id(formula_key.widget_id())
.on_input(move |v| Message::PropParamInput { index, field: ParamField::Formula, value: v, }) .on_input(move |v| Message::PropParamInput { index, field: ParamField::Formula, value: v })
.on_submit(Message::PropParamCommit { index, field: ParamField::Formula, }) .on_submit(Message::PropParamCommit { index, field: ParamField::Formula })
.size(FONT_SZ) .size(FONT_SZ)
.style(text_input_style) .style(text_input_style)
.padding([3, 6]) .padding([3, 6])
@@ -1563,10 +1567,9 @@ impl PropertiesPanel {
Err(_) => ("—".to_string(), true), Err(_) => ("—".to_string(), true),
}; };
let value_label = container( let value_label = container(
text(value_text).size(FONT_SZ).style(move |theme: &Theme| { iced::widget::text::Style { text(value_text).size(FONT_SZ).style(move |theme: &Theme| iced::widget::text::Style {
color: is_error.then_some(theme.palette().danger.base.color), color: is_error.then_some(theme.palette().danger.base.color),
} }),
})
) )
.width(Length::FillPortion(2)) .width(Length::FillPortion(2))
.align_x(iced::Right); .align_x(iced::Right);
@@ -1584,15 +1587,13 @@ impl PropertiesPanel {
Background::Color(theme.palette().background.base.color) Background::Color(theme.palette().background.base.color)
} }
}; };
let content = container(row![name_input, formula_input, value_label, delete_btn] let content = container(row![name_input, formula_input, value_label, delete_btn].spacing(4).align_y(iced::Center))
.spacing(4)
.align_y(iced::Center),)
.style(move |theme: &Theme| container::Style { background: Some(bg(theme)), ..Default::default() }) .style(move |theme: &Theme| container::Style { background: Some(bg(theme)), ..Default::default() })
.padding([2, 6]) .padding([2, 6])
.width(Length::Fill); .width(Length::Fill);
if let Err(err) = resolved { if let Err(err) = resolved {
tooltip(content, text(err.as_str()).size(FONT_SZ), tooltip::Position::Top,) tooltip(content, text(err.as_str()).size(FONT_SZ), tooltip::Position::Top)
.gap(4.0) .gap(4.0)
.padding(6.0) .padding(6.0)
.style(|theme: &Theme| { .style(|theme: &Theme| {
@@ -1600,7 +1601,7 @@ impl PropertiesPanel {
container::Style { container::Style {
background: Some(Background::Color(palette.danger.weak.color)), background: Some(Background::Color(palette.danger.weak.color)),
text_color: Some(palette.danger.weak.text), text_color: Some(palette.danger.weak.text),
border: Border { color: palette.danger.base.color, width: 1.0, radius: 4.0.into(), }, border: Border { color: palette.danger.base.color, width: 1.0, radius: 4.0.into() },
..Default::default() ..Default::default()
} }
}) })
@@ -1658,16 +1659,22 @@ impl PropertiesPanel {
return prop_row_widget(label, head.into()); return prop_row_widget(label, head.into());
} }
let popup = hatch_pattern_picker_content( &self.hatch_pattern_search, let popup = hatch_pattern_picker_content(
&self.hatch_pattern_search,
self.hatch_pattern_focus, self.hatch_pattern_focus,
current,"hatch-pattern-search",Message::PropHatchPatternSearchChanged,Message::PropHatchPatternConfirm, current,
"hatch-pattern-search",
Message::PropHatchPatternSearchChanged,
Message::PropHatchPatternConfirm,
Message::PropHatchPatternFocus, Message::PropHatchPatternFocus,
Message::PropHatchPatternChanged, Message::PropHatchPatternChanged,
); );
prop_row_widget( prop_row_widget(
label,crate::ui::color_select::drop_down_below( label,
head.into(), popup, crate::ui::color_select::drop_down_below(
head.into(),
popup,
Some(Length::Fixed(PATTERN_PICKER_W)), Some(Length::Fixed(PATTERN_PICKER_W)),
Length::Fixed(PATTERN_PICKER_H), Length::Fixed(PATTERN_PICKER_H),
Message::PropHatchPatternPickerToggle(current.to_string()), Message::PropHatchPatternPickerToggle(current.to_string()),
@@ -1723,10 +1730,11 @@ fn render_bool_row<'a>(label: &'a str, field: &'static str, value: bool) -> Elem
}; };
let btn = let btn =
button( button(
text(btn_label).size(FONT_SZ).style(move |theme: &Theme| { iced::widget::text::Style { text(btn_label)
.size(FONT_SZ)
.style(move |theme: &Theme| iced::widget::text::Style {
color: value.then_some(theme.palette().warning.base.color), color: value.then_some(theme.palette().warning.base.color),
} }),
})
) )
.on_press(Message::PropBoolToggle(field)) .on_press(Message::PropBoolToggle(field))
.style(move |theme: &Theme, status| { .style(move |theme: &Theme, status| {
@@ -1869,7 +1877,7 @@ fn render_group_row(
let label_col = container(label_btn) let label_col = container(label_btn)
.style(|theme: &Theme| container::Style { .style(|theme: &Theme| container::Style {
background: Some(Background::Color( background: Some(Background::Color(
theme.palette().background.weakest.color theme.palette().background.weakest.color,
)), )),
..Default::default() ..Default::default()
}) })
@@ -1882,7 +1890,7 @@ fn render_group_row(
let value_col = container(value_field) let value_col = container(value_field)
.style(|theme: &Theme| container::Style { .style(|theme: &Theme| container::Style {
background: Some(Background::Color( background: Some(Background::Color(
theme.palette().background.base.color theme.palette().background.base.color,
)), )),
..Default::default() ..Default::default()
}) })
@@ -1910,7 +1918,7 @@ fn render_group_row(
} }
fn render_annotative_scale_row<'a>( fn render_annotative_scale_row<'a>(
label: &'a str, label: &'a str,
value: &'a str value: &'a str,
) -> Element<'a, Message> { ) -> Element<'a, Message> {
let field = crate::ui::read_only::field(value, FONT_SZ, Length::Fill); let field = crate::ui::read_only::field(value, FONT_SZ, Length::Fill);
@@ -1956,7 +1964,7 @@ fn render_ro_with_tooltip_row<'a>(
tooltip_text: &'a str, tooltip_text: &'a str,
) -> Element<'a, Message> { ) -> Element<'a, Message> {
let field = crate::ui::read_only::field(value, FONT_SZ, Length::Fill); let field = crate::ui::read_only::field(value, FONT_SZ, Length::Fill);
let wrapped = tooltip(field, text(tooltip_text).size(FONT_SZ), tooltip::Position::Top,) let wrapped = tooltip(field, text(tooltip_text).size(FONT_SZ), tooltip::Position::Top)
.gap(4.0) .gap(4.0)
.padding(6.0) .padding(6.0)
.style(|theme: &Theme| { .style(|theme: &Theme| {
@@ -2004,7 +2012,7 @@ fn render_entity_link_row<'a>(
}; };
button::Style { button::Style {
background: Some(Background::Color(pair.color)), background: Some(Background::Color(pair.color)),
border: Border { color: palette.background.neutral.color, width: 1.0, radius: 2.0.into(), }, border: Border { color: palette.background.neutral.color, width: 1.0, radius: 2.0.into() },
text_color: pair.text, text_color: pair.text,
..Default::default() ..Default::default()
} }
@@ -2027,10 +2035,7 @@ fn render_entity_link_row<'a>(
/// — avoids a separate "pending new row" concept, since every row always /// — avoids a separate "pending new row" concept, since every row always
/// reflects a real committed table entry. /// reflects a real committed table entry.
fn render_param_add_row<'a>() -> Element<'a, Message> { fn render_param_add_row<'a>() -> Element<'a, Message> {
let btn = button(row![text("+").size(FONT_SZ), text(t!("Add parameter").into_owned()).size(FONT_SZ) let btn = button(row![text("+").size(FONT_SZ), text(t!("Add parameter").into_owned()).size(FONT_SZ)].spacing(6).align_y(iced::Center))
]
.spacing(6)
.align_y(iced::Center),)
.on_press(Message::PropParamAddNew) .on_press(Message::PropParamAddNew)
.style(button::text) .style(button::text)
.padding([4, 8]) .padding([4, 8])
@@ -2063,18 +2068,14 @@ fn render_params_visibility_toggle_row<'a>(value: bool) -> Element<'a, Message>
}; };
button::Style { button::Style {
background: Some(Background::Color(pair.color)), background: Some(Background::Color(pair.color)),
border: Border { color: palette.background.neutral.color, width: 1.0, radius: 2.0.into(), }, border: Border { color: palette.background.neutral.color, width: 1.0, radius: 2.0.into() },
text_color: pair.text, text_color: pair.text,
..Default::default() ..Default::default()
} }
}) })
.padding([4, 8]) .padding([4, 8])
.width(Length::Fill); .width(Length::Fill);
container(row![text(t!("Values").into_owned()) container(row![text(t!("Values").into_owned()).size(FONT_SZ).width(Length::Fill), btn].align_y(iced::Center))
.size(FONT_SZ)
.width(Length::Fill), btn
]
.align_y(iced::Center),)
.width(Length::Fill) .width(Length::Fill)
.into() .into()
} }
@@ -2224,9 +2225,7 @@ fn text_input_style(theme: &Theme, status: text_input::Status) -> text_input::St
} }
} }
pub(crate) pub(crate) fn combo_input_style(theme: &Theme, status: text_input::Status) -> text_input::Style {
fn combo_input_style(theme: &Theme, status: text_input::Status) -> text_input::Style {
text_input_style(theme, status) text_input_style(theme, status)
} }
@@ -2250,7 +2249,7 @@ mod tests {
}; };
fn sample_sections() -> Vec<crate::scene::model::object::PropSection> { fn sample_sections() -> Vec<crate::scene::model::object::PropSection> {
use crate::scene::model::object::{PropValue,Property}; use crate::scene::model::object::{Property, PropValue};
vec![ vec![
crate::scene::model::object::PropSection { crate::scene::model::object::PropSection {
title: "Geometry".into(), title: "Geometry".into(),
+85 -28
View File
@@ -1,5 +1,8 @@
//! Gradient flyout of the Graphic Attributes palette.
use crate::app::Message; use crate::app::Message;
use crate::scene::model::hatch_model::GradientKind; use crate::scene::model::hatch_model::GradientKind;
use crate::ui::window::graphic_attributes::GraphicAttributesMsg;
use acadrust::types::Color as AcadColor; use acadrust::types::Color as AcadColor;
use iced::widget::{ use iced::widget::{
button, checkbox, column, container, image, mouse_area, row, slider, text, text_input, tooltip, button, checkbox, column, container, image, mouse_area, row, slider, text, text_input, tooltip,
@@ -24,7 +27,26 @@ pub struct GradientEditorState {
pub inverted: bool, pub inverted: bool,
pub angle: String, pub angle: String,
pub centered: bool, pub centered: bool,
pub color_open: Option<u8>, }
#[derive(Debug, Clone)]
pub enum GradientMsg {
Open,
Cancel,
Apply,
ColorMode(GradientColorMode),
/// Stop colour 1 or 2.
Color(u8, AcadColor),
ShadeTint(f32),
Kind(GradientKind),
InvertToggled,
Angle(String),
AngleReset,
Centered(bool),
}
fn msg(message: GradientMsg) -> Message {
Message::GraphicAttributes(GraphicAttributesMsg::Gradient(message))
} }
impl GradientEditorState { impl GradientEditorState {
@@ -37,7 +59,7 @@ impl GradientEditorState {
.gradient_color .gradient_color
.colors .colors
.get(index) .get(index)
.map(|entry| entry.color.clone()) .map(|entry| entry.color)
.unwrap_or(AcadColor::Index(7)) .unwrap_or(AcadColor::Index(7))
}; };
let (kind, inverted) = GradientKind::from_name(&hatch.gradient_color.name); let (kind, inverted) = GradientKind::from_name(&hatch.gradient_color.name);
@@ -55,7 +77,30 @@ impl GradientEditorState {
inverted, inverted,
angle: format!("{:.1}°", hatch.gradient_color.angle.to_degrees()), angle: format!("{:.1}°", hatch.gradient_color.angle.to_degrees()),
centered: hatch.gradient_color.shift.abs() < 1e-9, centered: hatch.gradient_color.shift.abs() < 1e-9,
color_open: None, }
}
pub fn update(&mut self, message: GradientMsg) {
match message {
GradientMsg::ColorMode(mode) => self.color_mode = mode,
GradientMsg::Color(1, color) => self.color_1 = color,
GradientMsg::Color(_, color) => self.color_2 = color,
GradientMsg::ShadeTint(value) => self.shade_tint = value.clamp(0.0, 1.0),
GradientMsg::Kind(kind) => {
self.kind = kind;
if kind == GradientKind::Linear {
self.inverted = false;
}
}
GradientMsg::InvertToggled => {
if self.kind != GradientKind::Linear {
self.inverted ^= true;
}
}
GradientMsg::Angle(value) => self.angle = value,
GradientMsg::AngleReset => self.angle = "0.0°".into(),
GradientMsg::Centered(value) => self.centered = value,
GradientMsg::Open | GradientMsg::Cancel | GradientMsg::Apply => {}
} }
} }
@@ -96,7 +141,7 @@ fn preview_sized(
state: &GradientEditorState, state: &GradientEditorState,
width: u32, width: u32,
height: u32, height: u32,
) -> iced::widget::image::Handle { ) -> Option<iced::widget::image::Handle> {
let c1 = rgb(&state.color_1); let c1 = rgb(&state.color_1);
let c2 = if state.color_mode == GradientColorMode::One { let c2 = if state.color_mode == GradientColorMode::One {
let c = crate::scene::gradient_tint_color(rgb_array(&state.color_1), state.shade_tint); let c = crate::scene::gradient_tint_color(rgb_array(&state.color_1), state.shade_tint);
@@ -110,8 +155,7 @@ fn preview_sized(
angle, angle,
if state.centered { 0.0 } else { 1.0 }, if state.centered { 0.0 } else { 1.0 },
cadkernel::geom2d::Tolerance::default(), cadkernel::geom2d::Tolerance::default(),
) )?;
.expect("the preview rectangle always defines a gradient frame");
let (sin, cos) = angle.sin_cos(); let (sin, cos) = angle.sin_cos();
let radial = state.kind.radial(); let radial = state.kind.radial();
let mut pixels = Vec::with_capacity((width * height * 4) as usize); let mut pixels = Vec::with_capacity((width * height * 4) as usize);
@@ -143,17 +187,30 @@ fn preview_sized(
}); });
} }
} }
iced::widget::image::Handle::from_rgba(width, height, pixels) Some(iced::widget::image::Handle::from_rgba(width, height, pixels))
} }
fn preview(state: &GradientEditorState) -> iced::widget::image::Handle { fn preview(state: &GradientEditorState) -> Element<'static, Message> {
preview_sized(state, 370, 80) preview_image(preview_sized(state, 370, 80))
} }
pub(crate) fn compact_preview( pub(crate) fn compact_preview(hatch: &acadrust::entities::Hatch) -> Element<'static, Message> {
hatch: &acadrust::entities::Hatch, preview_image(preview_sized(
) -> iced::widget::image::Handle { &GradientEditorState::from_hatch(Vec::new(), hatch),
preview_sized(&GradientEditorState::from_hatch(Vec::new(), hatch), 256, 26) 256,
26,
))
}
fn preview_image(handle: Option<iced::widget::image::Handle>) -> Element<'static, Message> {
match handle {
Some(handle) => image(handle)
.width(Fill)
.height(Fill)
.content_fit(iced::ContentFit::Fill)
.into(),
None => Space::new().width(Fill).height(Fill).into(),
}
} }
fn icon(kind: GradientKind, inverted: bool) -> &'static [u8] { fn icon(kind: GradientKind, inverted: bool) -> &'static [u8] {
@@ -192,7 +249,7 @@ pub fn view(state: &GradientEditorState, dimmed: bool) -> Element<'_, Message> {
let segment = |label, mode| { let segment = |label, mode| {
let selected = state.color_mode == mode; let selected = state.color_mode == mode;
button(text(label).size(11)) button(text(label).size(11))
.on_press(Message::GradientColorModeChanged(mode)) .on_press(msg(GradientMsg::ColorMode(mode)))
.width(Fill) .width(Fill)
.style(move |theme: &Theme, status| { .style(move |theme: &Theme, status| {
if selected { if selected {
@@ -203,8 +260,8 @@ pub fn view(state: &GradientEditorState, dimmed: bool) -> Element<'_, Message> {
}) })
}; };
let color_field = |label: &'static str, index, color: AcadColor| { let color_field = |label: &'static str, index, color: AcadColor| {
let (background, _) = crate::ui::properties::acad_color_display(color.clone()); let (background, _) = crate::ui::properties::acad_color_display(color);
let value = crate::ui::color_select::color_display_name(color.clone()); let value = crate::ui::color_select::color_display_name(color);
let picker = button( let picker = button(
row![ row![
crate::ui::color_select::swatch(background), crate::ui::color_select::swatch(background),
@@ -265,7 +322,7 @@ pub fn view(state: &GradientEditorState, dimmed: bool) -> Element<'_, Message> {
] ]
.align_x(iced::Center), .align_x(iced::Center),
) )
.on_press(Message::GradientTypeChanged(kind)) .on_press(msg(GradientMsg::Kind(kind)))
.width(48) .width(48)
.height(48) .height(48)
.style(move |theme: &Theme, status| { .style(move |theme: &Theme, status| {
@@ -285,7 +342,7 @@ pub fn view(state: &GradientEditorState, dimmed: bool) -> Element<'_, Message> {
let invert_selected = state.inverted && state.kind != GradientKind::Linear; let invert_selected = state.inverted && state.kind != GradientKind::Linear;
let invert = button(column![text("⇄").size(18), text("Invert").size(9)].align_x(iced::Center)) let invert = button(column![text("⇄").size(18), text("Invert").size(9)].align_x(iced::Center))
.on_press_maybe( .on_press_maybe(
(state.kind != GradientKind::Linear).then_some(Message::GradientInvertToggled), (state.kind != GradientKind::Linear).then(|| msg(GradientMsg::InvertToggled)),
) )
.width(42) .width(42)
.height(48) .height(48)
@@ -309,18 +366,18 @@ pub fn view(state: &GradientEditorState, dimmed: bool) -> Element<'_, Message> {
)); ));
let colors: Element<'_, Message> = if state.color_mode == GradientColorMode::Two { let colors: Element<'_, Message> = if state.color_mode == GradientColorMode::Two {
row![ row![
color_field("Color 1", 1, state.color_1.clone()), color_field("Color 1", 1, state.color_1),
color_field("Color 2", 2, state.color_2.clone()) color_field("Color 2", 2, state.color_2)
] ]
.spacing(6) .spacing(6)
.into() .into()
} else { } else {
row![ row![
color_field("Color 1", 1, state.color_1.clone()), color_field("Color 1", 1, state.color_1),
row![ row![
text("Tone").size(11).width(43), text("Tone").size(11).width(43),
slider(0..=100, (state.shade_tint * 100.0) as i32, |v| { slider(0..=100, (state.shade_tint * 100.0) as i32, |v| {
Message::GradientShadeTintChanged(v as f32 / 100.0) msg(GradientMsg::ShadeTint(v as f32 / 100.0))
}) })
.width(Fill) .width(Fill)
] ]
@@ -336,12 +393,12 @@ pub fn view(state: &GradientEditorState, dimmed: bool) -> Element<'_, Message> {
let angle = row![ let angle = row![
text("Angle").size(11).width(43), text("Angle").size(11).width(43),
text_input("0.0°", &state.angle) text_input("0.0°", &state.angle)
.on_input(Message::GradientAngleChanged) .on_input(|value| msg(GradientMsg::Angle(value)))
.size(11) .size(11)
.width(Length::Fixed(92.0)), .width(Length::Fixed(92.0)),
tooltip( tooltip(
button(crate::ui::icons::themed_undo(11.0, true)) button(crate::ui::icons::themed_undo(11.0, true))
.on_press(Message::GradientAngleReset) .on_press(msg(GradientMsg::AngleReset))
.style(button::subtle) .style(button::subtle)
.padding(3) .padding(3)
.width(24) .width(24)
@@ -352,7 +409,7 @@ pub fn view(state: &GradientEditorState, dimmed: bool) -> Element<'_, Message> {
Space::new().width(Fill), Space::new().width(Fill),
checkbox(state.centered) checkbox(state.centered)
.label("Centered") .label("Centered")
.on_toggle(Message::GradientCenteredChanged) .on_toggle(|value| msg(GradientMsg::Centered(value)))
.size(14) .size(14)
.text_size(11) .text_size(11)
] ]
@@ -364,11 +421,11 @@ pub fn view(state: &GradientEditorState, dimmed: bool) -> Element<'_, Message> {
let actions = container( let actions = container(
row![ row![
button(text(crate::t!("Cancel")).size(11)) button(text(crate::t!("Cancel")).size(11))
.on_press(Message::GradientCancel) .on_press(msg(GradientMsg::Cancel))
.style(button::secondary) .style(button::secondary)
.padding([5, 14]), .padding([5, 14]),
button(text(crate::t!("OK")).size(11)) button(text(crate::t!("OK")).size(11))
.on_press(Message::GradientApply) .on_press(msg(GradientMsg::Apply))
.style(button::primary) .style(button::primary)
.padding([5, 18]), .padding([5, 18]),
] ]
@@ -377,7 +434,7 @@ pub fn view(state: &GradientEditorState, dimmed: bool) -> Element<'_, Message> {
.width(Fill) .width(Fill)
.align_x(iced::alignment::Horizontal::Right); .align_x(iced::alignment::Horizontal::Right);
let content = column![ let content = column![
image(preview(state)).width(Fill).height(80), container(preview(state)).width(Fill).height(80),
row![ row![
segment("One Color", GradientColorMode::One), segment("One Color", GradientColorMode::One),
segment("Two Colors", GradientColorMode::Two) segment("Two Colors", GradientColorMode::Two)
File diff suppressed because it is too large. Load diff
+16 -16
View File
@@ -1,32 +1,32 @@
pub mod about; pub mod about;
pub mod alias_editor;
pub mod annotation_data;
pub mod attribute_editor;
pub mod auto_constrain_settings;
pub mod block_definition; pub mod block_definition;
pub mod block_palette; pub mod block_palette;
pub mod browser; pub mod browser;
pub mod drafting_settings; pub mod layout_manager;
pub mod layer_state_manager;
pub mod drawing_units; pub mod drawing_units;
pub mod find_replace;
pub mod geometric_tolerance; pub mod geometric_tolerance;
pub mod gradient_editor; pub mod gradient_editor;
pub mod graphic_attributes; pub mod graphic_attributes;
pub mod layer_state_manager; pub mod drafting_settings;
pub mod auto_constrain_settings;
pub mod layer_translator; pub mod layer_translator;
pub mod layers;
pub mod layout_manager;
pub mod missing_fonts;
pub mod named_parameters;
pub mod open_progress;
pub mod options;
pub mod plot; pub mod plot;
pub mod plugin_manager;
pub mod print_all; pub mod print_all;
pub mod recovery; pub mod plugin_manager;
pub mod shortcuts; pub mod shortcuts;
pub mod layers;
pub mod update_notice; pub mod update_notice;
pub mod wblock; pub mod open_progress;
pub mod missing_fonts;
pub mod recovery;
pub mod options;
pub mod attribute_editor;
pub mod annotation_data;
pub mod alias_editor;
pub mod find_replace;
pub mod named_parameters;
pub mod xref_attach; pub mod xref_attach;
pub mod xref_help; pub mod xref_help;
pub mod xref_manager; pub mod xref_manager;
pub mod wblock;