forked from 1b-agent/opencadstudio-upstream
Improve graphic attribute handling for open boundaries and varied values
This commit is contained in:
1 parent
abef285cc6
commit
85e2dda6ba
5 files changed
+427
-75
No files matched your search
@@ -780,6 +780,11 @@ pub(super) struct OpenCADStudio {
|
|||||||
fill_transparency_menu_open: bool,
|
fill_transparency_menu_open: bool,
|
||||||
gradient_editor: Option<crate::ui::window::gradient_editor::GradientEditorState>,
|
gradient_editor: Option<crate::ui::window::gradient_editor::GradientEditorState>,
|
||||||
hatch_editor_handles: Option<Vec<acadrust::Handle>>,
|
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).
|
||||||
@@ -4167,6 +4172,7 @@ impl OpenCADStudio {
|
|||||||
fill_transparency_menu_open: false,
|
fill_transparency_menu_open: false,
|
||||||
gradient_editor: None,
|
gradient_editor: None,
|
||||||
hatch_editor_handles: 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,
|
||||||
|
|||||||
+144
-4
@@ -90,6 +90,46 @@ fn set_hatch_graphic_attribute(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_open_fill_boundary(entity: &acadrust::EntityType) -> bool {
|
||||||
|
match entity {
|
||||||
|
acadrust::EntityType::LwPolyline(polyline) => {
|
||||||
|
!polyline.is_closed && polyline.vertices.len() >= 3
|
||||||
|
}
|
||||||
|
acadrust::EntityType::Polyline(polyline) => {
|
||||||
|
!polyline.flags.is_closed() && polyline.vertices.len() >= 3
|
||||||
|
}
|
||||||
|
acadrust::EntityType::Polyline2D(polyline) => {
|
||||||
|
!polyline.is_closed() && polyline.vertices.len() >= 3
|
||||||
|
}
|
||||||
|
acadrust::EntityType::Polyline3D(polyline) => {
|
||||||
|
!polyline.flags.closed && polyline.vertices.len() >= 3
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn close_fill_boundary(entity: &mut acadrust::EntityType) -> bool {
|
||||||
|
match entity {
|
||||||
|
acadrust::EntityType::LwPolyline(polyline) if !polyline.is_closed => {
|
||||||
|
polyline.is_closed = true;
|
||||||
|
true
|
||||||
|
}
|
||||||
|
acadrust::EntityType::Polyline(polyline) if !polyline.flags.is_closed() => {
|
||||||
|
polyline.flags.set_closed(true);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
acadrust::EntityType::Polyline2D(polyline) if !polyline.is_closed() => {
|
||||||
|
polyline.flags.set_closed(true);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
acadrust::EntityType::Polyline3D(polyline) if !polyline.flags.closed => {
|
||||||
|
polyline.flags.closed = true;
|
||||||
|
true
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl OpenCADStudio {
|
impl OpenCADStudio {
|
||||||
pub(super) fn open_gradient_editor(&mut self) {
|
pub(super) fn open_gradient_editor(&mut self) {
|
||||||
use crate::ui::window::gradient_editor::GradientEditorState;
|
use crate::ui::window::gradient_editor::GradientEditorState;
|
||||||
@@ -195,6 +235,49 @@ impl OpenCADStudio {
|
|||||||
pub(super) fn on_graphic_attribute(
|
pub(super) fn on_graphic_attribute(
|
||||||
&mut self,
|
&mut self,
|
||||||
value: crate::ui::window::graphic_attributes::GraphicAttribute,
|
value: crate::ui::window::graphic_attributes::GraphicAttribute,
|
||||||
|
) -> iced::Task<Message> {
|
||||||
|
self.pending_graphic_fill_close = None;
|
||||||
|
self.on_graphic_attribute_impl(value, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn on_graphic_fill_close_response(
|
||||||
|
&mut self,
|
||||||
|
close: bool,
|
||||||
|
) -> iced::Task<Message> {
|
||||||
|
let Some((value, handles, _)) = self.pending_graphic_fill_close.take() else {
|
||||||
|
return iced::Task::none();
|
||||||
|
};
|
||||||
|
if close {
|
||||||
|
let i = self.active_tab;
|
||||||
|
let unlocked: Vec<_> = handles
|
||||||
|
.into_iter()
|
||||||
|
.filter(|handle| !self.tabs[i].scene.is_layer_locked(*handle))
|
||||||
|
.collect();
|
||||||
|
if !unlocked.is_empty() {
|
||||||
|
let pending = self.begin_undo(i, "Close fill boundaries", unlocked.len(), true);
|
||||||
|
for handle in unlocked {
|
||||||
|
let Some(mut entity) =
|
||||||
|
self.tabs[i].scene.document.get_entity(handle).cloned()
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if close_fill_boundary(&mut entity) {
|
||||||
|
self.tabs[i].scene.update_entity(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.tabs[i].dirty = true;
|
||||||
|
if let Some(pending) = pending {
|
||||||
|
self.commit_undo_delta(i, pending);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.on_graphic_attribute_impl(value, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_graphic_attribute_impl(
|
||||||
|
&mut self,
|
||||||
|
value: crate::ui::window::graphic_attributes::GraphicAttribute,
|
||||||
|
ask_to_close: bool,
|
||||||
) -> iced::Task<Message> {
|
) -> iced::Task<Message> {
|
||||||
use crate::scene::model::hatch_model::{GradientKind, HatchPattern};
|
use crate::scene::model::hatch_model::{GradientKind, HatchPattern};
|
||||||
use crate::ui::window::graphic_attributes::GraphicAttribute;
|
use crate::ui::window::graphic_attributes::GraphicAttribute;
|
||||||
@@ -224,6 +307,22 @@ impl OpenCADStudio {
|
|||||||
.push_info(crate::t!("Select one or more closed objects first.").as_ref());
|
.push_info(crate::t!("Select one or more closed objects first.").as_ref());
|
||||||
return iced::Task::none();
|
return iced::Task::none();
|
||||||
}
|
}
|
||||||
|
let open_boundaries: rustc_hash::FxHashSet<_> = selected
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.filter(|handle| {
|
||||||
|
self.tabs[i]
|
||||||
|
.scene
|
||||||
|
.document
|
||||||
|
.get_entity(*handle)
|
||||||
|
.is_some_and(is_open_fill_boundary)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
if ask_to_close && value != GraphicAttribute::None && !open_boundaries.is_empty() {
|
||||||
|
self.pending_graphic_fill_close =
|
||||||
|
Some((value, open_boundaries.iter().copied().collect(), selected.clone()));
|
||||||
|
return iced::Task::none();
|
||||||
|
}
|
||||||
let selected_handles: rustc_hash::FxHashSet<_> = selected.iter().copied().collect();
|
let selected_handles: rustc_hash::FxHashSet<_> = selected.iter().copied().collect();
|
||||||
let (associated, new_hatches, updated_hatches, skipped) = if value == GraphicAttribute::None
|
let (associated, new_hatches, updated_hatches, skipped) = if value == GraphicAttribute::None
|
||||||
{
|
{
|
||||||
@@ -331,7 +430,9 @@ impl OpenCADStudio {
|
|||||||
hatches.push(hatch);
|
hatches.push(hatch);
|
||||||
changed_boundaries.insert(*handle);
|
changed_boundaries.insert(*handle);
|
||||||
} else {
|
} else {
|
||||||
skipped += 1;
|
if !open_boundaries.contains(handle) {
|
||||||
|
skipped += 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Replace only fills belonging to boundaries that successfully
|
// Replace only fills belonging to boundaries that successfully
|
||||||
@@ -367,11 +468,10 @@ impl OpenCADStudio {
|
|||||||
}
|
}
|
||||||
if skipped > 0 {
|
if skipped > 0 {
|
||||||
let message = if skipped == 1 {
|
let message = if skipped == 1 {
|
||||||
"Graphic Attributes: 1 object was not changed because it is not closed or supported."
|
"Graphic Attributes: 1 unsupported object was not changed.".to_owned()
|
||||||
.to_owned()
|
|
||||||
} else {
|
} else {
|
||||||
format!(
|
format!(
|
||||||
"Graphic Attributes: {skipped} objects were not changed because they are not closed or supported."
|
"Graphic Attributes: {skipped} unsupported objects were not changed."
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
self.command_line.push_info(&message);
|
self.command_line.push_info(&message);
|
||||||
@@ -2007,6 +2107,12 @@ mod tests {
|
|||||||
matches!(entity, EntityType::Hatch(_)).then_some(entity.common().handle)
|
matches!(entity, EntityType::Hatch(_)).then_some(entity.common().handle)
|
||||||
})
|
})
|
||||||
.expect("gradient hatch");
|
.expect("gradient hatch");
|
||||||
|
let associated_lines = crate::ui::window::graphic_attributes::line_handles(
|
||||||
|
&app.tabs[i].scene.document,
|
||||||
|
&[selected_hatch],
|
||||||
|
);
|
||||||
|
assert_eq!(associated_lines.len(), 1);
|
||||||
|
assert!(boundaries.contains(&associated_lines[0]));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
crate::ui::window::graphic_attributes::current(
|
crate::ui::window::graphic_attributes::current(
|
||||||
&app.tabs[i].scene.document,
|
&app.tabs[i].scene.document,
|
||||||
@@ -2261,6 +2367,40 @@ mod tests {
|
|||||||
assert_eq!(app.tabs[i].history.undo_stack.len(), undo_before + 1);
|
assert_eq!(app.tabs[i].history.undo_stack.len(), undo_before + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn graphic_attributes_can_close_an_open_boundary_from_the_command_prompt() {
|
||||||
|
use crate::ui::window::graphic_attributes::GraphicAttribute;
|
||||||
|
use acadrust::entities::LwPolyline;
|
||||||
|
use acadrust::types::Vector2;
|
||||||
|
|
||||||
|
let mut app = fresh();
|
||||||
|
let i = app.active_tab;
|
||||||
|
let mut polyline = LwPolyline::new();
|
||||||
|
for (x, y) in [(0.0, 0.0), (10.0, 0.0), (10.0, 10.0), (0.0, 10.0)] {
|
||||||
|
polyline.add_point(Vector2::new(x, y));
|
||||||
|
}
|
||||||
|
let boundary = app.tabs[i]
|
||||||
|
.scene
|
||||||
|
.add_entity(EntityType::LwPolyline(polyline));
|
||||||
|
app.tabs[i].scene.select_entity(boundary, false);
|
||||||
|
|
||||||
|
let _ = app.update(Message::GraphicAttributeChanged(GraphicAttribute::Solid));
|
||||||
|
assert!(app.pending_graphic_fill_close.is_some());
|
||||||
|
assert!(!app.tabs[i].scene.document.entities().any(|entity| {
|
||||||
|
matches!(entity, EntityType::Hatch(_))
|
||||||
|
}));
|
||||||
|
|
||||||
|
let _ = app.update(Message::CommandOptionPick("Y".to_string()));
|
||||||
|
assert!(app.pending_graphic_fill_close.is_none());
|
||||||
|
assert!(matches!(
|
||||||
|
app.tabs[i].scene.document.get_entity(boundary),
|
||||||
|
Some(EntityType::LwPolyline(polyline)) if polyline.is_closed
|
||||||
|
));
|
||||||
|
assert!(app.tabs[i].scene.document.entities().any(|entity| {
|
||||||
|
matches!(entity, EntityType::Hatch(hatch) if hatch.is_solid && hatch.is_associative)
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn graphic_line_controls_update_creation_defaults_without_a_selection() {
|
fn graphic_line_controls_update_creation_defaults_without_a_selection() {
|
||||||
let mut app = fresh();
|
let mut app = fresh();
|
||||||
|
|||||||
@@ -432,6 +432,18 @@ impl OpenCADStudio {
|
|||||||
}) {
|
}) {
|
||||||
self.hatch_editor_handles = None;
|
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();
|
||||||
// Close the document-level first-touch transaction started by
|
// Close the document-level first-touch transaction started by
|
||||||
@@ -445,6 +457,19 @@ impl OpenCADStudio {
|
|||||||
.active_cmd
|
.active_cmd
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|c| c.prompt())
|
.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.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).
|
||||||
@@ -452,6 +477,14 @@ impl OpenCADStudio {
|
|||||||
.active_cmd
|
.active_cmd
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|c| c.options())
|
.map(|c| c.options())
|
||||||
|
.or_else(|| {
|
||||||
|
self.pending_graphic_fill_close.as_ref().map(|_| {
|
||||||
|
vec![
|
||||||
|
crate::command::CmdOption::new("Yes", "Y"),
|
||||||
|
crate::command::CmdOption::new("No", "N"),
|
||||||
|
]
|
||||||
|
})
|
||||||
|
})
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
self.command_line.set_step_options(opts);
|
self.command_line.set_step_options(opts);
|
||||||
// Persist UI preferences whenever a toggle changes them (issue #68).
|
// Persist UI preferences whenever a toggle changes them (issue #68).
|
||||||
@@ -2406,6 +2439,14 @@ 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 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 kw.is_empty() {
|
if kw.is_empty() {
|
||||||
return self.feed_command(crate::command::StepInput::Enter);
|
return self.feed_command(crate::command::StepInput::Enter);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,6 +55,9 @@ pub fn linetype_display_name(name: &str) -> String {
|
|||||||
|
|
||||||
impl fmt::Display for LinetypeItem {
|
impl fmt::Display for LinetypeItem {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
if self.name.is_empty() && !self.art.is_empty() {
|
||||||
|
return f.write_str(&self.art);
|
||||||
|
}
|
||||||
let name = linetype_display_name(&self.name);
|
let name = linetype_display_name(&self.name);
|
||||||
if self.art.is_empty() {
|
if self.art.is_empty() {
|
||||||
write!(f, "{name}")
|
write!(f, "{name}")
|
||||||
|
|||||||
@@ -26,14 +26,16 @@ enum PropertyMode {
|
|||||||
ByLayer,
|
ByLayer,
|
||||||
ByBlock,
|
ByBlock,
|
||||||
Custom,
|
Custom,
|
||||||
|
Varies,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PropertyMode {
|
impl PropertyMode {
|
||||||
fn button_label(self) -> &'static str {
|
fn button_label(self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
Self::ByLayer => "[L]",
|
Self::ByLayer => "L",
|
||||||
Self::ByBlock => "[B]",
|
Self::ByBlock => "B",
|
||||||
Self::Custom => "[C]",
|
Self::Custom => "C",
|
||||||
|
Self::Varies => "V",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -54,6 +56,7 @@ struct LinetypeScaleInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const PALETTE_CONTROL_ICON_SIZE: f32 = 20.0;
|
const PALETTE_CONTROL_ICON_SIZE: f32 = 20.0;
|
||||||
|
const COMPACT_BUTTON_HEIGHT: f32 = 22.0;
|
||||||
const TRANSPARENCY_ICON: &[u8] =
|
const TRANSPARENCY_ICON: &[u8] =
|
||||||
include_bytes!("../../../assets/icons/attributes/transparency.svg");
|
include_bytes!("../../../assets/icons/attributes/transparency.svg");
|
||||||
const LINEWEIGHT_ICON: &[u8] = include_bytes!("../../../assets/icons/attributes/lineweight.svg");
|
const LINEWEIGHT_ICON: &[u8] = include_bytes!("../../../assets/icons/attributes/lineweight.svg");
|
||||||
@@ -156,15 +159,28 @@ pub(crate) fn fill_handles(document: &CadDocument, selected: &[Handle]) -> Vec<H
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn line_handles(document: &CadDocument, selected: &[Handle]) -> Vec<Handle> {
|
pub(crate) fn line_handles(document: &CadDocument, selected: &[Handle]) -> Vec<Handle> {
|
||||||
selected
|
let mut handles = Vec::new();
|
||||||
.iter()
|
let mut seen = rustc_hash::FxHashSet::default();
|
||||||
.copied()
|
for handle in selected {
|
||||||
.filter(|handle| {
|
match document.get_entity(*handle) {
|
||||||
document
|
Some(EntityType::Hatch(hatch)) if hatch.is_associative => {
|
||||||
.get_entity(*handle)
|
for boundary in hatch.paths.iter().flat_map(|path| &path.boundary_handles) {
|
||||||
.is_some_and(|entity| !matches!(entity, EntityType::Hatch(_)))
|
if seen.insert(*boundary)
|
||||||
})
|
&& document
|
||||||
.collect()
|
.get_entity(*boundary)
|
||||||
|
.is_some_and(|entity| !matches!(entity, EntityType::Hatch(_)))
|
||||||
|
{
|
||||||
|
handles.push(*boundary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(entity) if !matches!(entity, EntityType::Hatch(_)) && seen.insert(*handle) => {
|
||||||
|
handles.push(*handle);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handles
|
||||||
}
|
}
|
||||||
|
|
||||||
fn top_handle(
|
fn top_handle(
|
||||||
@@ -276,6 +292,33 @@ fn lineweight_info(
|
|||||||
Some(LineweightInfo { value, varies })
|
Some(LineweightInfo { value, varies })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn effective_lineweight(
|
||||||
|
document: &CadDocument,
|
||||||
|
handles: &[Handle],
|
||||||
|
draw_depth: &rustc_hash::FxHashMap<u64, [f32; 2]>,
|
||||||
|
stored: acadrust::types::LineWeight,
|
||||||
|
) -> acadrust::types::LineWeight {
|
||||||
|
if !matches!(stored, acadrust::types::LineWeight::ByLayer) {
|
||||||
|
return stored;
|
||||||
|
}
|
||||||
|
let layer_name = top_handle(handles, draw_depth)
|
||||||
|
.and_then(|handle| document.get_entity(handle))
|
||||||
|
.map(|entity| entity.common().layer.as_str())
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
if document.header.current_layer_name.is_empty() {
|
||||||
|
"0"
|
||||||
|
} else {
|
||||||
|
document.header.current_layer_name.as_str()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
document
|
||||||
|
.layers
|
||||||
|
.get(layer_name)
|
||||||
|
.map_or(acadrust::types::LineWeight::Default, |layer| {
|
||||||
|
layer.line_weight
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn linetype_scale_info(
|
fn linetype_scale_info(
|
||||||
document: &CadDocument,
|
document: &CadDocument,
|
||||||
handles: &[Handle],
|
handles: &[Handle],
|
||||||
@@ -364,7 +407,12 @@ fn linetype_preview_art(
|
|||||||
} else {
|
} else {
|
||||||
logical_name
|
logical_name
|
||||||
};
|
};
|
||||||
document
|
if effective_name.eq_ignore_ascii_case("Continuous")
|
||||||
|
|| effective_name.eq_ignore_ascii_case("Solid")
|
||||||
|
{
|
||||||
|
return "_".repeat(80);
|
||||||
|
}
|
||||||
|
let art = document
|
||||||
.line_types
|
.line_types
|
||||||
.iter()
|
.iter()
|
||||||
.find(|line_type| line_type.name.eq_ignore_ascii_case(effective_name))
|
.find(|line_type| line_type.name.eq_ignore_ascii_case(effective_name))
|
||||||
@@ -375,7 +423,26 @@ fn linetype_preview_art(
|
|||||||
crate::io::linetypes::extract_pattern(&line_type.description)
|
crate::io::linetypes::extract_pattern(&line_type.description)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.unwrap_or_default()
|
.unwrap_or_default();
|
||||||
|
let pattern_start = art.split_whitespace().find_map(|part| {
|
||||||
|
(part
|
||||||
|
.chars()
|
||||||
|
.filter(|character| matches!(character, '_' | '.' | '-' | '/' | '\\' | '|' | '~'))
|
||||||
|
.count()
|
||||||
|
>= 2)
|
||||||
|
.then(|| art.find(part))
|
||||||
|
.flatten()
|
||||||
|
});
|
||||||
|
let pattern = pattern_start.map_or(art.as_str(), |start| &art[start..]);
|
||||||
|
if pattern.is_empty() {
|
||||||
|
return String::new();
|
||||||
|
}
|
||||||
|
let mut full_width = pattern.to_string();
|
||||||
|
while full_width.chars().count() < 80 {
|
||||||
|
full_width.push(' ');
|
||||||
|
full_width.push_str(pattern);
|
||||||
|
}
|
||||||
|
full_width
|
||||||
}
|
}
|
||||||
|
|
||||||
fn top_fill_hatch<'a>(
|
fn top_fill_hatch<'a>(
|
||||||
@@ -418,6 +485,21 @@ fn top_fill_hatch<'a>(
|
|||||||
.map(|(_, hatch)| hatch)
|
.map(|(_, hatch)| hatch)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn gradients_vary(document: &CadDocument, selected: &[Handle]) -> bool {
|
||||||
|
let mut gradients = fill_handles(document, selected)
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|handle| match document.get_entity(handle) {
|
||||||
|
Some(EntityType::Hatch(hatch)) if hatch.gradient_color.enabled => {
|
||||||
|
Some(&hatch.gradient_color)
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
});
|
||||||
|
let Some(first) = gradients.next() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
gradients.any(|gradient| gradient != first)
|
||||||
|
}
|
||||||
|
|
||||||
fn popup_alignment(side: crate::app::config::DockSide) -> iced_aw::drop_down::Alignment {
|
fn popup_alignment(side: crate::app::config::DockSide) -> iced_aw::drop_down::Alignment {
|
||||||
match side {
|
match side {
|
||||||
crate::app::config::DockSide::Left => iced_aw::drop_down::Alignment::BottomEnd,
|
crate::app::config::DockSide::Left => iced_aw::drop_down::Alignment::BottomEnd,
|
||||||
@@ -425,6 +507,19 @@ fn popup_alignment(side: crate::app::config::DockSide) -> iced_aw::drop_down::Al
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn compact_button_content(label: &'static str) -> Element<'static, Message> {
|
||||||
|
container(text(label).size(crate::ui::ROW_H * 0.42))
|
||||||
|
.width(12)
|
||||||
|
.height(Fill)
|
||||||
|
.align_x(iced::Center)
|
||||||
|
.align_y(iced::Center)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mode_button_content(mode: PropertyMode) -> Element<'static, Message> {
|
||||||
|
compact_button_content(mode.button_label())
|
||||||
|
}
|
||||||
|
|
||||||
fn by_mode_menu<'a>(
|
fn by_mode_menu<'a>(
|
||||||
side: crate::app::config::DockSide,
|
side: crate::app::config::DockSide,
|
||||||
mode: PropertyMode,
|
mode: PropertyMode,
|
||||||
@@ -434,7 +529,7 @@ fn by_mode_menu<'a>(
|
|||||||
by_layer: Message,
|
by_layer: Message,
|
||||||
by_block: Message,
|
by_block: Message,
|
||||||
) -> Element<'a, Message> {
|
) -> Element<'a, Message> {
|
||||||
let more = button(text(mode.button_label()).size(crate::ui::ROW_H * 0.42))
|
let more = button(mode_button_content(mode))
|
||||||
.on_press(toggle)
|
.on_press(toggle)
|
||||||
.style(move |theme: &Theme, status| {
|
.style(move |theme: &Theme, status| {
|
||||||
if open {
|
if open {
|
||||||
@@ -443,8 +538,8 @@ fn by_mode_menu<'a>(
|
|||||||
button::secondary(theme, status)
|
button::secondary(theme, status)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.height(crate::ui::ROW_H)
|
.height(COMPACT_BUTTON_HEIGHT)
|
||||||
.padding([2, 7]);
|
.padding([1, 3]);
|
||||||
if !open {
|
if !open {
|
||||||
return more.into();
|
return more.into();
|
||||||
}
|
}
|
||||||
@@ -483,7 +578,7 @@ fn color_control<'a>(
|
|||||||
Some("*VARIES*")
|
Some("*VARIES*")
|
||||||
} else {
|
} else {
|
||||||
match info.color {
|
match info.color {
|
||||||
acadrust::types::Color::ByLayer => Some("ByLayer"),
|
acadrust::types::Color::ByLayer => None,
|
||||||
acadrust::types::Color::ByBlock => Some("ByBlock"),
|
acadrust::types::Color::ByBlock => Some("ByBlock"),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
@@ -498,7 +593,7 @@ fn color_control<'a>(
|
|||||||
let color = button(label.unwrap_or_else(|| container(Space::new()).width(Fill).height(Fill)))
|
let color = button(label.unwrap_or_else(|| container(Space::new()).width(Fill).height(Fill)))
|
||||||
.on_press(Message::OpenColorWindow(target.clone(), info.color))
|
.on_press(Message::OpenColorWindow(target.clone(), info.color))
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.height(crate::ui::ROW_H)
|
.height(COMPACT_BUTTON_HEIGHT)
|
||||||
.padding(0)
|
.padding(0)
|
||||||
.style(move |theme: &Theme, status| button::Style {
|
.style(move |theme: &Theme, status| button::Style {
|
||||||
background: Some(Background::Color(info.display)),
|
background: Some(Background::Color(info.display)),
|
||||||
@@ -521,7 +616,7 @@ fn color_control<'a>(
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
let mode = if info.varies {
|
let mode = if info.varies {
|
||||||
PropertyMode::Custom
|
PropertyMode::Varies
|
||||||
} else {
|
} else {
|
||||||
match info.color {
|
match info.color {
|
||||||
acadrust::types::Color::ByLayer => PropertyMode::ByLayer,
|
acadrust::types::Color::ByLayer => PropertyMode::ByLayer,
|
||||||
@@ -529,7 +624,7 @@ fn color_control<'a>(
|
|||||||
_ => PropertyMode::Custom,
|
_ => PropertyMode::Custom,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let more = button(text(mode.button_label()).size(crate::ui::ROW_H * 0.42))
|
let more = button(mode_button_content(mode))
|
||||||
.on_press(toggle)
|
.on_press(toggle)
|
||||||
.style(move |theme: &Theme, status| {
|
.style(move |theme: &Theme, status| {
|
||||||
if menu_open {
|
if menu_open {
|
||||||
@@ -538,8 +633,8 @@ fn color_control<'a>(
|
|||||||
button::secondary(theme, status)
|
button::secondary(theme, status)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.height(crate::ui::ROW_H)
|
.height(COMPACT_BUTTON_HEIGHT)
|
||||||
.padding([2, 7]);
|
.padding([1, 3]);
|
||||||
let more: Element<'a, Message> = if menu_open {
|
let more: Element<'a, Message> = if menu_open {
|
||||||
let choice = |label, color| {
|
let choice = |label, color| {
|
||||||
button(text(label).size(11))
|
button(text(label).size(11))
|
||||||
@@ -599,11 +694,11 @@ fn transparency_control<'a>(
|
|||||||
.align_y(iced::Center)
|
.align_y(iced::Center)
|
||||||
.height(crate::ui::ROW_H);
|
.height(crate::ui::ROW_H);
|
||||||
let mode = if info.varies {
|
let mode = if info.varies {
|
||||||
PropertyMode::Custom
|
PropertyMode::Varies
|
||||||
} else {
|
} else {
|
||||||
info.mode
|
info.mode
|
||||||
};
|
};
|
||||||
let more = button(text(mode.button_label()).size(crate::ui::ROW_H * 0.42))
|
let more = button(mode_button_content(mode))
|
||||||
.on_press(toggle)
|
.on_press(toggle)
|
||||||
.style(move |theme: &Theme, status| {
|
.style(move |theme: &Theme, status| {
|
||||||
if menu_open {
|
if menu_open {
|
||||||
@@ -613,7 +708,7 @@ fn transparency_control<'a>(
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.height(crate::ui::ROW_H)
|
.height(crate::ui::ROW_H)
|
||||||
.padding([2, 7]);
|
.padding([1, 3]);
|
||||||
let more: Element<'a, Message> = if menu_open {
|
let more: Element<'a, Message> = if menu_open {
|
||||||
let row_button = |label, message| {
|
let row_button = |label, message| {
|
||||||
button(text(label).size(11))
|
button(text(label).size(11))
|
||||||
@@ -823,7 +918,9 @@ pub fn view<'a>(
|
|||||||
});
|
});
|
||||||
if let Some(info) = linetype.as_ref() {
|
if let Some(info) = linetype.as_ref() {
|
||||||
let selected = (!info.varies).then(|| crate::ui::properties::LinetypeItem {
|
let selected = (!info.varies).then(|| crate::ui::properties::LinetypeItem {
|
||||||
name: info.value.clone(),
|
// An empty name makes only the preview art visible in the closed
|
||||||
|
// Graphic Attributes control. Menu items retain their full names.
|
||||||
|
name: String::new(),
|
||||||
art: linetype_preview_art(document, &line_handles, draw_depth, &info.value),
|
art: linetype_preview_art(document, &line_handles, draw_depth, &info.value),
|
||||||
});
|
});
|
||||||
let combo = combo_box(
|
let combo = combo_box(
|
||||||
@@ -844,7 +941,7 @@ pub fn view<'a>(
|
|||||||
let more = by_mode_menu(
|
let more = by_mode_menu(
|
||||||
side,
|
side,
|
||||||
if info.varies {
|
if info.varies {
|
||||||
PropertyMode::Custom
|
PropertyMode::Varies
|
||||||
} else if info.value.eq_ignore_ascii_case("ByLayer") {
|
} else if info.value.eq_ignore_ascii_case("ByLayer") {
|
||||||
PropertyMode::ByLayer
|
PropertyMode::ByLayer
|
||||||
} else if info.value.eq_ignore_ascii_case("ByBlock") {
|
} else if info.value.eq_ignore_ascii_case("ByBlock") {
|
||||||
@@ -890,7 +987,15 @@ pub fn view<'a>(
|
|||||||
let mut scale_input = text_input("*VARIES*", &scale_text)
|
let mut scale_input = text_input("*VARIES*", &scale_text)
|
||||||
.size(crate::ui::ROW_H * 0.42)
|
.size(crate::ui::ROW_H * 0.42)
|
||||||
.padding([3, 5])
|
.padding([3, 5])
|
||||||
.style(crate::ui::properties::combo_input_style)
|
.style(move |theme: &Theme, status| {
|
||||||
|
let mut style = crate::ui::properties::combo_input_style(theme, status);
|
||||||
|
if continuous_linetype {
|
||||||
|
let disabled = theme.palette().background.base.text.scale_alpha(0.42);
|
||||||
|
style.value = disabled;
|
||||||
|
style.placeholder = disabled;
|
||||||
|
}
|
||||||
|
style
|
||||||
|
})
|
||||||
.on_submit(Message::Noop)
|
.on_submit(Message::Noop)
|
||||||
.width(Fill);
|
.width(Fill);
|
||||||
if !continuous_linetype {
|
if !continuous_linetype {
|
||||||
@@ -902,7 +1007,10 @@ pub fn view<'a>(
|
|||||||
} else {
|
} else {
|
||||||
crate::ui::icons::themed_arrow_down(7.0)
|
crate::ui::icons::themed_arrow_down(7.0)
|
||||||
};
|
};
|
||||||
let button = button(icon).style(button::secondary).padding([0, 3]);
|
let button = button(icon)
|
||||||
|
.style(button::subtle)
|
||||||
|
.height(10)
|
||||||
|
.padding([0, 3]);
|
||||||
if continuous_linetype {
|
if continuous_linetype {
|
||||||
button
|
button
|
||||||
} else {
|
} else {
|
||||||
@@ -912,18 +1020,48 @@ pub fn view<'a>(
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let scale = row![
|
let scale_icon: Element<'_, Message> = if continuous_linetype {
|
||||||
crate::ui::icons::semantic(GraphicAttribute::Solid.icon(), PALETTE_CONTROL_ICON_SIZE),
|
crate::ui::icons::themed_disabled(
|
||||||
scale_input,
|
GraphicAttribute::Solid.icon(),
|
||||||
column![step(true), step(false)].spacing(0),
|
PALETTE_CONTROL_ICON_SIZE,
|
||||||
]
|
)
|
||||||
|
} else {
|
||||||
|
crate::ui::icons::semantic(
|
||||||
|
GraphicAttribute::Solid.icon(),
|
||||||
|
PALETTE_CONTROL_ICON_SIZE,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
let spinner_divider = container(Space::new())
|
||||||
|
.width(Fill)
|
||||||
|
.height(1)
|
||||||
|
.style(|theme: &Theme| container::Style {
|
||||||
|
background: Some(Background::Color(theme.palette().background.neutral.color)),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
let spinner = container(column![step(true), spinner_divider, step(false)].spacing(0))
|
||||||
|
.height(COMPACT_BUTTON_HEIGHT)
|
||||||
|
.style(|theme: &Theme| container::Style {
|
||||||
|
background: Some(Background::Color(theme.palette().background.base.color)),
|
||||||
|
border: Border {
|
||||||
|
color: theme.palette().background.neutral.color,
|
||||||
|
width: 1.0,
|
||||||
|
radius: 2.0.into(),
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
let scale_field = row![scale_input, spinner]
|
||||||
|
.spacing(0)
|
||||||
|
.align_y(iced::Center)
|
||||||
|
.width(Fill);
|
||||||
|
let scale = row![scale_icon, scale_field]
|
||||||
.spacing(3)
|
.spacing(3)
|
||||||
.align_y(iced::Center)
|
.align_y(iced::Center)
|
||||||
.width(Length::FillPortion(1));
|
.width(Length::FillPortion(1));
|
||||||
let selected =
|
let selected = (!lineweight.varies).then_some(crate::ui::properties::LwItem(
|
||||||
(!lineweight.varies).then_some(crate::ui::properties::LwItem(lineweight.value));
|
effective_lineweight(document, &line_handles, draw_depth, lineweight.value),
|
||||||
|
));
|
||||||
let lineweight_mode = if lineweight.varies {
|
let lineweight_mode = if lineweight.varies {
|
||||||
PropertyMode::Custom
|
PropertyMode::Varies
|
||||||
} else {
|
} else {
|
||||||
match lineweight.value {
|
match lineweight.value {
|
||||||
acadrust::types::LineWeight::ByLayer => PropertyMode::ByLayer,
|
acadrust::types::LineWeight::ByLayer => PropertyMode::ByLayer,
|
||||||
@@ -1038,12 +1176,25 @@ pub fn view<'a>(
|
|||||||
} else {
|
} else {
|
||||||
Message::GradientEditorOpen
|
Message::GradientEditorOpen
|
||||||
};
|
};
|
||||||
let preview = button(
|
let preview_image = image(crate::ui::window::gradient_editor::compact_preview(hatch))
|
||||||
image(crate::ui::window::gradient_editor::compact_preview(hatch))
|
.width(Fill)
|
||||||
.width(Fill)
|
.height(Fill)
|
||||||
.height(Fill)
|
.content_fit(iced::ContentFit::Fill);
|
||||||
.content_fit(iced::ContentFit::Fill),
|
let preview_content: Element<'_, Message> = if gradients_vary(document, selected) {
|
||||||
)
|
iced::widget::stack![
|
||||||
|
preview_image,
|
||||||
|
container(text("*VARIES*").size(10))
|
||||||
|
.width(Fill)
|
||||||
|
.height(Fill)
|
||||||
|
.align_x(iced::Left)
|
||||||
|
.align_y(iced::Center)
|
||||||
|
.padding([0, 6]),
|
||||||
|
]
|
||||||
|
.into()
|
||||||
|
} else {
|
||||||
|
preview_image.into()
|
||||||
|
};
|
||||||
|
let preview = button(preview_content)
|
||||||
.on_press(toggle.clone())
|
.on_press(toggle.clone())
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.height(crate::ui::ROW_H)
|
.height(crate::ui::ROW_H)
|
||||||
@@ -1063,7 +1214,7 @@ pub fn view<'a>(
|
|||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
let edit = button(text("...").size(crate::ui::ROW_H * 0.42))
|
let edit = button(compact_button_content("⋮"))
|
||||||
.on_press(toggle)
|
.on_press(toggle)
|
||||||
.style(move |theme: &Theme, status| {
|
.style(move |theme: &Theme, status| {
|
||||||
if gradient_editor.is_some() {
|
if gradient_editor.is_some() {
|
||||||
@@ -1072,8 +1223,8 @@ pub fn view<'a>(
|
|||||||
button::secondary(theme, status)
|
button::secondary(theme, status)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.height(crate::ui::ROW_H)
|
.height(COMPACT_BUTTON_HEIGHT)
|
||||||
.padding([2, 7]);
|
.padding([1, 3]);
|
||||||
let edit = tooltip(
|
let edit = tooltip(
|
||||||
edit,
|
edit,
|
||||||
text("Edit gradient").size(10),
|
text("Edit gradient").size(10),
|
||||||
@@ -1122,7 +1273,7 @@ pub fn view<'a>(
|
|||||||
Message::HatchEditorOpen
|
Message::HatchEditorOpen
|
||||||
})
|
})
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.height(crate::ui::ROW_H)
|
.height(COMPACT_BUTTON_HEIGHT)
|
||||||
.padding(0)
|
.padding(0)
|
||||||
.style(move |theme: &Theme, status| button::Style {
|
.style(move |theme: &Theme, status| button::Style {
|
||||||
background: Some(Background::Color(theme.palette().background.base.color)),
|
background: Some(Background::Color(theme.palette().background.base.color)),
|
||||||
@@ -1139,28 +1290,7 @@ pub fn view<'a>(
|
|||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
let preview: Element<'_, Message> = if hatch_editor_open {
|
let edit = button(compact_button_content("⋮"))
|
||||||
let popup = crate::ui::properties::hatch_pattern_picker_content(
|
|
||||||
hatch_pattern_search,
|
|
||||||
hatch_pattern_focus,
|
|
||||||
current_pattern,
|
|
||||||
"graphic-hatch-pattern-search",
|
|
||||||
Message::GraphicHatchPatternSearchChanged,
|
|
||||||
Message::GraphicHatchPatternConfirm,
|
|
||||||
Message::GraphicHatchPatternFocus,
|
|
||||||
Message::GraphicHatchPatternChanged,
|
|
||||||
);
|
|
||||||
iced_aw::DropDown::new(preview, popup, true)
|
|
||||||
.width(Length::Fixed(348.0))
|
|
||||||
.height(Length::Fixed(720.0))
|
|
||||||
.alignment(popup_alignment(side))
|
|
||||||
.offset(4.0)
|
|
||||||
.on_dismiss(Message::HatchEditorClose)
|
|
||||||
.into()
|
|
||||||
} else {
|
|
||||||
preview.into()
|
|
||||||
};
|
|
||||||
let edit = button(text("...").size(crate::ui::ROW_H * 0.42))
|
|
||||||
.on_press(if hatch_editor_open {
|
.on_press(if hatch_editor_open {
|
||||||
Message::HatchEditorClose
|
Message::HatchEditorClose
|
||||||
} else {
|
} else {
|
||||||
@@ -1174,8 +1304,40 @@ pub fn view<'a>(
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.height(crate::ui::ROW_H)
|
.height(crate::ui::ROW_H)
|
||||||
.padding([2, 7]);
|
.padding([1, 3]);
|
||||||
let edit = tooltip(edit, text("Edit hatch").size(10), tooltip::Position::Bottom);
|
let edit = tooltip(edit, text("Edit hatch").size(10), tooltip::Position::Bottom);
|
||||||
|
let edit: Element<'_, Message> = if hatch_editor_open {
|
||||||
|
let popup = crate::ui::properties::hatch_pattern_picker_content(
|
||||||
|
hatch_pattern_search,
|
||||||
|
hatch_pattern_focus,
|
||||||
|
current_pattern,
|
||||||
|
"graphic-hatch-pattern-search",
|
||||||
|
Message::GraphicHatchPatternSearchChanged,
|
||||||
|
Message::GraphicHatchPatternConfirm,
|
||||||
|
Message::GraphicHatchPatternFocus,
|
||||||
|
Message::GraphicHatchPatternChanged,
|
||||||
|
);
|
||||||
|
let popup = container(popup)
|
||||||
|
.padding(1)
|
||||||
|
.style(|theme: &Theme| container::Style {
|
||||||
|
background: Some(Background::Color(theme.palette().background.base.color)),
|
||||||
|
border: Border {
|
||||||
|
color: theme.palette().primary.base.color,
|
||||||
|
width: 1.0,
|
||||||
|
radius: 3.0.into(),
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
iced_aw::DropDown::new(edit, popup, true)
|
||||||
|
.width(Length::Fixed(352.0))
|
||||||
|
.height(Length::Fixed(724.0))
|
||||||
|
.alignment(popup_alignment(side))
|
||||||
|
.offset(4.0)
|
||||||
|
.on_dismiss(Message::HatchEditorClose)
|
||||||
|
.into()
|
||||||
|
} else {
|
||||||
|
edit.into()
|
||||||
|
};
|
||||||
body = body.push(row![preview, edit].spacing(4).align_y(iced::Center));
|
body = body.push(row![preview, edit].spacing(4).align_y(iced::Center));
|
||||||
}
|
}
|
||||||
if current != GraphicAttribute::None {
|
if current != GraphicAttribute::None {
|
||||||
|
|||||||
Reference in new issue
Block a user