forked from 1b-agent/opencadstudio-upstream
feat(layer-state): add saved-state editor
This commit is contained in:
1 parent
1e624cc6b7
commit
6ecd67ff6e
12 files changed
+691
-14
No files matched your search
@@ -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(¤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<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()
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
})
|
||||
|
||||
Reference in new issue
Block a user