feat(layer-state): add saved-state editor

This commit is contained in:
Hakan Seven committed 2026-07-29 18:39:55 +03:00
1 parent 1e624cc6b7
commit 6ecd67ff6e
12 files changed
+691 -14

No files matched your search

Generated
+1 -1
View File
@@ -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",
+1 -1
View File
@@ -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).
+49
View File
@@ -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<acadrust::LayerState>,
layer_state_edit_filter: String,
layer_state_edit_color_open: Option<usize>,
// ── 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<acadrust::types::Transparency>),
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(),
+223
View File
@@ -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<String> = 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()
}
+3
View File
@@ -991,6 +991,9 @@ pub(super) fn on_text_style_dialog_open(&mut self) -> Task<Message> {
_ => None,
}
}
Some(crate::app::ColorPickTarget::LayerState(idx)) => {
Some(Message::LayerStateEditorLayerColor(idx, color))
}
None => None,
};
if let Some(m) = edit {
+1
View File
@@ -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),
+41
View File
@@ -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<String> = 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)
}
+9 -2
View File
@@ -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
};
+4 -1
View File
@@ -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<String> {
+20 -4
View File
@@ -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
};
+338 -4
View File
@@ -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<acadrust::types::Transparency>);
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<acadrust::types::Transparency>) -> Vec<TransparencyItem> {
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(&current) {
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<String>,
) -> 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<usize>,
linetypes: Vec<String>,
) -> 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<String> = 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()
}
+1 -1
View File
@@ -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,
}
})