forked from 1b-agent/opencadstudio-upstream
Add graphic attributes controls and layer creation
This commit is contained in:
1 parent
85e2dda6ba
commit
3b74c7e7ea
9 files changed
+586
-52
No files matched your search
Generated
+12
@@ -2571,6 +2571,8 @@ dependencies = [
|
|||||||
"iced_core",
|
"iced_core",
|
||||||
"iced_fonts_macros",
|
"iced_fonts_macros",
|
||||||
"iced_widget",
|
"iced_widget",
|
||||||
|
"num-format",
|
||||||
|
"num-traits",
|
||||||
"web-time",
|
"web-time",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -3768,6 +3770,16 @@ dependencies = [
|
|||||||
"syn 2.0.119",
|
"syn 2.0.119",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-format"
|
||||||
|
version = "0.4.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3"
|
||||||
|
dependencies = [
|
||||||
|
"arrayvec",
|
||||||
|
"itoa",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-integer"
|
name = "num-integer"
|
||||||
version = "0.1.46"
|
version = "0.1.46"
|
||||||
|
|||||||
+1
-1
@@ -26,7 +26,7 @@ url = "2"
|
|||||||
iced = { git = "https://github.com/iced-rs/iced.git", rev = "23604ff22ab0aad9e00b9327cb7b8546ed84db39", features = ["image", "svg", "advanced", "canvas", "smol", "markdown"] }
|
iced = { git = "https://github.com/iced-rs/iced.git", rev = "23604ff22ab0aad9e00b9327cb7b8546ed84db39", features = ["image", "svg", "advanced", "canvas", "smol", "markdown"] }
|
||||||
iced_runtime = { git = "https://github.com/iced-rs/iced.git", rev = "23604ff22ab0aad9e00b9327cb7b8546ed84db39" }
|
iced_runtime = { git = "https://github.com/iced-rs/iced.git", rev = "23604ff22ab0aad9e00b9327cb7b8546ed84db39" }
|
||||||
iced_core = { git = "https://github.com/iced-rs/iced.git", rev = "23604ff22ab0aad9e00b9327cb7b8546ed84db39" }
|
iced_core = { git = "https://github.com/iced-rs/iced.git", rev = "23604ff22ab0aad9e00b9327cb7b8546ed84db39" }
|
||||||
iced_aw = { git = "https://github.com/HakanSeven12/iced_aw.git", branch = "agent/fix-iced-fonts", features = ["menu", "context_menu", "color_picker", "drop_down"] }
|
iced_aw = { git = "https://github.com/HakanSeven12/iced_aw.git", branch = "agent/fix-iced-fonts", features = ["menu", "context_menu", "color_picker", "drop_down", "number_input"] }
|
||||||
bytemuck = { version = "1.25", features = ["derive"] }
|
bytemuck = { version = "1.25", features = ["derive"] }
|
||||||
glam = { version = "0.33", features = ["bytemuck"] }
|
glam = { version = "0.33", features = ["bytemuck"] }
|
||||||
rfd = "0.17"
|
rfd = "0.17"
|
||||||
|
|||||||
@@ -3,6 +3,10 @@ use super::*;
|
|||||||
impl OpenCADStudio {
|
impl OpenCADStudio {
|
||||||
pub(super) fn dispatch_layers(&mut self, cmd: &str, i: usize) -> Option<Task<Message>> {
|
pub(super) fn dispatch_layers(&mut self, cmd: &str, i: usize) -> Option<Task<Message>> {
|
||||||
match cmd {
|
match cmd {
|
||||||
|
cmd if cmd.starts_with("GRAPHICATTRIBUTELAYER ") => {
|
||||||
|
let name = cmd.trim_start_matches("GRAPHICATTRIBUTELAYER").trim();
|
||||||
|
return Some(self.create_graphic_attributes_layer(name));
|
||||||
|
}
|
||||||
// ── Layer object commands ──────────────────────────────────────
|
// ── Layer object commands ──────────────────────────────────────
|
||||||
"LAYOFF" => {
|
"LAYOFF" => {
|
||||||
let handles: Vec<_> = self.tabs[i]
|
let handles: Vec<_> = self.tabs[i]
|
||||||
|
|||||||
@@ -769,6 +769,7 @@ pub(super) struct OpenCADStudio {
|
|||||||
/// Whether the Properties panel is shown on the left (PROPERTIES).
|
/// Whether the Properties panel is shown on the left (PROPERTIES).
|
||||||
show_properties: bool,
|
show_properties: bool,
|
||||||
show_graphic_attributes: bool,
|
show_graphic_attributes: bool,
|
||||||
|
graphic_attributes_header_menu_open: bool,
|
||||||
graphic_attribute_menu_open: bool,
|
graphic_attribute_menu_open: bool,
|
||||||
line_color_menu_open: bool,
|
line_color_menu_open: bool,
|
||||||
line_linetype_menu_open: bool,
|
line_linetype_menu_open: bool,
|
||||||
@@ -2066,6 +2067,12 @@ pub enum ArrowKey {
|
|||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Message {
|
pub enum Message {
|
||||||
|
ToggleGraphicAttributesHeaderMenu,
|
||||||
|
CloseGraphicAttributesHeaderMenu,
|
||||||
|
GraphicAttributesSetAllByLayer,
|
||||||
|
GraphicAttributesSetAllByBlock,
|
||||||
|
GraphicAttributesRemoveReferences,
|
||||||
|
GraphicAttributesCreateLayer,
|
||||||
ToggleGraphicAttributeDropdown,
|
ToggleGraphicAttributeDropdown,
|
||||||
CloseGraphicAttributeDropdown,
|
CloseGraphicAttributeDropdown,
|
||||||
GraphicAttributeChanged(crate::ui::window::graphic_attributes::GraphicAttribute),
|
GraphicAttributeChanged(crate::ui::window::graphic_attributes::GraphicAttribute),
|
||||||
@@ -4161,6 +4168,7 @@ impl OpenCADStudio {
|
|||||||
render_mode_preview: None,
|
render_mode_preview: None,
|
||||||
show_properties: true,
|
show_properties: true,
|
||||||
show_graphic_attributes: true,
|
show_graphic_attributes: true,
|
||||||
|
graphic_attributes_header_menu_open: false,
|
||||||
graphic_attribute_menu_open: false,
|
graphic_attribute_menu_open: false,
|
||||||
line_color_menu_open: false,
|
line_color_menu_open: false,
|
||||||
line_linetype_menu_open: false,
|
line_linetype_menu_open: false,
|
||||||
|
|||||||
+408
-1
@@ -130,7 +130,324 @@ fn close_fill_boundary(entity: &mut acadrust::EntityType) -> bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct ConcreteGraphicAttributes {
|
||||||
|
color: acadrust::types::Color,
|
||||||
|
linetype: String,
|
||||||
|
lineweight: acadrust::types::LineWeight,
|
||||||
|
transparency: acadrust::types::Transparency,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn concrete_graphic_attributes(
|
||||||
|
color: acadrust::types::Color,
|
||||||
|
linetype: &str,
|
||||||
|
lineweight: acadrust::types::LineWeight,
|
||||||
|
transparency: acadrust::types::Transparency,
|
||||||
|
layer: Option<&acadrust::tables::Layer>,
|
||||||
|
) -> ConcreteGraphicAttributes {
|
||||||
|
let layer_color = layer.map_or(acadrust::types::Color::Index(7), |layer| {
|
||||||
|
match layer.color {
|
||||||
|
acadrust::types::Color::ByLayer | acadrust::types::Color::ByBlock => {
|
||||||
|
acadrust::types::Color::Index(7)
|
||||||
|
}
|
||||||
|
color => color,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let layer_linetype = layer
|
||||||
|
.map(|layer| layer.line_type.as_str())
|
||||||
|
.filter(|value| {
|
||||||
|
!value.is_empty()
|
||||||
|
&& !value.eq_ignore_ascii_case("ByLayer")
|
||||||
|
&& !value.eq_ignore_ascii_case("ByBlock")
|
||||||
|
})
|
||||||
|
.unwrap_or("Continuous");
|
||||||
|
let layer_lineweight = layer.map_or(acadrust::types::LineWeight::Default, |layer| {
|
||||||
|
match layer.line_weight {
|
||||||
|
acadrust::types::LineWeight::ByLayer | acadrust::types::LineWeight::ByBlock => {
|
||||||
|
acadrust::types::LineWeight::Default
|
||||||
|
}
|
||||||
|
value => value,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let layer_transparency = layer.map_or_else(
|
||||||
|
|| acadrust::types::Transparency::from_percent(0.0),
|
||||||
|
|layer| {
|
||||||
|
if layer.transparency.is_by_layer() || layer.transparency.is_by_block() {
|
||||||
|
acadrust::types::Transparency::from_percent(0.0)
|
||||||
|
} else {
|
||||||
|
layer.transparency
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
ConcreteGraphicAttributes {
|
||||||
|
color: match color {
|
||||||
|
acadrust::types::Color::ByLayer => layer_color,
|
||||||
|
acadrust::types::Color::ByBlock => acadrust::types::Color::Index(7),
|
||||||
|
value => value,
|
||||||
|
},
|
||||||
|
linetype: if linetype.is_empty() || linetype.eq_ignore_ascii_case("ByLayer") {
|
||||||
|
layer_linetype.to_owned()
|
||||||
|
} else if linetype.eq_ignore_ascii_case("ByBlock") {
|
||||||
|
"Continuous".to_owned()
|
||||||
|
} else {
|
||||||
|
linetype.to_owned()
|
||||||
|
},
|
||||||
|
lineweight: match lineweight {
|
||||||
|
acadrust::types::LineWeight::ByLayer => layer_lineweight,
|
||||||
|
acadrust::types::LineWeight::ByBlock => acadrust::types::LineWeight::Default,
|
||||||
|
value => value,
|
||||||
|
},
|
||||||
|
transparency: if transparency.is_by_layer() {
|
||||||
|
layer_transparency
|
||||||
|
} else if transparency.is_by_block() {
|
||||||
|
acadrust::types::Transparency::from_percent(0.0)
|
||||||
|
} else {
|
||||||
|
transparency
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn valid_layer_name(name: &str) -> bool {
|
||||||
|
!name.is_empty()
|
||||||
|
&& name.chars().count() <= 255
|
||||||
|
&& !name.chars().any(|character| {
|
||||||
|
matches!(
|
||||||
|
character,
|
||||||
|
'<' | '>' | '/' | '\\' | '"' | ':' | ';' | '?' | '*' | '|' | '=' | '`'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
impl OpenCADStudio {
|
impl OpenCADStudio {
|
||||||
|
fn graphic_attribute_handles(&self, tab: usize) -> Vec<Handle> {
|
||||||
|
let selected = self.tabs[tab].scene.selected_handles_in_order();
|
||||||
|
let document = &self.tabs[tab].scene.document;
|
||||||
|
let mut seen = rustc_hash::FxHashSet::default();
|
||||||
|
crate::ui::window::graphic_attributes::line_handles(document, &selected)
|
||||||
|
.into_iter()
|
||||||
|
.chain(crate::ui::window::graphic_attributes::fill_handles(
|
||||||
|
document, &selected,
|
||||||
|
))
|
||||||
|
.filter(|handle| seen.insert(*handle))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn current_graphic_attributes(&self, tab: usize) -> ConcreteGraphicAttributes {
|
||||||
|
let document = &self.tabs[tab].scene.document;
|
||||||
|
let handles = self.graphic_attribute_handles(tab);
|
||||||
|
let draw_depth = self.tabs[tab].scene.draw_depth_map();
|
||||||
|
let entity = handles
|
||||||
|
.iter()
|
||||||
|
.filter_map(|handle| {
|
||||||
|
document.get_entity(*handle).map(|entity| {
|
||||||
|
let depth = draw_depth
|
||||||
|
.get(&handle.value())
|
||||||
|
.map_or(0.0, |value| value[0]);
|
||||||
|
(depth, entity)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.max_by(|(left, _), (right, _)| left.total_cmp(right))
|
||||||
|
.map(|(_, entity)| entity);
|
||||||
|
|
||||||
|
if let Some(entity) = entity {
|
||||||
|
let common = entity.common();
|
||||||
|
return concrete_graphic_attributes(
|
||||||
|
common.color,
|
||||||
|
&common.linetype,
|
||||||
|
common.line_weight,
|
||||||
|
common.transparency,
|
||||||
|
document.layers.get(&common.layer),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let header = &document.header;
|
||||||
|
let layer_name = if header.current_layer_name.is_empty() {
|
||||||
|
self.tabs[tab].active_layer.as_str()
|
||||||
|
} else {
|
||||||
|
header.current_layer_name.as_str()
|
||||||
|
};
|
||||||
|
concrete_graphic_attributes(
|
||||||
|
header.current_entity_color,
|
||||||
|
&header.current_linetype_name,
|
||||||
|
acadrust::types::LineWeight::from_value(header.current_line_weight),
|
||||||
|
document.current_entity_transparency(),
|
||||||
|
document.layers.get(layer_name),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn on_graphic_attributes_set_all(&mut self, by_layer: bool) -> iced::Task<Message> {
|
||||||
|
self.graphic_attributes_header_menu_open = false;
|
||||||
|
let i = self.active_tab;
|
||||||
|
let selected = self.tabs[i].scene.selected_handles_in_order();
|
||||||
|
let (color, linetype, lineweight, transparency) = if by_layer {
|
||||||
|
(
|
||||||
|
acadrust::types::Color::ByLayer,
|
||||||
|
"ByLayer",
|
||||||
|
acadrust::types::LineWeight::ByLayer,
|
||||||
|
acadrust::types::Transparency::BY_LAYER,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
acadrust::types::Color::ByBlock,
|
||||||
|
"ByBlock",
|
||||||
|
acadrust::types::LineWeight::ByBlock,
|
||||||
|
acadrust::types::Transparency::BY_BLOCK,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
if selected.is_empty() {
|
||||||
|
let header = &mut self.tabs[i].scene.document.header;
|
||||||
|
header.current_entity_color = color;
|
||||||
|
header.current_linetype_name = linetype.to_owned();
|
||||||
|
header.current_linetype_handle = acadrust::Handle::NULL;
|
||||||
|
header.current_line_weight = lineweight.value();
|
||||||
|
self.tabs[i]
|
||||||
|
.scene
|
||||||
|
.document
|
||||||
|
.set_current_entity_transparency(transparency);
|
||||||
|
self.tabs[i].dirty = true;
|
||||||
|
self.sync_ribbon_from_selection();
|
||||||
|
self.refresh_properties();
|
||||||
|
return iced::Task::none();
|
||||||
|
}
|
||||||
|
|
||||||
|
let handles: Vec<_> = self
|
||||||
|
.graphic_attribute_handles(i)
|
||||||
|
.into_iter()
|
||||||
|
.filter(|handle| !self.tabs[i].scene.is_layer_locked(*handle))
|
||||||
|
.collect();
|
||||||
|
self.apply_property_op(i, "Graphic Attributes", &handles, |app, handle| {
|
||||||
|
if let Some(entity) = app.tabs[i].scene.document.get_entity_mut(handle) {
|
||||||
|
crate::scene::view::dispatch::apply_color(entity, color);
|
||||||
|
crate::scene::view::dispatch::apply_common_prop(entity, "linetype", linetype);
|
||||||
|
crate::scene::view::dispatch::apply_line_weight(entity, lineweight);
|
||||||
|
entity.common_mut().transparency = transparency;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
iced::Task::none()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn on_graphic_attributes_remove_references(&mut self) -> iced::Task<Message> {
|
||||||
|
self.graphic_attributes_header_menu_open = false;
|
||||||
|
let i = self.active_tab;
|
||||||
|
let selected = self.tabs[i].scene.selected_handles_in_order();
|
||||||
|
if selected.is_empty() {
|
||||||
|
let values = self.current_graphic_attributes(i);
|
||||||
|
let linetype_handle = self.tabs[i]
|
||||||
|
.scene
|
||||||
|
.document
|
||||||
|
.line_types
|
||||||
|
.iter()
|
||||||
|
.find(|linetype| linetype.name.eq_ignore_ascii_case(&values.linetype))
|
||||||
|
.map_or(acadrust::Handle::NULL, |linetype| linetype.handle);
|
||||||
|
let header = &mut self.tabs[i].scene.document.header;
|
||||||
|
header.current_entity_color = values.color;
|
||||||
|
header.current_linetype_name = values.linetype;
|
||||||
|
header.current_linetype_handle = linetype_handle;
|
||||||
|
header.current_line_weight = values.lineweight.value();
|
||||||
|
self.tabs[i]
|
||||||
|
.scene
|
||||||
|
.document
|
||||||
|
.set_current_entity_transparency(values.transparency);
|
||||||
|
self.tabs[i].dirty = true;
|
||||||
|
self.sync_ribbon_from_selection();
|
||||||
|
self.refresh_properties();
|
||||||
|
return iced::Task::none();
|
||||||
|
}
|
||||||
|
|
||||||
|
let handles: Vec<_> = self
|
||||||
|
.graphic_attribute_handles(i)
|
||||||
|
.into_iter()
|
||||||
|
.filter(|handle| !self.tabs[i].scene.is_layer_locked(*handle))
|
||||||
|
.collect();
|
||||||
|
let values: rustc_hash::FxHashMap<_, _> = handles
|
||||||
|
.iter()
|
||||||
|
.filter_map(|handle| {
|
||||||
|
let entity = self.tabs[i].scene.document.get_entity(*handle)?;
|
||||||
|
let common = entity.common();
|
||||||
|
Some((
|
||||||
|
*handle,
|
||||||
|
concrete_graphic_attributes(
|
||||||
|
common.color,
|
||||||
|
&common.linetype,
|
||||||
|
common.line_weight,
|
||||||
|
common.transparency,
|
||||||
|
self.tabs[i].scene.document.layers.get(&common.layer),
|
||||||
|
),
|
||||||
|
))
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
self.apply_property_op(i, "Graphic Attributes", &handles, |app, handle| {
|
||||||
|
let Some(values) = values.get(&handle) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if let Some(entity) = app.tabs[i].scene.document.get_entity_mut(handle) {
|
||||||
|
crate::scene::view::dispatch::apply_color(entity, values.color);
|
||||||
|
crate::scene::view::dispatch::apply_common_prop(
|
||||||
|
entity,
|
||||||
|
"linetype",
|
||||||
|
&values.linetype,
|
||||||
|
);
|
||||||
|
crate::scene::view::dispatch::apply_line_weight(entity, values.lineweight);
|
||||||
|
entity.common_mut().transparency = values.transparency;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
iced::Task::none()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn create_graphic_attributes_layer(
|
||||||
|
&mut self,
|
||||||
|
raw_name: &str,
|
||||||
|
) -> iced::Task<Message> {
|
||||||
|
let i = self.active_tab;
|
||||||
|
let name = raw_name.trim();
|
||||||
|
if !valid_layer_name(name) {
|
||||||
|
self.command_line
|
||||||
|
.push_error("Graphic Attributes: enter a valid layer name without <>/\\\":;?*|=`.");
|
||||||
|
return iced::Task::none();
|
||||||
|
}
|
||||||
|
if self.tabs[i]
|
||||||
|
.scene
|
||||||
|
.document
|
||||||
|
.layers
|
||||||
|
.iter()
|
||||||
|
.any(|layer| layer.name.eq_ignore_ascii_case(name))
|
||||||
|
{
|
||||||
|
self.command_line.push_error(
|
||||||
|
crate::tf!("Graphic Attributes: layer \"{name}\" already exists.").as_ref(),
|
||||||
|
);
|
||||||
|
return iced::Task::none();
|
||||||
|
}
|
||||||
|
|
||||||
|
let values = self.current_graphic_attributes(i);
|
||||||
|
let layer_name = name.to_owned();
|
||||||
|
let undo = self.begin_layer_undo(
|
||||||
|
i,
|
||||||
|
"Create layer with active settings",
|
||||||
|
std::slice::from_ref(&layer_name),
|
||||||
|
);
|
||||||
|
let mut layer = acadrust::tables::Layer::new(name);
|
||||||
|
layer.handle = self.tabs[i].scene.document.allocate_handle();
|
||||||
|
layer.color = values.color;
|
||||||
|
layer.line_type = values.linetype;
|
||||||
|
layer.line_weight = values.lineweight;
|
||||||
|
layer.transparency = values.transparency;
|
||||||
|
use acadrust::tables::Table;
|
||||||
|
if self.tabs[i].scene.document.layers.add(layer).is_err() {
|
||||||
|
self.command_line
|
||||||
|
.push_error("Graphic Attributes: the layer could not be created.");
|
||||||
|
return iced::Task::none();
|
||||||
|
}
|
||||||
|
self.tabs[i].dirty = true;
|
||||||
|
self.commit_layer_undo(i, undo);
|
||||||
|
self.refresh_layer_panel();
|
||||||
|
self.command_line.push_output(
|
||||||
|
crate::tf!("Created layer \"{name}\" with the active Graphic Attributes settings.")
|
||||||
|
.as_ref(),
|
||||||
|
);
|
||||||
|
iced::Task::none()
|
||||||
|
}
|
||||||
|
|
||||||
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;
|
||||||
let i = self.active_tab;
|
let i = self.active_tab;
|
||||||
@@ -601,7 +918,7 @@ impl OpenCADStudio {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn on_line_linetype_scale(&mut self, scale: f64) -> iced::Task<Message> {
|
pub(super) fn on_line_linetype_scale(&mut self, scale: f64) -> iced::Task<Message> {
|
||||||
let scale = scale.clamp(0.01, 1000.0);
|
let scale = ((scale * 10.0).round() / 10.0).clamp(0.1, 1000.0);
|
||||||
let i = self.active_tab;
|
let i = self.active_tab;
|
||||||
let selected = self.tabs[i].scene.selected_handles_in_order();
|
let selected = self.tabs[i].scene.selected_handles_in_order();
|
||||||
if selected.is_empty() {
|
if selected.is_empty() {
|
||||||
@@ -1278,6 +1595,7 @@ impl OpenCADStudio {
|
|||||||
}
|
}
|
||||||
PanelId::GraphicAttributes => {
|
PanelId::GraphicAttributes => {
|
||||||
self.show_graphic_attributes = false;
|
self.show_graphic_attributes = false;
|
||||||
|
self.graphic_attributes_header_menu_open = false;
|
||||||
self.graphic_attribute_menu_open = false;
|
self.graphic_attribute_menu_open = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2270,6 +2588,17 @@ mod tests {
|
|||||||
.abs()
|
.abs()
|
||||||
< f64::EPSILON
|
< f64::EPSILON
|
||||||
);
|
);
|
||||||
|
let _ = app.update(Message::LineLinetypeScaleChanged(1.2000000000000002));
|
||||||
|
assert_eq!(
|
||||||
|
app.tabs[i]
|
||||||
|
.scene
|
||||||
|
.document
|
||||||
|
.get_entity(line)
|
||||||
|
.expect("line")
|
||||||
|
.common()
|
||||||
|
.linetype_scale,
|
||||||
|
1.2
|
||||||
|
);
|
||||||
let _ = app.on_line_transparency(acadrust::types::Transparency::from_percent(0.2));
|
let _ = app.on_line_transparency(acadrust::types::Transparency::from_percent(0.2));
|
||||||
assert!(
|
assert!(
|
||||||
(app.tabs[i]
|
(app.tabs[i]
|
||||||
@@ -2433,6 +2762,84 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn graphic_attributes_bulk_modes_and_remove_references_preserve_visible_values() {
|
||||||
|
let mut app = fresh();
|
||||||
|
let i = app.active_tab;
|
||||||
|
{
|
||||||
|
let layer = app.tabs[i]
|
||||||
|
.scene
|
||||||
|
.document
|
||||||
|
.layers
|
||||||
|
.get_mut("0")
|
||||||
|
.expect("default layer");
|
||||||
|
layer.color = acadrust::types::Color::Index(3);
|
||||||
|
layer.line_type = "Continuous".to_string();
|
||||||
|
layer.line_weight = acadrust::types::LineWeight::Value(50);
|
||||||
|
layer.transparency = acadrust::types::Transparency::from_percent(0.25);
|
||||||
|
}
|
||||||
|
let handle = app.tabs[i].scene.add_entity(EntityType::Line(Line::new()));
|
||||||
|
app.tabs[i].scene.select_entity(handle, false);
|
||||||
|
|
||||||
|
let _ = app.on_graphic_attributes_set_all(false);
|
||||||
|
let common = app.tabs[i]
|
||||||
|
.scene
|
||||||
|
.document
|
||||||
|
.get_entity(handle)
|
||||||
|
.expect("line")
|
||||||
|
.common();
|
||||||
|
assert_eq!(common.color, acadrust::types::Color::ByBlock);
|
||||||
|
assert_eq!(common.linetype, "ByBlock");
|
||||||
|
assert_eq!(common.line_weight, acadrust::types::LineWeight::ByBlock);
|
||||||
|
assert!(common.transparency.is_by_block());
|
||||||
|
|
||||||
|
let _ = app.on_graphic_attributes_set_all(true);
|
||||||
|
let _ = app.on_graphic_attributes_remove_references();
|
||||||
|
let common = app.tabs[i]
|
||||||
|
.scene
|
||||||
|
.document
|
||||||
|
.get_entity(handle)
|
||||||
|
.expect("line")
|
||||||
|
.common();
|
||||||
|
assert_eq!(common.color, acadrust::types::Color::Index(3));
|
||||||
|
assert_eq!(common.linetype, "Continuous");
|
||||||
|
assert_eq!(common.line_weight, acadrust::types::LineWeight::Value(50));
|
||||||
|
assert!((common.transparency.as_percent() - 0.25).abs() < 0.01);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn graphic_attributes_create_layer_prompt_keeps_spaces_and_active_settings() {
|
||||||
|
let mut app = fresh();
|
||||||
|
let i = app.active_tab;
|
||||||
|
app.tabs[i].scene.deselect_all();
|
||||||
|
let _ = app.on_line_color(acadrust::types::Color::Index(2));
|
||||||
|
let _ = app.on_line_linetype("Continuous".to_string());
|
||||||
|
let _ = app.on_line_lineweight(acadrust::types::LineWeight::Value(35));
|
||||||
|
let _ = app.on_line_transparency(acadrust::types::Transparency::from_percent(0.3));
|
||||||
|
|
||||||
|
let _ = app.update(Message::GraphicAttributesCreateLayer);
|
||||||
|
assert_eq!(
|
||||||
|
app.tabs[i]
|
||||||
|
.active_cmd
|
||||||
|
.as_ref()
|
||||||
|
.map(|command| command.name()),
|
||||||
|
Some("GRAPHICATTRIBUTELAYER")
|
||||||
|
);
|
||||||
|
let _ = app.update(Message::CommandInput("Graphic Settings".to_string()));
|
||||||
|
let _ = app.update(Message::CommandFinalize);
|
||||||
|
|
||||||
|
let layer = app.tabs[i]
|
||||||
|
.scene
|
||||||
|
.document
|
||||||
|
.layers
|
||||||
|
.get("Graphic Settings")
|
||||||
|
.expect("created layer");
|
||||||
|
assert_eq!(layer.color, acadrust::types::Color::Index(2));
|
||||||
|
assert_eq!(layer.line_type, "Continuous");
|
||||||
|
assert_eq!(layer.line_weight, acadrust::types::LineWeight::Value(35));
|
||||||
|
assert!((layer.transparency.as_percent() - 0.3).abs() < 0.01);
|
||||||
|
}
|
||||||
|
|
||||||
/// A foreign document: the fresh scene's document plus one model-space LINE.
|
/// A foreign document: the fresh scene's document plus one model-space LINE.
|
||||||
fn foreign_doc(app: &OpenCADStudio) -> acadrust::CadDocument {
|
fn foreign_doc(app: &OpenCADStudio) -> acadrust::CadDocument {
|
||||||
let mut doc = app.tabs[app.active_tab].scene.document.clone();
|
let mut doc = app.tabs[app.active_tab].scene.document.clone();
|
||||||
|
|||||||
@@ -9577,6 +9577,29 @@ impl OpenCADStudio {
|
|||||||
Message::PlotDialogOpen => self.on_plot_dialog_open(),
|
Message::PlotDialogOpen => self.on_plot_dialog_open(),
|
||||||
Message::PlotDlg(m) => self.on_plot_dlg(m),
|
Message::PlotDlg(m) => self.on_plot_dlg(m),
|
||||||
Message::BlockPalette(m) => self.on_block_palette(m),
|
Message::BlockPalette(m) => self.on_block_palette(m),
|
||||||
|
Message::ToggleGraphicAttributesHeaderMenu => {
|
||||||
|
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 => {
|
Message::ToggleGraphicAttributeDropdown => {
|
||||||
self.graphic_attribute_menu_open ^= true;
|
self.graphic_attribute_menu_open ^= true;
|
||||||
self.line_color_menu_open = false;
|
self.line_color_menu_open = false;
|
||||||
|
|||||||
@@ -2953,6 +2953,7 @@ impl OpenCADStudio {
|
|||||||
width,
|
width,
|
||||||
auto_collapse,
|
auto_collapse,
|
||||||
side,
|
side,
|
||||||
|
self.graphic_attributes_header_menu_open,
|
||||||
self.graphic_attribute_menu_open,
|
self.graphic_attribute_menu_open,
|
||||||
self.line_color_menu_open,
|
self.line_color_menu_open,
|
||||||
self.line_linetype_menu_open,
|
self.line_linetype_menu_open,
|
||||||
|
|||||||
@@ -347,6 +347,46 @@ impl CadCommand for ValuePromptCommand {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// One-value prompt that preserves spaces and letter casing. This is intended
|
||||||
|
/// for symbol names entered through the command line, where a space belongs to
|
||||||
|
/// the value instead of submitting the current token.
|
||||||
|
pub struct FreeTextValuePromptCommand {
|
||||||
|
name: &'static str,
|
||||||
|
prompt: &'static str,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FreeTextValuePromptCommand {
|
||||||
|
pub fn new(name: &'static str, prompt: &'static str) -> Self {
|
||||||
|
Self { name, prompt }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CadCommand for FreeTextValuePromptCommand {
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
self.name
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prompt(&self) -> String {
|
||||||
|
crate::t!(self.prompt).into_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn input_kind(&self) -> InputKind {
|
||||||
|
InputKind::FreeText
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_text_input(&mut self, text: &str) -> Option<CmdResult> {
|
||||||
|
Some(CmdResult::Dispatch(format!("{} {}", self.name, text.trim())))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_point(&mut self, _pt: DVec3) -> CmdResult {
|
||||||
|
CmdResult::NeedPoint
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_enter(&mut self) -> CmdResult {
|
||||||
|
CmdResult::Dispatch(format!("{} ", self.name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Interactive pick for `UCS FACE` and `UCS OBJECT`.
|
/// Interactive pick for `UCS FACE` and `UCS OBJECT`.
|
||||||
///
|
///
|
||||||
/// Hands the picked entity to the inline `UCS FACE <handle> <x,y,z>` /
|
/// Hands the picked entity to the inline `UCS FACE <handle> <x,y,z>` /
|
||||||
|
|||||||
@@ -745,6 +745,7 @@ pub fn view<'a>(
|
|||||||
width: f32,
|
width: f32,
|
||||||
auto_collapse: bool,
|
auto_collapse: bool,
|
||||||
side: crate::app::config::DockSide,
|
side: crate::app::config::DockSide,
|
||||||
|
header_menu_open: bool,
|
||||||
menu_open: bool,
|
menu_open: bool,
|
||||||
line_color_menu_open: bool,
|
line_color_menu_open: bool,
|
||||||
line_linetype_menu_open: bool,
|
line_linetype_menu_open: bool,
|
||||||
@@ -758,6 +759,57 @@ pub fn view<'a>(
|
|||||||
gradient_editor: Option<&'a crate::ui::window::gradient_editor::GradientEditorState>,
|
gradient_editor: Option<&'a crate::ui::window::gradient_editor::GradientEditorState>,
|
||||||
gradient_color_picker_open: bool,
|
gradient_color_picker_open: bool,
|
||||||
) -> Element<'a, Message> {
|
) -> Element<'a, Message> {
|
||||||
|
let menu_button = button(crate::ui::icons::themed_secondary(
|
||||||
|
crate::ui::icons::MENU,
|
||||||
|
12.0,
|
||||||
|
))
|
||||||
|
.on_press(Message::ToggleGraphicAttributesHeaderMenu)
|
||||||
|
.style(move |theme: &Theme, status| {
|
||||||
|
if header_menu_open {
|
||||||
|
button::primary(theme, status)
|
||||||
|
} else {
|
||||||
|
button::subtle(theme, status)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.padding([3, 5]);
|
||||||
|
let menu: Element<'a, Message> = if header_menu_open {
|
||||||
|
let item = |label: &'static str, message| {
|
||||||
|
button(text(label).size(11))
|
||||||
|
.on_press(message)
|
||||||
|
.style(crate::ui::color_select::list_row_style)
|
||||||
|
.width(Fill)
|
||||||
|
.padding([4, 8])
|
||||||
|
};
|
||||||
|
let popup = container(column![
|
||||||
|
item(
|
||||||
|
"Set all attributes ByLayer",
|
||||||
|
Message::GraphicAttributesSetAllByLayer,
|
||||||
|
),
|
||||||
|
item(
|
||||||
|
"Set all attributes ByBlock",
|
||||||
|
Message::GraphicAttributesSetAllByBlock,
|
||||||
|
),
|
||||||
|
item(
|
||||||
|
"Remove ByLayer / ByBlock",
|
||||||
|
Message::GraphicAttributesRemoveReferences,
|
||||||
|
),
|
||||||
|
item(
|
||||||
|
"Create Layer with active settings",
|
||||||
|
Message::GraphicAttributesCreateLayer,
|
||||||
|
),
|
||||||
|
])
|
||||||
|
.style(crate::ui::color_select::popup_panel_style)
|
||||||
|
.padding(2);
|
||||||
|
iced_aw::DropDown::new(menu_button, popup, true)
|
||||||
|
.width(Length::Fixed(220.0))
|
||||||
|
.height(Length::Shrink)
|
||||||
|
.alignment(popup_alignment(side))
|
||||||
|
.offset(2.0)
|
||||||
|
.on_dismiss(Message::CloseGraphicAttributesHeaderMenu)
|
||||||
|
.into()
|
||||||
|
} else {
|
||||||
|
menu_button.into()
|
||||||
|
};
|
||||||
let pin_icon = if auto_collapse {
|
let pin_icon = if auto_collapse {
|
||||||
crate::ui::icons::themed_primary_weak_text(crate::ui::icons::PIN, 12.0)
|
crate::ui::icons::themed_primary_weak_text(crate::ui::icons::PIN, 12.0)
|
||||||
} else {
|
} else {
|
||||||
@@ -793,6 +845,7 @@ pub fn view<'a>(
|
|||||||
row![
|
row![
|
||||||
text(crate::t!("Graphic Attributes")).size(12),
|
text(crate::t!("Graphic Attributes")).size(12),
|
||||||
Space::new().width(Fill),
|
Space::new().width(Fill),
|
||||||
|
menu,
|
||||||
pin,
|
pin,
|
||||||
close
|
close
|
||||||
]
|
]
|
||||||
@@ -979,15 +1032,26 @@ pub fn view<'a>(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
if let (Some(scale), Some(lineweight)) = (linetype_scale, lineweight) {
|
if let (Some(scale), Some(lineweight)) = (linetype_scale, lineweight) {
|
||||||
let scale_text = if scale.varies {
|
let scale_field: Element<'_, Message> = if scale.varies {
|
||||||
String::new()
|
text_input("*VARIES*", "")
|
||||||
|
.size(crate::ui::ROW_H * 0.42)
|
||||||
|
.padding([5, 5])
|
||||||
|
.style(crate::ui::properties::combo_input_style)
|
||||||
|
.width(Fill)
|
||||||
|
.into()
|
||||||
} else {
|
} else {
|
||||||
format!("{:.2}", scale.value)
|
let scale_value = ((scale.value * 10.0).round() / 10.0).clamp(0.1, 1000.0);
|
||||||
};
|
let mut input = iced_aw::number_input(
|
||||||
let mut scale_input = text_input("*VARIES*", &scale_text)
|
&scale_value,
|
||||||
.size(crate::ui::ROW_H * 0.42)
|
0.1..=1000.0,
|
||||||
.padding([3, 5])
|
|value: f64| {
|
||||||
.style(move |theme: &Theme, status| {
|
Message::LineLinetypeScaleChanged((value * 10.0).round() / 10.0)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.step(0.1)
|
||||||
|
.set_size(crate::ui::ROW_H * 0.42)
|
||||||
|
.padding([5, 5])
|
||||||
|
.input_style(move |theme: &Theme, status| {
|
||||||
let mut style = crate::ui::properties::combo_input_style(theme, status);
|
let mut style = crate::ui::properties::combo_input_style(theme, status);
|
||||||
if continuous_linetype {
|
if continuous_linetype {
|
||||||
let disabled = theme.palette().background.base.text.scale_alpha(0.42);
|
let disabled = theme.palette().background.base.text.scale_alpha(0.42);
|
||||||
@@ -996,29 +1060,26 @@ pub fn view<'a>(
|
|||||||
}
|
}
|
||||||
style
|
style
|
||||||
})
|
})
|
||||||
.on_submit(Message::Noop)
|
.style(move |theme: &Theme, status| {
|
||||||
|
let text = theme.palette().background.base.text;
|
||||||
|
iced_aw::number_input::number_input::Style {
|
||||||
|
button_background: None,
|
||||||
|
icon_color: if continuous_linetype
|
||||||
|
|| status == iced_aw::style::Status::Disabled
|
||||||
|
{
|
||||||
|
text.scale_alpha(0.42)
|
||||||
|
} else {
|
||||||
|
text
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
.width(Fill);
|
.width(Fill);
|
||||||
if !continuous_linetype {
|
|
||||||
scale_input = scale_input.on_input(Message::LineLinetypeScaleInput);
|
|
||||||
}
|
|
||||||
let step = |up: bool| {
|
|
||||||
let icon: Element<'_, Message> = if up {
|
|
||||||
crate::ui::icons::themed_arrow_up(7.0)
|
|
||||||
} else {
|
|
||||||
crate::ui::icons::themed_arrow_down(7.0)
|
|
||||||
};
|
|
||||||
let button = button(icon)
|
|
||||||
.style(button::subtle)
|
|
||||||
.height(10)
|
|
||||||
.padding([0, 3]);
|
|
||||||
if continuous_linetype {
|
if continuous_linetype {
|
||||||
button
|
input = input.on_input_maybe(None::<fn(f64) -> Message>);
|
||||||
} else {
|
} else {
|
||||||
let delta = if up { 0.1 } else { -0.1 };
|
input = input.on_submit(Message::Noop);
|
||||||
button.on_press(Message::LineLinetypeScaleChanged(
|
}
|
||||||
(scale.value + delta).clamp(0.01, 1000.0),
|
input.into()
|
||||||
))
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
let scale_icon: Element<'_, Message> = if continuous_linetype {
|
let scale_icon: Element<'_, Message> = if continuous_linetype {
|
||||||
crate::ui::icons::themed_disabled(
|
crate::ui::icons::themed_disabled(
|
||||||
@@ -1031,28 +1092,6 @@ pub fn view<'a>(
|
|||||||
PALETTE_CONTROL_ICON_SIZE,
|
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]
|
let scale = row![scale_icon, scale_field]
|
||||||
.spacing(3)
|
.spacing(3)
|
||||||
.align_y(iced::Center)
|
.align_y(iced::Center)
|
||||||
|
|||||||
Reference in new issue
Block a user