feat(styles): unify the five style managers behind a shared frame
The text/dimension/table/multileader/multiline managers were hand-copied, so their list CRUD kept drifting and breaking: Dimension's New created nothing (#73), new text styles never reached the ribbon dropdown (#74), and several were added with a NULL handle (dropped on DWG save, #67). Collapse the duplicated parts into three shared pieces and leave only the per-manager property editor bespoke: - app/style_ops.rs: one StyleKind-dispatched CRUD layer (new / copy / delete / rename) handling handle allocation, ribbon sync, Table re-key and name-reference rewrites in a single place. - ui/style_manager.rs: the window scaffold — uniform toolbar (New / Copy / Delete on the left, Set Current + Apply on the right), style list and chrome. Each manager now only builds its editor. - ui/style_list.rs: the list row — single click selects, double click starts an inline rename. New capabilities, now consistent across all five: - Copy duplicates the selected style. - Double-click a name to rename it inline. - Right side is always Set Current + Apply (added Table set-current; MLine apply is a documented placeholder until its editor is editable). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
9cdb5ea5fd
commit
f2dfd01795
12 changed files with 2113 additions and 1607 deletions
148
src/app/mod.rs
148
src/app/mod.rs
|
|
@ -1,19 +1,22 @@
|
||||||
mod cmd_result;
|
mod cmd_result;
|
||||||
mod commands;
|
mod commands;
|
||||||
pub mod plugin_host;
|
|
||||||
mod document;
|
mod document;
|
||||||
mod expr_eval;
|
mod expr_eval;
|
||||||
mod helpers;
|
mod helpers;
|
||||||
mod history;
|
mod history;
|
||||||
mod layers;
|
mod layers;
|
||||||
mod mtext_editor;
|
|
||||||
mod model_ops;
|
mod model_ops;
|
||||||
|
mod mtext_editor;
|
||||||
|
pub mod plugin_host;
|
||||||
mod properties;
|
mod properties;
|
||||||
mod settings;
|
mod settings;
|
||||||
|
mod style_ops;
|
||||||
mod text_inline;
|
mod text_inline;
|
||||||
mod update;
|
mod update;
|
||||||
mod view;
|
mod view;
|
||||||
|
|
||||||
|
pub use style_ops::StyleKind;
|
||||||
|
|
||||||
use document::DocumentTab;
|
use document::DocumentTab;
|
||||||
|
|
||||||
use crate::modules::ModuleEvent;
|
use crate::modules::ModuleEvent;
|
||||||
|
|
@ -382,6 +385,13 @@ pub(super) struct OpenCADStudio {
|
||||||
ts_border_color: [[String; 6]; 3],
|
ts_border_color: [[String; 6]; 3],
|
||||||
ts_border_spacing: [[String; 6]; 3],
|
ts_border_spacing: [[String; 6]; 3],
|
||||||
|
|
||||||
|
// ── Shared style-manager inline rename ────────────────────────────────
|
||||||
|
/// Original name of the style currently being renamed inline (double-click
|
||||||
|
/// a style name in any style manager). `None` when not renaming.
|
||||||
|
style_rename: Option<String>,
|
||||||
|
/// Edit buffer for the inline rename text input.
|
||||||
|
style_rename_buf: String,
|
||||||
|
|
||||||
// ── TextStyle Font Browser ────────────────────────────────────────────
|
// ── TextStyle Font Browser ────────────────────────────────────────────
|
||||||
textstyle_selected: String,
|
textstyle_selected: String,
|
||||||
/// Edit buffer for font file name.
|
/// Edit buffer for font file name.
|
||||||
|
|
@ -1091,6 +1101,13 @@ pub enum Message {
|
||||||
TextStyleDialogSelect(String),
|
TextStyleDialogSelect(String),
|
||||||
TextStyleDialogSetCurrent,
|
TextStyleDialogSetCurrent,
|
||||||
TextStyleDialogNew,
|
TextStyleDialogNew,
|
||||||
|
TextStyleDialogCopy,
|
||||||
|
// Shared inline-rename messages for every style manager. `StyleKind`
|
||||||
|
// routes the commit to the right backing store.
|
||||||
|
StyleRenameStart(StyleKind, String),
|
||||||
|
StyleRenameEdit(String),
|
||||||
|
StyleRenameCommit(StyleKind),
|
||||||
|
StyleRenameCancel,
|
||||||
TextStyleDialogDelete,
|
TextStyleDialogDelete,
|
||||||
/// Edit a string field (FontFile / Width / Oblique).
|
/// Edit a string field (FontFile / Width / Oblique).
|
||||||
TextStyleEdit {
|
TextStyleEdit {
|
||||||
|
|
@ -1110,14 +1127,19 @@ pub enum Message {
|
||||||
TableStyleDialogClose,
|
TableStyleDialogClose,
|
||||||
TableStyleDialogSelect(String),
|
TableStyleDialogSelect(String),
|
||||||
TableStyleDialogNew,
|
TableStyleDialogNew,
|
||||||
|
TableStyleDialogCopy,
|
||||||
TableStyleDialogDelete,
|
TableStyleDialogDelete,
|
||||||
|
TableStyleDialogSetCurrent,
|
||||||
/// Toggle the Annotative flag on the selected table style.
|
/// Toggle the Annotative flag on the selected table style.
|
||||||
TableStyleToggleAnnotative,
|
TableStyleToggleAnnotative,
|
||||||
/// Toggle a boolean flag (title_suppressed / header_suppressed / flow) on
|
/// Toggle a boolean flag (title_suppressed / header_suppressed / flow) on
|
||||||
/// the selected table style.
|
/// the selected table style.
|
||||||
TableStyleToggle(&'static str),
|
TableStyleToggle(&'static str),
|
||||||
/// Update a general edit buffer (hmargin / vmargin).
|
/// Update a general edit buffer (hmargin / vmargin).
|
||||||
TableStyleEdit { field: &'static str, value: String },
|
TableStyleEdit {
|
||||||
|
field: &'static str,
|
||||||
|
value: String,
|
||||||
|
},
|
||||||
/// Write the general edit buffers back into the selected table style.
|
/// Write the general edit buffers back into the selected table style.
|
||||||
TableStyleApply,
|
TableStyleApply,
|
||||||
/// Update a per-cell edit buffer (row 0=Data,1=Header,2=Title).
|
/// Update a per-cell edit buffer (row 0=Data,1=Header,2=Title).
|
||||||
|
|
@ -1161,7 +1183,9 @@ pub enum Message {
|
||||||
MlStyleDialogClose,
|
MlStyleDialogClose,
|
||||||
MlStyleDialogSelect(String),
|
MlStyleDialogSelect(String),
|
||||||
MlStyleDialogSetCurrent,
|
MlStyleDialogSetCurrent,
|
||||||
|
MlStyleApply,
|
||||||
MlStyleDialogNew,
|
MlStyleDialogNew,
|
||||||
|
MlStyleDialogCopy,
|
||||||
MlStyleDialogDelete,
|
MlStyleDialogDelete,
|
||||||
// ── MLeaderStyle Dialog ───────────────────────────────────────────────
|
// ── MLeaderStyle Dialog ───────────────────────────────────────────────
|
||||||
MLeaderStyleDialogOpen,
|
MLeaderStyleDialogOpen,
|
||||||
|
|
@ -1170,6 +1194,7 @@ pub enum Message {
|
||||||
MLeaderStyleDialogSelect(String),
|
MLeaderStyleDialogSelect(String),
|
||||||
MLeaderStyleDialogSetCurrent,
|
MLeaderStyleDialogSetCurrent,
|
||||||
MLeaderStyleDialogNew,
|
MLeaderStyleDialogNew,
|
||||||
|
MLeaderStyleDialogCopy,
|
||||||
MLeaderStyleDialogDelete,
|
MLeaderStyleDialogDelete,
|
||||||
MLeaderStyleEdit {
|
MLeaderStyleEdit {
|
||||||
field: &'static str,
|
field: &'static str,
|
||||||
|
|
@ -1198,6 +1223,7 @@ pub enum Message {
|
||||||
DimStyleDialogTab(u8),
|
DimStyleDialogTab(u8),
|
||||||
/// Create a new empty style (prompts via command line).
|
/// Create a new empty style (prompts via command line).
|
||||||
DimStyleDialogNew,
|
DimStyleDialogNew,
|
||||||
|
DimStyleDialogCopy,
|
||||||
/// Set the selected style as the document's current dim style.
|
/// Set the selected style as the document's current dim style.
|
||||||
DimStyleDialogSetCurrent,
|
DimStyleDialogSetCurrent,
|
||||||
/// Delete the selected style.
|
/// Delete the selected style.
|
||||||
|
|
@ -1362,6 +1388,8 @@ impl OpenCADStudio {
|
||||||
ps_lineweight_buf: "255".to_string(),
|
ps_lineweight_buf: "255".to_string(),
|
||||||
ps_screening_buf: "100".to_string(),
|
ps_screening_buf: "100".to_string(),
|
||||||
// TextStyle font browser
|
// TextStyle font browser
|
||||||
|
style_rename: None,
|
||||||
|
style_rename_buf: String::new(),
|
||||||
textstyle_selected: "Standard".to_string(),
|
textstyle_selected: "Standard".to_string(),
|
||||||
textstyle_font: String::new(),
|
textstyle_font: String::new(),
|
||||||
textstyle_width: "1.0".to_string(),
|
textstyle_width: "1.0".to_string(),
|
||||||
|
|
@ -1551,59 +1579,63 @@ impl OpenCADStudio {
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
pub fn run() -> iced::Result {
|
pub fn run() -> iced::Result {
|
||||||
iced::daemon(OpenCADStudio::boot, OpenCADStudio::update, OpenCADStudio::view)
|
iced::daemon(
|
||||||
.subscription(OpenCADStudio::subscription)
|
OpenCADStudio::boot,
|
||||||
.title(|state: &OpenCADStudio, window_id: window::Id| {
|
OpenCADStudio::update,
|
||||||
if Some(window_id) == state.layer_window {
|
OpenCADStudio::view,
|
||||||
return "Layer Properties Manager".into();
|
)
|
||||||
}
|
.subscription(OpenCADStudio::subscription)
|
||||||
if Some(window_id) == state.page_setup_window {
|
.title(|state: &OpenCADStudio, window_id: window::Id| {
|
||||||
return "Page Setup".into();
|
if Some(window_id) == state.layer_window {
|
||||||
}
|
return "Layer Properties Manager".into();
|
||||||
if Some(window_id) == state.textstyle_window {
|
}
|
||||||
return "Text Style".into();
|
if Some(window_id) == state.page_setup_window {
|
||||||
}
|
return "Page Setup".into();
|
||||||
if Some(window_id) == state.tablestyle_window {
|
}
|
||||||
return "Table Style".into();
|
if Some(window_id) == state.textstyle_window {
|
||||||
}
|
return "Text Style".into();
|
||||||
if Some(window_id) == state.mlstyle_window {
|
}
|
||||||
return "Multiline Style".into();
|
if Some(window_id) == state.tablestyle_window {
|
||||||
}
|
return "Table Style".into();
|
||||||
if Some(window_id) == state.mleaderstyle_window {
|
}
|
||||||
return "Multileader Style".into();
|
if Some(window_id) == state.mlstyle_window {
|
||||||
}
|
return "Multiline Style".into();
|
||||||
if Some(window_id) == state.layout_manager_window {
|
}
|
||||||
return "Layout Manager".into();
|
if Some(window_id) == state.mleaderstyle_window {
|
||||||
}
|
return "Multileader Style".into();
|
||||||
if Some(window_id) == state.plotstyle_window {
|
}
|
||||||
return "Plot Style Table Editor".into();
|
if Some(window_id) == state.layout_manager_window {
|
||||||
}
|
return "Layout Manager".into();
|
||||||
if Some(window_id) == state.dimstyle_window {
|
}
|
||||||
return "Dimension Style Manager".into();
|
if Some(window_id) == state.plotstyle_window {
|
||||||
}
|
return "Plot Style Table Editor".into();
|
||||||
if Some(window_id) == state.shortcuts_window {
|
}
|
||||||
return "Keyboard Shortcuts".into();
|
if Some(window_id) == state.dimstyle_window {
|
||||||
}
|
return "Dimension Style Manager".into();
|
||||||
if Some(window_id) == state.about_window {
|
}
|
||||||
return "About Open CAD Studio".into();
|
if Some(window_id) == state.shortcuts_window {
|
||||||
}
|
return "Keyboard Shortcuts".into();
|
||||||
if Some(window_id) == state.update_notice_window {
|
}
|
||||||
return "Update Available".into();
|
if Some(window_id) == state.about_window {
|
||||||
}
|
return "About Open CAD Studio".into();
|
||||||
if Some(window_id) == state.unsaved_dialog_window {
|
}
|
||||||
return "Unsaved Changes".into();
|
if Some(window_id) == state.update_notice_window {
|
||||||
}
|
return "Update Available".into();
|
||||||
if Some(window_id) == state.save_dialog_window {
|
}
|
||||||
return "Save As".into();
|
if Some(window_id) == state.unsaved_dialog_window {
|
||||||
}
|
return "Unsaved Changes".into();
|
||||||
if let Some(tab) = state.tabs.get(state.active_tab) {
|
}
|
||||||
let dot = if tab.dirty { "● " } else { "" };
|
if Some(window_id) == state.save_dialog_window {
|
||||||
let name = tab.tab_display_name();
|
return "Save As".into();
|
||||||
format!("{}Open CAD Studio — {}", dot, name)
|
}
|
||||||
} else {
|
if let Some(tab) = state.tabs.get(state.active_tab) {
|
||||||
"Open CAD Studio".to_string()
|
let dot = if tab.dirty { "● " } else { "" };
|
||||||
}
|
let name = tab.tab_display_name();
|
||||||
})
|
format!("{}Open CAD Studio — {}", dot, name)
|
||||||
.theme(|state: &OpenCADStudio, _| state.active_theme.clone())
|
} else {
|
||||||
.run()
|
"Open CAD Studio".to_string()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.theme(|state: &OpenCADStudio, _| state.active_theme.clone())
|
||||||
|
.run()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
475
src/app/style_ops.rs
Normal file
475
src/app/style_ops.rs
Normal file
|
|
@ -0,0 +1,475 @@
|
||||||
|
//! Shared CRUD layer for every style manager (text / dimension / table /
|
||||||
|
//! multileader / multiline).
|
||||||
|
//!
|
||||||
|
//! The five managers all expose the same list operations — New, Copy, Delete,
|
||||||
|
//! Rename, Set-Current — over a named collection of styles. Only the *property
|
||||||
|
//! editor* and the *storage backend* differ, so those are the only parts kept
|
||||||
|
//! per-manager:
|
||||||
|
//!
|
||||||
|
//! * **Table-backed** (text, dim): live in `Table<T>`, keyed by upper-cased
|
||||||
|
//! name. Renaming must re-key the entry and rewrite name-based entity
|
||||||
|
//! references (TEXT/MTEXT `style`, DIMENSION `style_name`).
|
||||||
|
//! * **Object-backed** (table, multileader, multiline): live in
|
||||||
|
//! `document.objects`, keyed by handle. Renaming only mutates the `name`
|
||||||
|
//! field; entities reference these by handle, so nothing else moves.
|
||||||
|
//!
|
||||||
|
//! Centralising the flow here is what fixes the bug class that kept recurring
|
||||||
|
//! when each manager was hand-copied: a dead New, a missing ribbon refresh, a
|
||||||
|
//! style added without a handle (dropped on DWG save, issue #67).
|
||||||
|
|
||||||
|
use super::OpenCADStudio;
|
||||||
|
use acadrust::objects::{MLineStyle, MultiLeaderStyle, ObjectType, TableStyle};
|
||||||
|
use acadrust::tables::{DimStyle, TextStyle};
|
||||||
|
use acadrust::types::Handle;
|
||||||
|
|
||||||
|
/// Which style manager an operation targets. Carried by the shared rename
|
||||||
|
/// messages so one handler can dispatch to the right storage.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum StyleKind {
|
||||||
|
Text,
|
||||||
|
Dim,
|
||||||
|
Table,
|
||||||
|
MLeader,
|
||||||
|
MLine,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StyleKind {
|
||||||
|
/// True when this style feeds the ribbon's quick-set dropdown.
|
||||||
|
fn in_ribbon(self) -> bool {
|
||||||
|
!matches!(self, StyleKind::MLine)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OpenCADStudio {
|
||||||
|
// ── Queries ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// All style names for `kind`, in display order (object-backed styles are
|
||||||
|
/// sorted by name so the `HashMap` backing them renders stably).
|
||||||
|
pub(super) fn style_names(&self, kind: StyleKind) -> Vec<String> {
|
||||||
|
let doc = &self.tabs[self.active_tab].scene.document;
|
||||||
|
let mut from_objects = |pick: fn(&ObjectType) -> Option<&str>| -> Vec<String> {
|
||||||
|
let mut v: Vec<String> = doc
|
||||||
|
.objects
|
||||||
|
.values()
|
||||||
|
.filter_map(pick)
|
||||||
|
.map(str::to_string)
|
||||||
|
.collect();
|
||||||
|
v.sort_by(|a, b| a.to_lowercase().cmp(&b.to_lowercase()));
|
||||||
|
v
|
||||||
|
};
|
||||||
|
match kind {
|
||||||
|
StyleKind::Text => doc.text_styles.iter().map(|s| s.name.clone()).collect(),
|
||||||
|
StyleKind::Dim => doc.dim_styles.iter().map(|s| s.name.clone()).collect(),
|
||||||
|
StyleKind::Table => from_objects(|o| match o {
|
||||||
|
ObjectType::TableStyle(s) => Some(s.name.as_str()),
|
||||||
|
_ => None,
|
||||||
|
}),
|
||||||
|
StyleKind::MLeader => from_objects(|o| match o {
|
||||||
|
ObjectType::MultiLeaderStyle(s) => Some(s.name.as_str()),
|
||||||
|
_ => None,
|
||||||
|
}),
|
||||||
|
StyleKind::MLine => from_objects(|o| match o {
|
||||||
|
ObjectType::MLineStyle(s) => Some(s.name.as_str()),
|
||||||
|
_ => None,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn style_selected(&self, kind: StyleKind) -> String {
|
||||||
|
match kind {
|
||||||
|
StyleKind::Text => self.textstyle_selected.clone(),
|
||||||
|
StyleKind::Dim => self.dimstyle_selected.clone(),
|
||||||
|
StyleKind::Table => self.tablestyle_selected.clone(),
|
||||||
|
StyleKind::MLeader => self.mleaderstyle_selected.clone(),
|
||||||
|
StyleKind::MLine => self.mlstyle_selected.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_style_selected(&mut self, kind: StyleKind, name: String) {
|
||||||
|
match kind {
|
||||||
|
StyleKind::Text => self.textstyle_selected = name,
|
||||||
|
StyleKind::Dim => self.dimstyle_selected = name,
|
||||||
|
StyleKind::Table => self.tablestyle_selected = name,
|
||||||
|
StyleKind::MLeader => self.mleaderstyle_selected = name,
|
||||||
|
StyleKind::MLine => self.mlstyle_selected = name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn style_exists(&self, kind: StyleKind, name: &str) -> bool {
|
||||||
|
self.style_names(kind)
|
||||||
|
.iter()
|
||||||
|
.any(|n| n.eq_ignore_ascii_case(name))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// First free `Style{n}` name for a fresh style.
|
||||||
|
fn unique_new_name(&self, kind: StyleKind) -> String {
|
||||||
|
(1u32..)
|
||||||
|
.map(|n| format!("Style{n}"))
|
||||||
|
.find(|c| !self.style_exists(kind, c))
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// First free `{base} ({n})` name for a copy / disambiguated entry.
|
||||||
|
fn unique_suffixed_name(&self, kind: StyleKind, base: &str) -> String {
|
||||||
|
(1u32..)
|
||||||
|
.map(|n| format!("{base} ({n})"))
|
||||||
|
.find(|c| !self.style_exists(kind, c))
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Per-manager glue (the only kind-specific list code) ────────────────
|
||||||
|
|
||||||
|
/// Reload the property-editor buffers for the kinds that have them.
|
||||||
|
fn load_style_bufs(&mut self, kind: StyleKind) {
|
||||||
|
let i = self.active_tab;
|
||||||
|
match kind {
|
||||||
|
StyleKind::Text => self.load_textstyle_bufs(i),
|
||||||
|
StyleKind::Dim => self.load_dimstyle_bufs(i),
|
||||||
|
StyleKind::MLeader => self.load_mleaderstyle_bufs(i),
|
||||||
|
StyleKind::Table | StyleKind::MLine => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Refresh anything that mirrors the style list / current style after a
|
||||||
|
/// mutation (ribbon dropdowns, geometry that depends on the style).
|
||||||
|
fn after_style_change(&mut self, kind: StyleKind) {
|
||||||
|
if kind.in_ribbon() {
|
||||||
|
self.sync_ribbon_styles();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert_default_style(&mut self, kind: StyleKind, name: &str, handle: Handle) {
|
||||||
|
let doc = &mut self.tabs[self.active_tab].scene.document;
|
||||||
|
match kind {
|
||||||
|
StyleKind::Text => {
|
||||||
|
let mut s = TextStyle::new(name);
|
||||||
|
s.handle = handle;
|
||||||
|
let _ = doc.text_styles.add(s);
|
||||||
|
}
|
||||||
|
StyleKind::Dim => {
|
||||||
|
let mut s = DimStyle::new(name);
|
||||||
|
s.handle = handle;
|
||||||
|
let _ = doc.dim_styles.add(s);
|
||||||
|
}
|
||||||
|
StyleKind::Table => {
|
||||||
|
let mut s = TableStyle::standard();
|
||||||
|
s.name = name.to_string();
|
||||||
|
s.handle = handle;
|
||||||
|
doc.objects.insert(handle, ObjectType::TableStyle(s));
|
||||||
|
}
|
||||||
|
StyleKind::MLeader => {
|
||||||
|
let mut s = MultiLeaderStyle::new(name);
|
||||||
|
s.handle = handle;
|
||||||
|
doc.objects.insert(handle, ObjectType::MultiLeaderStyle(s));
|
||||||
|
}
|
||||||
|
StyleKind::MLine => {
|
||||||
|
let mut s = MLineStyle::standard();
|
||||||
|
s.name = name.to_string();
|
||||||
|
s.handle = handle;
|
||||||
|
doc.objects.insert(handle, ObjectType::MLineStyle(s));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clone the style named `src` under `name` with a fresh `handle`.
|
||||||
|
/// Returns false if `src` no longer exists.
|
||||||
|
fn clone_style_as(&mut self, kind: StyleKind, src: &str, name: &str, handle: Handle) -> bool {
|
||||||
|
let doc = &mut self.tabs[self.active_tab].scene.document;
|
||||||
|
match kind {
|
||||||
|
StyleKind::Text => {
|
||||||
|
if let Some(mut s) = doc.text_styles.get(src).cloned() {
|
||||||
|
s.name = name.to_string();
|
||||||
|
s.handle = handle;
|
||||||
|
let _ = doc.text_styles.add(s);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StyleKind::Dim => {
|
||||||
|
if let Some(mut s) = doc.dim_styles.get(src).cloned() {
|
||||||
|
s.name = name.to_string();
|
||||||
|
s.handle = handle;
|
||||||
|
let _ = doc.dim_styles.add(s);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StyleKind::Table => {
|
||||||
|
if let Some(mut s) = find_object_style(doc, src, |o| match o {
|
||||||
|
ObjectType::TableStyle(s) => Some((s.name.as_str(), s.clone())),
|
||||||
|
_ => None,
|
||||||
|
}) {
|
||||||
|
s.name = name.to_string();
|
||||||
|
s.handle = handle;
|
||||||
|
doc.objects.insert(handle, ObjectType::TableStyle(s));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StyleKind::MLeader => {
|
||||||
|
if let Some(mut s) = find_object_style(doc, src, |o| match o {
|
||||||
|
ObjectType::MultiLeaderStyle(s) => Some((s.name.as_str(), s.clone())),
|
||||||
|
_ => None,
|
||||||
|
}) {
|
||||||
|
s.name = name.to_string();
|
||||||
|
s.handle = handle;
|
||||||
|
doc.objects.insert(handle, ObjectType::MultiLeaderStyle(s));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StyleKind::MLine => {
|
||||||
|
if let Some(mut s) = find_object_style(doc, src, |o| match o {
|
||||||
|
ObjectType::MLineStyle(s) => Some((s.name.as_str(), s.clone())),
|
||||||
|
_ => None,
|
||||||
|
}) {
|
||||||
|
s.name = name.to_string();
|
||||||
|
s.handle = handle;
|
||||||
|
doc.objects.insert(handle, ObjectType::MLineStyle(s));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_style_storage(&mut self, kind: StyleKind, name: &str) -> bool {
|
||||||
|
let doc = &mut self.tabs[self.active_tab].scene.document;
|
||||||
|
match kind {
|
||||||
|
StyleKind::Text => doc.text_styles.remove(name).is_some(),
|
||||||
|
StyleKind::Dim => doc.dim_styles.remove(name).is_some(),
|
||||||
|
StyleKind::Table | StyleKind::MLeader | StyleKind::MLine => {
|
||||||
|
let kind2 = kind;
|
||||||
|
if let Some(h) = object_handle(doc, name, kind2) {
|
||||||
|
doc.objects.remove(&h).is_some()
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Rename `old`→`new` in the backing store, re-keying table entries and
|
||||||
|
/// rewriting name-based references + current-style pointers.
|
||||||
|
fn rename_style_storage(&mut self, kind: StyleKind, old: &str, new: &str) {
|
||||||
|
let i = self.active_tab;
|
||||||
|
match kind {
|
||||||
|
StyleKind::Text => {
|
||||||
|
let doc = &mut self.tabs[i].scene.document;
|
||||||
|
if let Some(mut s) = doc.text_styles.get(old).cloned() {
|
||||||
|
s.name = new.to_string();
|
||||||
|
if !s.handle.is_valid() {
|
||||||
|
s.handle = doc.allocate_handle();
|
||||||
|
}
|
||||||
|
let _ = doc.text_styles.add(s);
|
||||||
|
}
|
||||||
|
doc.text_styles.remove(old);
|
||||||
|
if doc.header.current_text_style_name.eq_ignore_ascii_case(old) {
|
||||||
|
doc.header.current_text_style_name = new.to_string();
|
||||||
|
}
|
||||||
|
for e in doc.entities_mut() {
|
||||||
|
match e {
|
||||||
|
acadrust::entities::EntityType::Text(t)
|
||||||
|
if t.style.eq_ignore_ascii_case(old) =>
|
||||||
|
{
|
||||||
|
t.style = new.to_string();
|
||||||
|
}
|
||||||
|
acadrust::entities::EntityType::MText(t)
|
||||||
|
if t.style.eq_ignore_ascii_case(old) =>
|
||||||
|
{
|
||||||
|
t.style = new.to_string();
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StyleKind::Dim => {
|
||||||
|
let doc = &mut self.tabs[i].scene.document;
|
||||||
|
if let Some(mut s) = doc.dim_styles.get(old).cloned() {
|
||||||
|
s.name = new.to_string();
|
||||||
|
if !s.handle.is_valid() {
|
||||||
|
s.handle = doc.allocate_handle();
|
||||||
|
}
|
||||||
|
let _ = doc.dim_styles.add(s);
|
||||||
|
}
|
||||||
|
doc.dim_styles.remove(old);
|
||||||
|
if doc.header.current_dimstyle_name.eq_ignore_ascii_case(old) {
|
||||||
|
doc.header.current_dimstyle_name = new.to_string();
|
||||||
|
}
|
||||||
|
for e in doc.entities_mut() {
|
||||||
|
if let acadrust::entities::EntityType::Dimension(d) = e {
|
||||||
|
if d.base().style_name.eq_ignore_ascii_case(old) {
|
||||||
|
d.base_mut().style_name = new.to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StyleKind::Table => {
|
||||||
|
let doc = &mut self.tabs[i].scene.document;
|
||||||
|
if let Some(h) = object_handle(doc, old, kind) {
|
||||||
|
if let Some(ObjectType::TableStyle(s)) = doc.objects.get_mut(&h) {
|
||||||
|
s.name = new.to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if self.ribbon.active_table_style.eq_ignore_ascii_case(old) {
|
||||||
|
self.ribbon.active_table_style = new.to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StyleKind::MLeader => {
|
||||||
|
let doc = &mut self.tabs[i].scene.document;
|
||||||
|
if let Some(h) = object_handle(doc, old, kind) {
|
||||||
|
if let Some(ObjectType::MultiLeaderStyle(s)) = doc.objects.get_mut(&h) {
|
||||||
|
s.name = new.to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if self.tabs[i].active_mleader_style.eq_ignore_ascii_case(old) {
|
||||||
|
self.tabs[i].active_mleader_style = new.to_string();
|
||||||
|
}
|
||||||
|
if self.ribbon.active_mleader_style.eq_ignore_ascii_case(old) {
|
||||||
|
self.ribbon.active_mleader_style = new.to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StyleKind::MLine => {
|
||||||
|
let doc = &mut self.tabs[i].scene.document;
|
||||||
|
if let Some(h) = object_handle(doc, old, kind) {
|
||||||
|
if let Some(ObjectType::MLineStyle(s)) = doc.objects.get_mut(&h) {
|
||||||
|
s.name = new.to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if doc.header.multiline_style.eq_ignore_ascii_case(old) {
|
||||||
|
doc.header.multiline_style = new.to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Public operations (called by the message handlers) ─────────────────
|
||||||
|
|
||||||
|
pub(super) fn style_new(&mut self, kind: StyleKind) {
|
||||||
|
let i = self.active_tab;
|
||||||
|
let name = self.unique_new_name(kind);
|
||||||
|
self.push_undo_snapshot(i, "STYLE NEW");
|
||||||
|
let h = self.tabs[i].scene.document.allocate_handle();
|
||||||
|
self.insert_default_style(kind, &name, h);
|
||||||
|
self.set_style_selected(kind, name.clone());
|
||||||
|
self.load_style_bufs(kind);
|
||||||
|
self.tabs[i].dirty = true;
|
||||||
|
self.after_style_change(kind);
|
||||||
|
self.command_line
|
||||||
|
.push_output(&format!("Style '{name}' created."));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn style_copy(&mut self, kind: StyleKind) {
|
||||||
|
let i = self.active_tab;
|
||||||
|
let src = self.style_selected(kind);
|
||||||
|
let name = self.unique_suffixed_name(kind, &src);
|
||||||
|
self.push_undo_snapshot(i, "STYLE COPY");
|
||||||
|
let h = self.tabs[i].scene.document.allocate_handle();
|
||||||
|
if !self.clone_style_as(kind, &src, &name, h) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.set_style_selected(kind, name.clone());
|
||||||
|
self.load_style_bufs(kind);
|
||||||
|
self.tabs[i].dirty = true;
|
||||||
|
self.after_style_change(kind);
|
||||||
|
self.command_line
|
||||||
|
.push_output(&format!("Style '{name}' created."));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn style_delete(&mut self, kind: StyleKind) {
|
||||||
|
let i = self.active_tab;
|
||||||
|
let name = self.style_selected(kind);
|
||||||
|
if name.eq_ignore_ascii_case("Standard") {
|
||||||
|
self.command_line
|
||||||
|
.push_error("Cannot delete the Standard style.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.push_undo_snapshot(i, "STYLE DEL");
|
||||||
|
if !self.remove_style_storage(kind, &name) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let first = self
|
||||||
|
.style_names(kind)
|
||||||
|
.into_iter()
|
||||||
|
.next()
|
||||||
|
.unwrap_or_else(|| "Standard".to_string());
|
||||||
|
self.set_style_selected(kind, first);
|
||||||
|
self.load_style_bufs(kind);
|
||||||
|
self.tabs[i].dirty = true;
|
||||||
|
self.after_style_change(kind);
|
||||||
|
self.command_line
|
||||||
|
.push_output(&format!("Style '{name}' deleted."));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Begin inline rename of the double-clicked style.
|
||||||
|
pub(super) fn style_rename_start(&mut self, kind: StyleKind, name: String) {
|
||||||
|
self.set_style_selected(kind, name.clone());
|
||||||
|
self.load_style_bufs(kind);
|
||||||
|
self.style_rename_buf = name.clone();
|
||||||
|
self.style_rename = Some(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Commit the inline rename. No-op (with feedback) on empty / unchanged /
|
||||||
|
/// colliding names, and the Standard style cannot be renamed.
|
||||||
|
pub(super) fn style_rename_commit(&mut self, kind: StyleKind) {
|
||||||
|
let i = self.active_tab;
|
||||||
|
let Some(old) = self.style_rename.take() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let new = self.style_rename_buf.trim().to_string();
|
||||||
|
self.style_rename_buf.clear();
|
||||||
|
if new.is_empty() || new.eq_ignore_ascii_case(&old) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if old.eq_ignore_ascii_case("Standard") {
|
||||||
|
self.command_line
|
||||||
|
.push_error("Cannot rename the Standard style.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if self.style_exists(kind, &new) {
|
||||||
|
self.command_line
|
||||||
|
.push_error(&format!("Style '{new}' already exists."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.push_undo_snapshot(i, "STYLE RENAME");
|
||||||
|
self.rename_style_storage(kind, &old, &new);
|
||||||
|
if self.style_selected(kind).eq_ignore_ascii_case(&old) {
|
||||||
|
self.set_style_selected(kind, new.clone());
|
||||||
|
}
|
||||||
|
self.load_style_bufs(kind);
|
||||||
|
self.tabs[i].dirty = true;
|
||||||
|
self.after_style_change(kind);
|
||||||
|
self.command_line
|
||||||
|
.push_output(&format!("Renamed '{old}' → '{new}'."));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn style_rename_cancel(&mut self) {
|
||||||
|
self.style_rename = None;
|
||||||
|
self.style_rename_buf.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Object-store helpers ───────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// Find the object-backed style named `name` and return a clone. `pick` maps a
|
||||||
|
/// matching variant to `(its name, a clone of the inner style)`.
|
||||||
|
fn find_object_style<T>(
|
||||||
|
doc: &acadrust::CadDocument,
|
||||||
|
name: &str,
|
||||||
|
pick: impl Fn(&ObjectType) -> Option<(&str, T)>,
|
||||||
|
) -> Option<T> {
|
||||||
|
doc.objects.values().find_map(|o| {
|
||||||
|
let (n, val) = pick(o)?;
|
||||||
|
n.eq_ignore_ascii_case(name).then_some(val)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn object_handle(doc: &acadrust::CadDocument, name: &str, kind: StyleKind) -> Option<Handle> {
|
||||||
|
doc.objects.iter().find_map(|(&h, o)| {
|
||||||
|
let matches = match (kind, o) {
|
||||||
|
(StyleKind::Table, ObjectType::TableStyle(s)) => s.name.eq_ignore_ascii_case(name),
|
||||||
|
(StyleKind::MLeader, ObjectType::MultiLeaderStyle(s)) => {
|
||||||
|
s.name.eq_ignore_ascii_case(name)
|
||||||
|
}
|
||||||
|
(StyleKind::MLine, ObjectType::MLineStyle(s)) => s.name.eq_ignore_ascii_case(name),
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
matches.then_some(h)
|
||||||
|
})
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load diff
710
src/app/view.rs
710
src/app/view.rs
File diff suppressed because it is too large
Load diff
|
|
@ -6,18 +6,6 @@ use iced::widget::{
|
||||||
};
|
};
|
||||||
use iced::{Background, Border, Color, Element, Fill, Theme};
|
use iced::{Background, Border, Color, Element, Fill, Theme};
|
||||||
|
|
||||||
const TB: Color = Color {
|
|
||||||
r: 0.13,
|
|
||||||
g: 0.13,
|
|
||||||
b: 0.13,
|
|
||||||
a: 1.0,
|
|
||||||
};
|
|
||||||
const BG: Color = Color {
|
|
||||||
r: 0.15,
|
|
||||||
g: 0.15,
|
|
||||||
b: 0.15,
|
|
||||||
a: 1.0,
|
|
||||||
};
|
|
||||||
const BORDER: Color = Color {
|
const BORDER: Color = Color {
|
||||||
r: 0.35,
|
r: 0.35,
|
||||||
g: 0.35,
|
g: 0.35,
|
||||||
|
|
@ -54,12 +42,6 @@ const FIELD: Color = Color {
|
||||||
b: 0.10,
|
b: 0.10,
|
||||||
a: 1.0,
|
a: 1.0,
|
||||||
};
|
};
|
||||||
const LIST: Color = Color {
|
|
||||||
r: 0.12,
|
|
||||||
g: 0.12,
|
|
||||||
b: 0.12,
|
|
||||||
a: 1.0,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// All DimStyle field values needed by the view.
|
/// All DimStyle field values needed by the view.
|
||||||
pub struct DimStyleValues<'a> {
|
pub struct DimStyleValues<'a> {
|
||||||
|
|
@ -146,39 +128,6 @@ pub struct DimStyleValues<'a> {
|
||||||
pub lt_opts: Vec<String>,
|
pub lt_opts: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn btn_s(accent: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
|
|
||||||
move |_: &Theme, st| button::Style {
|
|
||||||
background: Some(Background::Color(match (accent, st) {
|
|
||||||
(true, button::Status::Hovered | button::Status::Pressed) => Color {
|
|
||||||
r: 0.20,
|
|
||||||
g: 0.42,
|
|
||||||
b: 0.72,
|
|
||||||
a: 1.0,
|
|
||||||
},
|
|
||||||
(false, button::Status::Hovered | button::Status::Pressed) => Color {
|
|
||||||
r: 0.28,
|
|
||||||
g: 0.28,
|
|
||||||
b: 0.28,
|
|
||||||
a: 1.0,
|
|
||||||
},
|
|
||||||
(true, _) => ACCENT,
|
|
||||||
_ => Color {
|
|
||||||
r: 0.22,
|
|
||||||
g: 0.22,
|
|
||||||
b: 0.22,
|
|
||||||
a: 1.0,
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
text_color: TEXT,
|
|
||||||
border: Border {
|
|
||||||
color: BORDER,
|
|
||||||
width: 1.0,
|
|
||||||
radius: 4.0.into(),
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn tab_btn_style(active: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
|
fn tab_btn_style(active: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
|
||||||
move |_: &Theme, st| button::Style {
|
move |_: &Theme, st| button::Style {
|
||||||
background: Some(Background::Color(match (active, st) {
|
background: Some(Background::Color(match (active, st) {
|
||||||
|
|
@ -206,33 +155,6 @@ fn tab_btn_style(active: bool) -> impl Fn(&Theme, button::Status) -> button::Sty
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn list_item_style(active: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
|
|
||||||
move |_: &Theme, st| button::Style {
|
|
||||||
background: Some(Background::Color(match (active, st) {
|
|
||||||
(true, _) => ACTIVE,
|
|
||||||
(false, button::Status::Hovered | button::Status::Pressed) => Color {
|
|
||||||
r: 0.28,
|
|
||||||
g: 0.28,
|
|
||||||
b: 0.28,
|
|
||||||
a: 1.0,
|
|
||||||
},
|
|
||||||
_ => Color {
|
|
||||||
r: 0.18,
|
|
||||||
g: 0.18,
|
|
||||||
b: 0.18,
|
|
||||||
a: 1.0,
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
text_color: TEXT,
|
|
||||||
border: Border {
|
|
||||||
color: BORDER,
|
|
||||||
width: 0.0,
|
|
||||||
radius: 3.0.into(),
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn field_style(_: &Theme, _: text_input::Status) -> text_input::Style {
|
fn field_style(_: &Theme, _: text_input::Status) -> text_input::Style {
|
||||||
text_input::Style {
|
text_input::Style {
|
||||||
background: Background::Color(FIELD),
|
background: Background::Color(FIELD),
|
||||||
|
|
@ -264,83 +186,9 @@ pub fn view_window<'a>(
|
||||||
selected: &'a str,
|
selected: &'a str,
|
||||||
tab: u8,
|
tab: u8,
|
||||||
vals: DimStyleValues<'a>,
|
vals: DimStyleValues<'a>,
|
||||||
|
rename_active: Option<&'a str>,
|
||||||
|
rename_buf: &'a str,
|
||||||
) -> Element<'a, Message> {
|
) -> Element<'a, Message> {
|
||||||
// ── Toolbar ───────────────────────────────────────────────────────────
|
|
||||||
let toolbar = container(
|
|
||||||
row![
|
|
||||||
button(text("New").size(11))
|
|
||||||
.on_press(Message::DimStyleDialogNew)
|
|
||||||
.style(btn_s(false))
|
|
||||||
.padding([4, 10]),
|
|
||||||
button(text("Delete").size(11))
|
|
||||||
.on_press(Message::DimStyleDialogDelete)
|
|
||||||
.style(btn_s(false))
|
|
||||||
.padding([4, 10]),
|
|
||||||
button(text("Set Current").size(11))
|
|
||||||
.on_press(Message::DimStyleDialogSetCurrent)
|
|
||||||
.style(btn_s(false))
|
|
||||||
.padding([4, 10]),
|
|
||||||
Space::new().width(Fill),
|
|
||||||
button(text("Apply").size(11))
|
|
||||||
.on_press(Message::DimStyleDialogApply)
|
|
||||||
.style(btn_s(true))
|
|
||||||
.padding([4, 14]),
|
|
||||||
button(text("Close").size(11))
|
|
||||||
.on_press(Message::DimStyleDialogClose)
|
|
||||||
.style(btn_s(false))
|
|
||||||
.padding([4, 10]),
|
|
||||||
]
|
|
||||||
.spacing(4)
|
|
||||||
.align_y(iced::Center),
|
|
||||||
)
|
|
||||||
.style(|_: &Theme| container::Style {
|
|
||||||
background: Some(Background::Color(TB)),
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.width(Fill)
|
|
||||||
.padding([5, 8]);
|
|
||||||
|
|
||||||
// ── Style list panel ──────────────────────────────────────────────────
|
|
||||||
let mut list_col = column![].spacing(2);
|
|
||||||
for name in &styles {
|
|
||||||
let active = name.as_str() == selected;
|
|
||||||
list_col = list_col.push(
|
|
||||||
button(text(name.clone()).size(11))
|
|
||||||
.on_press(Message::DimStyleDialogSelect(name.clone()))
|
|
||||||
.style(list_item_style(active))
|
|
||||||
.padding([4, 8])
|
|
||||||
.width(Fill),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
let style_list = container(
|
|
||||||
column![
|
|
||||||
text("Styles").size(10).color(DIM),
|
|
||||||
container(scrollable(list_col).height(Fill))
|
|
||||||
.style(|_: &Theme| container::Style {
|
|
||||||
background: Some(Background::Color(LIST)),
|
|
||||||
border: Border {
|
|
||||||
color: BORDER,
|
|
||||||
width: 1.0,
|
|
||||||
radius: 3.0.into()
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.width(180)
|
|
||||||
.height(Fill)
|
|
||||||
.padding(2),
|
|
||||||
]
|
|
||||||
.spacing(4)
|
|
||||||
.height(Fill),
|
|
||||||
)
|
|
||||||
.width(180)
|
|
||||||
.height(Fill)
|
|
||||||
.padding(iced::Padding {
|
|
||||||
top: 12.0,
|
|
||||||
right: 8.0,
|
|
||||||
bottom: 12.0,
|
|
||||||
left: 12.0,
|
|
||||||
});
|
|
||||||
|
|
||||||
// ── Tab bar ───────────────────────────────────────────────────────────
|
// ── Tab bar ───────────────────────────────────────────────────────────
|
||||||
let tabs = row![
|
let tabs = row![
|
||||||
button(text("Lines").size(11))
|
button(text("Lines").size(11))
|
||||||
|
|
@ -448,24 +296,88 @@ pub fn view_window<'a>(
|
||||||
.align_y(iced::Center),
|
.align_y(iced::Center),
|
||||||
chk("Suppress 1st line (DIMSE1)", vals.dimse1, DsField::Dimse1),
|
chk("Suppress 1st line (DIMSE1)", vals.dimse1, DsField::Dimse1),
|
||||||
chk("Suppress 2nd line (DIMSE2)", vals.dimse2, DsField::Dimse2),
|
chk("Suppress 2nd line (DIMSE2)", vals.dimse2, DsField::Dimse2),
|
||||||
row![lbl("Dim line color ACI (DIMCLRD)"), mk_field(DsField::Dimclrd, vals.dimclrd)].spacing(8).align_y(iced::Center),
|
row![
|
||||||
row![lbl("Dim line weight (DIMLWD)"), mk_field(DsField::Dimlwd, vals.dimlwd)].spacing(8).align_y(iced::Center),
|
lbl("Dim line color ACI (DIMCLRD)"),
|
||||||
row![lbl("Ext line color ACI (DIMCLRE)"), mk_field(DsField::Dimclre, vals.dimclre)].spacing(8).align_y(iced::Center),
|
mk_field(DsField::Dimclrd, vals.dimclrd)
|
||||||
row![lbl("Ext line weight (DIMLWE)"), mk_field(DsField::Dimlwe, vals.dimlwe)].spacing(8).align_y(iced::Center),
|
]
|
||||||
chk("Fixed-length ext lines (DIMFXLON)", vals.dimfxlon, DsField::Dimfxlon),
|
.spacing(8)
|
||||||
row![lbl("Fixed length (DIMFXL)"), mk_field(DsField::Dimfxl, vals.dimfxl)].spacing(8).align_y(iced::Center),
|
.align_y(iced::Center),
|
||||||
hrow("Dim line linetype (DIMLTYPE)", vals.lt_opts.clone(), vals.dimltex_name.clone(), "dimltex_handle"),
|
row![
|
||||||
hrow("Ext line 1 linetype (DIMLTEX1)", vals.lt_opts.clone(), vals.dimltex1_name.clone(), "dimltex1_handle"),
|
lbl("Dim line weight (DIMLWD)"),
|
||||||
hrow("Ext line 2 linetype (DIMLTEX2)", vals.lt_opts.clone(), vals.dimltex2_name.clone(), "dimltex2_handle"),
|
mk_field(DsField::Dimlwd, vals.dimlwd)
|
||||||
|
]
|
||||||
|
.spacing(8)
|
||||||
|
.align_y(iced::Center),
|
||||||
|
row![
|
||||||
|
lbl("Ext line color ACI (DIMCLRE)"),
|
||||||
|
mk_field(DsField::Dimclre, vals.dimclre)
|
||||||
|
]
|
||||||
|
.spacing(8)
|
||||||
|
.align_y(iced::Center),
|
||||||
|
row![
|
||||||
|
lbl("Ext line weight (DIMLWE)"),
|
||||||
|
mk_field(DsField::Dimlwe, vals.dimlwe)
|
||||||
|
]
|
||||||
|
.spacing(8)
|
||||||
|
.align_y(iced::Center),
|
||||||
|
chk(
|
||||||
|
"Fixed-length ext lines (DIMFXLON)",
|
||||||
|
vals.dimfxlon,
|
||||||
|
DsField::Dimfxlon
|
||||||
|
),
|
||||||
|
row![
|
||||||
|
lbl("Fixed length (DIMFXL)"),
|
||||||
|
mk_field(DsField::Dimfxl, vals.dimfxl)
|
||||||
|
]
|
||||||
|
.spacing(8)
|
||||||
|
.align_y(iced::Center),
|
||||||
|
hrow(
|
||||||
|
"Dim line linetype (DIMLTYPE)",
|
||||||
|
vals.lt_opts.clone(),
|
||||||
|
vals.dimltex_name.clone(),
|
||||||
|
"dimltex_handle"
|
||||||
|
),
|
||||||
|
hrow(
|
||||||
|
"Ext line 1 linetype (DIMLTEX1)",
|
||||||
|
vals.lt_opts.clone(),
|
||||||
|
vals.dimltex1_name.clone(),
|
||||||
|
"dimltex1_handle"
|
||||||
|
),
|
||||||
|
hrow(
|
||||||
|
"Ext line 2 linetype (DIMLTEX2)",
|
||||||
|
vals.lt_opts.clone(),
|
||||||
|
vals.dimltex2_name.clone(),
|
||||||
|
"dimltex2_handle"
|
||||||
|
),
|
||||||
]
|
]
|
||||||
.spacing(7)
|
.spacing(7)
|
||||||
.into(),
|
.into(),
|
||||||
1 => column![
|
1 => column![
|
||||||
text("Arrows").size(11).color(ACCENT),
|
text("Arrows").size(11).color(ACCENT),
|
||||||
hrow("Arrowhead (DIMBLK)", vals.block_opts.clone(), vals.dimblk_name.clone(), "dimblk"),
|
hrow(
|
||||||
hrow("1st arrowhead (DIMBLK1)", vals.block_opts.clone(), vals.dimblk1_name.clone(), "dimblk1"),
|
"Arrowhead (DIMBLK)",
|
||||||
hrow("2nd arrowhead (DIMBLK2)", vals.block_opts.clone(), vals.dimblk2_name.clone(), "dimblk2"),
|
vals.block_opts.clone(),
|
||||||
hrow("Leader arrowhead (DIMLDRBLK)", vals.block_opts.clone(), vals.dimldrblk_name.clone(), "dimldrblk"),
|
vals.dimblk_name.clone(),
|
||||||
|
"dimblk"
|
||||||
|
),
|
||||||
|
hrow(
|
||||||
|
"1st arrowhead (DIMBLK1)",
|
||||||
|
vals.block_opts.clone(),
|
||||||
|
vals.dimblk1_name.clone(),
|
||||||
|
"dimblk1"
|
||||||
|
),
|
||||||
|
hrow(
|
||||||
|
"2nd arrowhead (DIMBLK2)",
|
||||||
|
vals.block_opts.clone(),
|
||||||
|
vals.dimblk2_name.clone(),
|
||||||
|
"dimblk2"
|
||||||
|
),
|
||||||
|
hrow(
|
||||||
|
"Leader arrowhead (DIMLDRBLK)",
|
||||||
|
vals.block_opts.clone(),
|
||||||
|
vals.dimldrblk_name.clone(),
|
||||||
|
"dimldrblk"
|
||||||
|
),
|
||||||
row![
|
row![
|
||||||
lbl("Arrow size (DIMASZ)"),
|
lbl("Arrow size (DIMASZ)"),
|
||||||
mk_field(DsField::Dimasz, vals.dimasz)
|
mk_field(DsField::Dimasz, vals.dimasz)
|
||||||
|
|
@ -484,9 +396,23 @@ pub fn view_window<'a>(
|
||||||
]
|
]
|
||||||
.spacing(8)
|
.spacing(8)
|
||||||
.align_y(iced::Center),
|
.align_y(iced::Center),
|
||||||
chk("Separate arrow blocks (DIMSAH)", vals.dimsah, DsField::Dimsah),
|
chk(
|
||||||
row![lbl("Arc length symbol (DIMARCSYM)"), mk_field(DsField::Dimarcsym, vals.dimarcsym)].spacing(8).align_y(iced::Center),
|
"Separate arrow blocks (DIMSAH)",
|
||||||
row![lbl("Jog angle ° (DIMJOGANG)"), mk_field(DsField::Dimjogang, vals.dimjogang)].spacing(8).align_y(iced::Center),
|
vals.dimsah,
|
||||||
|
DsField::Dimsah
|
||||||
|
),
|
||||||
|
row![
|
||||||
|
lbl("Arc length symbol (DIMARCSYM)"),
|
||||||
|
mk_field(DsField::Dimarcsym, vals.dimarcsym)
|
||||||
|
]
|
||||||
|
.spacing(8)
|
||||||
|
.align_y(iced::Center),
|
||||||
|
row![
|
||||||
|
lbl("Jog angle ° (DIMJOGANG)"),
|
||||||
|
mk_field(DsField::Dimjogang, vals.dimjogang)
|
||||||
|
]
|
||||||
|
.spacing(8)
|
||||||
|
.align_y(iced::Center),
|
||||||
]
|
]
|
||||||
.spacing(7)
|
.spacing(7)
|
||||||
.into(),
|
.into(),
|
||||||
|
|
@ -512,12 +438,41 @@ pub fn view_window<'a>(
|
||||||
.align_y(iced::Center),
|
.align_y(iced::Center),
|
||||||
chk("Horizontal inside (DIMTIH)", vals.dimtih, DsField::Dimtih),
|
chk("Horizontal inside (DIMTIH)", vals.dimtih, DsField::Dimtih),
|
||||||
chk("Horizontal outside (DIMTOH)", vals.dimtoh, DsField::Dimtoh),
|
chk("Horizontal outside (DIMTOH)", vals.dimtoh, DsField::Dimtoh),
|
||||||
row![lbl("Text color ACI (DIMCLRT)"), mk_field(DsField::Dimclrt, vals.dimclrt)].spacing(8).align_y(iced::Center),
|
row![
|
||||||
row![lbl("Horizontal just (DIMJUST)"), mk_field(DsField::Dimjust, vals.dimjust)].spacing(8).align_y(iced::Center),
|
lbl("Text color ACI (DIMCLRT)"),
|
||||||
row![lbl("Vertical pos (DIMTVP)"), mk_field(DsField::Dimtvp, vals.dimtvp)].spacing(8).align_y(iced::Center),
|
mk_field(DsField::Dimclrt, vals.dimclrt)
|
||||||
row![lbl("Text fill mode (DIMTFILL)"), mk_field(DsField::Dimtfill, vals.dimtfill)].spacing(8).align_y(iced::Center),
|
]
|
||||||
row![lbl("Fill color ACI (DIMTFILLCLR)"), mk_field(DsField::Dimtfillclr, vals.dimtfillclr)].spacing(8).align_y(iced::Center),
|
.spacing(8)
|
||||||
chk("Left-to-right (DIMTXTDIRECTION)", vals.dimtxtdirection, DsField::Dimtxtdirection),
|
.align_y(iced::Center),
|
||||||
|
row![
|
||||||
|
lbl("Horizontal just (DIMJUST)"),
|
||||||
|
mk_field(DsField::Dimjust, vals.dimjust)
|
||||||
|
]
|
||||||
|
.spacing(8)
|
||||||
|
.align_y(iced::Center),
|
||||||
|
row![
|
||||||
|
lbl("Vertical pos (DIMTVP)"),
|
||||||
|
mk_field(DsField::Dimtvp, vals.dimtvp)
|
||||||
|
]
|
||||||
|
.spacing(8)
|
||||||
|
.align_y(iced::Center),
|
||||||
|
row![
|
||||||
|
lbl("Text fill mode (DIMTFILL)"),
|
||||||
|
mk_field(DsField::Dimtfill, vals.dimtfill)
|
||||||
|
]
|
||||||
|
.spacing(8)
|
||||||
|
.align_y(iced::Center),
|
||||||
|
row![
|
||||||
|
lbl("Fill color ACI (DIMTFILLCLR)"),
|
||||||
|
mk_field(DsField::Dimtfillclr, vals.dimtfillclr)
|
||||||
|
]
|
||||||
|
.spacing(8)
|
||||||
|
.align_y(iced::Center),
|
||||||
|
chk(
|
||||||
|
"Left-to-right (DIMTXTDIRECTION)",
|
||||||
|
vals.dimtxtdirection,
|
||||||
|
DsField::Dimtxtdirection
|
||||||
|
),
|
||||||
]
|
]
|
||||||
.spacing(7)
|
.spacing(7)
|
||||||
.into(),
|
.into(),
|
||||||
|
|
@ -555,36 +510,143 @@ pub fn view_window<'a>(
|
||||||
]
|
]
|
||||||
.spacing(8)
|
.spacing(8)
|
||||||
.align_y(iced::Center),
|
.align_y(iced::Center),
|
||||||
row![lbl("Decimal sep ASCII (DIMDSEP)"), mk_field(DsField::Dimdsep, vals.dimdsep)].spacing(8).align_y(iced::Center),
|
row![
|
||||||
row![lbl("Round off (DIMRND)"), mk_field(DsField::Dimrnd, vals.dimrnd)].spacing(8).align_y(iced::Center),
|
lbl("Decimal sep ASCII (DIMDSEP)"),
|
||||||
row![lbl("Zero suppress (DIMZIN)"), mk_field(DsField::Dimzin, vals.dimzin)].spacing(8).align_y(iced::Center),
|
mk_field(DsField::Dimdsep, vals.dimdsep)
|
||||||
row![lbl("Fraction format (DIMFRAC)"), mk_field(DsField::Dimfrac, vals.dimfrac)].spacing(8).align_y(iced::Center),
|
]
|
||||||
row![lbl("Angular unit (DIMAUNIT)"), mk_field(DsField::Dimaunit, vals.dimaunit)].spacing(8).align_y(iced::Center),
|
.spacing(8)
|
||||||
row![lbl("Angular decimals (DIMADEC)"), mk_field(DsField::Dimadec, vals.dimadec)].spacing(8).align_y(iced::Center),
|
.align_y(iced::Center),
|
||||||
row![lbl("Unit format (DIMUNIT)"), mk_field(DsField::Dimunit, vals.dimunit)].spacing(8).align_y(iced::Center),
|
row![
|
||||||
row![lbl("Angular zero supp (DIMAZIN)"), mk_field(DsField::Dimazin, vals.dimazin)].spacing(8).align_y(iced::Center),
|
lbl("Round off (DIMRND)"),
|
||||||
|
mk_field(DsField::Dimrnd, vals.dimrnd)
|
||||||
|
]
|
||||||
|
.spacing(8)
|
||||||
|
.align_y(iced::Center),
|
||||||
|
row![
|
||||||
|
lbl("Zero suppress (DIMZIN)"),
|
||||||
|
mk_field(DsField::Dimzin, vals.dimzin)
|
||||||
|
]
|
||||||
|
.spacing(8)
|
||||||
|
.align_y(iced::Center),
|
||||||
|
row![
|
||||||
|
lbl("Fraction format (DIMFRAC)"),
|
||||||
|
mk_field(DsField::Dimfrac, vals.dimfrac)
|
||||||
|
]
|
||||||
|
.spacing(8)
|
||||||
|
.align_y(iced::Center),
|
||||||
|
row![
|
||||||
|
lbl("Angular unit (DIMAUNIT)"),
|
||||||
|
mk_field(DsField::Dimaunit, vals.dimaunit)
|
||||||
|
]
|
||||||
|
.spacing(8)
|
||||||
|
.align_y(iced::Center),
|
||||||
|
row![
|
||||||
|
lbl("Angular decimals (DIMADEC)"),
|
||||||
|
mk_field(DsField::Dimadec, vals.dimadec)
|
||||||
|
]
|
||||||
|
.spacing(8)
|
||||||
|
.align_y(iced::Center),
|
||||||
|
row![
|
||||||
|
lbl("Unit format (DIMUNIT)"),
|
||||||
|
mk_field(DsField::Dimunit, vals.dimunit)
|
||||||
|
]
|
||||||
|
.spacing(8)
|
||||||
|
.align_y(iced::Center),
|
||||||
|
row![
|
||||||
|
lbl("Angular zero supp (DIMAZIN)"),
|
||||||
|
mk_field(DsField::Dimazin, vals.dimazin)
|
||||||
|
]
|
||||||
|
.spacing(8)
|
||||||
|
.align_y(iced::Center),
|
||||||
text("Fit").size(11).color(ACCENT),
|
text("Fit").size(11).color(ACCENT),
|
||||||
row![lbl("Fit (DIMATFIT)"), mk_field(DsField::Dimatfit, vals.dimatfit)].spacing(8).align_y(iced::Center),
|
row![
|
||||||
row![lbl("Text movement (DIMTMOVE)"), mk_field(DsField::Dimtmove, vals.dimtmove)].spacing(8).align_y(iced::Center),
|
lbl("Fit (DIMATFIT)"),
|
||||||
row![lbl("Fit (legacy DIMFIT)"), mk_field(DsField::Dimfit, vals.dimfit)].spacing(8).align_y(iced::Center),
|
mk_field(DsField::Dimatfit, vals.dimatfit)
|
||||||
|
]
|
||||||
|
.spacing(8)
|
||||||
|
.align_y(iced::Center),
|
||||||
|
row![
|
||||||
|
lbl("Text movement (DIMTMOVE)"),
|
||||||
|
mk_field(DsField::Dimtmove, vals.dimtmove)
|
||||||
|
]
|
||||||
|
.spacing(8)
|
||||||
|
.align_y(iced::Center),
|
||||||
|
row![
|
||||||
|
lbl("Fit (legacy DIMFIT)"),
|
||||||
|
mk_field(DsField::Dimfit, vals.dimfit)
|
||||||
|
]
|
||||||
|
.spacing(8)
|
||||||
|
.align_y(iced::Center),
|
||||||
chk("Force text inside (DIMTIX)", vals.dimtix, DsField::Dimtix),
|
chk("Force text inside (DIMTIX)", vals.dimtix, DsField::Dimtix),
|
||||||
chk("Suppress outside arrows (DIMSOXD)", vals.dimsoxd, DsField::Dimsoxd),
|
chk(
|
||||||
|
"Suppress outside arrows (DIMSOXD)",
|
||||||
|
vals.dimsoxd,
|
||||||
|
DsField::Dimsoxd
|
||||||
|
),
|
||||||
chk("Place text manually (DIMUPT)", vals.dimupt, DsField::Dimupt),
|
chk("Place text manually (DIMUPT)", vals.dimupt, DsField::Dimupt),
|
||||||
chk("Dim line between ext (DIMTOFL)", vals.dimtofl, DsField::Dimtofl),
|
chk(
|
||||||
|
"Dim line between ext (DIMTOFL)",
|
||||||
|
vals.dimtofl,
|
||||||
|
DsField::Dimtofl
|
||||||
|
),
|
||||||
]
|
]
|
||||||
.spacing(7)
|
.spacing(7)
|
||||||
.into(),
|
.into(),
|
||||||
5 => column![
|
5 => column![
|
||||||
text("Alternate Units").size(11).color(ACCENT),
|
text("Alternate Units").size(11).color(ACCENT),
|
||||||
chk("Enable alternate units (DIMALT)", vals.dimalt, DsField::Dimalt),
|
chk(
|
||||||
row![lbl("Multiplier (DIMALTF)"), mk_field(DsField::Dimaltf, vals.dimaltf)].spacing(8).align_y(iced::Center),
|
"Enable alternate units (DIMALT)",
|
||||||
row![lbl("Decimals (DIMALTD)"), mk_field(DsField::Dimaltd, vals.dimaltd)].spacing(8).align_y(iced::Center),
|
vals.dimalt,
|
||||||
row![lbl("Unit format (DIMALTU)"), mk_field(DsField::Dimaltu, vals.dimaltu)].spacing(8).align_y(iced::Center),
|
DsField::Dimalt
|
||||||
row![lbl("Tol decimals (DIMALTTD)"), mk_field(DsField::Dimalttd, vals.dimalttd)].spacing(8).align_y(iced::Center),
|
),
|
||||||
row![lbl("Round off (DIMALTRND)"), mk_field(DsField::Dimaltrnd, vals.dimaltrnd)].spacing(8).align_y(iced::Center),
|
row![
|
||||||
row![lbl("Suffix (DIMAPOST)"), mk_field(DsField::Dimapost, vals.dimapost)].spacing(8).align_y(iced::Center),
|
lbl("Multiplier (DIMALTF)"),
|
||||||
row![lbl("Zero suppress (DIMALTZ)"), mk_field(DsField::Dimaltz, vals.dimaltz)].spacing(8).align_y(iced::Center),
|
mk_field(DsField::Dimaltf, vals.dimaltf)
|
||||||
row![lbl("Tol zero supp (DIMALTTZ)"), mk_field(DsField::Dimalttz, vals.dimalttz)].spacing(8).align_y(iced::Center),
|
]
|
||||||
|
.spacing(8)
|
||||||
|
.align_y(iced::Center),
|
||||||
|
row![
|
||||||
|
lbl("Decimals (DIMALTD)"),
|
||||||
|
mk_field(DsField::Dimaltd, vals.dimaltd)
|
||||||
|
]
|
||||||
|
.spacing(8)
|
||||||
|
.align_y(iced::Center),
|
||||||
|
row![
|
||||||
|
lbl("Unit format (DIMALTU)"),
|
||||||
|
mk_field(DsField::Dimaltu, vals.dimaltu)
|
||||||
|
]
|
||||||
|
.spacing(8)
|
||||||
|
.align_y(iced::Center),
|
||||||
|
row![
|
||||||
|
lbl("Tol decimals (DIMALTTD)"),
|
||||||
|
mk_field(DsField::Dimalttd, vals.dimalttd)
|
||||||
|
]
|
||||||
|
.spacing(8)
|
||||||
|
.align_y(iced::Center),
|
||||||
|
row![
|
||||||
|
lbl("Round off (DIMALTRND)"),
|
||||||
|
mk_field(DsField::Dimaltrnd, vals.dimaltrnd)
|
||||||
|
]
|
||||||
|
.spacing(8)
|
||||||
|
.align_y(iced::Center),
|
||||||
|
row![
|
||||||
|
lbl("Suffix (DIMAPOST)"),
|
||||||
|
mk_field(DsField::Dimapost, vals.dimapost)
|
||||||
|
]
|
||||||
|
.spacing(8)
|
||||||
|
.align_y(iced::Center),
|
||||||
|
row![
|
||||||
|
lbl("Zero suppress (DIMALTZ)"),
|
||||||
|
mk_field(DsField::Dimaltz, vals.dimaltz)
|
||||||
|
]
|
||||||
|
.spacing(8)
|
||||||
|
.align_y(iced::Center),
|
||||||
|
row![
|
||||||
|
lbl("Tol zero supp (DIMALTTZ)"),
|
||||||
|
mk_field(DsField::Dimalttz, vals.dimalttz)
|
||||||
|
]
|
||||||
|
.spacing(8)
|
||||||
|
.align_y(iced::Center),
|
||||||
]
|
]
|
||||||
.spacing(7)
|
.spacing(7)
|
||||||
.into(),
|
.into(),
|
||||||
|
|
@ -616,8 +678,18 @@ pub fn view_window<'a>(
|
||||||
]
|
]
|
||||||
.spacing(8)
|
.spacing(8)
|
||||||
.align_y(iced::Center),
|
.align_y(iced::Center),
|
||||||
row![lbl("Tol. vert just (DIMTOLJ)"), mk_field(DsField::Dimtolj, vals.dimtolj)].spacing(8).align_y(iced::Center),
|
row![
|
||||||
row![lbl("Tol. zero supp (DIMTZIN)"), mk_field(DsField::Dimtzin, vals.dimtzin)].spacing(8).align_y(iced::Center),
|
lbl("Tol. vert just (DIMTOLJ)"),
|
||||||
|
mk_field(DsField::Dimtolj, vals.dimtolj)
|
||||||
|
]
|
||||||
|
.spacing(8)
|
||||||
|
.align_y(iced::Center),
|
||||||
|
row![
|
||||||
|
lbl("Tol. zero supp (DIMTZIN)"),
|
||||||
|
mk_field(DsField::Dimtzin, vals.dimtzin)
|
||||||
|
]
|
||||||
|
.spacing(8)
|
||||||
|
.align_y(iced::Center),
|
||||||
]
|
]
|
||||||
.spacing(7)
|
.spacing(7)
|
||||||
.into(),
|
.into(),
|
||||||
|
|
@ -648,23 +720,19 @@ pub fn view_window<'a>(
|
||||||
left: 0.0,
|
left: 0.0,
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── Vertical separator ────────────────────────────────────────────────
|
crate::ui::style_manager::view(crate::ui::style_manager::Scaffold {
|
||||||
let vsep = container(Space::new().width(1).height(Fill))
|
kind: crate::app::StyleKind::Dim,
|
||||||
.width(1)
|
styles: &styles,
|
||||||
.height(Fill)
|
selected,
|
||||||
.style(|_: &Theme| container::Style {
|
current: None,
|
||||||
background: Some(Background::Color(BORDER)),
|
rename_active,
|
||||||
..Default::default()
|
rename_buf,
|
||||||
});
|
on_new: Message::DimStyleDialogNew,
|
||||||
|
on_copy: Message::DimStyleDialogCopy,
|
||||||
let body = row![style_list, vsep, right_panel].height(Fill);
|
on_delete: Message::DimStyleDialogDelete,
|
||||||
|
on_select: Message::DimStyleDialogSelect,
|
||||||
container(column![toolbar, hdivider(), body].spacing(0))
|
on_set_current: Message::DimStyleDialogSetCurrent,
|
||||||
.style(|_: &Theme| container::Style {
|
on_apply: Message::DimStyleDialogApply,
|
||||||
background: Some(Background::Color(BG)),
|
editor: right_panel.into(),
|
||||||
..Default::default()
|
})
|
||||||
})
|
|
||||||
.width(Fill)
|
|
||||||
.height(Fill)
|
|
||||||
.into()
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,18 +6,6 @@ use iced::widget::{
|
||||||
};
|
};
|
||||||
use iced::{Background, Border, Color, Element, Fill, Theme};
|
use iced::{Background, Border, Color, Element, Fill, Theme};
|
||||||
|
|
||||||
const TB: Color = Color {
|
|
||||||
r: 0.13,
|
|
||||||
g: 0.13,
|
|
||||||
b: 0.13,
|
|
||||||
a: 1.0,
|
|
||||||
};
|
|
||||||
const BG: Color = Color {
|
|
||||||
r: 0.15,
|
|
||||||
g: 0.15,
|
|
||||||
b: 0.15,
|
|
||||||
a: 1.0,
|
|
||||||
};
|
|
||||||
const BORDER: Color = Color {
|
const BORDER: Color = Color {
|
||||||
r: 0.35,
|
r: 0.35,
|
||||||
g: 0.35,
|
g: 0.35,
|
||||||
|
|
@ -42,18 +30,6 @@ const ACCENT: Color = Color {
|
||||||
b: 0.85,
|
b: 0.85,
|
||||||
a: 1.0,
|
a: 1.0,
|
||||||
};
|
};
|
||||||
const ACTIVE: Color = Color {
|
|
||||||
r: 0.20,
|
|
||||||
g: 0.40,
|
|
||||||
b: 0.70,
|
|
||||||
a: 1.0,
|
|
||||||
};
|
|
||||||
const LIST: Color = Color {
|
|
||||||
r: 0.12,
|
|
||||||
g: 0.12,
|
|
||||||
b: 0.12,
|
|
||||||
a: 1.0,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn btn_s(accent: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
|
fn btn_s(accent: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
|
||||||
move |_: &Theme, st| button::Style {
|
move |_: &Theme, st| button::Style {
|
||||||
|
|
@ -88,45 +64,6 @@ fn btn_s(accent: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn list_item(active: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
|
|
||||||
move |_: &Theme, st| button::Style {
|
|
||||||
background: Some(Background::Color(match (active, st) {
|
|
||||||
(true, _) => ACTIVE,
|
|
||||||
(false, button::Status::Hovered | button::Status::Pressed) => Color {
|
|
||||||
r: 0.26,
|
|
||||||
g: 0.26,
|
|
||||||
b: 0.26,
|
|
||||||
a: 1.0,
|
|
||||||
},
|
|
||||||
_ => Color::TRANSPARENT,
|
|
||||||
})),
|
|
||||||
text_color: TEXT,
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn hdivider<'a>() -> Element<'a, Message> {
|
|
||||||
container(Space::new().width(Fill).height(1))
|
|
||||||
.width(Fill)
|
|
||||||
.height(1)
|
|
||||||
.style(|_: &Theme| container::Style {
|
|
||||||
background: Some(Background::Color(BORDER)),
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn vsep<'a>() -> Element<'a, Message> {
|
|
||||||
container(Space::new().width(1).height(Fill))
|
|
||||||
.width(1)
|
|
||||||
.height(Fill)
|
|
||||||
.style(|_: &Theme| container::Style {
|
|
||||||
background: Some(Background::Color(BORDER)),
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// View-model borrowed from the app for one render of the dialog.
|
/// View-model borrowed from the app for one render of the dialog.
|
||||||
pub struct MLeaderStyleView<'a> {
|
pub struct MLeaderStyleView<'a> {
|
||||||
pub styles: Vec<String>,
|
pub styles: Vec<String>,
|
||||||
|
|
@ -161,6 +98,10 @@ pub struct MLeaderStyleView<'a> {
|
||||||
pub arrowhead_name: String,
|
pub arrowhead_name: String,
|
||||||
pub text_style_name: String,
|
pub text_style_name: String,
|
||||||
pub block_content_name: String,
|
pub block_content_name: String,
|
||||||
|
/// Name of the style being renamed inline (double-clicked), if any.
|
||||||
|
pub rename_active: Option<&'a str>,
|
||||||
|
/// Edit buffer for the inline rename text input.
|
||||||
|
pub rename_buf: &'a str,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn section<'a>(label: &'static str) -> Element<'a, Message> {
|
fn section<'a>(label: &'static str) -> Element<'a, Message> {
|
||||||
|
|
@ -254,76 +195,6 @@ fn chk<'a>(label: &'static str, val: bool, field: &'static str) -> Element<'a, M
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn view_window<'a>(v: MLeaderStyleView<'a>) -> Element<'a, Message> {
|
pub fn view_window<'a>(v: MLeaderStyleView<'a>) -> Element<'a, Message> {
|
||||||
// ── Toolbar ───────────────────────────────────────────────────────────
|
|
||||||
let toolbar = container(
|
|
||||||
row![
|
|
||||||
button(text("New").size(11))
|
|
||||||
.on_press(Message::MLeaderStyleDialogNew)
|
|
||||||
.style(btn_s(true))
|
|
||||||
.padding([4, 10]),
|
|
||||||
button(text("Delete").size(11))
|
|
||||||
.on_press(Message::MLeaderStyleDialogDelete)
|
|
||||||
.style(btn_s(false))
|
|
||||||
.padding([4, 10]),
|
|
||||||
button(text("Set Current").size(11))
|
|
||||||
.on_press(Message::MLeaderStyleDialogSetCurrent)
|
|
||||||
.style(btn_s(false))
|
|
||||||
.padding([4, 10]),
|
|
||||||
]
|
|
||||||
.spacing(4)
|
|
||||||
.align_y(iced::Center),
|
|
||||||
)
|
|
||||||
.style(|_: &Theme| container::Style {
|
|
||||||
background: Some(Background::Color(TB)),
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.width(Fill)
|
|
||||||
.padding([5, 8]);
|
|
||||||
|
|
||||||
// ── Left: Style list ──────────────────────────────────────────────────
|
|
||||||
let style_items: Vec<Element<'_, Message>> = v
|
|
||||||
.styles
|
|
||||||
.iter()
|
|
||||||
.map(|name| {
|
|
||||||
let is_sel = name.as_str() == v.selected;
|
|
||||||
button(text(name.clone()).size(11))
|
|
||||||
.on_press(Message::MLeaderStyleDialogSelect(name.clone()))
|
|
||||||
.style(list_item(is_sel))
|
|
||||||
.padding([4, 8])
|
|
||||||
.width(Fill)
|
|
||||||
.into()
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let style_list = container(
|
|
||||||
column![
|
|
||||||
text(format!("Current: {}", v.current)).size(10).color(DIM),
|
|
||||||
container(scrollable(column(style_items).spacing(2)).height(Fill))
|
|
||||||
.style(|_: &Theme| container::Style {
|
|
||||||
background: Some(Background::Color(LIST)),
|
|
||||||
border: Border {
|
|
||||||
color: BORDER,
|
|
||||||
width: 1.0,
|
|
||||||
radius: 3.0.into()
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.width(Fill)
|
|
||||||
.height(Fill)
|
|
||||||
.padding(2),
|
|
||||||
]
|
|
||||||
.spacing(4)
|
|
||||||
.height(Fill),
|
|
||||||
)
|
|
||||||
.width(200)
|
|
||||||
.height(Fill)
|
|
||||||
.padding(iced::Padding {
|
|
||||||
top: 12.0,
|
|
||||||
right: 8.0,
|
|
||||||
bottom: 12.0,
|
|
||||||
left: 12.0,
|
|
||||||
});
|
|
||||||
|
|
||||||
// ── Right: Details panel ──────────────────────────────────────────────
|
// ── Right: Details panel ──────────────────────────────────────────────
|
||||||
let details: Element<'_, Message> = if let Some(s) = v.style {
|
let details: Element<'_, Message> = if let Some(s) = v.style {
|
||||||
scrollable(
|
scrollable(
|
||||||
|
|
@ -356,17 +227,37 @@ pub fn view_window<'a>(v: MLeaderStyleView<'a>) -> Element<'a, Message> {
|
||||||
v.arrowhead_name.clone(),
|
v.arrowhead_name.clone(),
|
||||||
"arrowhead_handle"
|
"arrowhead_handle"
|
||||||
),
|
),
|
||||||
num_row("Arrowhead size:", "0.18", v.arrowhead_size, "arrowhead_size"),
|
num_row(
|
||||||
|
"Arrowhead size:",
|
||||||
|
"0.18",
|
||||||
|
v.arrowhead_size,
|
||||||
|
"arrowhead_size"
|
||||||
|
),
|
||||||
num_row("Break gap size:", "0.125", v.break_gap, "break_gap"),
|
num_row("Break gap size:", "0.125", v.break_gap, "break_gap"),
|
||||||
// Leader Structure
|
// Leader Structure
|
||||||
section("Leader Structure"),
|
section("Leader Structure"),
|
||||||
chk("Enable landing", s.enable_landing, "enable_landing"),
|
chk("Enable landing", s.enable_landing, "enable_landing"),
|
||||||
chk("Enable dogleg", s.enable_dogleg, "enable_dogleg"),
|
chk("Enable dogleg", s.enable_dogleg, "enable_dogleg"),
|
||||||
num_row("Landing distance:", "8.0", v.landing_distance, "landing_distance"),
|
num_row(
|
||||||
|
"Landing distance:",
|
||||||
|
"8.0",
|
||||||
|
v.landing_distance,
|
||||||
|
"landing_distance"
|
||||||
|
),
|
||||||
num_row("Landing gap:", "0.09", v.landing_gap, "landing_gap"),
|
num_row("Landing gap:", "0.09", v.landing_gap, "landing_gap"),
|
||||||
num_row("Max leader points:", "2", v.max_points, "max_points"),
|
num_row("Max leader points:", "2", v.max_points, "max_points"),
|
||||||
num_row("First seg. angle:", "0", v.first_seg_angle, "first_seg_angle"),
|
num_row(
|
||||||
num_row("Second seg. angle:", "0", v.second_seg_angle, "second_seg_angle"),
|
"First seg. angle:",
|
||||||
|
"0",
|
||||||
|
v.first_seg_angle,
|
||||||
|
"first_seg_angle"
|
||||||
|
),
|
||||||
|
num_row(
|
||||||
|
"Second seg. angle:",
|
||||||
|
"0",
|
||||||
|
v.second_seg_angle,
|
||||||
|
"second_seg_angle"
|
||||||
|
),
|
||||||
num_row("Scale factor:", "1.0", v.scale_factor, "scale_factor"),
|
num_row("Scale factor:", "1.0", v.scale_factor, "scale_factor"),
|
||||||
num_row("Align space:", "4.0", v.align_space, "align_space"),
|
num_row("Align space:", "4.0", v.align_space, "align_space"),
|
||||||
enum_row(
|
enum_row(
|
||||||
|
|
@ -462,7 +353,11 @@ pub fn view_window<'a>(v: MLeaderStyleView<'a>) -> Element<'a, Message> {
|
||||||
num_row("Block scale X:", "1.0", v.block_scale_x, "block_scale_x"),
|
num_row("Block scale X:", "1.0", v.block_scale_x, "block_scale_x"),
|
||||||
num_row("Block scale Y:", "1.0", v.block_scale_y, "block_scale_y"),
|
num_row("Block scale Y:", "1.0", v.block_scale_y, "block_scale_y"),
|
||||||
num_row("Block scale Z:", "1.0", v.block_scale_z, "block_scale_z"),
|
num_row("Block scale Z:", "1.0", v.block_scale_z, "block_scale_z"),
|
||||||
chk("Enable block scale", s.enable_block_scale, "enable_block_scale"),
|
chk(
|
||||||
|
"Enable block scale",
|
||||||
|
s.enable_block_scale,
|
||||||
|
"enable_block_scale"
|
||||||
|
),
|
||||||
chk(
|
chk(
|
||||||
"Enable block rotation",
|
"Enable block rotation",
|
||||||
s.enable_block_rotation,
|
s.enable_block_rotation,
|
||||||
|
|
@ -487,14 +382,19 @@ pub fn view_window<'a>(v: MLeaderStyleView<'a>) -> Element<'a, Message> {
|
||||||
|
|
||||||
let right_panel = container(details).width(Fill).height(Fill);
|
let right_panel = container(details).width(Fill).height(Fill);
|
||||||
|
|
||||||
let body = row![style_list, vsep(), right_panel].height(Fill);
|
crate::ui::style_manager::view(crate::ui::style_manager::Scaffold {
|
||||||
|
kind: crate::app::StyleKind::MLeader,
|
||||||
container(column![toolbar, hdivider(), body].spacing(0))
|
styles: &v.styles,
|
||||||
.style(|_: &Theme| container::Style {
|
selected: v.selected,
|
||||||
background: Some(Background::Color(BG)),
|
current: Some(v.current.as_str()),
|
||||||
..Default::default()
|
rename_active: v.rename_active,
|
||||||
})
|
rename_buf: v.rename_buf,
|
||||||
.width(Fill)
|
on_new: Message::MLeaderStyleDialogNew,
|
||||||
.height(Fill)
|
on_copy: Message::MLeaderStyleDialogCopy,
|
||||||
.into()
|
on_delete: Message::MLeaderStyleDialogDelete,
|
||||||
|
on_select: Message::MLeaderStyleDialogSelect,
|
||||||
|
on_set_current: Message::MLeaderStyleDialogSetCurrent,
|
||||||
|
on_apply: Message::MLeaderStyleApply,
|
||||||
|
editor: right_panel.into(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,211 +1,24 @@
|
||||||
//! Multiline Style Manager window — fills the entire OS window.
|
//! Multiline Style Manager window — fills the entire OS window.
|
||||||
|
|
||||||
use crate::app::Message;
|
use crate::app::Message;
|
||||||
use iced::widget::{button, column, container, row, scrollable, text, Space};
|
use iced::widget::{column, container, row, scrollable, text, Space};
|
||||||
use iced::{Background, Border, Color, Element, Fill, Theme};
|
use iced::{Background, Color, Element, Fill, Theme};
|
||||||
|
|
||||||
const TB: Color = Color {
|
|
||||||
r: 0.13,
|
|
||||||
g: 0.13,
|
|
||||||
b: 0.13,
|
|
||||||
a: 1.0,
|
|
||||||
};
|
|
||||||
const BG: Color = Color {
|
|
||||||
r: 0.15,
|
|
||||||
g: 0.15,
|
|
||||||
b: 0.15,
|
|
||||||
a: 1.0,
|
|
||||||
};
|
|
||||||
const BORDER: Color = Color {
|
|
||||||
r: 0.35,
|
|
||||||
g: 0.35,
|
|
||||||
b: 0.35,
|
|
||||||
a: 1.0,
|
|
||||||
};
|
|
||||||
const TEXT: Color = Color {
|
|
||||||
r: 0.88,
|
|
||||||
g: 0.88,
|
|
||||||
b: 0.88,
|
|
||||||
a: 1.0,
|
|
||||||
};
|
|
||||||
const DIM: Color = Color {
|
const DIM: Color = Color {
|
||||||
r: 0.55,
|
r: 0.55,
|
||||||
g: 0.55,
|
g: 0.55,
|
||||||
b: 0.55,
|
b: 0.55,
|
||||||
a: 1.0,
|
a: 1.0,
|
||||||
};
|
};
|
||||||
const ACCENT: Color = Color {
|
|
||||||
r: 0.25,
|
|
||||||
g: 0.50,
|
|
||||||
b: 0.85,
|
|
||||||
a: 1.0,
|
|
||||||
};
|
|
||||||
const ACTIVE: Color = Color {
|
|
||||||
r: 0.20,
|
|
||||||
g: 0.40,
|
|
||||||
b: 0.70,
|
|
||||||
a: 1.0,
|
|
||||||
};
|
|
||||||
const LIST: Color = Color {
|
|
||||||
r: 0.12,
|
|
||||||
g: 0.12,
|
|
||||||
b: 0.12,
|
|
||||||
a: 1.0,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn btn_s(accent: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
|
|
||||||
move |_: &Theme, st| button::Style {
|
|
||||||
background: Some(Background::Color(match (accent, st) {
|
|
||||||
(true, button::Status::Hovered | button::Status::Pressed) => Color {
|
|
||||||
r: 0.20,
|
|
||||||
g: 0.42,
|
|
||||||
b: 0.72,
|
|
||||||
a: 1.0,
|
|
||||||
},
|
|
||||||
(false, button::Status::Hovered | button::Status::Pressed) => Color {
|
|
||||||
r: 0.28,
|
|
||||||
g: 0.28,
|
|
||||||
b: 0.28,
|
|
||||||
a: 1.0,
|
|
||||||
},
|
|
||||||
(true, _) => ACCENT,
|
|
||||||
_ => Color {
|
|
||||||
r: 0.22,
|
|
||||||
g: 0.22,
|
|
||||||
b: 0.22,
|
|
||||||
a: 1.0,
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
text_color: TEXT,
|
|
||||||
border: Border {
|
|
||||||
color: BORDER,
|
|
||||||
width: 1.0,
|
|
||||||
radius: 4.0.into(),
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn list_item(active: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
|
|
||||||
move |_: &Theme, st| button::Style {
|
|
||||||
background: Some(Background::Color(match (active, st) {
|
|
||||||
(true, _) => ACTIVE,
|
|
||||||
(false, button::Status::Hovered | button::Status::Pressed) => Color {
|
|
||||||
r: 0.26,
|
|
||||||
g: 0.26,
|
|
||||||
b: 0.26,
|
|
||||||
a: 1.0,
|
|
||||||
},
|
|
||||||
_ => Color::TRANSPARENT,
|
|
||||||
})),
|
|
||||||
text_color: TEXT,
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn hdivider<'a>() -> Element<'a, Message> {
|
|
||||||
container(Space::new().width(Fill).height(1))
|
|
||||||
.width(Fill)
|
|
||||||
.height(1)
|
|
||||||
.style(|_: &Theme| container::Style {
|
|
||||||
background: Some(Background::Color(BORDER)),
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn vsep<'a>() -> Element<'a, Message> {
|
|
||||||
container(Space::new().width(1).height(Fill))
|
|
||||||
.width(1)
|
|
||||||
.height(Fill)
|
|
||||||
.style(|_: &Theme| container::Style {
|
|
||||||
background: Some(Background::Color(BORDER)),
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn view_window<'a>(
|
pub fn view_window<'a>(
|
||||||
styles: Vec<String>,
|
styles: Vec<String>,
|
||||||
selected: &'a str,
|
selected: &'a str,
|
||||||
selected_style: Option<&'a acadrust::objects::MLineStyle>,
|
selected_style: Option<&'a acadrust::objects::MLineStyle>,
|
||||||
current_style: String,
|
current_style: String,
|
||||||
|
rename_active: Option<&'a str>,
|
||||||
|
rename_buf: &'a str,
|
||||||
) -> Element<'a, Message> {
|
) -> Element<'a, Message> {
|
||||||
// ── Toolbar ───────────────────────────────────────────────────────────
|
|
||||||
let toolbar = container(
|
|
||||||
row![
|
|
||||||
button(text("Set Current").size(11))
|
|
||||||
.on_press(Message::MlStyleDialogSetCurrent)
|
|
||||||
.style(btn_s(true))
|
|
||||||
.padding([4, 10]),
|
|
||||||
button(text("New").size(11))
|
|
||||||
.on_press(Message::MlStyleDialogNew)
|
|
||||||
.style(btn_s(false))
|
|
||||||
.padding([4, 10]),
|
|
||||||
button(text("Delete").size(11))
|
|
||||||
.on_press(Message::MlStyleDialogDelete)
|
|
||||||
.style(btn_s(false))
|
|
||||||
.padding([4, 10]),
|
|
||||||
]
|
|
||||||
.spacing(4)
|
|
||||||
.align_y(iced::Center),
|
|
||||||
)
|
|
||||||
.style(|_: &Theme| container::Style {
|
|
||||||
background: Some(Background::Color(TB)),
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.width(Fill)
|
|
||||||
.padding([5, 8]);
|
|
||||||
|
|
||||||
// ── Left: Style list ──────────────────────────────────────────────────
|
|
||||||
let style_items: Vec<Element<'_, Message>> = styles
|
|
||||||
.iter()
|
|
||||||
.map(|name| {
|
|
||||||
let is_sel = name.as_str() == selected;
|
|
||||||
let is_cur = *name == current_style;
|
|
||||||
let label = if is_cur {
|
|
||||||
format!("{name} ◀")
|
|
||||||
} else {
|
|
||||||
name.clone()
|
|
||||||
};
|
|
||||||
button(text(label).size(11))
|
|
||||||
.on_press(Message::MlStyleDialogSelect(name.clone()))
|
|
||||||
.style(list_item(is_sel))
|
|
||||||
.padding([4, 8])
|
|
||||||
.width(Fill)
|
|
||||||
.into()
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let style_list = container(
|
|
||||||
column![
|
|
||||||
text("Styles").size(10).color(DIM),
|
|
||||||
container(scrollable(column(style_items).spacing(2)).height(Fill))
|
|
||||||
.style(|_: &Theme| container::Style {
|
|
||||||
background: Some(Background::Color(LIST)),
|
|
||||||
border: Border {
|
|
||||||
color: BORDER,
|
|
||||||
width: 1.0,
|
|
||||||
radius: 3.0.into()
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.width(Fill)
|
|
||||||
.height(Fill)
|
|
||||||
.padding(2),
|
|
||||||
]
|
|
||||||
.spacing(4)
|
|
||||||
.height(Fill),
|
|
||||||
)
|
|
||||||
.width(200)
|
|
||||||
.height(Fill)
|
|
||||||
.padding(iced::Padding {
|
|
||||||
top: 12.0,
|
|
||||||
right: 8.0,
|
|
||||||
bottom: 12.0,
|
|
||||||
left: 12.0,
|
|
||||||
});
|
|
||||||
|
|
||||||
// ── Right: Details panel ──────────────────────────────────────────────
|
// ── Right: Details panel ──────────────────────────────────────────────
|
||||||
let info_row = |label: &'static str, val: String| -> Element<'_, Message> {
|
let info_row = |label: &'static str, val: String| -> Element<'_, Message> {
|
||||||
row![
|
row![
|
||||||
|
|
@ -266,14 +79,19 @@ pub fn view_window<'a>(
|
||||||
|
|
||||||
let right_panel = container(details).width(Fill).height(Fill);
|
let right_panel = container(details).width(Fill).height(Fill);
|
||||||
|
|
||||||
let body = row![style_list, vsep(), right_panel].height(Fill);
|
crate::ui::style_manager::view(crate::ui::style_manager::Scaffold {
|
||||||
|
kind: crate::app::StyleKind::MLine,
|
||||||
container(column![toolbar, hdivider(), body].spacing(0))
|
styles: &styles,
|
||||||
.style(|_: &Theme| container::Style {
|
selected,
|
||||||
background: Some(Background::Color(BG)),
|
current: Some(current_style.as_str()),
|
||||||
..Default::default()
|
rename_active,
|
||||||
})
|
rename_buf,
|
||||||
.width(Fill)
|
on_new: Message::MlStyleDialogNew,
|
||||||
.height(Fill)
|
on_copy: Message::MlStyleDialogCopy,
|
||||||
.into()
|
on_delete: Message::MlStyleDialogDelete,
|
||||||
|
on_select: Message::MlStyleDialogSelect,
|
||||||
|
on_set_current: Message::MlStyleDialogSetCurrent,
|
||||||
|
on_apply: Message::MlStyleApply,
|
||||||
|
editor: right_panel.into(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,10 @@ pub const ROW_H: f32 = 26.0;
|
||||||
|
|
||||||
pub mod about;
|
pub mod about;
|
||||||
pub mod app_menu;
|
pub mod app_menu;
|
||||||
pub mod isolate_popup;
|
|
||||||
pub mod update_notice;
|
|
||||||
pub mod command_line;
|
pub mod command_line;
|
||||||
pub mod cycle_popup;
|
pub mod cycle_popup;
|
||||||
pub mod dimstyle;
|
pub mod dimstyle;
|
||||||
|
pub mod isolate_popup;
|
||||||
pub mod layers;
|
pub mod layers;
|
||||||
pub mod layout_manager;
|
pub mod layout_manager;
|
||||||
pub mod mleaderstyle;
|
pub mod mleaderstyle;
|
||||||
|
|
@ -26,10 +25,13 @@ pub mod snap_popup;
|
||||||
pub mod statusbar;
|
pub mod statusbar;
|
||||||
pub mod statusbar_config;
|
pub mod statusbar_config;
|
||||||
pub mod statusbar_menu;
|
pub mod statusbar_menu;
|
||||||
|
pub mod style_list;
|
||||||
|
pub mod style_manager;
|
||||||
pub mod tablestyle;
|
pub mod tablestyle;
|
||||||
pub mod text_util;
|
pub mod text_util;
|
||||||
pub mod textstyle;
|
pub mod textstyle;
|
||||||
pub mod units_popup;
|
pub mod units_popup;
|
||||||
|
pub mod update_notice;
|
||||||
|
|
||||||
pub use app_menu::AppMenu;
|
pub use app_menu::AppMenu;
|
||||||
pub use command_line::CommandLine;
|
pub use command_line::CommandLine;
|
||||||
|
|
|
||||||
43
src/ui/style_list.rs
Normal file
43
src/ui/style_list.rs
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
//! Shared style-manager list row.
|
||||||
|
//!
|
||||||
|
//! Every style manager renders the same left-hand list of style names where a
|
||||||
|
//! single click selects and a double click renames inline. Only the row's
|
||||||
|
//! button style differs per manager, so it is passed in.
|
||||||
|
|
||||||
|
use crate::app::{Message, StyleKind};
|
||||||
|
use iced::widget::button::{Status, Style};
|
||||||
|
use iced::widget::{button, mouse_area, text, text_input};
|
||||||
|
use iced::{Element, Fill, Theme};
|
||||||
|
|
||||||
|
/// One row of the style list. Renders an editable `text_input` when `name` is
|
||||||
|
/// the style being renamed (`rename_active`), otherwise a selectable button
|
||||||
|
/// wrapped in a `mouse_area` whose double click starts the rename.
|
||||||
|
pub fn item<'a>(
|
||||||
|
name: &str,
|
||||||
|
label: String,
|
||||||
|
kind: StyleKind,
|
||||||
|
on_select: Message,
|
||||||
|
rename_active: Option<&str>,
|
||||||
|
rename_buf: &'a str,
|
||||||
|
style: impl Fn(&Theme, Status) -> Style + 'a,
|
||||||
|
) -> Element<'a, Message> {
|
||||||
|
if rename_active == Some(name) {
|
||||||
|
text_input("", rename_buf)
|
||||||
|
.on_input(Message::StyleRenameEdit)
|
||||||
|
.on_submit(Message::StyleRenameCommit(kind))
|
||||||
|
.size(11)
|
||||||
|
.padding([4, 8])
|
||||||
|
.width(Fill)
|
||||||
|
.into()
|
||||||
|
} else {
|
||||||
|
mouse_area(
|
||||||
|
button(text(label).size(11))
|
||||||
|
.on_press(on_select)
|
||||||
|
.style(style)
|
||||||
|
.padding([4, 8])
|
||||||
|
.width(Fill),
|
||||||
|
)
|
||||||
|
.on_double_click(Message::StyleRenameStart(kind, name.to_string()))
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
262
src/ui/style_manager.rs
Normal file
262
src/ui/style_manager.rs
Normal file
|
|
@ -0,0 +1,262 @@
|
||||||
|
//! Shared scaffold for every style-manager window.
|
||||||
|
//!
|
||||||
|
//! All five managers (text / dimension / table / multileader / multiline)
|
||||||
|
//! share the same frame: a top toolbar (New / Copy / Delete on the left,
|
||||||
|
//! manager-specific actions such as Set Current / Apply on the right), a style
|
||||||
|
//! list on the left, and a property editor on the right. Only the editor
|
||||||
|
//! differs, so each manager builds just that and hands it to [`view`]; the
|
||||||
|
//! toolbar, list, inline-rename wiring and chrome live here once.
|
||||||
|
|
||||||
|
use crate::app::{Message, StyleKind};
|
||||||
|
use iced::widget::button::{Status, Style};
|
||||||
|
use iced::widget::{button, column, container, row, scrollable, text, Space};
|
||||||
|
use iced::{Background, Border, Color, Element, Fill, Theme};
|
||||||
|
|
||||||
|
const TB: Color = Color {
|
||||||
|
r: 0.13,
|
||||||
|
g: 0.13,
|
||||||
|
b: 0.13,
|
||||||
|
a: 1.0,
|
||||||
|
};
|
||||||
|
const BG: Color = Color {
|
||||||
|
r: 0.15,
|
||||||
|
g: 0.15,
|
||||||
|
b: 0.15,
|
||||||
|
a: 1.0,
|
||||||
|
};
|
||||||
|
const BORDER: Color = Color {
|
||||||
|
r: 0.35,
|
||||||
|
g: 0.35,
|
||||||
|
b: 0.35,
|
||||||
|
a: 1.0,
|
||||||
|
};
|
||||||
|
const TEXT: Color = Color {
|
||||||
|
r: 0.88,
|
||||||
|
g: 0.88,
|
||||||
|
b: 0.88,
|
||||||
|
a: 1.0,
|
||||||
|
};
|
||||||
|
const DIM: Color = Color {
|
||||||
|
r: 0.55,
|
||||||
|
g: 0.55,
|
||||||
|
b: 0.55,
|
||||||
|
a: 1.0,
|
||||||
|
};
|
||||||
|
const ACCENT: Color = Color {
|
||||||
|
r: 0.25,
|
||||||
|
g: 0.50,
|
||||||
|
b: 0.85,
|
||||||
|
a: 1.0,
|
||||||
|
};
|
||||||
|
const ACTIVE: Color = Color {
|
||||||
|
r: 0.20,
|
||||||
|
g: 0.40,
|
||||||
|
b: 0.70,
|
||||||
|
a: 1.0,
|
||||||
|
};
|
||||||
|
const LIST: Color = Color {
|
||||||
|
r: 0.12,
|
||||||
|
g: 0.12,
|
||||||
|
b: 0.12,
|
||||||
|
a: 1.0,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Everything the shared frame needs. The per-manager `editor` element is the
|
||||||
|
/// only bespoke part.
|
||||||
|
///
|
||||||
|
/// The toolbar is uniform across every manager: New / Copy / Delete on the
|
||||||
|
/// left, then **Set Current** and **Apply** on the right. Each manager just
|
||||||
|
/// supplies the two messages, so the right side can never drift or go missing.
|
||||||
|
///
|
||||||
|
/// Two lifetimes: `'a` is what the returned element keeps alive (the editor and
|
||||||
|
/// the rename buffer the inline `text_input` borrows); `'b` is the transient
|
||||||
|
/// list data (`styles`, `selected`, …) that the frame only reads while building
|
||||||
|
/// rows, so callers may pass a locally-built `Vec`.
|
||||||
|
pub struct Scaffold<'a, 'b> {
|
||||||
|
pub kind: StyleKind,
|
||||||
|
pub styles: &'b [String],
|
||||||
|
pub selected: &'b str,
|
||||||
|
/// Current style for this manager, marked with a ◀ in the list. `None`
|
||||||
|
/// when the manager has no "current" concept.
|
||||||
|
pub current: Option<&'b str>,
|
||||||
|
pub rename_active: Option<&'b str>,
|
||||||
|
pub rename_buf: &'a str,
|
||||||
|
pub on_new: Message,
|
||||||
|
pub on_copy: Message,
|
||||||
|
pub on_delete: Message,
|
||||||
|
/// Tuple-variant constructor for the per-row select message
|
||||||
|
/// (e.g. `Message::TextStyleDialogSelect`).
|
||||||
|
pub on_select: fn(String) -> Message,
|
||||||
|
/// "Set Current" action (right side). Every manager has one.
|
||||||
|
pub on_set_current: Message,
|
||||||
|
/// "Apply" action (right side, primary). Every manager has one.
|
||||||
|
pub on_apply: Message,
|
||||||
|
pub editor: Element<'a, Message>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn view<'a, 'b>(s: Scaffold<'a, 'b>) -> Element<'a, Message> {
|
||||||
|
// ── Toolbar: New / Copy / Delete | … | Set Current / Apply ────────────
|
||||||
|
let bar = row![
|
||||||
|
tb_button("New", s.on_new, false),
|
||||||
|
tb_button("Copy", s.on_copy, false),
|
||||||
|
tb_button("Delete", s.on_delete, false),
|
||||||
|
Space::new().width(Fill),
|
||||||
|
tb_button("Set Current", s.on_set_current, false),
|
||||||
|
tb_button("Apply", s.on_apply, true),
|
||||||
|
]
|
||||||
|
.spacing(4)
|
||||||
|
.align_y(iced::Center);
|
||||||
|
let toolbar = container(bar)
|
||||||
|
.style(|_: &Theme| container::Style {
|
||||||
|
background: Some(Background::Color(TB)),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.width(Fill)
|
||||||
|
.padding([5, 8]);
|
||||||
|
|
||||||
|
// ── Left: style list (single click selects, double click renames) ─────
|
||||||
|
let rows: Vec<Element<'_, Message>> = s
|
||||||
|
.styles
|
||||||
|
.iter()
|
||||||
|
.map(|name| {
|
||||||
|
let is_sel = name.as_str() == s.selected;
|
||||||
|
let label = if s.current == Some(name.as_str()) {
|
||||||
|
format!("{name} ◀")
|
||||||
|
} else {
|
||||||
|
name.clone()
|
||||||
|
};
|
||||||
|
crate::ui::style_list::item(
|
||||||
|
name,
|
||||||
|
label,
|
||||||
|
s.kind,
|
||||||
|
(s.on_select)(name.clone()),
|
||||||
|
s.rename_active,
|
||||||
|
s.rename_buf,
|
||||||
|
list_item(is_sel),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let list_panel = container(
|
||||||
|
column![
|
||||||
|
text("Styles").size(10).color(DIM),
|
||||||
|
container(scrollable(column(rows).spacing(1)).height(Fill))
|
||||||
|
.style(|_: &Theme| container::Style {
|
||||||
|
background: Some(Background::Color(LIST)),
|
||||||
|
border: Border {
|
||||||
|
color: BORDER,
|
||||||
|
width: 1.0,
|
||||||
|
radius: 3.0.into()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.width(Fill)
|
||||||
|
.height(Fill)
|
||||||
|
.padding(2),
|
||||||
|
]
|
||||||
|
.spacing(4)
|
||||||
|
.height(Fill),
|
||||||
|
)
|
||||||
|
.width(170)
|
||||||
|
.height(Fill)
|
||||||
|
.padding(iced::Padding {
|
||||||
|
top: 12.0,
|
||||||
|
right: 8.0,
|
||||||
|
bottom: 12.0,
|
||||||
|
left: 12.0,
|
||||||
|
});
|
||||||
|
|
||||||
|
let body = row![list_panel, vsep(), s.editor].height(Fill);
|
||||||
|
|
||||||
|
container(column![toolbar, hdivider(), body])
|
||||||
|
.style(|_: &Theme| container::Style {
|
||||||
|
background: Some(Background::Color(BG)),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.width(Fill)
|
||||||
|
.height(Fill)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Shared chrome ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
fn tb_button<'a>(label: &'a str, msg: Message, accent: bool) -> Element<'a, Message> {
|
||||||
|
let pad = if accent { [4, 14] } else { [4, 10] };
|
||||||
|
button(text(label).size(11))
|
||||||
|
.on_press(msg)
|
||||||
|
.style(btn_s(accent))
|
||||||
|
.padding(pad)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn btn_s(accent: bool) -> impl Fn(&Theme, Status) -> Style {
|
||||||
|
move |_: &Theme, st| Style {
|
||||||
|
background: Some(Background::Color(match (accent, st) {
|
||||||
|
(true, Status::Hovered | Status::Pressed) => Color {
|
||||||
|
r: 0.20,
|
||||||
|
g: 0.42,
|
||||||
|
b: 0.72,
|
||||||
|
a: 1.0,
|
||||||
|
},
|
||||||
|
(false, Status::Hovered | Status::Pressed) => Color {
|
||||||
|
r: 0.28,
|
||||||
|
g: 0.28,
|
||||||
|
b: 0.28,
|
||||||
|
a: 1.0,
|
||||||
|
},
|
||||||
|
(true, _) => ACCENT,
|
||||||
|
_ => Color {
|
||||||
|
r: 0.22,
|
||||||
|
g: 0.22,
|
||||||
|
b: 0.22,
|
||||||
|
a: 1.0,
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
text_color: TEXT,
|
||||||
|
border: Border {
|
||||||
|
color: BORDER,
|
||||||
|
width: 1.0,
|
||||||
|
radius: 4.0.into(),
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn list_item(active: bool) -> impl Fn(&Theme, Status) -> Style {
|
||||||
|
move |_: &Theme, st| Style {
|
||||||
|
background: Some(Background::Color(match (active, st) {
|
||||||
|
(true, _) => ACTIVE,
|
||||||
|
(false, Status::Hovered | Status::Pressed) => Color {
|
||||||
|
r: 0.26,
|
||||||
|
g: 0.26,
|
||||||
|
b: 0.26,
|
||||||
|
a: 1.0,
|
||||||
|
},
|
||||||
|
_ => Color::TRANSPARENT,
|
||||||
|
})),
|
||||||
|
text_color: TEXT,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hdivider<'a>() -> Element<'a, Message> {
|
||||||
|
container(Space::new().width(Fill).height(1))
|
||||||
|
.width(Fill)
|
||||||
|
.height(1)
|
||||||
|
.style(|_: &Theme| container::Style {
|
||||||
|
background: Some(Background::Color(BORDER)),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn vsep<'a>() -> Element<'a, Message> {
|
||||||
|
container(Space::new().width(1).height(Fill))
|
||||||
|
.width(1)
|
||||||
|
.height(Fill)
|
||||||
|
.style(|_: &Theme| container::Style {
|
||||||
|
background: Some(Background::Color(BORDER)),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
@ -7,18 +7,6 @@ use iced::widget::{
|
||||||
};
|
};
|
||||||
use iced::{Background, Border, Color, Element, Fill, Theme};
|
use iced::{Background, Border, Color, Element, Fill, Theme};
|
||||||
|
|
||||||
const TB: Color = Color {
|
|
||||||
r: 0.13,
|
|
||||||
g: 0.13,
|
|
||||||
b: 0.13,
|
|
||||||
a: 1.0,
|
|
||||||
};
|
|
||||||
const BG: Color = Color {
|
|
||||||
r: 0.15,
|
|
||||||
g: 0.15,
|
|
||||||
b: 0.15,
|
|
||||||
a: 1.0,
|
|
||||||
};
|
|
||||||
const BORDER: Color = Color {
|
const BORDER: Color = Color {
|
||||||
r: 0.35,
|
r: 0.35,
|
||||||
g: 0.35,
|
g: 0.35,
|
||||||
|
|
@ -43,18 +31,6 @@ const ACCENT: Color = Color {
|
||||||
b: 0.85,
|
b: 0.85,
|
||||||
a: 1.0,
|
a: 1.0,
|
||||||
};
|
};
|
||||||
const ACTIVE: Color = Color {
|
|
||||||
r: 0.20,
|
|
||||||
g: 0.40,
|
|
||||||
b: 0.70,
|
|
||||||
a: 1.0,
|
|
||||||
};
|
|
||||||
const LIST: Color = Color {
|
|
||||||
r: 0.12,
|
|
||||||
g: 0.12,
|
|
||||||
b: 0.12,
|
|
||||||
a: 1.0,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn btn_s(accent: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
|
fn btn_s(accent: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
|
||||||
move |_: &Theme, st| button::Style {
|
move |_: &Theme, st| button::Style {
|
||||||
|
|
@ -89,45 +65,6 @@ fn btn_s(accent: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn list_item(active: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
|
|
||||||
move |_: &Theme, st| button::Style {
|
|
||||||
background: Some(Background::Color(match (active, st) {
|
|
||||||
(true, _) => ACTIVE,
|
|
||||||
(false, button::Status::Hovered | button::Status::Pressed) => Color {
|
|
||||||
r: 0.26,
|
|
||||||
g: 0.26,
|
|
||||||
b: 0.26,
|
|
||||||
a: 1.0,
|
|
||||||
},
|
|
||||||
_ => Color::TRANSPARENT,
|
|
||||||
})),
|
|
||||||
text_color: TEXT,
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn hdivider<'a>() -> Element<'a, Message> {
|
|
||||||
container(Space::new().width(Fill).height(1))
|
|
||||||
.width(Fill)
|
|
||||||
.height(1)
|
|
||||||
.style(|_: &Theme| container::Style {
|
|
||||||
background: Some(Background::Color(BORDER)),
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn vsep<'a>() -> Element<'a, Message> {
|
|
||||||
container(Space::new().width(1).height(Fill))
|
|
||||||
.width(1)
|
|
||||||
.height(Fill)
|
|
||||||
.style(|_: &Theme| container::Style {
|
|
||||||
background: Some(Background::Color(BORDER)),
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn view_window<'a>(
|
pub fn view_window<'a>(
|
||||||
styles: Vec<String>,
|
styles: Vec<String>,
|
||||||
selected: &'a str,
|
selected: &'a str,
|
||||||
|
|
@ -145,72 +82,9 @@ pub fn view_window<'a>(
|
||||||
border_lw: &'a [[String; 6]; 3],
|
border_lw: &'a [[String; 6]; 3],
|
||||||
border_color: &'a [[String; 6]; 3],
|
border_color: &'a [[String; 6]; 3],
|
||||||
border_spacing: &'a [[String; 6]; 3],
|
border_spacing: &'a [[String; 6]; 3],
|
||||||
|
rename_active: Option<&'a str>,
|
||||||
|
rename_buf: &'a str,
|
||||||
) -> Element<'a, Message> {
|
) -> Element<'a, Message> {
|
||||||
// ── Toolbar ───────────────────────────────────────────────────────────
|
|
||||||
let toolbar = container(
|
|
||||||
row![
|
|
||||||
button(text("New").size(11))
|
|
||||||
.on_press(Message::TableStyleDialogNew)
|
|
||||||
.style(btn_s(true))
|
|
||||||
.padding([4, 10]),
|
|
||||||
button(text("Delete").size(11))
|
|
||||||
.on_press(Message::TableStyleDialogDelete)
|
|
||||||
.style(btn_s(false))
|
|
||||||
.padding([4, 10]),
|
|
||||||
]
|
|
||||||
.spacing(4)
|
|
||||||
.align_y(iced::Center),
|
|
||||||
)
|
|
||||||
.style(|_: &Theme| container::Style {
|
|
||||||
background: Some(Background::Color(TB)),
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.width(Fill)
|
|
||||||
.padding([5, 8]);
|
|
||||||
|
|
||||||
// ── Left: Style list ──────────────────────────────────────────────────
|
|
||||||
let style_items: Vec<Element<'_, Message>> = styles
|
|
||||||
.iter()
|
|
||||||
.map(|name| {
|
|
||||||
let is_sel = name.as_str() == selected;
|
|
||||||
button(text(name.clone()).size(11))
|
|
||||||
.on_press(Message::TableStyleDialogSelect(name.clone()))
|
|
||||||
.style(list_item(is_sel))
|
|
||||||
.padding([4, 8])
|
|
||||||
.width(Fill)
|
|
||||||
.into()
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let style_list = container(
|
|
||||||
column![
|
|
||||||
text("Styles").size(10).color(DIM),
|
|
||||||
container(scrollable(column(style_items).spacing(2)).height(Fill))
|
|
||||||
.style(|_: &Theme| container::Style {
|
|
||||||
background: Some(Background::Color(LIST)),
|
|
||||||
border: Border {
|
|
||||||
color: BORDER,
|
|
||||||
width: 1.0,
|
|
||||||
radius: 3.0.into()
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.width(Fill)
|
|
||||||
.height(Fill)
|
|
||||||
.padding(2),
|
|
||||||
]
|
|
||||||
.spacing(4)
|
|
||||||
.height(Fill),
|
|
||||||
)
|
|
||||||
.width(200)
|
|
||||||
.height(Fill)
|
|
||||||
.padding(iced::Padding {
|
|
||||||
top: 12.0,
|
|
||||||
right: 8.0,
|
|
||||||
bottom: 12.0,
|
|
||||||
left: 12.0,
|
|
||||||
});
|
|
||||||
|
|
||||||
// ── Right: Details panel ──────────────────────────────────────────────
|
// ── Right: Details panel ──────────────────────────────────────────────
|
||||||
let info_row = |label: &'static str, val: String| -> Element<'_, Message> {
|
let info_row = |label: &'static str, val: String| -> Element<'_, Message> {
|
||||||
row![
|
row![
|
||||||
|
|
@ -235,7 +109,11 @@ pub fn view_window<'a>(
|
||||||
row![
|
row![
|
||||||
text(label).size(11).color(DIM).width(150),
|
text(label).size(11).color(DIM).width(150),
|
||||||
text_input(placeholder, value)
|
text_input(placeholder, value)
|
||||||
.on_input(move |v| Message::TableStyleCellEdit { row, field, value: v })
|
.on_input(move |v| Message::TableStyleCellEdit {
|
||||||
|
row,
|
||||||
|
field,
|
||||||
|
value: v
|
||||||
|
})
|
||||||
.size(11)
|
.size(11)
|
||||||
.width(100),
|
.width(100),
|
||||||
]
|
]
|
||||||
|
|
@ -246,10 +124,25 @@ pub fn view_window<'a>(
|
||||||
let mut col = Column::new()
|
let mut col = Column::new()
|
||||||
.spacing(3)
|
.spacing(3)
|
||||||
.push(text(row_label).size(11).color(ACCENT))
|
.push(text(row_label).size(11).color(ACCENT))
|
||||||
.push(cell_in(" Text style:", "Standard", &cell_textstyle[r], "textstyle"))
|
.push(cell_in(
|
||||||
|
" Text style:",
|
||||||
|
"Standard",
|
||||||
|
&cell_textstyle[r],
|
||||||
|
"textstyle",
|
||||||
|
))
|
||||||
.push(cell_in(" Text height:", "0.18", &cell_height[r], "height"))
|
.push(cell_in(" Text height:", "0.18", &cell_height[r], "height"))
|
||||||
.push(cell_in(" Text color (ACI):", "256", &cell_textcolor[r], "textcolor"))
|
.push(cell_in(
|
||||||
.push(cell_in(" Fill color (ACI):", "256", &cell_fillcolor[r], "fillcolor"))
|
" Text color (ACI):",
|
||||||
|
"256",
|
||||||
|
&cell_textcolor[r],
|
||||||
|
"textcolor",
|
||||||
|
))
|
||||||
|
.push(cell_in(
|
||||||
|
" Fill color (ACI):",
|
||||||
|
"256",
|
||||||
|
&cell_fillcolor[r],
|
||||||
|
"fillcolor",
|
||||||
|
))
|
||||||
.push(
|
.push(
|
||||||
row![
|
row![
|
||||||
text(" Alignment:").size(11).color(DIM).width(150),
|
text(" Alignment:").size(11).color(DIM).width(150),
|
||||||
|
|
@ -287,7 +180,11 @@ pub fn view_window<'a>(
|
||||||
.push(cell_in(" Data type:", "0", &cell_datatype[r], "datatype"))
|
.push(cell_in(" Data type:", "0", &cell_datatype[r], "datatype"))
|
||||||
.push(cell_in(" Unit type:", "0", &cell_unittype[r], "unittype"))
|
.push(cell_in(" Unit type:", "0", &cell_unittype[r], "unittype"))
|
||||||
.push(cell_in(" Format string:", "", &cell_format[r], "format"))
|
.push(cell_in(" Format string:", "", &cell_format[r], "format"))
|
||||||
.push(text(" Borders (type / weight / color / spacing / hidden)").size(10).color(DIM));
|
.push(
|
||||||
|
text(" Borders (type / weight / color / spacing / hidden)")
|
||||||
|
.size(10)
|
||||||
|
.color(DIM),
|
||||||
|
);
|
||||||
|
|
||||||
let borders: [(&'static str, &acadrust::objects::TableCellBorder); 6] = [
|
let borders: [(&'static str, &acadrust::objects::TableCellBorder); 6] = [
|
||||||
("L", &rs.left_border),
|
("L", &rs.left_border),
|
||||||
|
|
@ -303,26 +200,51 @@ pub fn view_window<'a>(
|
||||||
row![
|
row![
|
||||||
text(format!(" {bname}")).size(11).color(DIM).width(28),
|
text(format!(" {bname}")).size(11).color(DIM).width(28),
|
||||||
pick_list(
|
pick_list(
|
||||||
["Single", "Double"].iter().map(|s| s.to_string()).collect::<Vec<_>>(),
|
["Single", "Double"]
|
||||||
|
.iter()
|
||||||
|
.map(|s| s.to_string())
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
Some(format!("{:?}", bd.border_type)),
|
Some(format!("{:?}", bd.border_type)),
|
||||||
move |value| Message::TableStyleBorderSetType { cell: row, border: bu, value },
|
move |value| Message::TableStyleBorderSetType {
|
||||||
|
cell: row,
|
||||||
|
border: bu,
|
||||||
|
value
|
||||||
|
},
|
||||||
)
|
)
|
||||||
.text_size(10)
|
.text_size(10)
|
||||||
.width(74),
|
.width(74),
|
||||||
text_input("wt", &border_lw[r][b])
|
text_input("wt", &border_lw[r][b])
|
||||||
.on_input(move |v| Message::TableStyleBorderEdit { cell: row, border: bu, field: "lw", value: v })
|
.on_input(move |v| Message::TableStyleBorderEdit {
|
||||||
|
cell: row,
|
||||||
|
border: bu,
|
||||||
|
field: "lw",
|
||||||
|
value: v
|
||||||
|
})
|
||||||
.size(10)
|
.size(10)
|
||||||
.width(46),
|
.width(46),
|
||||||
text_input("clr", &border_color[r][b])
|
text_input("clr", &border_color[r][b])
|
||||||
.on_input(move |v| Message::TableStyleBorderEdit { cell: row, border: bu, field: "color", value: v })
|
.on_input(move |v| Message::TableStyleBorderEdit {
|
||||||
|
cell: row,
|
||||||
|
border: bu,
|
||||||
|
field: "color",
|
||||||
|
value: v
|
||||||
|
})
|
||||||
.size(10)
|
.size(10)
|
||||||
.width(46),
|
.width(46),
|
||||||
text_input("gap", &border_spacing[r][b])
|
text_input("gap", &border_spacing[r][b])
|
||||||
.on_input(move |v| Message::TableStyleBorderEdit { cell: row, border: bu, field: "spacing", value: v })
|
.on_input(move |v| Message::TableStyleBorderEdit {
|
||||||
|
cell: row,
|
||||||
|
border: bu,
|
||||||
|
field: "spacing",
|
||||||
|
value: v
|
||||||
|
})
|
||||||
.size(10)
|
.size(10)
|
||||||
.width(46),
|
.width(46),
|
||||||
checkbox(bd.is_invisible)
|
checkbox(bd.is_invisible)
|
||||||
.on_toggle(move |_| Message::TableStyleBorderToggleInvisible { cell: row, border: bu })
|
.on_toggle(move |_| Message::TableStyleBorderToggleInvisible {
|
||||||
|
cell: row,
|
||||||
|
border: bu
|
||||||
|
})
|
||||||
.size(13),
|
.size(13),
|
||||||
]
|
]
|
||||||
.spacing(5)
|
.spacing(5)
|
||||||
|
|
@ -346,7 +268,10 @@ pub fn view_window<'a>(
|
||||||
row![
|
row![
|
||||||
text("Description:").size(11).color(DIM).width(160),
|
text("Description:").size(11).color(DIM).width(160),
|
||||||
text_input("", description_buf)
|
text_input("", description_buf)
|
||||||
.on_input(|v| Message::TableStyleEdit { field: "description", value: v })
|
.on_input(|v| Message::TableStyleEdit {
|
||||||
|
field: "description",
|
||||||
|
value: v
|
||||||
|
})
|
||||||
.size(11)
|
.size(11)
|
||||||
.width(160),
|
.width(160),
|
||||||
]
|
]
|
||||||
|
|
@ -355,7 +280,10 @@ pub fn view_window<'a>(
|
||||||
row![
|
row![
|
||||||
text("Flow direction:").size(11).color(DIM).width(160),
|
text("Flow direction:").size(11).color(DIM).width(160),
|
||||||
pick_list(
|
pick_list(
|
||||||
["Down", "Up"].iter().map(|s| s.to_string()).collect::<Vec<_>>(),
|
["Down", "Up"]
|
||||||
|
.iter()
|
||||||
|
.map(|s| s.to_string())
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
Some(format!("{:?}", s.flow_direction)),
|
Some(format!("{:?}", s.flow_direction)),
|
||||||
Message::TableStyleSetFlow,
|
Message::TableStyleSetFlow,
|
||||||
)
|
)
|
||||||
|
|
@ -372,7 +300,10 @@ pub fn view_window<'a>(
|
||||||
row![
|
row![
|
||||||
text("H Margin:").size(11).color(DIM).width(160),
|
text("H Margin:").size(11).color(DIM).width(160),
|
||||||
text_input("1.5", hmargin_buf)
|
text_input("1.5", hmargin_buf)
|
||||||
.on_input(|v| Message::TableStyleEdit { field: "hmargin", value: v })
|
.on_input(|v| Message::TableStyleEdit {
|
||||||
|
field: "hmargin",
|
||||||
|
value: v
|
||||||
|
})
|
||||||
.size(11)
|
.size(11)
|
||||||
.width(100),
|
.width(100),
|
||||||
]
|
]
|
||||||
|
|
@ -381,7 +312,10 @@ pub fn view_window<'a>(
|
||||||
row![
|
row![
|
||||||
text("V Margin:").size(11).color(DIM).width(160),
|
text("V Margin:").size(11).color(DIM).width(160),
|
||||||
text_input("1.5", vmargin_buf)
|
text_input("1.5", vmargin_buf)
|
||||||
.on_input(|v| Message::TableStyleEdit { field: "vmargin", value: v })
|
.on_input(|v| Message::TableStyleEdit {
|
||||||
|
field: "vmargin",
|
||||||
|
value: v
|
||||||
|
})
|
||||||
.size(11)
|
.size(11)
|
||||||
.width(100),
|
.width(100),
|
||||||
]
|
]
|
||||||
|
|
@ -419,14 +353,19 @@ pub fn view_window<'a>(
|
||||||
|
|
||||||
let right_panel = container(details).width(Fill).height(Fill);
|
let right_panel = container(details).width(Fill).height(Fill);
|
||||||
|
|
||||||
let body = row![style_list, vsep(), right_panel].height(Fill);
|
crate::ui::style_manager::view(crate::ui::style_manager::Scaffold {
|
||||||
|
kind: crate::app::StyleKind::Table,
|
||||||
container(column![toolbar, hdivider(), body].spacing(0))
|
styles: &styles,
|
||||||
.style(|_: &Theme| container::Style {
|
selected,
|
||||||
background: Some(Background::Color(BG)),
|
current: None,
|
||||||
..Default::default()
|
rename_active,
|
||||||
})
|
rename_buf,
|
||||||
.width(Fill)
|
on_new: Message::TableStyleDialogNew,
|
||||||
.height(Fill)
|
on_copy: Message::TableStyleDialogCopy,
|
||||||
.into()
|
on_delete: Message::TableStyleDialogDelete,
|
||||||
|
on_select: Message::TableStyleDialogSelect,
|
||||||
|
on_set_current: Message::TableStyleDialogSetCurrent,
|
||||||
|
on_apply: Message::TableStyleApply,
|
||||||
|
editor: right_panel.into(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
//! Text Style Font Browser window — fills the entire OS window.
|
//! Text Style Font Browser window — fills the entire OS window.
|
||||||
|
|
||||||
use crate::app::Message;
|
use crate::app::Message;
|
||||||
|
use crate::app::StyleKind;
|
||||||
use iced::widget::{button, checkbox, column, container, row, scrollable, text, text_input, Space};
|
use iced::widget::{button, checkbox, column, container, row, scrollable, text, text_input, Space};
|
||||||
use iced::{Background, Border, Color, Element, Fill, Theme};
|
use iced::{Background, Border, Color, Element, Fill, Theme};
|
||||||
|
|
||||||
|
|
@ -17,20 +18,12 @@ pub struct TextStyleView<'a> {
|
||||||
pub backward: bool,
|
pub backward: bool,
|
||||||
pub upside_down: bool,
|
pub upside_down: bool,
|
||||||
pub annotative: bool,
|
pub annotative: bool,
|
||||||
|
/// Name of the style being renamed inline (double-clicked), if any.
|
||||||
|
pub rename_active: Option<&'a str>,
|
||||||
|
/// Edit buffer for the inline rename text input.
|
||||||
|
pub rename_buf: &'a str,
|
||||||
}
|
}
|
||||||
|
|
||||||
const TB: Color = Color {
|
|
||||||
r: 0.13,
|
|
||||||
g: 0.13,
|
|
||||||
b: 0.13,
|
|
||||||
a: 1.0,
|
|
||||||
};
|
|
||||||
const BG: Color = Color {
|
|
||||||
r: 0.15,
|
|
||||||
g: 0.15,
|
|
||||||
b: 0.15,
|
|
||||||
a: 1.0,
|
|
||||||
};
|
|
||||||
const BORDER: Color = Color {
|
const BORDER: Color = Color {
|
||||||
r: 0.35,
|
r: 0.35,
|
||||||
g: 0.35,
|
g: 0.35,
|
||||||
|
|
@ -75,61 +68,11 @@ const LIST: Color = Color {
|
||||||
};
|
};
|
||||||
|
|
||||||
const BUILTIN_FONTS: &[&str] = &[
|
const BUILTIN_FONTS: &[&str] = &[
|
||||||
"Standard",
|
"Standard", "ISO", "Simplex", "RomanS", "RomanD", "RomanC", "RomanT", "ItalicC", "ItalicT",
|
||||||
"ISO",
|
"ScriptS", "ScriptC", "GothGBT", "GothGRT", "GothITT", "Cursive", "GreekC", "Symbol", "ISO",
|
||||||
"Simplex",
|
"ISO3098", "Unicode",
|
||||||
"RomanS",
|
|
||||||
"RomanD",
|
|
||||||
"RomanC",
|
|
||||||
"RomanT",
|
|
||||||
"ItalicC",
|
|
||||||
"ItalicT",
|
|
||||||
"ScriptS",
|
|
||||||
"ScriptC",
|
|
||||||
"GothGBT",
|
|
||||||
"GothGRT",
|
|
||||||
"GothITT",
|
|
||||||
"Cursive",
|
|
||||||
"GreekC",
|
|
||||||
"Symbol",
|
|
||||||
"ISO",
|
|
||||||
"ISO3098",
|
|
||||||
"Unicode",
|
|
||||||
];
|
];
|
||||||
|
|
||||||
fn btn_s(accent: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
|
|
||||||
move |_: &Theme, st| button::Style {
|
|
||||||
background: Some(Background::Color(match (accent, st) {
|
|
||||||
(true, button::Status::Hovered | button::Status::Pressed) => Color {
|
|
||||||
r: 0.20,
|
|
||||||
g: 0.42,
|
|
||||||
b: 0.72,
|
|
||||||
a: 1.0,
|
|
||||||
},
|
|
||||||
(false, button::Status::Hovered | button::Status::Pressed) => Color {
|
|
||||||
r: 0.28,
|
|
||||||
g: 0.28,
|
|
||||||
b: 0.28,
|
|
||||||
a: 1.0,
|
|
||||||
},
|
|
||||||
(true, _) => ACCENT,
|
|
||||||
_ => Color {
|
|
||||||
r: 0.22,
|
|
||||||
g: 0.22,
|
|
||||||
b: 0.22,
|
|
||||||
a: 1.0,
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
text_color: TEXT,
|
|
||||||
border: Border {
|
|
||||||
color: BORDER,
|
|
||||||
width: 1.0,
|
|
||||||
radius: 4.0.into(),
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn list_item(active: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
|
fn list_item(active: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
|
||||||
move |_: &Theme, st| button::Style {
|
move |_: &Theme, st| button::Style {
|
||||||
background: Some(Background::Color(match (active, st) {
|
background: Some(Background::Color(match (active, st) {
|
||||||
|
|
@ -162,17 +105,6 @@ fn field_style(_: &Theme, _: text_input::Status) -> text_input::Style {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hdivider<'a>() -> Element<'a, Message> {
|
|
||||||
container(Space::new().width(Fill).height(1))
|
|
||||||
.width(Fill)
|
|
||||||
.height(1)
|
|
||||||
.style(|_: &Theme| container::Style {
|
|
||||||
background: Some(Background::Color(BORDER)),
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn vsep<'a>() -> Element<'a, Message> {
|
fn vsep<'a>() -> Element<'a, Message> {
|
||||||
container(Space::new().width(1).height(Fill))
|
container(Space::new().width(1).height(Fill))
|
||||||
.width(1)
|
.width(1)
|
||||||
|
|
@ -197,81 +129,9 @@ pub fn view_window<'a>(v: TextStyleView<'a>) -> Element<'a, Message> {
|
||||||
backward,
|
backward,
|
||||||
upside_down,
|
upside_down,
|
||||||
annotative,
|
annotative,
|
||||||
|
rename_active,
|
||||||
|
rename_buf,
|
||||||
} = v;
|
} = v;
|
||||||
// ── Toolbar ───────────────────────────────────────────────────────────
|
|
||||||
let toolbar = container(
|
|
||||||
row![
|
|
||||||
button(text("New").size(11))
|
|
||||||
.on_press(Message::TextStyleDialogNew)
|
|
||||||
.style(btn_s(false))
|
|
||||||
.padding([4, 10]),
|
|
||||||
button(text("Delete").size(11))
|
|
||||||
.on_press(Message::TextStyleDialogDelete)
|
|
||||||
.style(btn_s(false))
|
|
||||||
.padding([4, 10]),
|
|
||||||
Space::new().width(Fill),
|
|
||||||
button(text("Set Current").size(11))
|
|
||||||
.on_press(Message::TextStyleDialogSetCurrent)
|
|
||||||
.style(btn_s(false))
|
|
||||||
.padding([4, 10]),
|
|
||||||
button(text("Apply").size(11))
|
|
||||||
.on_press(Message::TextStyleApply)
|
|
||||||
.style(btn_s(true))
|
|
||||||
.padding([4, 14]),
|
|
||||||
]
|
|
||||||
.spacing(4)
|
|
||||||
.align_y(iced::Center),
|
|
||||||
)
|
|
||||||
.style(|_: &Theme| container::Style {
|
|
||||||
background: Some(Background::Color(TB)),
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.width(Fill)
|
|
||||||
.padding([5, 8]);
|
|
||||||
|
|
||||||
// ── Left: Style list ──────────────────────────────────────────────────
|
|
||||||
let style_items: Vec<Element<'_, Message>> = styles
|
|
||||||
.iter()
|
|
||||||
.map(|name| {
|
|
||||||
let is_sel = name.as_str() == selected;
|
|
||||||
button(text(name.clone()).size(11))
|
|
||||||
.on_press(Message::TextStyleDialogSelect(name.clone()))
|
|
||||||
.style(list_item(is_sel))
|
|
||||||
.padding([4, 8])
|
|
||||||
.width(Fill)
|
|
||||||
.into()
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let style_panel = container(
|
|
||||||
column![
|
|
||||||
text("Styles").size(10).color(DIM),
|
|
||||||
container(scrollable(column(style_items).spacing(1)).height(Fill))
|
|
||||||
.style(|_: &Theme| container::Style {
|
|
||||||
background: Some(Background::Color(LIST)),
|
|
||||||
border: Border {
|
|
||||||
color: BORDER,
|
|
||||||
width: 1.0,
|
|
||||||
radius: 3.0.into()
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.width(Fill)
|
|
||||||
.height(Fill)
|
|
||||||
.padding(2),
|
|
||||||
]
|
|
||||||
.spacing(4)
|
|
||||||
.height(Fill),
|
|
||||||
)
|
|
||||||
.width(170)
|
|
||||||
.height(Fill)
|
|
||||||
.padding(iced::Padding {
|
|
||||||
top: 12.0,
|
|
||||||
right: 8.0,
|
|
||||||
bottom: 12.0,
|
|
||||||
left: 12.0,
|
|
||||||
});
|
|
||||||
|
|
||||||
// ── Middle: Font browser ──────────────────────────────────────────────
|
// ── Middle: Font browser ──────────────────────────────────────────────
|
||||||
let font_items: Vec<Element<'_, Message>> = BUILTIN_FONTS
|
let font_items: Vec<Element<'_, Message>> = BUILTIN_FONTS
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -319,7 +179,12 @@ pub fn view_window<'a>(v: TextStyleView<'a>) -> Element<'a, Message> {
|
||||||
.padding([12, 8]);
|
.padding([12, 8]);
|
||||||
|
|
||||||
// Labeled numeric/text field row → TextStyleEdit { field, value }.
|
// Labeled numeric/text field row → TextStyleEdit { field, value }.
|
||||||
fn frow<'a>(label: &'a str, ph: &'a str, buf: &'a str, field: &'static str) -> Element<'a, Message> {
|
fn frow<'a>(
|
||||||
|
label: &'a str,
|
||||||
|
ph: &'a str,
|
||||||
|
buf: &'a str,
|
||||||
|
field: &'static str,
|
||||||
|
) -> Element<'a, Message> {
|
||||||
row![
|
row![
|
||||||
text(label).size(11).color(DIM).width(120),
|
text(label).size(11).color(DIM).width(120),
|
||||||
text_input(ph, buf)
|
text_input(ph, buf)
|
||||||
|
|
@ -387,14 +252,21 @@ pub fn view_window<'a>(v: TextStyleView<'a>) -> Element<'a, Message> {
|
||||||
left: 8.0,
|
left: 8.0,
|
||||||
});
|
});
|
||||||
|
|
||||||
let body = row![style_panel, vsep(), font_panel, vsep(), props_panel].height(Fill);
|
let editor = row![font_panel, vsep(), props_panel].height(Fill);
|
||||||
|
|
||||||
container(column![toolbar, hdivider(), body].spacing(0))
|
crate::ui::style_manager::view(crate::ui::style_manager::Scaffold {
|
||||||
.style(|_: &Theme| container::Style {
|
kind: StyleKind::Text,
|
||||||
background: Some(Background::Color(BG)),
|
styles: &styles,
|
||||||
..Default::default()
|
selected,
|
||||||
})
|
current: None,
|
||||||
.width(Fill)
|
rename_active,
|
||||||
.height(Fill)
|
rename_buf,
|
||||||
.into()
|
on_new: Message::TextStyleDialogNew,
|
||||||
|
on_copy: Message::TextStyleDialogCopy,
|
||||||
|
on_delete: Message::TextStyleDialogDelete,
|
||||||
|
on_select: Message::TextStyleDialogSelect,
|
||||||
|
on_set_current: Message::TextStyleDialogSetCurrent,
|
||||||
|
on_apply: Message::TextStyleApply,
|
||||||
|
editor: editor.into(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue