forked from 1b-agent/opencadstudio-upstream
feat: TableStyle manager (TABLESTYLE command + dialog)
- TABLESTYLE/TS command: LIST, NEW subcommands for managing table styles - Table Style Manager dialog: style list with details panel showing name, margins, suppression flags, and row styles (data/header/title) with text style, height, and alignment per row type - New / Delete buttons in the dialog Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
34076b26bb
commit
552ab7eb6a
4 files changed
+295
-1
No files matched your search
@@ -2085,6 +2085,59 @@ impl H7CAD {
|
||||
}
|
||||
|
||||
// ── DimStyle management ───────────────────────────────────────
|
||||
// TABLESTYLE — Table Style Manager.
|
||||
cmd if cmd == "TABLESTYLE" || cmd == "TS" || cmd.starts_with("TABLESTYLE ") => {
|
||||
use acadrust::objects::{TableStyle, ObjectType};
|
||||
let raw_rest = cmd.split_once(' ').map(|(_, r)| r.trim()).unwrap_or("");
|
||||
let parts: Vec<&str> = raw_rest.split_whitespace().collect();
|
||||
let sub = parts.first().map(|s| s.to_uppercase()).unwrap_or_default();
|
||||
match sub.as_str() {
|
||||
"" | "DIALOG" | "UI" => {
|
||||
return Task::done(Message::TableStyleDialogOpen);
|
||||
}
|
||||
"LIST" | "?" => {
|
||||
let doc = &self.tabs[i].scene.document;
|
||||
let styles: Vec<String> = doc.objects.values()
|
||||
.filter_map(|o| if let ObjectType::TableStyle(s) = o { Some(s) } else { None })
|
||||
.map(|s| format!("{} (h_margin:{:.2} v_margin:{:.2})", s.name, s.horizontal_margin, s.vertical_margin))
|
||||
.collect();
|
||||
if styles.is_empty() {
|
||||
self.command_line.push_output("No table styles.");
|
||||
} else {
|
||||
self.command_line.push_output(&format!("TableStyles:\n {}", styles.join("\n ")));
|
||||
}
|
||||
}
|
||||
"NEW" | "N" => {
|
||||
let name = parts.get(1).copied().unwrap_or("").to_string();
|
||||
if name.is_empty() {
|
||||
self.command_line.push_error("Usage: TABLESTYLE NEW <name>");
|
||||
} else {
|
||||
let doc = &self.tabs[i].scene.document;
|
||||
let exists = doc.objects.values().any(|o| {
|
||||
matches!(o, ObjectType::TableStyle(s) if s.name.eq_ignore_ascii_case(&name))
|
||||
});
|
||||
if exists {
|
||||
self.command_line.push_error(&format!("TABLESTYLE: '{}' already exists.", name));
|
||||
} else {
|
||||
self.push_undo_snapshot(i, "TABLESTYLE NEW");
|
||||
let mut style = TableStyle::standard();
|
||||
style.name = name.clone();
|
||||
let nh = acadrust::Handle::new(self.tabs[i].scene.document.next_handle());
|
||||
style.handle = nh;
|
||||
self.tabs[i].scene.document.objects.insert(nh, ObjectType::TableStyle(style));
|
||||
self.tabs[i].dirty = true;
|
||||
self.command_line.push_output(&format!("TABLESTYLE: '{}' created.", name));
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
self.command_line.push_error(
|
||||
"Usage: TABLESTYLE [LIST|NEW <name>]"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MLSTYLE — Multiline Style Manager.
|
||||
// Usage:
|
||||
// MLSTYLE — open dialog
|
||||
|
||||
@@ -94,6 +94,10 @@ pub(super) struct H7CAD {
|
||||
mlstyle_open: bool,
|
||||
mlstyle_selected: String,
|
||||
|
||||
// ── TableStyle Dialog ─────────────────────────────────────────────────
|
||||
tablestyle_open: bool,
|
||||
tablestyle_selected: String,
|
||||
|
||||
// ── DimStyle Dialog ───────────────────────────────────────────────────
|
||||
dimstyle_open: bool,
|
||||
/// Name of the style currently shown in the dialog.
|
||||
@@ -339,6 +343,12 @@ pub enum Message {
|
||||
PlotStyleLoaded(Option<crate::io::plot_style::PlotStyleTable>),
|
||||
/// Clear the active plot style table.
|
||||
PlotStyleClear,
|
||||
// ── TableStyle Dialog ─────────────────────────────────────────────────
|
||||
TableStyleDialogOpen,
|
||||
TableStyleDialogClose,
|
||||
TableStyleDialogSelect(String),
|
||||
TableStyleDialogNew,
|
||||
TableStyleDialogDelete,
|
||||
// ── MLineStyle Dialog ─────────────────────────────────────────────────
|
||||
MlStyleDialogOpen,
|
||||
MlStyleDialogClose,
|
||||
@@ -407,6 +417,9 @@ impl H7CAD {
|
||||
page_setup_scale: "Fit".to_string(),
|
||||
// Plot style
|
||||
active_plot_style: None,
|
||||
// TableStyle dialog
|
||||
tablestyle_open: false,
|
||||
tablestyle_selected: "Standard".to_string(),
|
||||
// MLineStyle dialog
|
||||
mlstyle_open: false,
|
||||
mlstyle_selected: "Standard".to_string(),
|
||||
|
||||
@@ -2126,6 +2126,73 @@ impl H7CAD {
|
||||
Task::none()
|
||||
}
|
||||
|
||||
// ── TableStyle Dialog ─────────────────────────────────────────────
|
||||
Message::TableStyleDialogOpen => {
|
||||
use acadrust::objects::ObjectType;
|
||||
let i = self.active_tab;
|
||||
self.tablestyle_selected = self.tabs[i].scene.document.objects.values()
|
||||
.find_map(|o| if let ObjectType::TableStyle(s) = o { Some(s.name.clone()) } else { None })
|
||||
.unwrap_or_else(|| "Standard".to_string());
|
||||
self.tablestyle_open = true;
|
||||
Task::none()
|
||||
}
|
||||
Message::TableStyleDialogClose => {
|
||||
self.tablestyle_open = false;
|
||||
Task::none()
|
||||
}
|
||||
Message::TableStyleDialogSelect(name) => {
|
||||
self.tablestyle_selected = name;
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::TableStyleDialogNew => {
|
||||
use acadrust::objects::ObjectType;
|
||||
let i = self.active_tab;
|
||||
let doc = &self.tabs[i].scene.document;
|
||||
let mut n = 1u32;
|
||||
let new_name = loop {
|
||||
let candidate = format!("TS{}", n);
|
||||
let taken = doc.objects.values().any(|o| {
|
||||
matches!(o, ObjectType::TableStyle(s) if s.name.eq_ignore_ascii_case(&candidate))
|
||||
});
|
||||
if !taken { break candidate; }
|
||||
n += 1;
|
||||
};
|
||||
self.push_undo_snapshot(i, "TABLESTYLE NEW");
|
||||
let mut style = acadrust::objects::TableStyle::standard();
|
||||
style.name = new_name.clone();
|
||||
let nh = acadrust::Handle::new(self.tabs[i].scene.document.next_handle());
|
||||
style.handle = nh;
|
||||
self.tabs[i].scene.document.objects.insert(nh, ObjectType::TableStyle(style));
|
||||
self.tablestyle_selected = new_name;
|
||||
self.tabs[i].dirty = true;
|
||||
Task::none()
|
||||
}
|
||||
Message::TableStyleDialogDelete => {
|
||||
use acadrust::objects::ObjectType;
|
||||
let i = self.active_tab;
|
||||
let name = self.tablestyle_selected.clone();
|
||||
if name.eq_ignore_ascii_case("Standard") {
|
||||
self.command_line.push_error("Cannot delete the Standard style.");
|
||||
return Task::none();
|
||||
}
|
||||
let handle = self.tabs[i].scene.document.objects.iter()
|
||||
.find_map(|(&h, o)| {
|
||||
if let ObjectType::TableStyle(s) = o {
|
||||
if s.name == name { Some(h) } else { None }
|
||||
} else { None }
|
||||
});
|
||||
if let Some(h) = handle {
|
||||
self.push_undo_snapshot(i, "TABLESTYLE DEL");
|
||||
self.tabs[i].scene.document.objects.remove(&h);
|
||||
self.tablestyle_selected = self.tabs[i].scene.document.objects.values()
|
||||
.find_map(|o| if let ObjectType::TableStyle(s) = o { Some(s.name.clone()) } else { None })
|
||||
.unwrap_or_else(|| "Standard".to_string());
|
||||
self.tabs[i].dirty = true;
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
||||
// ── MLineStyle Dialog ─────────────────────────────────────────────
|
||||
Message::MlStyleDialogOpen => {
|
||||
use acadrust::objects::ObjectType;
|
||||
|
||||
+162
-1
@@ -227,6 +227,21 @@ impl H7CAD {
|
||||
iced::widget::Space::new().width(0).height(0).into()
|
||||
};
|
||||
|
||||
let tablestyle_layer: Element<'_, Message> = if self.tablestyle_open {
|
||||
use acadrust::objects::ObjectType;
|
||||
let tab = &self.tabs[self.active_tab];
|
||||
let styles: Vec<String> = tab.scene.document.objects.values()
|
||||
.filter_map(|o| if let ObjectType::TableStyle(s) = o { Some(s.name.clone()) } else { None })
|
||||
.collect();
|
||||
let selected_style = tab.scene.document.objects.values()
|
||||
.find_map(|o| if let ObjectType::TableStyle(s) = o {
|
||||
if s.name == self.tablestyle_selected { Some(s) } else { None }
|
||||
} else { None });
|
||||
tablestyle_overlay(styles, &self.tablestyle_selected, selected_style)
|
||||
} else {
|
||||
iced::widget::Space::new().width(0).height(0).into()
|
||||
};
|
||||
|
||||
let mlstyle_layer: Element<'_, Message> = if self.mlstyle_open {
|
||||
use acadrust::objects::ObjectType;
|
||||
let tab = &self.tabs[self.active_tab];
|
||||
@@ -256,7 +271,7 @@ impl H7CAD {
|
||||
iced::widget::Space::new().width(0).height(0).into()
|
||||
};
|
||||
|
||||
stack![main_ui, self.app_menu.view(), snap_layer, dropdown_layer, layout_ctx_layer, page_setup_layer, mlstyle_layer, dimstyle_layer].into()
|
||||
stack![main_ui, self.app_menu.view(), snap_layer, dropdown_layer, layout_ctx_layer, page_setup_layer, tablestyle_layer, mlstyle_layer, dimstyle_layer].into()
|
||||
}
|
||||
|
||||
pub fn subscription(&self) -> Subscription<Message> {
|
||||
@@ -1037,6 +1052,152 @@ fn dimstyle_overlay<'a>(
|
||||
stack![catcher, positioned].into()
|
||||
}
|
||||
|
||||
// ── TableStyle Dialog overlay ───────────────────────────────────────────────
|
||||
|
||||
fn tablestyle_overlay<'a>(
|
||||
styles: Vec<String>,
|
||||
selected: &'a str,
|
||||
selected_style: Option<&'a acadrust::objects::TableStyle>,
|
||||
) -> Element<'a, Message> {
|
||||
use iced::Length::Shrink;
|
||||
|
||||
const PANEL_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_COL: Color = Color { r: 0.88, g: 0.88, b: 0.88, a: 1.0 };
|
||||
const DIM_COL: 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_BG: Color = Color { r: 0.20, g: 0.40, b: 0.70, a: 1.0 };
|
||||
|
||||
let btn_style = |accent: bool| move |_: &Theme, status: button::Status| button::Style {
|
||||
background: Some(Background::Color(match (accent, status) {
|
||||
(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_COL,
|
||||
border: Border { color: BORDER, width: 1.0, radius: 4.0.into() },
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// 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).color(TEXT_COL))
|
||||
.on_press(Message::TableStyleDialogSelect(name.clone()))
|
||||
.style(move |_: &Theme, st| button::Style {
|
||||
background: Some(Background::Color(match (is_sel, st) {
|
||||
(true, _) => ACTIVE_BG,
|
||||
(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_COL,
|
||||
..Default::default()
|
||||
})
|
||||
.padding([3, 8])
|
||||
.width(Fill)
|
||||
.into()
|
||||
}).collect();
|
||||
|
||||
let style_panel = container(
|
||||
column(style_items).spacing(2)
|
||||
)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(Color { r: 0.12, g: 0.12, b: 0.12, a: 1.0 })),
|
||||
border: Border { color: BORDER, width: 1.0, radius: 4.0.into() },
|
||||
..Default::default()
|
||||
})
|
||||
.padding(4)
|
||||
.width(160)
|
||||
.height(240);
|
||||
|
||||
// Right panel: details.
|
||||
let details: Element<'_, Message> = if let Some(s) = selected_style {
|
||||
let info_row = |label: &'static str, val: String| -> Element<'_, Message> {
|
||||
row![
|
||||
text(label).size(11).color(DIM_COL).width(140),
|
||||
text(val).size(11).color(TEXT_COL),
|
||||
].spacing(8).align_y(iced::Center).into()
|
||||
};
|
||||
let row_info = |row_label: &'static str, rs: &acadrust::objects::RowCellStyle| -> Element<'_, Message> {
|
||||
column![
|
||||
text(row_label).size(11).color(ACCENT),
|
||||
info_row(" Text Style:", rs.text_style_name.clone()),
|
||||
info_row(" Text Height:", format!("{:.4}", rs.text_height)),
|
||||
info_row(" Alignment:", format!("{:?}", rs.alignment)),
|
||||
].spacing(3).into()
|
||||
};
|
||||
column![
|
||||
info_row("Name:", s.name.clone()),
|
||||
info_row("H Margin:", format!("{:.4}", s.horizontal_margin)),
|
||||
info_row("V Margin:", format!("{:.4}", s.vertical_margin)),
|
||||
info_row("Title Suppressed:", s.title_suppressed.to_string()),
|
||||
info_row("Header Suppressed:", s.header_suppressed.to_string()),
|
||||
row_info("Data Row:", &s.data_row_style),
|
||||
row_info("Header Row:", &s.header_row_style),
|
||||
row_info("Title Row:", &s.title_row_style),
|
||||
].spacing(5).into()
|
||||
} else {
|
||||
text("No style selected.").size(11).color(DIM_COL).into()
|
||||
};
|
||||
|
||||
let right_panel = column![
|
||||
details,
|
||||
Space::new().height(Fill),
|
||||
row![
|
||||
button(text("New").size(11))
|
||||
.on_press(Message::TableStyleDialogNew)
|
||||
.style(btn_style(true))
|
||||
.padding([5, 10]),
|
||||
button(text("Delete").size(11))
|
||||
.on_press(Message::TableStyleDialogDelete)
|
||||
.style(btn_style(false))
|
||||
.padding([5, 10]),
|
||||
].spacing(6),
|
||||
]
|
||||
.spacing(10)
|
||||
.width(280)
|
||||
.height(240);
|
||||
|
||||
let panel = container(
|
||||
column![
|
||||
row![
|
||||
text("Table Style Manager").size(13).color(TEXT_COL),
|
||||
Space::new().width(Fill),
|
||||
button(text("✕").size(12).color(DIM_COL))
|
||||
.on_press(Message::TableStyleDialogClose)
|
||||
.style(|_: &Theme, _| button::Style {
|
||||
background: Some(Background::Color(Color::TRANSPARENT)),
|
||||
text_color: DIM_COL,
|
||||
..Default::default()
|
||||
})
|
||||
.padding([2, 6]),
|
||||
].align_y(iced::Center),
|
||||
row![style_panel, right_panel].spacing(12).align_y(iced::Top),
|
||||
].spacing(10).padding(16)
|
||||
)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(PANEL_BG)),
|
||||
border: Border { color: BORDER, width: 1.0, radius: 6.0.into() },
|
||||
..Default::default()
|
||||
})
|
||||
.width(Shrink);
|
||||
|
||||
let catcher = mouse_area(
|
||||
container(iced::widget::Space::new().width(Fill).height(Fill))
|
||||
).on_press(Message::TableStyleDialogClose);
|
||||
|
||||
let positioned = container(panel)
|
||||
.width(Fill).height(Fill)
|
||||
.align_x(iced::Alignment::Center)
|
||||
.align_y(iced::Alignment::Center);
|
||||
|
||||
stack![catcher, positioned].into()
|
||||
}
|
||||
|
||||
// ── MLineStyle Dialog overlay ───────────────────────────────────────────────
|
||||
|
||||
fn mlstyle_overlay<'a>(
|
||||
|
||||
Reference in new issue
Block a user