feat(tablestyle): complete field coverage (flow, description, cell data + borders)

Table Style editor now exposes every TableStyle / RowCellStyle field:
- General: description, flow direction (Down/Up dropdown).
- Per cell: data type, unit type, format string, plus all six borders
  (left/right/top/bottom/horizontal-inside/vertical-inside) with line
  type (Single/Double), line weight, color (ACI), double-line spacing
  and a visibility toggle.
Border type/visibility apply immediately; the numeric border fields and
data/unit/format are written by the existing "Apply cell" button.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Hakan Seven 2026-06-03 21:09:37 +03:00
commit 10d674f606
4 changed files with 308 additions and 43 deletions

View file

@ -323,11 +323,21 @@ pub(super) struct OpenCADStudio {
/// Edit buffers for the table style's general margins.
ts_hmargin: String,
ts_vmargin: String,
/// General table-style description buffer.
ts_description: String,
/// Per-cell edit buffers, indexed 0=Data, 1=Header, 2=Title.
ts_cell_textstyle: [String; 3],
ts_cell_height: [String; 3],
ts_cell_textcolor: [String; 3],
ts_cell_fillcolor: [String; 3],
ts_cell_datatype: [String; 3],
ts_cell_unittype: [String; 3],
ts_cell_format: [String; 3],
/// Per-cell, per-border numeric buffers ([cell][border], border order:
/// 0=left 1=right 2=top 3=bottom 4=horizontal-inside 5=vertical-inside).
ts_border_lw: [[String; 6]; 3],
ts_border_color: [[String; 6]; 3],
ts_border_spacing: [[String; 6]; 3],
// ── TextStyle Font Browser ────────────────────────────────────────────
textstyle_selected: String,
@ -1043,6 +1053,26 @@ pub enum Message {
},
/// Write a cell's edit buffers back into the selected table style.
TableStyleCellApply(u8),
/// Set the table flow direction from the dropdown.
TableStyleSetFlow(String),
/// Update a per-cell, per-border numeric edit buffer.
TableStyleBorderEdit {
cell: u8,
border: u8,
field: &'static str,
value: String,
},
/// Set a border's line type (Single / Double).
TableStyleBorderSetType {
cell: u8,
border: u8,
value: String,
},
/// Toggle a border's visibility.
TableStyleBorderToggleInvisible {
cell: u8,
border: u8,
},
// ── MLineStyle Dialog ─────────────────────────────────────────────────
MlStyleDialogOpen,
#[allow(dead_code)]
@ -1247,10 +1277,17 @@ impl OpenCADStudio {
tablestyle_selected: "Standard".to_string(),
ts_hmargin: "1.5".to_string(),
ts_vmargin: "1.5".to_string(),
ts_cell_textstyle: [String::new(), String::new(), String::new()],
ts_cell_height: [String::new(), String::new(), String::new()],
ts_cell_textcolor: [String::new(), String::new(), String::new()],
ts_cell_fillcolor: [String::new(), String::new(), String::new()],
ts_description: String::new(),
ts_cell_textstyle: Default::default(),
ts_cell_height: Default::default(),
ts_cell_textcolor: Default::default(),
ts_cell_fillcolor: Default::default(),
ts_cell_datatype: Default::default(),
ts_cell_unittype: Default::default(),
ts_cell_format: Default::default(),
ts_border_lw: Default::default(),
ts_border_color: Default::default(),
ts_border_spacing: Default::default(),
// MLineStyle dialog
mlstyle_selected: "Standard".to_string(),
// MLeaderStyle dialog

View file

@ -5755,6 +5755,7 @@ impl OpenCADStudio {
match field {
"hmargin" => self.ts_hmargin = value,
"vmargin" => self.ts_vmargin = value,
"description" => self.ts_description = value,
_ => {}
}
Task::none()
@ -5766,6 +5767,7 @@ impl OpenCADStudio {
let name = self.tablestyle_selected.clone();
let h: Option<f64> = self.ts_hmargin.trim().parse().ok();
let v: Option<f64> = self.ts_vmargin.trim().parse().ok();
let desc = self.ts_description.clone();
self.push_undo_snapshot(i, "TABLESTYLE EDIT");
for obj in self.tabs[i].scene.document.objects.values_mut() {
if let ObjectType::TableStyle(s) = obj {
@ -5776,6 +5778,7 @@ impl OpenCADStudio {
if let Some(v) = v {
s.vertical_margin = v;
}
s.description = desc.clone();
}
}
}
@ -5783,6 +5786,20 @@ impl OpenCADStudio {
Task::none()
}
Message::TableStyleSetFlow(value) => {
use acadrust::objects::TableFlowDirection;
let i = self.active_tab;
if let Some(s) = self.tablestyle_mut(i) {
s.flow_direction = match value.as_str() {
"Up" => TableFlowDirection::Up,
_ => TableFlowDirection::Down,
};
self.push_undo_snapshot(i, "TABLESTYLE EDIT");
self.tabs[i].dirty = true;
}
Task::none()
}
Message::TableStyleCellEdit { row, field, value } => {
let r = row as usize;
if r < 3 {
@ -5791,12 +5808,69 @@ impl OpenCADStudio {
"height" => self.ts_cell_height[r] = value,
"textcolor" => self.ts_cell_textcolor[r] = value,
"fillcolor" => self.ts_cell_fillcolor[r] = value,
"datatype" => self.ts_cell_datatype[r] = value,
"unittype" => self.ts_cell_unittype[r] = value,
"format" => self.ts_cell_format[r] = value,
_ => {}
}
}
Task::none()
}
Message::TableStyleBorderEdit {
cell,
border,
field,
value,
} => {
let (c, b) = (cell as usize, border as usize);
if c < 3 && b < 6 {
match field {
"lw" => self.ts_border_lw[c][b] = value,
"color" => self.ts_border_color[c][b] = value,
"spacing" => self.ts_border_spacing[c][b] = value,
_ => {}
}
}
Task::none()
}
Message::TableStyleBorderSetType {
cell,
border,
value,
} => {
use acadrust::objects::TableBorderType;
let i = self.active_tab;
if let Some(s) = self.tablestyle_mut(i) {
if let Some(bd) = Self::ts_cell_of(s, cell).and_then(|c| Self::ts_border_of(c, border))
{
bd.border_type = match value.as_str() {
"Double" => TableBorderType::Double,
_ => TableBorderType::Single,
};
}
self.push_undo_snapshot(i, "TABLESTYLE EDIT");
self.tabs[i].dirty = true;
self.tabs[i].scene.bump_geometry();
}
Task::none()
}
Message::TableStyleBorderToggleInvisible { cell, border } => {
let i = self.active_tab;
if let Some(s) = self.tablestyle_mut(i) {
if let Some(bd) = Self::ts_cell_of(s, cell).and_then(|c| Self::ts_border_of(c, border))
{
bd.is_invisible = !bd.is_invisible;
}
self.push_undo_snapshot(i, "TABLESTYLE EDIT");
self.tabs[i].dirty = true;
self.tabs[i].scene.bump_geometry();
}
Task::none()
}
Message::TableStyleCellToggleFill(row) => {
let i = self.active_tab;
if let Some(s) = self.tablestyle_mut(i) {
@ -5844,6 +5918,18 @@ impl OpenCADStudio {
let height: Option<f64> = self.ts_cell_height[r].trim().parse().ok();
let tc: Option<i16> = self.ts_cell_textcolor[r].trim().parse().ok();
let fc: Option<i16> = self.ts_cell_fillcolor[r].trim().parse().ok();
let dtype: Option<i32> = self.ts_cell_datatype[r].trim().parse().ok();
let utype: Option<i32> = self.ts_cell_unittype[r].trim().parse().ok();
let fmt = self.ts_cell_format[r].clone();
// Per-border numeric edits for this cell.
let border_vals: [(Option<i16>, Option<i16>, Option<f64>); 6] =
std::array::from_fn(|b| {
(
self.ts_border_lw[r][b].trim().parse().ok(),
self.ts_border_color[r][b].trim().parse().ok(),
self.ts_border_spacing[r][b].trim().parse().ok(),
)
});
if let Some(c) = self.tablestyle_mut(i).and_then(|s| Self::ts_cell_of(s, row)) {
if !ts.is_empty() {
c.text_style_name = ts;
@ -5857,6 +5943,26 @@ impl OpenCADStudio {
if let Some(v) = fc {
c.fill_color = acadrust::types::Color::from_index(v);
}
if let Some(v) = dtype {
c.data_type = v;
}
if let Some(v) = utype {
c.unit_type = v;
}
c.format_string = fmt;
for (b, (lw, color, spacing)) in border_vals.into_iter().enumerate() {
if let Some(bd) = Self::ts_border_of(c, b as u8) {
if let Some(v) = lw {
bd.line_weight = acadrust::types::LineWeight::from_value(v);
}
if let Some(v) = color {
bd.color = acadrust::types::Color::from_index(v);
}
if let Some(v) = spacing {
bd.double_line_spacing = v;
}
}
}
self.push_undo_snapshot(i, "TABLESTYLE EDIT");
self.tabs[i].dirty = true;
self.tabs[i].scene.bump_geometry();
@ -7115,6 +7221,23 @@ impl OpenCADStudio {
}
}
/// Mutable access to a cell's border by index
/// (0=left 1=right 2=top 3=bottom 4=horizontal-inside 5=vertical-inside).
fn ts_border_of(
c: &mut acadrust::objects::RowCellStyle,
border: u8,
) -> Option<&mut acadrust::objects::TableCellBorder> {
match border {
0 => Some(&mut c.left_border),
1 => Some(&mut c.right_border),
2 => Some(&mut c.top_border),
3 => Some(&mut c.bottom_border),
4 => Some(&mut c.horizontal_inside_border),
5 => Some(&mut c.vertical_inside_border),
_ => None,
}
}
/// Populate margin + per-cell edit buffers from the selected table style.
fn load_tablestyle_bufs(&mut self, tab: usize) {
use acadrust::objects::ObjectType;
@ -7133,6 +7256,7 @@ impl OpenCADStudio {
};
self.ts_hmargin = format!("{:.4}", s.horizontal_margin);
self.ts_vmargin = format!("{:.4}", s.vertical_margin);
self.ts_description = s.description.clone();
for (r, c) in [&s.data_row_style, &s.header_row_style, &s.title_row_style]
.into_iter()
.enumerate()
@ -7143,6 +7267,23 @@ impl OpenCADStudio {
c.text_color.index().map(|v| v.to_string()).unwrap_or_default();
self.ts_cell_fillcolor[r] =
c.fill_color.index().map(|v| v.to_string()).unwrap_or_default();
self.ts_cell_datatype[r] = c.data_type.to_string();
self.ts_cell_unittype[r] = c.unit_type.to_string();
self.ts_cell_format[r] = c.format_string.clone();
let borders = [
&c.left_border,
&c.right_border,
&c.top_border,
&c.bottom_border,
&c.horizontal_inside_border,
&c.vertical_inside_border,
];
for (b, bd) in borders.into_iter().enumerate() {
self.ts_border_lw[r][b] = bd.line_weight.value().to_string();
self.ts_border_color[r][b] =
bd.color.index().map(|v| v.to_string()).unwrap_or_default();
self.ts_border_spacing[r][b] = format!("{:.4}", bd.double_line_spacing);
}
}
}

View file

@ -119,10 +119,17 @@ impl OpenCADStudio {
selected_style,
&self.ts_hmargin,
&self.ts_vmargin,
&self.ts_description,
&self.ts_cell_textstyle,
&self.ts_cell_height,
&self.ts_cell_textcolor,
&self.ts_cell_fillcolor,
&self.ts_cell_datatype,
&self.ts_cell_unittype,
&self.ts_cell_format,
&self.ts_border_lw,
&self.ts_border_color,
&self.ts_border_spacing,
);
}
if Some(window_id) == self.mlstyle_window {

View file

@ -2,7 +2,8 @@
use crate::app::Message;
use iced::widget::{
button, checkbox, column, container, pick_list, row, scrollable, text, text_input, Space,
button, checkbox, column, container, pick_list, row, scrollable, text, text_input, Column,
Space,
};
use iced::{Background, Border, Color, Element, Fill, Theme};
@ -133,10 +134,17 @@ pub fn view_window<'a>(
selected_style: Option<&'a acadrust::objects::TableStyle>,
hmargin_buf: &'a str,
vmargin_buf: &'a str,
description_buf: &'a str,
cell_textstyle: &'a [String; 3],
cell_height: &'a [String; 3],
cell_textcolor: &'a [String; 3],
cell_fillcolor: &'a [String; 3],
cell_datatype: &'a [String; 3],
cell_unittype: &'a [String; 3],
cell_format: &'a [String; 3],
border_lw: &'a [[String; 6]; 3],
border_color: &'a [[String; 6]; 3],
border_spacing: &'a [[String; 6]; 3],
) -> Element<'a, Message> {
// ── Toolbar ───────────────────────────────────────────────────────────
let toolbar = container(
@ -235,48 +243,99 @@ pub fn view_window<'a>(
.align_y(iced::Center)
.into()
};
column![
text(row_label).size(11).color(ACCENT),
cell_in(" Text style:", "Standard", &cell_textstyle[r], "textstyle"),
cell_in(" Text height:", "0.18", &cell_height[r], "height"),
cell_in(" Text color (ACI):", "256", &cell_textcolor[r], "textcolor"),
cell_in(" Fill color (ACI):", "256", &cell_fillcolor[r], "fillcolor"),
row![
text(" Alignment:").size(11).color(DIM).width(150),
pick_list(
[
"TopLeft",
"TopCenter",
"TopRight",
"MiddleLeft",
"MiddleCenter",
"MiddleRight",
"BottomLeft",
"BottomCenter",
"BottomRight",
]
.iter()
.map(|s| s.to_string())
.collect::<Vec<_>>(),
Some(format!("{:?}", rs.alignment)),
move |value| Message::TableStyleCellSetAlign { row, value },
)
.text_size(11)
.width(140),
]
.spacing(8)
.align_y(iced::Center),
checkbox(rs.fill_enabled)
.label(" Background fill enabled")
.on_toggle(move |_| Message::TableStyleCellToggleFill(row))
.size(14)
.text_size(11),
let mut col = Column::new()
.spacing(3)
.push(text(row_label).size(11).color(ACCENT))
.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 color (ACI):", "256", &cell_textcolor[r], "textcolor"))
.push(cell_in(" Fill color (ACI):", "256", &cell_fillcolor[r], "fillcolor"))
.push(
row![
text(" Alignment:").size(11).color(DIM).width(150),
pick_list(
[
"TopLeft",
"TopCenter",
"TopRight",
"MiddleLeft",
"MiddleCenter",
"MiddleRight",
"BottomLeft",
"BottomCenter",
"BottomRight",
]
.iter()
.map(|s| s.to_string())
.collect::<Vec<_>>(),
Some(format!("{:?}", rs.alignment)),
move |value| Message::TableStyleCellSetAlign { row, value },
)
.text_size(11)
.width(140),
]
.spacing(8)
.align_y(iced::Center),
)
.push(
checkbox(rs.fill_enabled)
.label(" Background fill enabled")
.on_toggle(move |_| Message::TableStyleCellToggleFill(row))
.size(14)
.text_size(11),
)
.push(cell_in(" Data type:", "0", &cell_datatype[r], "datatype"))
.push(cell_in(" Unit type:", "0", &cell_unittype[r], "unittype"))
.push(cell_in(" Format string:", "", &cell_format[r], "format"))
.push(text(" Borders (type / weight / color / spacing / hidden)").size(10).color(DIM));
let borders: [(&'static str, &acadrust::objects::TableCellBorder); 6] = [
("L", &rs.left_border),
("R", &rs.right_border),
("T", &rs.top_border),
("B", &rs.bottom_border),
("H", &rs.horizontal_inside_border),
("V", &rs.vertical_inside_border),
];
for (b, (bname, bd)) in borders.into_iter().enumerate() {
let bu = b as u8;
col = col.push(
row![
text(format!(" {bname}")).size(11).color(DIM).width(28),
pick_list(
["Single", "Double"].iter().map(|s| s.to_string()).collect::<Vec<_>>(),
Some(format!("{:?}", bd.border_type)),
move |value| Message::TableStyleBorderSetType { cell: row, border: bu, value },
)
.text_size(10)
.width(74),
text_input("wt", &border_lw[r][b])
.on_input(move |v| Message::TableStyleBorderEdit { cell: row, border: bu, field: "lw", value: v })
.size(10)
.width(46),
text_input("clr", &border_color[r][b])
.on_input(move |v| Message::TableStyleBorderEdit { cell: row, border: bu, field: "color", value: v })
.size(10)
.width(46),
text_input("gap", &border_spacing[r][b])
.on_input(move |v| Message::TableStyleBorderEdit { cell: row, border: bu, field: "spacing", value: v })
.size(10)
.width(46),
checkbox(bd.is_invisible)
.on_toggle(move |_| Message::TableStyleBorderToggleInvisible { cell: row, border: bu })
.size(13),
]
.spacing(5)
.align_y(iced::Center),
);
}
col.push(
button(text("Apply cell").size(11))
.on_press(Message::TableStyleCellApply(row))
.style(btn_s(true))
.padding([4, 12]),
]
.spacing(3)
)
.into()
};
@ -284,6 +343,27 @@ pub fn view_window<'a>(
scrollable(
column![
info_row("Name:", s.name.clone()),
row![
text("Description:").size(11).color(DIM).width(160),
text_input("", description_buf)
.on_input(|v| Message::TableStyleEdit { field: "description", value: v })
.size(11)
.width(160),
]
.spacing(8)
.align_y(iced::Center),
row![
text("Flow direction:").size(11).color(DIM).width(160),
pick_list(
["Down", "Up"].iter().map(|s| s.to_string()).collect::<Vec<_>>(),
Some(format!("{:?}", s.flow_direction)),
Message::TableStyleSetFlow,
)
.text_size(11)
.width(100),
]
.spacing(8)
.align_y(iced::Center),
checkbox(s.annotative)
.label("Annotative")
.on_toggle(|_| Message::TableStyleToggleAnnotative)