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_fonts_macros",
|
||||
"iced_widget",
|
||||
"num-format",
|
||||
"num-traits",
|
||||
"web-time",
|
||||
]
|
||||
|
||||
@@ -3768,6 +3770,16 @@ dependencies = [
|
||||
"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]]
|
||||
name = "num-integer"
|
||||
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_runtime = { 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"] }
|
||||
glam = { version = "0.33", features = ["bytemuck"] }
|
||||
rfd = "0.17"
|
||||
|
||||
@@ -3,6 +3,10 @@ use super::*;
|
||||
impl OpenCADStudio {
|
||||
pub(super) fn dispatch_layers(&mut self, cmd: &str, i: usize) -> Option<Task<Message>> {
|
||||
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 ──────────────────────────────────────
|
||||
"LAYOFF" => {
|
||||
let handles: Vec<_> = self.tabs[i]
|
||||
|
||||
@@ -769,6 +769,7 @@ pub(super) struct OpenCADStudio {
|
||||
/// Whether the Properties panel is shown on the left (PROPERTIES).
|
||||
show_properties: bool,
|
||||
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,
|
||||
@@ -2066,6 +2067,12 @@ pub enum ArrowKey {
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Message {
|
||||
ToggleGraphicAttributesHeaderMenu,
|
||||
CloseGraphicAttributesHeaderMenu,
|
||||
GraphicAttributesSetAllByLayer,
|
||||
GraphicAttributesSetAllByBlock,
|
||||
GraphicAttributesRemoveReferences,
|
||||
GraphicAttributesCreateLayer,
|
||||
ToggleGraphicAttributeDropdown,
|
||||
CloseGraphicAttributeDropdown,
|
||||
GraphicAttributeChanged(crate::ui::window::graphic_attributes::GraphicAttribute),
|
||||
@@ -4161,6 +4168,7 @@ impl OpenCADStudio {
|
||||
render_mode_preview: None,
|
||||
show_properties: true,
|
||||
show_graphic_attributes: true,
|
||||
graphic_attributes_header_menu_open: false,
|
||||
graphic_attribute_menu_open: false,
|
||||
line_color_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 {
|
||||
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) {
|
||||
use crate::ui::window::gradient_editor::GradientEditorState;
|
||||
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> {
|
||||
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 selected = self.tabs[i].scene.selected_handles_in_order();
|
||||
if selected.is_empty() {
|
||||
@@ -1278,6 +1595,7 @@ impl OpenCADStudio {
|
||||
}
|
||||
PanelId::GraphicAttributes => {
|
||||
self.show_graphic_attributes = false;
|
||||
self.graphic_attributes_header_menu_open = false;
|
||||
self.graphic_attribute_menu_open = false;
|
||||
}
|
||||
}
|
||||
@@ -2270,6 +2588,17 @@ mod tests {
|
||||
.abs()
|
||||
< 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));
|
||||
assert!(
|
||||
(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.
|
||||
fn foreign_doc(app: &OpenCADStudio) -> acadrust::CadDocument {
|
||||
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::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;
|
||||
|
||||
@@ -2953,6 +2953,7 @@ impl OpenCADStudio {
|
||||
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,
|
||||
|
||||
@@ -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`.
|
||||
///
|
||||
/// Hands the picked entity to the inline `UCS FACE <handle> <x,y,z>` /
|
||||
|
||||
@@ -745,6 +745,7 @@ pub fn view<'a>(
|
||||
width: f32,
|
||||
auto_collapse: bool,
|
||||
side: crate::app::config::DockSide,
|
||||
header_menu_open: bool,
|
||||
menu_open: bool,
|
||||
line_color_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_color_picker_open: bool,
|
||||
) -> 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 {
|
||||
crate::ui::icons::themed_primary_weak_text(crate::ui::icons::PIN, 12.0)
|
||||
} else {
|
||||
@@ -793,6 +845,7 @@ pub fn view<'a>(
|
||||
row![
|
||||
text(crate::t!("Graphic Attributes")).size(12),
|
||||
Space::new().width(Fill),
|
||||
menu,
|
||||
pin,
|
||||
close
|
||||
]
|
||||
@@ -979,15 +1032,26 @@ pub fn view<'a>(
|
||||
));
|
||||
}
|
||||
if let (Some(scale), Some(lineweight)) = (linetype_scale, lineweight) {
|
||||
let scale_text = if scale.varies {
|
||||
String::new()
|
||||
let scale_field: Element<'_, Message> = if scale.varies {
|
||||
text_input("*VARIES*", "")
|
||||
.size(crate::ui::ROW_H * 0.42)
|
||||
.padding([5, 5])
|
||||
.style(crate::ui::properties::combo_input_style)
|
||||
.width(Fill)
|
||||
.into()
|
||||
} else {
|
||||
format!("{:.2}", scale.value)
|
||||
};
|
||||
let mut scale_input = text_input("*VARIES*", &scale_text)
|
||||
.size(crate::ui::ROW_H * 0.42)
|
||||
.padding([3, 5])
|
||||
.style(move |theme: &Theme, status| {
|
||||
let scale_value = ((scale.value * 10.0).round() / 10.0).clamp(0.1, 1000.0);
|
||||
let mut input = iced_aw::number_input(
|
||||
&scale_value,
|
||||
0.1..=1000.0,
|
||||
|value: f64| {
|
||||
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);
|
||||
if continuous_linetype {
|
||||
let disabled = theme.palette().background.base.text.scale_alpha(0.42);
|
||||
@@ -996,29 +1060,26 @@ pub fn view<'a>(
|
||||
}
|
||||
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);
|
||||
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 {
|
||||
button
|
||||
input = input.on_input_maybe(None::<fn(f64) -> Message>);
|
||||
} else {
|
||||
let delta = if up { 0.1 } else { -0.1 };
|
||||
button.on_press(Message::LineLinetypeScaleChanged(
|
||||
(scale.value + delta).clamp(0.01, 1000.0),
|
||||
))
|
||||
}
|
||||
input = input.on_submit(Message::Noop);
|
||||
}
|
||||
input.into()
|
||||
};
|
||||
let scale_icon: Element<'_, Message> = if continuous_linetype {
|
||||
crate::ui::icons::themed_disabled(
|
||||
@@ -1031,28 +1092,6 @@ pub fn view<'a>(
|
||||
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)
|
||||
.align_y(iced::Center)
|
||||
|
||||
Reference in new issue
Block a user