diff --git a/Cargo.lock b/Cargo.lock index 4d8911f49..e1e36f36f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -78,7 +78,7 @@ checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618" [[package]] name = "acadrust" version = "0.4.0" -source = "git+https://github.com/OpenAEC-Foundation/acadifc.git?rev=a737c2c#a737c2c6c10d3639b6b82f19aa04ad33a52fa187" +source = "git+https://github.com/OpenAEC-Foundation/acadifc.git?rev=a7b1e07#a7b1e07f6b3884b139684b763a8a86f27705eb70" dependencies = [ "ahash 0.8.12", "anyhow", diff --git a/Cargo.toml b/Cargo.toml index e9c4e5f45..2e87611bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -98,7 +98,7 @@ windows-sys = { version = "0.61", features = ["Win32_UI_Shell", "Win32_UI_Window [patch.crates-io] # Track standard DWG object relationships used by the scene integration. -acadrust = { git = "https://github.com/OpenAEC-Foundation/acadifc.git", rev = "a737c2c" } +acadrust = { git = "https://github.com/OpenAEC-Foundation/acadifc.git", rev = "a7b1e07" } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] # Native enables the plugin host runtime (out-of-process plugins). diff --git a/src/app/mod.rs b/src/app/mod.rs index 108c4cf52..cfb3956e9 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -740,6 +740,9 @@ pub(super) struct OpenCADStudio { layer_state_name_buf: String, layer_state_description_buf: String, layer_state_filter: String, + layer_state_edit_draft: Option, + layer_state_edit_filter: String, + layer_state_edit_color_open: Option, // ── Annotation-scale Manager ────────────────────────────────────────── scale_manager_selected: String, @@ -1003,6 +1006,8 @@ pub enum ColorPickTarget { Ribbon, /// A layer's colour, by panel row index. Layer(usize), + /// A saved layer-state layer's colour, by editor row index. + LayerState(usize), /// The MText editor's selection (or global) colour. MText, } @@ -1324,6 +1329,7 @@ pub enum ModalKind { UpdateNotice, Layers, LayerStateManager, + LayerStateEditor, Plot, LayoutManager, Plotstyle, @@ -1352,6 +1358,31 @@ pub enum ModalKind { AnnoObjectScale, } +/// A property group controlled by a layer state's restore mask. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum LayerStateProperty { + On, + Frozen, + Locked, + Plot, + NewViewport, + Color, + LineType, + LineWeight, + PlotStyle, + Transparency, +} + +/// A boolean saved for one layer inside a named layer state. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum LayerStateLayerFlag { + On, + Frozen, + Locked, + Plot, + NewViewport, +} + /// Identifies a DimStyle field that can be edited in the dialog. #[derive(Debug, Clone, PartialEq, Eq)] pub enum DsField { @@ -1688,6 +1719,21 @@ pub enum Message { LayerStateManagerSave, LayerStateManagerRestore, LayerStateManagerDelete, + LayerStateManagerEdit, + LayerStateEditorMaskToggle(LayerStateProperty), + LayerStateEditorLayerFlagToggle(usize, LayerStateLayerFlag), + LayerStateEditorLayerColorToggle(usize), + LayerStateEditorLayerColor(usize, acadrust::types::Color), + LayerStateEditorLayerLinetype(usize, String), + LayerStateEditorLayerLineweight(usize, LineWeight), + LayerStateEditorLayerPlotStyle(usize, String), + LayerStateEditorLayerTransparency(usize, Option), + LayerStateEditorName(String), + LayerStateEditorDescription(String), + LayerStateEditorCurrentLayer(String), + LayerStateEditorFilter(String), + LayerStateEditorSave, + LayerStateEditorCancel, CursorMoved(Point), ViewportClick, ViewportMove(Point), @@ -2729,6 +2775,9 @@ impl OpenCADStudio { layer_state_name_buf: String::new(), layer_state_description_buf: String::new(), layer_state_filter: String::new(), + layer_state_edit_draft: None, + layer_state_edit_filter: String::new(), + layer_state_edit_color_open: None, scale_manager_selected: String::new(), scale_manager_paper_buf: String::new(), scale_manager_drawing_buf: String::new(), diff --git a/src/app/update/mod.rs b/src/app/update/mod.rs index 5eac1ffbf..8dfc046cf 100644 --- a/src/app/update/mod.rs +++ b/src/app/update/mod.rs @@ -136,6 +136,11 @@ impl OpenCADStudio { Some(Aliases) => { self.alias_editor_rows.clear(); } + Some(LayerStateEditor) => { + self.layer_state_edit_draft = None; + self.layer_state_edit_filter.clear(); + self.layer_state_edit_color_open = None; + } _ => {} } // The tool that opened this dialog is done with it now. Keep the @@ -1014,10 +1019,13 @@ impl OpenCADStudio { Some( super::ModalKind::LayoutManager | super::ModalKind::LayerStateManager + | super::ModalKind::LayerStateEditor ) ) { self.close_active_modal(); + } else if self.active_modal == Some(super::ModalKind::LayerStateEditor) { + self.close_active_modal(); } else if self.active_modal == Some(super::ModalKind::LayerStateManager) { let mut names: Vec = self.tabs[idx] .scene @@ -1560,6 +1568,211 @@ impl OpenCADStudio { } Task::none() } + Message::LayerStateManagerEdit => { + let i = self.active_tab; + let Some(name) = self.layer_state_selected.clone() else { + return Task::none(); + }; + let Some(state) = self.tabs[i].scene.document.layer_state(&name) else { + self.command_line + .push_error(&format!("Layer state \"{name}\" was not found.")); + return Task::none(); + }; + self.layer_state_edit_draft = Some(state); + self.layer_state_edit_filter.clear(); + self.layer_state_edit_color_open = None; + self.active_modal = Some(super::ModalKind::LayerStateEditor); + Task::none() + } + Message::LayerStateEditorMaskToggle(property) => { + let flag = match property { + super::LayerStateProperty::On => acadrust::LayerStateMask::ON, + super::LayerStateProperty::Frozen => acadrust::LayerStateMask::FROZEN, + super::LayerStateProperty::Locked => acadrust::LayerStateMask::LOCKED, + super::LayerStateProperty::Plot => acadrust::LayerStateMask::PLOT, + super::LayerStateProperty::NewViewport => { + acadrust::LayerStateMask::NEW_VIEWPORT + } + super::LayerStateProperty::Color => acadrust::LayerStateMask::COLOR, + super::LayerStateProperty::LineType => acadrust::LayerStateMask::LINE_TYPE, + super::LayerStateProperty::LineWeight => { + acadrust::LayerStateMask::LINE_WEIGHT + } + super::LayerStateProperty::PlotStyle => acadrust::LayerStateMask::PLOT_STYLE, + super::LayerStateProperty::Transparency => { + acadrust::LayerStateMask::TRANSPARENCY + } + }; + if let Some(state) = self.layer_state_edit_draft.as_mut() { + state.mask = + acadrust::LayerStateMask::from_bits(state.mask.bits() ^ flag.bits()); + } + Task::none() + } + Message::LayerStateEditorLayerFlagToggle(index, flag) => { + let Some(layer) = self + .layer_state_edit_draft + .as_mut() + .and_then(|state| state.layers.get_mut(index)) + else { + return Task::none(); + }; + match flag { + super::LayerStateLayerFlag::On => layer.off = !layer.off, + super::LayerStateLayerFlag::Frozen => layer.frozen = !layer.frozen, + super::LayerStateLayerFlag::Locked => layer.locked = !layer.locked, + super::LayerStateLayerFlag::Plot => layer.plottable = !layer.plottable, + super::LayerStateLayerFlag::NewViewport => { + layer.new_viewport_frozen = !layer.new_viewport_frozen + } + } + Task::none() + } + Message::LayerStateEditorLayerColorToggle(index) => { + self.layer_state_edit_color_open = if self.layer_state_edit_color_open == Some(index) + { + None + } else { + Some(index) + }; + Task::none() + } + Message::LayerStateEditorLayerColor(index, color) => { + if let Some(layer) = self + .layer_state_edit_draft + .as_mut() + .and_then(|state| state.layers.get_mut(index)) + { + layer.color = color; + } + self.layer_state_edit_color_open = None; + Task::none() + } + Message::LayerStateEditorLayerLinetype(index, value) => { + if let Some(layer) = self + .layer_state_edit_draft + .as_mut() + .and_then(|state| state.layers.get_mut(index)) + { + layer.line_type = value; + } + Task::none() + } + Message::LayerStateEditorLayerLineweight(index, value) => { + if let Some(layer) = self + .layer_state_edit_draft + .as_mut() + .and_then(|state| state.layers.get_mut(index)) + { + layer.line_weight = value; + } + Task::none() + } + Message::LayerStateEditorLayerPlotStyle(index, value) => { + if let Some(layer) = self + .layer_state_edit_draft + .as_mut() + .and_then(|state| state.layers.get_mut(index)) + { + layer.plot_style = value; + } + Task::none() + } + Message::LayerStateEditorLayerTransparency(index, value) => { + if let Some(layer) = self + .layer_state_edit_draft + .as_mut() + .and_then(|state| state.layers.get_mut(index)) + { + layer.transparency = value; + } + Task::none() + } + Message::LayerStateEditorName(value) => { + if let Some(state) = self.layer_state_edit_draft.as_mut() { + state.name = value; + } + Task::none() + } + Message::LayerStateEditorDescription(value) => { + if let Some(state) = self.layer_state_edit_draft.as_mut() { + state.description = value; + } + Task::none() + } + Message::LayerStateEditorCurrentLayer(value) => { + if let Some(state) = self.layer_state_edit_draft.as_mut() { + state.current_layer = value; + } + Task::none() + } + Message::LayerStateEditorFilter(value) => { + self.layer_state_edit_filter = value; + Task::none() + } + Message::LayerStateEditorSave => { + let i = self.active_tab; + let Some(draft) = self.layer_state_edit_draft.as_ref() else { + return Task::none(); + }; + let name = draft.name.trim().to_string(); + if name.is_empty() { + self.command_line + .push_error("Layer state name cannot be empty."); + return Task::none(); + } + let old_name = self.layer_state_selected.clone(); + let duplicate = self.tabs[i] + .scene + .document + .layer_states() + .into_iter() + .any(|state| { + state.name.eq_ignore_ascii_case(&name) + && old_name + .as_deref() + .is_none_or(|old| !state.name.eq_ignore_ascii_case(old)) + }); + if duplicate { + self.command_line + .push_error(&format!("Layer state \"{name}\" already exists.")); + return Task::none(); + } + let Some(state) = self.layer_state_edit_draft.take() else { + return Task::none(); + }; + let mut state = state; + state.name.clone_from(&name); + state.description = state.description.trim().to_string(); + let description = state.description.clone(); + self.push_undo_snapshot(i, "LAYERSTATE EDIT"); + if let Some(old_name) = old_name.as_deref() { + if !old_name.eq_ignore_ascii_case(&name) { + self.tabs[i] + .scene + .document + .rename_layer_state(old_name, &name); + } + } + self.tabs[i].scene.document.store_layer_state(state); + self.tabs[i].dirty = true; + self.layer_state_selected = Some(name.clone()); + self.layer_state_name_buf = name.clone(); + self.layer_state_description_buf = description; + self.layer_state_edit_filter.clear(); + self.layer_state_edit_color_open = None; + self.active_modal = Some(super::ModalKind::LayerStateManager); + self.command_line + .push_output(&format!("LAYERSTATE: updated \"{name}\".")); + Task::none() + } + Message::LayerStateEditorCancel => { + self.layer_state_edit_draft = None; + self.layer_state_edit_filter.clear(); + self.layer_state_edit_color_open = None; + self.active_modal = Some(super::ModalKind::LayerStateManager); + Task::none() + } Message::WindowCloseRequested(id) => { if self.main_window == Some(id) { @@ -1867,12 +2080,22 @@ impl OpenCADStudio { if let Some(v) = val { let targets = self.layer_row_action_targets(i, idx); for name in &targets { + if let Some(layer) = + self.tabs[i].scene.document.layers.get_mut(name) + { + layer.transparency = + acadrust::types::Transparency::from_percent(v as f64 / 100.0); + } if let Some(pl) = self.tabs[i].layers.layers.iter_mut().find(|l| &l.name == name) { pl.transparency = v; } } + if !targets.is_empty() { + self.tabs[i].scene.invalidate_layer_dependencies(&targets); + self.tabs[i].dirty = true; + } } Task::none() } diff --git a/src/app/update/style.rs b/src/app/update/style.rs index 90bd1ac0f..057b272b6 100644 --- a/src/app/update/style.rs +++ b/src/app/update/style.rs @@ -991,6 +991,9 @@ pub(super) fn on_text_style_dialog_open(&mut self) -> Task { _ => None, } } + Some(crate::app::ColorPickTarget::LayerState(idx)) => { + Some(Message::LayerStateEditorLayerColor(idx, color)) + } None => None, }; if let Some(m) = edit { diff --git a/src/app/view/mod.rs b/src/app/view/mod.rs index 59770b707..8de8da68e 100644 --- a/src/app/view/mod.rs +++ b/src/app/view/mod.rs @@ -1572,6 +1572,7 @@ impl OpenCADStudio { UpdateNotice => (560, 460), Layers => (900, 360), LayerStateManager => (720, 420), + LayerStateEditor => (1180, 560), Plot => (760, 540), LayoutManager => (640, 320), Plotstyle => (780, 540), diff --git a/src/app/view/modal.rs b/src/app/view/modal.rs index 66af9cabc..0f1981fee 100644 --- a/src/app/view/modal.rs +++ b/src/app/view/modal.rs @@ -16,6 +16,7 @@ impl OpenCADStudio { Some(K::UpdateNotice) => "Update Available", Some(K::Layers) => "Layer Manager", Some(K::LayerStateManager) => "Layer State Manager", + Some(K::LayerStateEditor) => "Edit Layer State", Some(K::Plot) => "Plot", Some(K::LayoutManager) => "Layout Manager", Some(K::ScaleManager) => "Scale Manager", @@ -134,6 +135,46 @@ impl OpenCADStudio { 420, ) } + super::super::ModalKind::LayerStateEditor => { + let tab = &self.tabs[self.active_tab]; + if let Some(state) = self.layer_state_edit_draft.as_ref() { + let mut linetypes: Vec = tab + .scene + .document + .line_types + .iter() + .map(|line_type| line_type.name.clone()) + .collect(); + for layer in &state.layers { + if !layer.line_type.is_empty() + && !linetypes + .iter() + .any(|name| name.eq_ignore_ascii_case(&layer.line_type)) + { + linetypes.push(layer.line_type.clone()); + } + } + linetypes.sort_by_key(|name| name.to_lowercase()); + sized( + crate::ui::window::layer_state_manager::view_editor( + state, + &self.layer_state_edit_filter, + self.layer_state_edit_color_open, + linetypes, + ), + 1180, + 560, + ) + } else { + sized( + container(text("The selected layer state is no longer available.")) + .padding(16) + .into(), + 520, + 180, + ) + } + } super::super::ModalKind::Plot => { sized(crate::ui::window::plot::view_window(&self.plot_dialog), 760, 540) } diff --git a/src/scene/cache/block_cache.rs b/src/scene/cache/block_cache.rs index 627827a06..e5f414ebd 100644 --- a/src/scene/cache/block_cache.rs +++ b/src/scene/cache/block_cache.rs @@ -78,6 +78,7 @@ pub struct LocalWire { /// *layer* (the layer-0 inheritance rule) instead of the cached layer-0 /// value baked here. pub color_l0: bool, + pub transparency_l0: bool, pub lt_l0: bool, pub lw_l0: bool, /// XY bounding box of this wire in block-local coordinates. @@ -641,6 +642,7 @@ fn tessellate_sub_local( let on_l0 = crate::scene::view::render::is_effective_layer_zero(&sub.common().layer); let color_l0 = !has_book_color && on_l0 && sub.common().color == AcadColor::ByLayer; + let transparency_l0 = on_l0 && sub.common().transparency.alpha() == 0; let lt_l0 = on_l0 && { let lt = &sub.common().linetype; lt.is_empty() || lt.eq_ignore_ascii_case("bylayer") @@ -719,6 +721,7 @@ fn tessellate_sub_local( lt_is_byblock, lw_is_byblock, color_l0: color_l0 && wire_on_base_color, + transparency_l0: transparency_l0 && wire_on_base_color, lt_l0, lw_l0, aabb_local, @@ -1542,8 +1545,12 @@ fn resolve_wire_color(lw: &LocalWire, ctx: &ExpandCtx) -> [f32; 4] { } else if lw.color_is_byblock { ctx.ins_color } else if lw.color_l0 { - // Inherit the insert layer's RGB but keep the child's own transparency. - [ctx.l0.color[0], ctx.l0.color[1], ctx.l0.color[2], lw.color[3]] + let alpha = if lw.transparency_l0 { + ctx.l0.color[3] + } else { + lw.color[3] + }; + [ctx.l0.color[0], ctx.l0.color[1], ctx.l0.color[2], alpha] } else { lw.color }; diff --git a/src/scene/entity.rs b/src/scene/entity.rs index 243256571..7d5042472 100644 --- a/src/scene/entity.rs +++ b/src/scene/entity.rs @@ -588,7 +588,10 @@ impl Scene { .map(|l| &l.color) .unwrap_or(&acadrust::types::Color::WHITE); let [r, g, b, _] = crate::scene::convert::tess_util::aci_to_rgba(color); - [r, g, b, 1.0] + let alpha = layer_entry + .map(|layer| 1.0 - layer.transparency.as_percent() as f32) + .unwrap_or(1.0); + [r, g, b, alpha] } pub fn custom_block_names(&self) -> Vec { diff --git a/src/scene/view/render.rs b/src/scene/view/render.rs index 560a7512d..43bef938a 100644 --- a/src/scene/view/render.rs +++ b/src/scene/view/render.rs @@ -1575,7 +1575,16 @@ pub(in crate::scene) fn render_style_for( _ => 0, }; let [r, g, b, _] = tess_util::aci_to_rgba(resolved); - let alpha = 1.0 - e.common().transparency.as_percent() as f32; + let transparency = if common.transparency.alpha() == 0 { + document + .layers + .get(layer_name) + .map(|layer| layer.transparency) + .unwrap_or(common.transparency) + } else { + common.transparency + }; + let alpha = 1.0 - transparency.as_percent() as f32; ([r, g, b, alpha], aci) }; @@ -1648,12 +1657,15 @@ pub(crate) fn layer_render_style(document: &CadDocument, layer_name: &str) -> In let layer = document.layers.get(layer_name); let color = layer.map(|l| &l.color).unwrap_or(&AcadColor::WHITE); let [r, g, b, _] = tess_util::aci_to_rgba(color); + let alpha = layer + .map(|layer| 1.0 - layer.transparency.as_percent() as f32) + .unwrap_or(1.0); let lt_name = layer.map(|l| l.line_type.as_str()).unwrap_or("Continuous"); let lt_scale = document.header.linetype_scale as f32; let (pat_len, pat) = resolve_pattern(&document.line_types, lt_name, lt_scale); let lw = layer.map(|l| &l.line_weight).unwrap_or(&LineWeight::Default); InheritStyle { - color: [r, g, b, 1.0], + color: [r, g, b, alpha], pat_len, pat, lw_px: lineweight_to_px(lw), @@ -1694,8 +1706,12 @@ pub(crate) fn render_style_for_block_sub( let final_color = if !has_book_color && common.color == AcadColor::ByBlock { insert_color } else if !has_book_color && on_l0 && common.color == AcadColor::ByLayer { - // Inherit the insert layer's RGB but keep the child's own transparency. - [l0.color[0], l0.color[1], l0.color[2], color[3]] + let alpha = if common.transparency.alpha() == 0 { + l0.color[3] + } else { + color[3] + }; + [l0.color[0], l0.color[1], l0.color[2], alpha] } else { color }; diff --git a/src/ui/window/layer_state_manager.rs b/src/ui/window/layer_state_manager.rs index a836a1c80..04c2543f2 100644 --- a/src/ui/window/layer_state_manager.rs +++ b/src/ui/window/layer_state_manager.rs @@ -1,9 +1,39 @@ //! Layer State Manager — native DWG/DXF named layer-state UI. -use crate::app::Message; +use crate::app::{LayerStateLayerFlag, LayerStateProperty, Message}; +use crate::ui::properties::{lw_options, LwItem}; use acadrust::{LayerState, LayerStateMask}; -use iced::widget::{button, column, container, row, scrollable, text, text_input, Space}; -use iced::{Background, Border, Element, Fill, Theme}; +use iced::widget::{ + button, checkbox, column, container, pick_list, row, scrollable, text, text_input, Space, +}; +use iced::{Background, Border, Element, Fill, Length, Theme}; +use std::fmt; + +#[derive(Clone, PartialEq, Debug)] +struct TransparencyItem(Option); + +impl fmt::Display for TransparencyItem { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.0 { + None => write!(f, "Not set"), + Some(value) => write!(f, "{}%", (value.as_percent() * 100.0).round() as u8), + } + } +} + +fn transparency_options(current: Option) -> Vec { + let mut options = vec![TransparencyItem(None)]; + for percent in (0..=90).step_by(10) { + options.push(TransparencyItem(Some( + acadrust::types::Transparency::from_percent(percent as f64 / 100.0), + ))); + } + let current = TransparencyItem(current); + if !options.contains(¤t) { + options.insert(1, current); + } + options +} fn muted(theme: &Theme) -> iced::widget::text::Style { iced::widget::text::Style { @@ -57,6 +87,7 @@ fn mask_summary(mask: LayerStateMask) -> String { (LayerStateMask::FROZEN, "Freeze"), (LayerStateMask::LOCKED, "Lock"), (LayerStateMask::PLOT, "Plot"), + (LayerStateMask::NEW_VIEWPORT, "New VP Freeze"), (LayerStateMask::COLOR, "Color"), (LayerStateMask::LINE_TYPE, "Linetype"), (LayerStateMask::LINE_WEIGHT, "Lineweight"), @@ -214,6 +245,13 @@ pub fn view_window<'a>( } else { button(text("Delete").size(11)).style(button::danger) }; + let edit = if selected_state.is_some() { + button(text("Edit").size(11)) + .on_press(Message::LayerStateManagerEdit) + .style(button_style(false)) + } else { + button(text("Edit").size(11)).style(button_style(false)) + }; let right = container( column![ @@ -222,6 +260,7 @@ pub fn view_window<'a>( .on_press(Message::LayerStateManagerNew) .style(button_style(false)) .padding([5, 12]), + edit.padding([5, 12]), Space::new().width(Fill), restore.padding([5, 12]), delete.padding([5, 12]), @@ -247,7 +286,7 @@ pub fn view_window<'a>( .size(10) .style(muted), button(text(if selected_state.is_some() { - "Save / Update" + "Update from Drawing" } else { "Save New State" }) @@ -268,3 +307,298 @@ pub fn view_window<'a>( .height(Fill) .into() } + +fn mask_for(property: LayerStateProperty) -> LayerStateMask { + match property { + LayerStateProperty::On => LayerStateMask::ON, + LayerStateProperty::Frozen => LayerStateMask::FROZEN, + LayerStateProperty::Locked => LayerStateMask::LOCKED, + LayerStateProperty::Plot => LayerStateMask::PLOT, + LayerStateProperty::NewViewport => LayerStateMask::NEW_VIEWPORT, + LayerStateProperty::Color => LayerStateMask::COLOR, + LayerStateProperty::LineType => LayerStateMask::LINE_TYPE, + LayerStateProperty::LineWeight => LayerStateMask::LINE_WEIGHT, + LayerStateProperty::PlotStyle => LayerStateMask::PLOT_STYLE, + LayerStateProperty::Transparency => LayerStateMask::TRANSPARENCY, + } +} + +fn mask_button<'a>( + state: &LayerState, + label: &'a str, + property: LayerStateProperty, +) -> Element<'a, Message> { + let enabled = state.mask.contains(mask_for(property)); + button( + row![ + text(if enabled { "✓" } else { " " }).size(10), + text(label).size(10), + ] + .spacing(4) + .align_y(iced::Center), + ) + .on_press(Message::LayerStateEditorMaskToggle(property)) + .style(list_style(enabled)) + .padding([4, 7]) + .into() +} + +fn bool_cell<'a>( + value: bool, + index: usize, + flag: LayerStateLayerFlag, + width: f32, +) -> Element<'a, Message> { + container( + checkbox(value) + .on_toggle(move |_| Message::LayerStateEditorLayerFlagToggle(index, flag)) + .size(14), + ) + .center_x(Length::Fixed(width)) + .width(Length::Fixed(width)) + .into() +} + +fn editor_header<'a>() -> Element<'a, Message> { + container( + row![ + text("Layer").size(10).style(muted).width(Length::Fixed(170.0)), + text("On").size(10).style(muted).width(Length::Fixed(44.0)), + text("Freeze").size(10).style(muted).width(Length::Fixed(54.0)), + text("Lock").size(10).style(muted).width(Length::Fixed(44.0)), + text("Plot").size(10).style(muted).width(Length::Fixed(44.0)), + text("New VP").size(10).style(muted).width(Length::Fixed(54.0)), + text("Color").size(10).style(muted).width(Length::Fixed(135.0)), + text("Linetype").size(10).style(muted).width(Length::Fixed(150.0)), + text("Lineweight").size(10).style(muted).width(Length::Fixed(115.0)), + text("Plot style").size(10).style(muted).width(Length::Fixed(135.0)), + text("Transparency").size(10).style(muted).width(Length::Fixed(105.0)), + ] + .spacing(4) + .align_y(iced::Center), + ) + .padding([5, 8]) + .style(|theme: &Theme| container::Style { + background: Some(Background::Color( + theme.extended_palette().background.weak.color, + )), + border: Border { + color: theme.extended_palette().background.neutral.color, + width: 1.0, + radius: 0.0.into(), + }, + ..Default::default() + }) + .width(Fill) + .into() +} + +fn editor_layer_row<'a>( + index: usize, + layer: &'a acadrust::LayerStateLayer, + color_open: bool, + linetypes: Vec, +) -> Element<'a, Message> { + let current_linetype = Some(layer.line_type.clone()); + let current_lineweight = Some(LwItem(layer.line_weight)); + let color = crate::ui::color_select::color_selector( + layer.color, + color_open, + crate::ui::color_select::ColorExtras { + by_layer: false, + by_block: false, + }, + move |color| Message::LayerStateEditorLayerColor(index, color), + Message::LayerStateEditorLayerColorToggle(index), + Message::OpenColorWindow(crate::app::ColorPickTarget::LayerState(index)), + ); + + container( + row![ + text(layer.layer_name.as_str()) + .size(11) + .width(Length::Fixed(170.0)), + bool_cell(!layer.off, index, LayerStateLayerFlag::On, 44.0), + bool_cell(layer.frozen, index, LayerStateLayerFlag::Frozen, 54.0), + bool_cell(layer.locked, index, LayerStateLayerFlag::Locked, 44.0), + bool_cell(layer.plottable, index, LayerStateLayerFlag::Plot, 44.0), + bool_cell( + layer.new_viewport_frozen, + index, + LayerStateLayerFlag::NewViewport, + 54.0 + ), + container(color).width(Length::Fixed(135.0)), + pick_list(linetypes, current_linetype, move |value| { + Message::LayerStateEditorLayerLinetype(index, value) + }) + .text_size(11) + .padding([3, 5]) + .width(Length::Fixed(150.0)), + pick_list(lw_options(), current_lineweight, move |item: LwItem| { + Message::LayerStateEditorLayerLineweight(index, item.0) + }) + .text_size(11) + .padding([3, 5]) + .width(Length::Fixed(115.0)), + text_input("Default", &layer.plot_style) + .on_input(move |value| Message::LayerStateEditorLayerPlotStyle(index, value)) + .size(11) + .padding([3, 5]) + .width(Length::Fixed(135.0)), + pick_list( + transparency_options(layer.transparency), + Some(TransparencyItem(layer.transparency)), + move |item| Message::LayerStateEditorLayerTransparency(index, item.0), + ) + .text_size(11) + .padding([3, 5]) + .width(Length::Fixed(105.0)), + ] + .spacing(4) + .align_y(iced::Center), + ) + .padding([3, 8]) + .style(move |theme: &Theme| container::Style { + background: (index % 2 == 1).then_some(Background::Color( + theme.extended_palette().background.weak.color, + )), + ..Default::default() + }) + .width(Fill) + .into() +} + +pub fn view_editor<'a>( + state: &'a LayerState, + filter: &'a str, + color_open: Option, + linetypes: Vec, +) -> Element<'a, Message> { + let properties = [ + ("On / Off", LayerStateProperty::On), + ("Freeze", LayerStateProperty::Frozen), + ("Lock", LayerStateProperty::Locked), + ("Plot", LayerStateProperty::Plot), + ("New VP", LayerStateProperty::NewViewport), + ("Color", LayerStateProperty::Color), + ("Linetype", LayerStateProperty::LineType), + ("Lineweight", LayerStateProperty::LineWeight), + ("Plot style", LayerStateProperty::PlotStyle), + ("Transparency", LayerStateProperty::Transparency), + ]; + let mask_controls = properties + .into_iter() + .fold(row![].spacing(5), |row, (label, property)| { + row.push(mask_button(state, label, property)) + }); + + let layer_names: Vec = state + .layers + .iter() + .map(|layer| layer.layer_name.clone()) + .collect(); + let query = filter.trim().to_lowercase(); + let rows = state + .layers + .iter() + .enumerate() + .filter(|(_, layer)| { + query.is_empty() || layer.layer_name.to_lowercase().contains(&query) + }) + .fold(column![].spacing(0), |rows, (index, layer)| { + rows.push(editor_layer_row( + index, + layer, + color_open == Some(index), + linetypes.clone(), + )) + }); + + container( + column![ + row![ + text("Name").size(10).style(muted), + text_input("Layer state name", &state.name) + .on_input(Message::LayerStateEditorName) + .size(11) + .padding([3, 6]) + .width(Length::Fixed(220.0)), + text("Description").size(10).style(muted), + text_input("Optional description", &state.description) + .on_input(Message::LayerStateEditorDescription) + .size(11) + .padding([3, 6]) + .width(Fill), + ] + .spacing(8) + .align_y(iced::Center), + row![ + text(format!("{} saved layers", state.layers.len())) + .size(10) + .style(muted), + Space::new().width(Fill), + text("Current layer").size(10).style(muted), + pick_list( + layer_names, + Some(state.current_layer.clone()), + Message::LayerStateEditorCurrentLayer, + ) + .text_size(11) + .padding([3, 6]) + .width(Length::Fixed(180.0)), + ] + .spacing(8) + .align_y(iced::Center), + divider(), + text("Properties restored by this state").size(10).style(muted), + mask_controls, + row![ + text("Saved layer values").size(12), + Space::new().width(Fill), + text_input("Search layers…", filter) + .on_input(Message::LayerStateEditorFilter) + .size(11) + .padding([4, 7]) + .width(Length::Fixed(190.0)), + ] + .align_y(iced::Center), + container( + column![editor_header(), scrollable(rows).height(Fill)] + .spacing(0) + .height(Fill), + ) + .height(Fill) + .style(|theme: &Theme| container::Style { + border: Border { + color: theme.extended_palette().background.neutral.color, + width: 1.0, + radius: 3.0.into(), + }, + ..Default::default() + }), + row![ + text("Changes affect the saved state only; the drawing is unchanged until Restore.") + .size(10) + .style(muted), + Space::new().width(Fill), + button(text("Cancel").size(11)) + .on_press(Message::LayerStateEditorCancel) + .style(button_style(false)) + .padding([5, 12]), + button(text("Save Changes").size(11)) + .on_press(Message::LayerStateEditorSave) + .style(button_style(true)) + .padding([5, 12]), + ] + .spacing(7) + .align_y(iced::Center), + ] + .spacing(8) + .height(Fill), + ) + .padding(12) + .width(Fill) + .height(Fill) + .into() +} diff --git a/src/ui/window/layers.rs b/src/ui/window/layers.rs index 40583c844..1f2994887 100644 --- a/src/ui/window/layers.rs +++ b/src/ui/window/layers.rs @@ -219,7 +219,7 @@ impl LayerPanel { l.line_type.clone() }, lineweight: l.line_weight, - transparency: 0, + transparency: (l.transparency.as_percent() * 100.0).round() as i32, vp_frozen, } })