From df44031ae800844529899a99e324f4175f2ae0b7 Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Wed, 3 Jun 2026 20:28:58 +0300 Subject: [PATCH] feat(tablestyle): per-cell editing for Data/Header/Title rows Each row's RowCellStyle is now editable in the Table Style manager: text style name, text height, text & fill color (ACI), background-fill toggle, and alignment (cycle through the 9 anchor positions). Margin and per-cell buffers load on open/select via a shared load_tablestyle_bufs; edits write through objects.values_mut() and bump geometry so tables using the style re-render. Co-Authored-By: Claude Opus 4.8 --- src/app/mod.rs | 21 ++++++ src/app/update.rs | 151 ++++++++++++++++++++++++++++++++++++++++--- src/app/view.rs | 4 ++ src/ui/tablestyle.rs | 65 +++++++++++++++---- 4 files changed, 221 insertions(+), 20 deletions(-) diff --git a/src/app/mod.rs b/src/app/mod.rs index b2991788a..6e9dbae47 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -315,6 +315,11 @@ pub(super) struct OpenCADStudio { /// Edit buffers for the table style's general margins. ts_hmargin: String, ts_vmargin: 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], // ── TextStyle Font Browser ──────────────────────────────────────────── textstyle_selected: String, @@ -1013,6 +1018,18 @@ pub enum Message { TableStyleEdit { field: &'static str, value: String }, /// Write the general edit buffers back into the selected table style. TableStyleApply, + /// Update a per-cell edit buffer (row 0=Data,1=Header,2=Title). + TableStyleCellEdit { + row: u8, + field: &'static str, + value: String, + }, + /// Toggle background fill on a cell style. + TableStyleCellToggleFill(u8), + /// Cycle the alignment of a cell style. + TableStyleCellCycleAlign(u8), + /// Write a cell's edit buffers back into the selected table style. + TableStyleCellApply(u8), // ── MLineStyle Dialog ───────────────────────────────────────────────── MlStyleDialogOpen, #[allow(dead_code)] @@ -1202,6 +1219,10 @@ 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()], // MLineStyle dialog mlstyle_selected: "Standard".to_string(), // MLeaderStyle dialog diff --git a/src/app/update.rs b/src/app/update.rs index 8cfc0958e..4adbccb26 100644 --- a/src/app/update.rs +++ b/src/app/update.rs @@ -5724,6 +5724,7 @@ impl OpenCADStudio { } }) .unwrap_or_else(|| "Standard".to_string()); + self.load_tablestyle_bufs(i); if let Some(id) = self.tablestyle_window { return window::gain_focus(id); } @@ -5744,17 +5745,9 @@ impl OpenCADStudio { } } Message::TableStyleDialogSelect(name) => { - use acadrust::objects::ObjectType; self.tablestyle_selected = name; let i = self.active_tab; - for obj in self.tabs[i].scene.document.objects.values() { - if let ObjectType::TableStyle(s) = obj { - if s.name == self.tablestyle_selected { - self.ts_hmargin = format!("{:.4}", s.horizontal_margin); - self.ts_vmargin = format!("{:.4}", s.vertical_margin); - } - } - } + self.load_tablestyle_bufs(i); Task::none() } @@ -5790,6 +5783,87 @@ impl OpenCADStudio { Task::none() } + Message::TableStyleCellEdit { row, field, value } => { + let r = row as usize; + if r < 3 { + match field { + "textstyle" => self.ts_cell_textstyle[r] = value, + "height" => self.ts_cell_height[r] = value, + "textcolor" => self.ts_cell_textcolor[r] = value, + "fillcolor" => self.ts_cell_fillcolor[r] = value, + _ => {} + } + } + Task::none() + } + + Message::TableStyleCellToggleFill(row) => { + let i = self.active_tab; + if let Some(s) = self.tablestyle_mut(i) { + if let Some(c) = Self::ts_cell_of(s, row) { + c.fill_enabled = !c.fill_enabled; + } + self.push_undo_snapshot(i, "TABLESTYLE EDIT"); + self.tabs[i].dirty = true; + self.tabs[i].scene.bump_geometry(); + } + Task::none() + } + + Message::TableStyleCellCycleAlign(row) => { + use acadrust::objects::CellAlignment; + let i = self.active_tab; + if let Some(s) = self.tablestyle_mut(i) { + if let Some(c) = Self::ts_cell_of(s, row) { + c.alignment = match c.alignment { + CellAlignment::TopLeft => CellAlignment::TopCenter, + CellAlignment::TopCenter => CellAlignment::TopRight, + CellAlignment::TopRight => CellAlignment::MiddleLeft, + CellAlignment::MiddleLeft => CellAlignment::MiddleCenter, + CellAlignment::MiddleCenter => CellAlignment::MiddleRight, + CellAlignment::MiddleRight => CellAlignment::BottomLeft, + CellAlignment::BottomLeft => CellAlignment::BottomCenter, + CellAlignment::BottomCenter => CellAlignment::BottomRight, + CellAlignment::BottomRight => CellAlignment::TopLeft, + }; + } + self.push_undo_snapshot(i, "TABLESTYLE EDIT"); + self.tabs[i].dirty = true; + self.tabs[i].scene.bump_geometry(); + } + Task::none() + } + + Message::TableStyleCellApply(row) => { + let i = self.active_tab; + let r = row as usize; + if r >= 3 { + return Task::none(); + } + let ts = self.ts_cell_textstyle[r].trim().to_string(); + let height: Option = self.ts_cell_height[r].trim().parse().ok(); + let tc: Option = self.ts_cell_textcolor[r].trim().parse().ok(); + let fc: Option = self.ts_cell_fillcolor[r].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; + } + if let Some(h) = height { + c.text_height = h; + } + if let Some(v) = tc { + c.text_color = acadrust::types::Color::from_index(v); + } + if let Some(v) = fc { + c.fill_color = acadrust::types::Color::from_index(v); + } + self.push_undo_snapshot(i, "TABLESTYLE EDIT"); + self.tabs[i].dirty = true; + self.tabs[i].scene.bump_geometry(); + } + Task::none() + } + Message::TableStyleToggle(field) => { use acadrust::objects::ObjectType; let i = self.active_tab; @@ -6844,6 +6918,65 @@ impl OpenCADStudio { Some(task) } + /// Mutable access to the currently selected table style. + fn tablestyle_mut(&mut self, tab: usize) -> Option<&mut acadrust::objects::TableStyle> { + use acadrust::objects::ObjectType; + let name = self.tablestyle_selected.clone(); + self.tabs[tab] + .scene + .document + .objects + .values_mut() + .find_map(|o| match o { + ObjectType::TableStyle(s) if s.name == name => Some(s), + _ => None, + }) + } + + /// Mutable access to a table style's cell style by row (0=Data,1=Header,2=Title). + fn ts_cell_of( + s: &mut acadrust::objects::TableStyle, + row: u8, + ) -> Option<&mut acadrust::objects::RowCellStyle> { + match row { + 0 => Some(&mut s.data_row_style), + 1 => Some(&mut s.header_row_style), + 2 => Some(&mut s.title_row_style), + _ => None, + } + } + + /// Populate margin + per-cell edit buffers from the selected table style. + fn load_tablestyle_bufs(&mut self, tab: usize) { + use acadrust::objects::ObjectType; + let name = self.tablestyle_selected.clone(); + let Some(s) = self.tabs[tab] + .scene + .document + .objects + .values() + .find_map(|o| match o { + ObjectType::TableStyle(s) if s.name == name => Some(s), + _ => None, + }) + else { + return; + }; + self.ts_hmargin = format!("{:.4}", s.horizontal_margin); + self.ts_vmargin = format!("{:.4}", s.vertical_margin); + for (r, c) in [&s.data_row_style, &s.header_row_style, &s.title_row_style] + .into_iter() + .enumerate() + { + self.ts_cell_textstyle[r] = c.text_style_name.clone(); + self.ts_cell_height[r] = format!("{:.4}", c.text_height); + self.ts_cell_textcolor[r] = + 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(); + } + } + /// Mutable access to the currently selected multileader style. fn mleaderstyle_mut( &mut self, diff --git a/src/app/view.rs b/src/app/view.rs index 967646e86..087181e24 100644 --- a/src/app/view.rs +++ b/src/app/view.rs @@ -119,6 +119,10 @@ impl OpenCADStudio { selected_style, &self.ts_hmargin, &self.ts_vmargin, + &self.ts_cell_textstyle, + &self.ts_cell_height, + &self.ts_cell_textcolor, + &self.ts_cell_fillcolor, ); } if Some(window_id) == self.mlstyle_window { diff --git a/src/ui/tablestyle.rs b/src/ui/tablestyle.rs index b6688b90d..956d8f651 100644 --- a/src/ui/tablestyle.rs +++ b/src/ui/tablestyle.rs @@ -131,6 +131,10 @@ pub fn view_window<'a>( selected_style: Option<&'a acadrust::objects::TableStyle>, hmargin_buf: &'a str, vmargin_buf: &'a str, + cell_textstyle: &'a [String; 3], + cell_height: &'a [String; 3], + cell_textcolor: &'a [String; 3], + cell_fillcolor: &'a [String; 3], ) -> Element<'a, Message> { // ── Toolbar ─────────────────────────────────────────────────────────── let toolbar = container( @@ -208,17 +212,56 @@ pub fn view_window<'a>( .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)), + let cell_editor = |row_label: &'static str, + row: u8, + rs: &acadrust::objects::RowCellStyle| + -> Element<'a, Message> { + let r = row as usize; + let cell_in = |label: &'static str, + placeholder: &'static str, + value: &'a str, + field: &'static str| + -> Element<'a, Message> { + row![ + text(label).size(11).color(DIM).width(150), + text_input(placeholder, value) + .on_input(move |v| Message::TableStyleCellEdit { row, field, value: v }) + .size(11) + .width(100), ] - .spacing(3) + .spacing(8) + .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), + text(format!("{:?}", rs.alignment)).size(11).width(120), + button(text("Cycle").size(10)) + .on_press(Message::TableStyleCellCycleAlign(row)) + .style(btn_s(false)) + .padding([2, 8]), + ] + .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), + button(text("Apply cell").size(11)) + .on_press(Message::TableStyleCellApply(row)) + .style(btn_s(true)) + .padding([4, 12]), + ] + .spacing(3) + .into() + }; let details: Element<'_, Message> = if let Some(s) = selected_style { scrollable( @@ -261,9 +304,9 @@ pub fn view_window<'a>( .on_press(Message::TableStyleApply) .style(btn_s(true)) .padding([4, 12]), - row_info("Data Row:", &s.data_row_style), - row_info("Header Row:", &s.header_row_style), - row_info("Title Row:", &s.title_row_style), + cell_editor("Data Row:", 0, &s.data_row_style), + cell_editor("Header Row:", 1, &s.header_row_style), + cell_editor("Title Row:", 2, &s.title_row_style), ] .spacing(6) .padding([12, 12]),