diff --git a/Cargo.lock b/Cargo.lock index 609c6bfd9..4d8911f49 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=5a2069a#5a2069a7abe6b440fbd3bbacb4dfff8555f082c9" +source = "git+https://github.com/OpenAEC-Foundation/acadifc.git?rev=a737c2c#a737c2c6c10d3639b6b82f19aa04ad33a52fa187" dependencies = [ "ahash 0.8.12", "anyhow", diff --git a/Cargo.toml b/Cargo.toml index 9dc1aaeb5..e9c4e5f45 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 = "5a2069a" } +acadrust = { git = "https://github.com/OpenAEC-Foundation/acadifc.git", rev = "a737c2c" } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] # Native enables the plugin host runtime (out-of-process plugins). diff --git a/src/app/commands/layers.rs b/src/app/commands/layers.rs index dcc68200c..9b7576675 100644 --- a/src/app/commands/layers.rs +++ b/src/app/commands/layers.rs @@ -212,22 +212,10 @@ impl OpenCADStudio { } // LAYERSTATE — save / restore named snapshots of all layer states - // (on/off, freeze, lock, colour, linetype, lineweight). + // in the drawing's native ACAD_LAYERSTATES dictionary. // LAYERSTATE SAVE | RESTORE | DELETE | ? (list) "LAYERSTATE" | "LAS" | "LMAN" => { - use crate::command::KeywordCommand; - let c = KeywordCommand::new( - "LAYERSTATE", - "LAYERSTATE [List / Save / Restore / Delete]:", - vec![ - ("List", "LIST", None), - ("Save", "SAVE", Some("LAYERSTATE SAVE new state name:")), - ("Restore", "RESTORE", Some("LAYERSTATE RESTORE state name:")), - ("Delete", "DELETE", Some("LAYERSTATE DELETE state name:")), - ], - ); - self.command_line.push_info(&c.prompt()); - self.tabs[i].active_cmd = Some(Box::new(c)); + return Some(Task::done(Message::LayerStateManagerOpen)); } cmd if cmd.starts_with("LAYERSTATE ") || cmd.starts_with("LAS ") @@ -243,13 +231,14 @@ impl OpenCADStudio { let arg = parts.next().unwrap_or("").trim(); match sub.as_str() { "" | "?" | "LIST" => { - let states = &self.tabs[i].layer_states; + let states = self.tabs[i].scene.document.layer_states(); if states.is_empty() { self.command_line.push_info( "LAYERSTATE: no saved states. Use LAYERSTATE SAVE .", ); } else { - let mut names: Vec<&str> = states.keys().map(|s| s.as_str()).collect(); + let mut names: Vec<&str> = + states.iter().map(|state| state.name.as_str()).collect(); names.sort_unstable(); self.command_line .push_output(&format!("Saved layer states: {}", names.join(", "))); @@ -259,7 +248,18 @@ impl OpenCADStudio { if arg.is_empty() { self.command_line.push_info("Usage: LAYERSTATE SAVE "); } else { - self.tabs[i].save_layer_state(arg); + let description = self.tabs[i] + .scene + .document + .layer_state(arg) + .map(|state| state.description) + .unwrap_or_default(); + self.push_undo_snapshot(i, "LAYERSTATE SAVE"); + self.tabs[i] + .scene + .document + .capture_layer_state(arg, description); + self.tabs[i].dirty = true; self.command_line .push_output(&format!("LAYERSTATE: saved \"{arg}\".")); } @@ -268,7 +268,7 @@ impl OpenCADStudio { if arg.is_empty() { self.command_line .push_info("Usage: LAYERSTATE RESTORE "); - } else if !self.tabs[i].layer_states.contains_key(arg) { + } else if self.tabs[i].scene.document.layer_state(arg).is_none() { self.command_line.push_error(&format!( "LAYERSTATE: no saved state named \"{arg}\"." )); @@ -280,11 +280,20 @@ impl OpenCADStudio { .iter() .map(|layer| layer.name.clone()) .collect(); - let undo = self.begin_layer_undo(i, "LAYERSTATE", &names); - let n = self.tabs[i].restore_layer_state(arg).unwrap_or(0); + self.push_undo_snapshot(i, "LAYERSTATE RESTORE"); + let n = self.tabs[i] + .scene + .document + .restore_layer_state(arg) + .unwrap_or(0); + self.tabs[i].active_layer = self.tabs[i] + .scene + .document + .header + .current_layer_name + .clone(); self.tabs[i].scene.invalidate_layer_dependencies(&names); self.tabs[i].dirty = true; - self.commit_layer_undo(i, undo); self.refresh_layer_panel(); self.command_line.push_output(&format!( "LAYERSTATE: restored \"{arg}\" ({n} layer(s))." @@ -292,13 +301,19 @@ impl OpenCADStudio { } } "DELETE" | "D" => { - if self.tabs[i].layer_states.remove(arg).is_some() { + if arg.is_empty() { self.command_line - .push_output(&format!("LAYERSTATE: deleted \"{arg}\".")); - } else { + .push_info("Usage: LAYERSTATE DELETE "); + } else if self.tabs[i].scene.document.layer_state(arg).is_none() { self.command_line.push_error(&format!( "LAYERSTATE: no saved state named \"{arg}\"." )); + } else { + self.push_undo_snapshot(i, "LAYERSTATE DELETE"); + self.tabs[i].scene.document.delete_layer_state(arg); + self.tabs[i].dirty = true; + self.command_line + .push_output(&format!("LAYERSTATE: deleted \"{arg}\".")); } } _ => { diff --git a/src/app/document.rs b/src/app/document.rs index 27cd29802..49268c3ef 100644 --- a/src/app/document.rs +++ b/src/app/document.rs @@ -102,60 +102,6 @@ fn default_role_for(component: DynComponent) -> crate::command::DynRole { // ── Per-document tab state ───────────────────────────────────────────────── -/// One layer's display state, captured and restored by LAYERSTATE. -#[derive(Clone)] -pub(super) struct LayerSnap { - pub name: String, - pub off: bool, - pub frozen: bool, - pub locked: bool, - pub color: acadrust::types::Color, - pub line_type: String, - pub line_weight: acadrust::types::LineWeight, -} - -impl DocumentTab { - /// Snapshot every layer's current display state under `name` (overwrites - /// any existing state of that name). - pub(super) fn save_layer_state(&mut self, name: &str) { - let snaps: Vec = self - .scene - .document - .layers - .iter() - .map(|l| LayerSnap { - name: l.name.clone(), - off: l.flags.off, - frozen: l.flags.frozen, - locked: l.flags.locked, - color: l.color.clone(), - line_type: l.line_type.clone(), - line_weight: l.line_weight.clone(), - }) - .collect(); - self.layer_states.insert(name.to_string(), snaps); - } - - /// Reapply the saved state `name` to the matching layers; returns the number - /// of layers updated, or `None` if no such state exists. - pub(super) fn restore_layer_state(&mut self, name: &str) -> Option { - let snaps = self.layer_states.get(name)?.clone(); - let mut applied = 0usize; - for s in &snaps { - if let Some(l) = self.scene.document.layers.get_mut(&s.name) { - l.flags.off = s.off; - l.flags.frozen = s.frozen; - l.flags.locked = s.locked; - l.color = s.color.clone(); - l.line_type = s.line_type.clone(); - l.line_weight = s.line_weight.clone(); - applied += 1; - } - } - Some(applied) - } -} - pub(super) struct DocumentTab { /// Stable identity across tab insert/remove operations. Background work /// must never target a tab by its transient vector index. @@ -221,8 +167,6 @@ pub(super) struct DocumentTab { pub(super) dyn_active: usize, pub(super) history: HistoryState, pub(super) active_layer: String, - /// Named layer-state snapshots saved by LAYERSTATE (name → per-layer state). - pub(super) layer_states: std::collections::HashMap>, /// Currently active UCS. `None` means WCS (identity transform). pub(super) active_ucs: Option, /// Custom model-space background color. `None` = default dark grey. @@ -486,7 +430,6 @@ impl DocumentTab { dyn_active: 0, history: HistoryState::default(), active_layer: "0".to_string(), - layer_states: std::collections::HashMap::new(), active_ucs: None, bg_color: None, paper_bg_color: None, diff --git a/src/app/layers.rs b/src/app/layers.rs index 81de99169..bb6c4716c 100644 --- a/src/app/layers.rs +++ b/src/app/layers.rs @@ -2,6 +2,36 @@ use super::OpenCADStudio; use crate::ui; impl OpenCADStudio { + pub(super) fn load_layer_state_editor(&mut self, selected: Option) { + let i = self.active_tab; + if let Some(name) = selected { + if let Some(state) = self.tabs[i].scene.document.layer_state(&name) { + self.layer_state_name_buf = state.name.clone(); + self.layer_state_description_buf = state.description; + self.layer_state_selected = Some(state.name); + return; + } + } + self.layer_state_selected = None; + self.layer_state_description_buf.clear(); + let names: Vec = self.tabs[i] + .scene + .document + .layer_states() + .into_iter() + .map(|state| state.name) + .collect(); + let n = (1usize..) + .find(|n| { + let candidate = format!("Layer State {n}"); + !names + .iter() + .any(|name| name.eq_ignore_ascii_case(&candidate)) + }) + .unwrap_or(1); + self.layer_state_name_buf = format!("Layer State {n}"); + } + /// Reload the `LayerPanel` cache from the document and push the /// fresh state through to the ribbon dropdown + every other /// layer-aware UI mirror. Use this whenever a command mutates the diff --git a/src/app/mod.rs b/src/app/mod.rs index c9b5cd076..108c4cf52 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -735,6 +735,12 @@ pub(super) struct OpenCADStudio { layout_manager_selected: String, layout_manager_rename_buf: String, + // ── Layer State Manager ─────────────────────────────────────────────── + layer_state_selected: Option, + layer_state_name_buf: String, + layer_state_description_buf: String, + layer_state_filter: String, + // ── Annotation-scale Manager ────────────────────────────────────────── scale_manager_selected: String, scale_manager_paper_buf: String, @@ -1317,6 +1323,7 @@ pub enum ModalKind { PluginManager, UpdateNotice, Layers, + LayerStateManager, Plot, LayoutManager, Plotstyle, @@ -1671,6 +1678,16 @@ pub enum Message { LayerLineweightSet(LineWeight), LayerTransparencyEdit(usize, String), LayerRenameCommit, + // ── Layer State Manager ───────────────────────────────────────────── + LayerStateManagerOpen, + LayerStateManagerSelect(String), + LayerStateManagerNew, + LayerStateManagerFilter(String), + LayerStateManagerName(String), + LayerStateManagerDescription(String), + LayerStateManagerSave, + LayerStateManagerRestore, + LayerStateManagerDelete, CursorMoved(Point), ViewportClick, ViewportMove(Point), @@ -2708,6 +2725,10 @@ impl OpenCADStudio { alias_editor_rows: Vec::new(), // Layout Manager layout_manager_selected: "Model".to_string(), + layer_state_selected: None, + layer_state_name_buf: String::new(), + layer_state_description_buf: String::new(), + layer_state_filter: String::new(), 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 6f4579373..5eac1ffbf 100644 --- a/src/app/update/mod.rs +++ b/src/app/update/mod.rs @@ -1009,9 +1009,25 @@ impl OpenCADStudio { } self.active_tab = idx; if self.tabs[idx].is_start - && self.active_modal == Some(super::ModalKind::LayoutManager) + && matches!( + self.active_modal, + Some( + super::ModalKind::LayoutManager + | super::ModalKind::LayerStateManager + ) + ) { self.close_active_modal(); + } else if self.active_modal == Some(super::ModalKind::LayerStateManager) { + let mut names: Vec = self.tabs[idx] + .scene + .document + .layer_states() + .into_iter() + .map(|state| state.name) + .collect(); + names.sort_by_key(|name| name.to_lowercase()); + self.load_layer_state_editor(names.into_iter().next()); } self.sync_ribbon_layers(); self.sync_ribbon_styles(); @@ -1401,6 +1417,150 @@ impl OpenCADStudio { Task::none() } + Message::LayerStateManagerOpen => { + let i = self.active_tab; + self.ribbon.close_dropdown(); + if self.tabs[i].is_start { + self.command_line + .push_info("Open or create a drawing to manage layer states."); + return Task::none(); + } + let mut names: Vec = self.tabs[i] + .scene + .document + .layer_states() + .into_iter() + .map(|state| state.name) + .collect(); + names.sort_by_key(|name| name.to_lowercase()); + self.load_layer_state_editor(names.into_iter().next()); + self.active_modal = Some(super::ModalKind::LayerStateManager); + Task::none() + } + Message::LayerStateManagerSelect(name) => { + self.load_layer_state_editor(Some(name)); + Task::none() + } + Message::LayerStateManagerNew => { + self.load_layer_state_editor(None); + Task::none() + } + Message::LayerStateManagerFilter(value) => { + self.layer_state_filter = value; + Task::none() + } + Message::LayerStateManagerName(value) => { + self.layer_state_name_buf = value; + Task::none() + } + Message::LayerStateManagerDescription(value) => { + self.layer_state_description_buf = value; + Task::none() + } + Message::LayerStateManagerSave => { + let i = self.active_tab; + let name = self.layer_state_name_buf.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(); + } + + self.push_undo_snapshot(i, "LAYERSTATE SAVE"); + 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.capture_layer_state( + &name, + self.layer_state_description_buf.trim(), + ); + self.tabs[i].dirty = true; + self.layer_state_selected = Some(name.clone()); + self.layer_state_name_buf = name.clone(); + self.command_line + .push_output(&format!("LAYERSTATE: saved \"{name}\" in the drawing.")); + Task::none() + } + Message::LayerStateManagerRestore => { + let i = self.active_tab; + let Some(name) = self.layer_state_selected.clone() else { + return Task::none(); + }; + let layer_names: Vec = self.tabs[i] + .scene + .document + .layers + .iter() + .map(|layer| layer.name.clone()) + .collect(); + self.push_undo_snapshot(i, "LAYERSTATE RESTORE"); + let restored = self.tabs[i] + .scene + .document + .restore_layer_state(&name) + .unwrap_or(0); + let active = self.tabs[i] + .scene + .document + .header + .current_layer_name + .clone(); + self.tabs[i].active_layer = active; + self.tabs[i] + .scene + .invalidate_layer_dependencies(&layer_names); + self.tabs[i].dirty = true; + self.refresh_layer_panel(); + self.command_line.push_output(&format!( + "LAYERSTATE: restored \"{name}\" ({restored} layer(s))." + )); + Task::none() + } + Message::LayerStateManagerDelete => { + let i = self.active_tab; + let Some(name) = self.layer_state_selected.clone() else { + return Task::none(); + }; + self.push_undo_snapshot(i, "LAYERSTATE DELETE"); + if self.tabs[i].scene.document.delete_layer_state(&name) { + self.tabs[i].dirty = true; + let mut names: Vec = self.tabs[i] + .scene + .document + .layer_states() + .into_iter() + .map(|state| state.name) + .collect(); + names.sort_by_key(|name| name.to_lowercase()); + self.load_layer_state_editor(names.into_iter().next()); + self.command_line + .push_output(&format!("LAYERSTATE: deleted \"{name}\".")); + } + Task::none() + } + Message::WindowCloseRequested(id) => { if self.main_window == Some(id) { if self.tabs.iter().any(|t| t.dirty) { diff --git a/src/app/view/mod.rs b/src/app/view/mod.rs index 3deb32a4d..59770b707 100644 --- a/src/app/view/mod.rs +++ b/src/app/view/mod.rs @@ -1571,6 +1571,7 @@ impl OpenCADStudio { PluginManager => (940, 600), UpdateNotice => (560, 460), Layers => (900, 360), + LayerStateManager => (720, 420), Plot => (760, 540), LayoutManager => (640, 320), Plotstyle => (780, 540), diff --git a/src/app/view/modal.rs b/src/app/view/modal.rs index d79f170e1..66af9cabc 100644 --- a/src/app/view/modal.rs +++ b/src/app/view/modal.rs @@ -15,6 +15,7 @@ impl OpenCADStudio { Some(K::PluginManager) => "Plugin Manager", Some(K::UpdateNotice) => "Update Available", Some(K::Layers) => "Layer Manager", + Some(K::LayerStateManager) => "Layer State Manager", Some(K::Plot) => "Plot", Some(K::LayoutManager) => "Layout Manager", Some(K::ScaleManager) => "Scale Manager", @@ -119,6 +120,20 @@ impl OpenCADStudio { let tab = &self.tabs[self.active_tab]; sized(tab.layers.view_window(self.layer_name_col_w), 900, 360) } + super::super::ModalKind::LayerStateManager => { + let states = self.tabs[self.active_tab].scene.document.layer_states(); + sized( + crate::ui::window::layer_state_manager::view_window( + states, + self.layer_state_selected.as_deref(), + &self.layer_state_name_buf, + &self.layer_state_description_buf, + &self.layer_state_filter, + ), + 720, + 420, + ) + } super::super::ModalKind::Plot => { sized(crate::ui::window::plot::view_window(&self.plot_dialog), 760, 540) } diff --git a/src/ui/ribbon/mod.rs b/src/ui/ribbon/mod.rs index 85190521e..b86be31ea 100644 --- a/src/ui/ribbon/mod.rs +++ b/src/ui/ribbon/mod.rs @@ -960,10 +960,33 @@ impl Ribbon { .on_input(Message::RibbonLayerFilterChanged) .size(11) .padding([4, 6]); + let state_manager = button( + row![ + crate::ui::icons::themed_arrow_right(9.0), + text("Layer State Manager…").size(11), + ] + .spacing(7) + .align_y(iced::Center), + ) + .on_press(Message::LayerStateManagerOpen) + .style(popup_row_style) + .width(Fill) + .padding([6, 10]); let panel = container( column![ container(search).padding([4, 4]), scrollable(column(rows)).height(Length::Fixed(list_h)), + container(state_manager) + .width(Fill) + .padding([3, 4]) + .style(|theme: &Theme| container::Style { + border: Border { + color: theme.extended_palette().background.neutral.color, + width: 1.0, + radius: 0.0.into(), + }, + ..Default::default() + }), ] .spacing(2), ) diff --git a/src/ui/window/layer_state_manager.rs b/src/ui/window/layer_state_manager.rs new file mode 100644 index 000000000..a836a1c80 --- /dev/null +++ b/src/ui/window/layer_state_manager.rs @@ -0,0 +1,270 @@ +//! Layer State Manager — native DWG/DXF named layer-state UI. + +use crate::app::Message; +use acadrust::{LayerState, LayerStateMask}; +use iced::widget::{button, column, container, row, scrollable, text, text_input, Space}; +use iced::{Background, Border, Element, Fill, Theme}; + +fn muted(theme: &Theme) -> iced::widget::text::Style { + iced::widget::text::Style { + color: Some( + theme + .extended_palette() + .background + .base + .text + .scale_alpha(0.65), + ), + } +} + +fn button_style(accent: bool) -> impl Fn(&Theme, button::Status) -> button::Style { + move |theme, status| { + if accent { + button::primary(theme, status) + } else { + button::secondary(theme, status) + } + } +} + +fn list_style(selected: bool) -> impl Fn(&Theme, button::Status) -> button::Style { + move |theme, status| { + if selected { + button::primary(theme, status) + } else { + button::subtle(theme, status) + } + } +} + +fn divider<'a>() -> Element<'a, Message> { + container(Space::new().width(Fill).height(1)) + .width(Fill) + .height(1) + .style(|theme: &Theme| container::Style { + background: Some(Background::Color( + theme.extended_palette().background.neutral.color, + )), + ..Default::default() + }) + .into() +} + +fn mask_summary(mask: LayerStateMask) -> String { + let properties = [ + (LayerStateMask::ON, "On/Off"), + (LayerStateMask::FROZEN, "Freeze"), + (LayerStateMask::LOCKED, "Lock"), + (LayerStateMask::PLOT, "Plot"), + (LayerStateMask::COLOR, "Color"), + (LayerStateMask::LINE_TYPE, "Linetype"), + (LayerStateMask::LINE_WEIGHT, "Lineweight"), + (LayerStateMask::PLOT_STYLE, "Plot style"), + (LayerStateMask::TRANSPARENCY, "Transparency"), + ]; + properties + .into_iter() + .filter_map(|(flag, label)| mask.contains(flag).then_some(label)) + .collect::>() + .join(", ") +} + +pub fn view_window<'a>( + states: Vec, + selected: Option<&'a str>, + name: &'a str, + description: &'a str, + filter: &'a str, +) -> Element<'a, Message> { + let selected_state = selected.and_then(|selected| { + states + .iter() + .find(|state| state.name.eq_ignore_ascii_case(selected)) + }); + let query = filter.trim().to_lowercase(); + let rows: Vec> = states + .iter() + .filter(|state| { + query.is_empty() + || state.name.to_lowercase().contains(&query) + || state.description.to_lowercase().contains(&query) + }) + .map(|state| { + let is_selected = + selected.is_some_and(|selected| state.name.eq_ignore_ascii_case(selected)); + let subtitle = if state.description.is_empty() { + format!("{} layers", state.layers.len()) + } else { + state.description.clone() + }; + button( + column![ + text(state.name.clone()).size(12), + text(subtitle).size(10).style(muted), + ] + .spacing(2), + ) + .on_press(Message::LayerStateManagerSelect(state.name.clone())) + .style(list_style(is_selected)) + .padding([6, 9]) + .width(Fill) + .into() + }) + .collect(); + + let empty: Element<'_, Message> = container( + column![ + text(if states.is_empty() { + "No layer states in this drawing" + } else { + "No matching layer states" + }) + .size(11) + .style(muted), + text("Choose New to capture the current layer settings.") + .size(10) + .style(muted), + ] + .spacing(4), + ) + .padding(14) + .into(); + + let state_list: Element<'_, Message> = if rows.is_empty() { + empty + } else { + scrollable(column(rows).spacing(2)).height(Fill).into() + }; + + let left = container( + column![ + text_input("Search layer states…", filter) + .on_input(Message::LayerStateManagerFilter) + .size(11) + .padding([5, 8]), + container(state_list) + .width(Fill) + .height(Fill) + .padding(3) + .style(|theme: &Theme| container::Style { + border: Border { + color: theme.extended_palette().background.neutral.color, + width: 1.0, + radius: 3.0.into(), + }, + ..Default::default() + }), + ] + .spacing(8) + .height(Fill), + ) + .width(280) + .height(Fill) + .padding(iced::Padding { + top: 12.0, + right: 8.0, + bottom: 12.0, + left: 12.0, + }); + + let details = if let Some(state) = selected_state { + column![ + text("Saved state details").size(13), + row![ + text("Layers").size(10).style(muted).width(92), + text(state.layers.len().to_string()).size(11), + ] + .spacing(8), + row![ + text("Current layer").size(10).style(muted).width(92), + text(if state.current_layer.is_empty() { + "—".to_string() + } else { + state.current_layer.clone() + }) + .size(11), + ] + .spacing(8), + text("Restored properties").size(10).style(muted), + text(mask_summary(state.mask)).size(11), + ] + .spacing(7) + } else { + column![ + text("New layer state").size(13), + text("Save captures the current settings of every layer in the drawing.") + .size(11) + .style(muted), + ] + .spacing(7) + }; + + let restore = if selected_state.is_some() { + button(text("Restore").size(11)) + .on_press(Message::LayerStateManagerRestore) + .style(button_style(true)) + } else { + button(text("Restore").size(11)).style(button_style(true)) + }; + let delete = if selected_state.is_some() { + button(text("Delete").size(11)) + .on_press(Message::LayerStateManagerDelete) + .style(button::danger) + } else { + button(text("Delete").size(11)).style(button::danger) + }; + + let right = container( + column![ + row![ + button(text("New").size(11)) + .on_press(Message::LayerStateManagerNew) + .style(button_style(false)) + .padding([5, 12]), + Space::new().width(Fill), + restore.padding([5, 12]), + delete.padding([5, 12]), + ] + .spacing(6) + .align_y(iced::Center), + divider(), + text("Name").size(10).style(muted), + text_input("Layer state name", name) + .on_input(Message::LayerStateManagerName) + .on_submit(Message::LayerStateManagerSave) + .size(11) + .padding([5, 8]), + text("Description").size(10).style(muted), + text_input("Optional description", description) + .on_input(Message::LayerStateManagerDescription) + .size(11) + .padding([5, 8]), + Space::new().height(8), + details, + Space::new().height(Fill), + text("Layer states are stored inside the drawing and remain available after reopening it.") + .size(10) + .style(muted), + button(text(if selected_state.is_some() { + "Save / Update" + } else { + "Save New State" + }) + .size(11)) + .on_press(Message::LayerStateManagerSave) + .style(button_style(true)) + .padding([6, 14]), + ] + .spacing(7) + .height(Fill), + ) + .width(Fill) + .height(Fill) + .padding([12, 12]); + + container(row![left, right].height(Fill)) + .width(Fill) + .height(Fill) + .into() +} diff --git a/src/ui/window/mod.rs b/src/ui/window/mod.rs index 205e81a13..530aff47b 100644 --- a/src/ui/window/mod.rs +++ b/src/ui/window/mod.rs @@ -1,5 +1,6 @@ pub mod about; pub mod layout_manager; +pub mod layer_state_manager; pub mod plot; pub mod plugin_manager; pub mod shortcuts;