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