diff --git a/src/app/mod.rs b/src/app/mod.rs index 126f77f8b..551034aa4 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -352,6 +352,8 @@ pub(super) struct OpenCADStudio { // ── MLeaderStyle Dialog ─────────────────────────────────────────────── mleaderstyle_selected: String, + /// Colour field whose expanded palette is open (line/text/block). + mls_color_open: Option<&'static str>, mls_landing_distance: String, mls_landing_gap: String, mls_arrowhead_size: String, @@ -381,6 +383,9 @@ pub(super) struct OpenCADStudio { /// General table-style description buffer. ts_description: String, /// Per-cell edit buffers, indexed 0=Data, 1=Header, 2=Title. + /// Table cell colour field (row class, "textcolor"/"fillcolor") whose + /// expanded palette is open. + ts_color_open: Option<(u8, &'static str)>, ts_cell_textstyle: [String; 3], ts_cell_height: [String; 3], ts_cell_textcolor: [String; 3], @@ -467,6 +472,8 @@ pub(super) struct OpenCADStudio { // ── DimStyle Dialog ─────────────────────────────────────────────────── /// Name of the style currently shown in the dialog. dimstyle_selected: String, + /// Which colour field currently has its expanded palette open (if any). + ds_color_open: Option, /// Active tab: 0=Lines, 1=Arrows, 2=Text, 3=Scale/Units, 4=Tolerances. dimstyle_tab: u8, // Edit buffers (strings while typing): @@ -1171,6 +1178,8 @@ pub enum Message { field: &'static str, value: String, }, + /// Toggle the expanded colour palette for a table cell colour field. + TableColorMore(u8, &'static str), /// Toggle background fill on a cell style. TableStyleCellToggleFill(u8), /// Set the alignment of a cell style from the dropdown. @@ -1223,6 +1232,8 @@ pub enum Message { field: &'static str, value: String, }, + /// Toggle the expanded colour palette for an MLeaderStyle colour field. + MLeaderColorMore(&'static str), MLeaderStyleToggle(&'static str), MLeaderStyleSetEnum { field: &'static str, @@ -1254,6 +1265,8 @@ pub enum Message { // Field edit messages: DsEdit(DsField, String), DsToggle(DsField), + /// Toggle the expanded colour palette for a DimStyle colour field. + DsColorMore(DsField), /// Set a block/linetype Handle field on the selected dim style from a /// dropdown of available block-records / linetypes (by name). DsSetHandle { @@ -1429,6 +1442,7 @@ impl OpenCADStudio { ts_hmargin: "1.5".to_string(), ts_vmargin: "1.5".to_string(), ts_description: String::new(), + ts_color_open: None, ts_cell_textstyle: Default::default(), ts_cell_height: Default::default(), ts_cell_textcolor: Default::default(), @@ -1443,6 +1457,7 @@ impl OpenCADStudio { mlstyle_selected: "Standard".to_string(), // MLeaderStyle dialog mleaderstyle_selected: "Standard".to_string(), + mls_color_open: None, mls_landing_distance: String::new(), mls_landing_gap: String::new(), mls_arrowhead_size: String::new(), @@ -1465,6 +1480,7 @@ impl OpenCADStudio { mls_block_scale_z: String::new(), // DimStyle dialog dimstyle_selected: "Standard".to_string(), + ds_color_open: None, dimstyle_tab: 0, ds_dimdle: "0".to_string(), ds_dimdli: "3.75".to_string(), diff --git a/src/app/update.rs b/src/app/update.rs index 510a4bf31..b3c472d28 100644 --- a/src/app/update.rs +++ b/src/app/update.rs @@ -6535,6 +6535,14 @@ impl OpenCADStudio { Task::none() } + Message::TableColorMore(row, field) => { + self.ts_color_open = if self.ts_color_open == Some((row, field)) { + None + } else { + Some((row, field)) + }; + Task::none() + } Message::TableStyleCellEdit { row, field, value } => { let r = row as usize; if r < 3 { @@ -6985,6 +6993,14 @@ impl OpenCADStudio { } Task::none() } + Message::MLeaderColorMore(field) => { + self.mls_color_open = if self.mls_color_open == Some(field) { + None + } else { + Some(field) + }; + Task::none() + } Message::MLeaderStyleSetEnum { field, value } => { use acadrust::objects::{ BlockContentConnectionType, LeaderContentType, LeaderDrawOrderType, @@ -7294,6 +7310,14 @@ impl OpenCADStudio { self.apply_ds_toggle(field); Task::none() } + Message::DsColorMore(field) => { + self.ds_color_open = if self.ds_color_open.as_ref() == Some(&field) { + None + } else { + Some(field) + }; + Task::none() + } Message::DsSetHandle { field, value } => { let i = self.active_tab; let name = self.dimstyle_selected.clone(); diff --git a/src/app/view.rs b/src/app/view.rs index e059cdfe1..a55d9a4da 100644 --- a/src/app/view.rs +++ b/src/app/view.rs @@ -136,6 +136,7 @@ impl OpenCADStudio { &self.ts_border_spacing, self.style_rename.as_deref(), &self.style_rename_buf, + self.ts_color_open, ); } if Some(window_id) == self.mlstyle_window { @@ -286,6 +287,7 @@ impl OpenCADStudio { block_content_name, rename_active: self.style_rename.as_deref(), rename_buf: &self.style_rename_buf, + color_open: self.mls_color_open, }, ); } @@ -457,6 +459,7 @@ impl OpenCADStudio { dimltex2_name, block_opts, lt_opts, + color_open: self.ds_color_open.clone(), }, self.style_rename.as_deref(), &self.style_rename_buf, diff --git a/src/ui/color_select.rs b/src/ui/color_select.rs new file mode 100644 index 000000000..cac732c68 --- /dev/null +++ b/src/ui/color_select.rs @@ -0,0 +1,171 @@ +//! Shared colour selector: a main-colour dropdown plus a "more colours" button +//! that expands the full ACI palette. Used by the properties panel and every +//! style editor so colour selection looks and behaves the same everywhere. + +use crate::app::Message; +use crate::ui::properties::acad_color_display; +use acadrust::types::Color as AcadColor; +use iced::widget::{button, column, container, pick_list, row, scrollable, text}; +use iced::{Background, Border, Color, Element, Theme}; + +/// Which "logical" entries the main dropdown offers besides the standard ACI +/// colours. +#[derive(Clone, Copy, Default)] +pub struct ColorExtras { + pub by_layer: bool, + pub by_block: bool, +} + +/// Encode a colour as the ACI integer string the style editors store +/// (ByBlock=0, ByLayer=256, indexed 1-255; RGB has no ACI slot → ByLayer). +pub fn color_to_aci_string(c: AcadColor) -> String { + match c { + AcadColor::ByBlock => "0".to_string(), + AcadColor::ByLayer => "256".to_string(), + AcadColor::Index(i) => i.to_string(), + AcadColor::Rgb { .. } => "256".to_string(), + } +} + +/// Decode an ACI integer string back into an `AcadColor`. +pub fn aci_string_to_color(s: &str) -> AcadColor { + match s.trim().parse::().unwrap_or(256) { + 0 => AcadColor::ByBlock, + 256 => AcadColor::ByLayer, + n if (1..=255).contains(&n) => AcadColor::Index(n as u8), + _ => AcadColor::ByLayer, + } +} + +/// A dropdown option wrapping an `AcadColor`, displayed by its colour name. +#[derive(Clone, PartialEq)] +struct ColorChoice(AcadColor); + +impl std::fmt::Display for ColorChoice { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let (_, name) = acad_color_display(self.0); + write!(f, "{name}") + } +} + +const PICKER_BG: Color = Color { + r: 0.12, + g: 0.12, + b: 0.12, + a: 1.0, +}; +const BORDER: Color = Color { + r: 0.35, + g: 0.35, + b: 0.35, + a: 1.0, +}; + +/// Build a colour selector. +/// +/// * `current` — the currently selected colour (shown in the dropdown). +/// * `palette_open` — whether the expanded 255-colour grid is shown. +/// * `extras` — whether ByLayer / ByBlock appear in the dropdown. +/// * `on_select` — called with the chosen colour (dropdown item or grid swatch). +/// * `on_more` — toggles the expanded palette. +pub fn color_selector<'a>( + current: AcadColor, + palette_open: bool, + extras: ColorExtras, + on_select: impl Fn(AcadColor) -> Message + 'a, + on_more: Message, +) -> Element<'a, Message> { + // Standard ACI 1-7 plus the requested extras, and the current colour if it + // isn't already one of them (so a non-standard index still shows). + let mut opts: Vec = Vec::new(); + if extras.by_layer { + opts.push(ColorChoice(AcadColor::ByLayer)); + } + if extras.by_block { + opts.push(ColorChoice(AcadColor::ByBlock)); + } + for i in 1u8..=7 { + opts.push(ColorChoice(AcadColor::Index(i))); + } + let cur_choice = ColorChoice(current); + if !opts.contains(&cur_choice) { + opts.push(cur_choice.clone()); + } + + // Expanded ACI palette. Built before the dropdown closure so it can borrow + // `on_select`; the dropdown then moves it. + let grid: Option> = if palette_open { + const COLS: u16 = 16; + let mut rows = column![].spacing(1); + let mut idx: u16 = 1; + while idx <= 255 { + let mut r = row![].spacing(1); + for _ in 0..COLS { + if idx > 255 { + break; + } + let ci = idx as u8; + let (bg, _) = acad_color_display(AcadColor::Index(ci)); + let msg = on_select(AcadColor::Index(ci)); + r = r.push( + button(text("").width(12).height(12)) + .on_press(msg) + .style(move |_: &Theme, status| button::Style { + background: Some(Background::Color(bg)), + border: Border { + color: if matches!(status, button::Status::Hovered) { + Color::WHITE + } else { + Color { + r: 0.0, + g: 0.0, + b: 0.0, + a: 0.4, + } + }, + width: if matches!(status, button::Status::Hovered) { + 1.5 + } else { + 1.0 + }, + radius: 1.0.into(), + }, + ..Default::default() + }) + .padding(0), + ); + idx += 1; + } + rows = rows.push(r); + } + Some( + container(scrollable(rows).height(150)) + .style(|_: &Theme| container::Style { + background: Some(Background::Color(PICKER_BG)), + border: Border { + color: BORDER, + width: 1.0, + radius: 0.0.into(), + }, + ..Default::default() + }) + .padding(4) + .into(), + ) + } else { + None + }; + + let dropdown = pick_list(opts, Some(cur_choice), move |c| on_select(c.0)) + .text_size(11) + .width(150); + let more = button(text(if palette_open { "×" } else { "⋯" }).size(11)) + .on_press(on_more) + .padding([2, 7]); + let top = row![dropdown, more].spacing(4).align_y(iced::Center); + + match grid { + Some(g) => column![top, g].spacing(2).into(), + None => top.into(), + } +} diff --git a/src/ui/dimstyle.rs b/src/ui/dimstyle.rs index 834be3105..82031b8b1 100644 --- a/src/ui/dimstyle.rs +++ b/src/ui/dimstyle.rs @@ -126,6 +126,8 @@ pub struct DimStyleValues<'a> { // Dropdown option lists shared by the arrowhead / linetype fields. pub block_opts: Vec, pub lt_opts: Vec, + /// Colour field whose expanded palette is currently open. + pub color_open: Option, } fn tab_btn_style(active: bool) -> impl Fn(&Theme, button::Status) -> button::Style { @@ -290,43 +292,24 @@ pub fn view_window<'a>( ("6", "Windows desktop"), ]; - // ACI colour swatches (1-9) + ByLayer; reuses the existing DsEdit path - // (the chosen index is sent as the field's string value). - let color_row = move |label: &'static str, fld: DsField, val: &'a str| -> Element<'a, Message> { - let cur: i16 = val.trim().parse().unwrap_or(256); - let mut swatches = row![].spacing(2).align_y(iced::Center); - for aci in 1u8..=9 { - let c = acadrust::types::Color::from_index(aci as i16); - let (bg, _) = crate::ui::properties::acad_color_display(c); - let sel = cur == aci as i16; - swatches = swatches.push( - button(text("").width(16).height(14)) - .on_press(Message::DsEdit(fld.clone(), aci.to_string())) - .style(move |_: &Theme, _| button::Style { - background: Some(Background::Color(bg)), - border: Border { - color: if sel { Color::WHITE } else { Color::BLACK }, - width: if sel { 2.0 } else { 1.0 }, - radius: 2.0.into(), - }, - ..Default::default() - }), - ); - } - // ByLayer (256) chip. - swatches = swatches.push( - button(text("ByLayer").size(10)) - .on_press(Message::DsEdit(fld.clone(), "256".to_string())) - .style(move |_: &Theme, _| button::Style { - border: Border { - color: if cur == 256 { Color::WHITE } else { BORDER }, - width: if cur == 256 { 2.0 } else { 1.0 }, - radius: 2.0.into(), - }, - ..Default::default() - }), + // Shared colour selector (main dropdown + "more" palette), reusing the + // existing DsEdit path (the chosen colour is sent as an ACI string). + let color_open = vals.color_open.clone(); + let color_row = move |label: &'static str, fld: DsField, _val: &'a str| -> Element<'a, Message> { + let cur = crate::ui::color_select::aci_string_to_color(_val); + let open = color_open.as_ref() == Some(&fld); + let f_sel = fld.clone(); + let selector = crate::ui::color_select::color_selector( + cur, + open, + crate::ui::color_select::ColorExtras { + by_layer: true, + by_block: true, + }, + move |c| Message::DsEdit(f_sel.clone(), crate::ui::color_select::color_to_aci_string(c)), + Message::DsColorMore(fld.clone()), ); - row![lbl(label), swatches] + row![lbl(label), selector] .spacing(8) .align_y(iced::Center) .into() diff --git a/src/ui/mleaderstyle.rs b/src/ui/mleaderstyle.rs index 8ed9219a0..dccbf0716 100644 --- a/src/ui/mleaderstyle.rs +++ b/src/ui/mleaderstyle.rs @@ -102,6 +102,8 @@ pub struct MLeaderStyleView<'a> { pub rename_active: Option<&'a str>, /// Edit buffer for the inline rename text input. pub rename_buf: &'a str, + /// Colour field whose expanded palette is currently open. + pub color_open: Option<&'static str>, } fn section<'a>(label: &'static str) -> Element<'a, Message> { @@ -126,48 +128,29 @@ fn num_row<'a>( .into() } -/// ACI colour swatch row (1-9 + ByLayer). Reuses MLeaderStyleEdit by sending -/// the chosen index as the field's string value. -fn color_row<'a>(label: &'static str, value: &'a str, field: &'static str) -> Element<'a, Message> { - let cur: i16 = value.trim().parse().unwrap_or(256); - let mut swatches = row![].spacing(2).align_y(iced::Center); - for aci in 1u8..=9 { - let c = acadrust::types::Color::from_index(aci as i16); - let (bg, _) = crate::ui::properties::acad_color_display(c); - let sel = cur == aci as i16; - swatches = swatches.push( - button(text("").width(16).height(14)) - .on_press(Message::MLeaderStyleEdit { - field, - value: aci.to_string(), - }) - .style(move |_: &Theme, _| button::Style { - background: Some(Background::Color(bg)), - border: Border { - color: if sel { Color::WHITE } else { Color::BLACK }, - width: if sel { 2.0 } else { 1.0 }, - radius: 2.0.into(), - }, - ..Default::default() - }), - ); - } - swatches = swatches.push( - button(text("ByLayer").size(10)) - .on_press(Message::MLeaderStyleEdit { - field, - value: "256".to_string(), - }) - .style(move |_: &Theme, _| button::Style { - border: Border { - color: if cur == 256 { Color::WHITE } else { BORDER }, - width: if cur == 256 { 2.0 } else { 1.0 }, - radius: 2.0.into(), - }, - ..Default::default() - }), +/// Shared colour selector row. Reuses MLeaderStyleEdit by sending the chosen +/// colour as an ACI string; `open` shows the expanded palette. +fn color_row<'a>( + label: &'static str, + value: &'a str, + field: &'static str, + open: bool, +) -> Element<'a, Message> { + let cur = crate::ui::color_select::aci_string_to_color(value); + let selector = crate::ui::color_select::color_selector( + cur, + open, + crate::ui::color_select::ColorExtras { + by_layer: true, + by_block: true, + }, + move |c| Message::MLeaderStyleEdit { + field, + value: crate::ui::color_select::color_to_aci_string(c), + }, + Message::MLeaderColorMore(field), ); - row![text(label).size(11).color(DIM).width(150), swatches] + row![text(label).size(11).color(DIM).width(150), selector] .spacing(8) .align_y(iced::Center) .into() @@ -260,7 +243,7 @@ pub fn view_window<'a>(v: MLeaderStyleView<'a>) -> Element<'a, Message> { format!("{:?}", s.path_type), "path_type" ), - color_row("Line color (ACI):", v.line_color, "line_color"), + color_row("Line color (ACI):", v.line_color, "line_color", v.color_open == Some("line_color")), num_row("Line weight:", "-2", v.line_weight, "line_weight"), handle_row( "Line type:", @@ -336,7 +319,7 @@ pub fn view_window<'a>(v: MLeaderStyleView<'a>) -> Element<'a, Message> { "text_style_handle" ), num_row("Text height:", "0.18", v.text_height, "text_height"), - color_row("Text color (ACI):", v.text_color, "text_color"), + color_row("Text color (ACI):", v.text_color, "text_color", v.color_open == Some("text_color")), enum_row( "Text angle:", opts(&["ParallelToLastLeaderLine", "Horizontal", "Optimized"]), @@ -389,7 +372,7 @@ pub fn view_window<'a>(v: MLeaderStyleView<'a>) -> Element<'a, Message> { v.block_content_name.clone(), "block_content_handle" ), - color_row("Block color (ACI):", v.block_color, "block_color"), + color_row("Block color (ACI):", v.block_color, "block_color", v.color_open == Some("block_color")), enum_row( "Block connection:", opts(&["BlockExtents", "BasePoint"]), diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 6bfc3d1b0..17b6ce4be 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -4,6 +4,7 @@ pub const ROW_H: f32 = 26.0; pub mod about; pub mod app_menu; +pub mod color_select; pub mod command_line; pub mod cycle_popup; pub mod dimstyle; diff --git a/src/ui/tablestyle.rs b/src/ui/tablestyle.rs index d2c851bb1..79a184042 100644 --- a/src/ui/tablestyle.rs +++ b/src/ui/tablestyle.rs @@ -84,6 +84,7 @@ pub fn view_window<'a>( border_spacing: &'a [[String; 6]; 3], rename_active: Option<&'a str>, rename_buf: &'a str, + color_open: Option<(u8, &'static str)>, ) -> Element<'a, Message> { // ── Right: Details panel ────────────────────────────────────────────── let info_row = |label: &'static str, val: String| -> Element<'_, Message> { @@ -121,49 +122,26 @@ pub fn view_window<'a>( .align_y(iced::Center) .into() }; - // ACI swatch row — sends the chosen index through the same cell edit. + // Shared colour selector — sends the chosen colour through the same + // cell edit as an ACI string. let cell_color = |label: &'static str, value: &'a str, field: &'static str| -> Element<'a, Message> { - let cur: i16 = value.trim().parse().unwrap_or(256); - let mut sw = row![].spacing(2).align_y(iced::Center); - for aci in 1u8..=9 { - let c = acadrust::types::Color::from_index(aci as i16); - let (bg, _) = crate::ui::properties::acad_color_display(c); - let sel = cur == aci as i16; - sw = sw.push( - button(text("").width(16).height(14)) - .on_press(Message::TableStyleCellEdit { - row, - field, - value: aci.to_string(), - }) - .style(move |_: &Theme, _| button::Style { - background: Some(Background::Color(bg)), - border: Border { - color: if sel { Color::WHITE } else { Color::BLACK }, - width: if sel { 2.0 } else { 1.0 }, - radius: 2.0.into(), - }, - ..Default::default() - }), - ); - } - sw = sw.push( - button(text("ByLayer").size(10)) - .on_press(Message::TableStyleCellEdit { - row, - field, - value: "256".to_string(), - }) - .style(move |_: &Theme, _| button::Style { - border: Border { - color: if cur == 256 { Color::WHITE } else { BORDER }, - width: if cur == 256 { 2.0 } else { 1.0 }, - radius: 2.0.into(), - }, - ..Default::default() - }), + let cur = crate::ui::color_select::aci_string_to_color(value); + let open = color_open == Some((row, field)); + let selector = crate::ui::color_select::color_selector( + cur, + open, + crate::ui::color_select::ColorExtras { + by_layer: true, + by_block: true, + }, + move |c| Message::TableStyleCellEdit { + row, + field, + value: crate::ui::color_select::color_to_aci_string(c), + }, + Message::TableColorMore(row, field), ); - row![text(label).size(11).color(DIM).width(150), sw] + row![text(label).size(11).color(DIM).width(150), selector] .spacing(8) .align_y(iced::Center) .into()