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
Generated
+1
-1
@@ -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
@@ -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).
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Vendored
+9
-2
@@ -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
@@ -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> {
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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