feat(ui): move full ACI palette to a separate "Select Color" window (#94)

The colour selector popup now lists only the named colours (ByLayer,
ByBlock, ACI 1-9) plus a "More…" entry. "More…" opens a standalone
"Select Color" window with the full 256-colour grid; picking there applies
to the originating field (dimension/multileader/table style) and closes
the window. Routed via a ColorPickTarget so one window serves every field.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Hakan Seven 2026-06-14 11:14:14 +03:00
commit 3d050fe1ab
7 changed files with 146 additions and 24 deletions

View file

@ -287,6 +287,9 @@ pub(super) struct OpenCADStudio {
layout_manager_window: Option<window::Id>, layout_manager_window: Option<window::Id>,
plotstyle_window: Option<window::Id>, plotstyle_window: Option<window::Id>,
dimstyle_window: Option<window::Id>, dimstyle_window: Option<window::Id>,
/// Standalone "Select Color" palette window + the field it targets.
color_pick_window: Option<window::Id>,
color_pick_target: Option<ColorPickTarget>,
shortcuts_window: Option<window::Id>, shortcuts_window: Option<window::Id>,
about_window: Option<window::Id>, about_window: Option<window::Id>,
/// New-release notification window — opened on startup when the /// New-release notification window — opened on startup when the
@ -565,6 +568,14 @@ pub(super) enum PendingClose {
Quit, Quit,
} }
/// Where a colour chosen in the standalone palette window should be applied.
#[derive(Debug, Clone)]
pub enum ColorPickTarget {
DimStyle(DsField),
MLeader(&'static str),
Table(u8, &'static str),
}
/// Identifies a DimStyle field that can be edited in the dialog. /// Identifies a DimStyle field that can be edited in the dialog.
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum DsField { pub enum DsField {
@ -1267,6 +1278,10 @@ pub enum Message {
DsToggle(DsField), DsToggle(DsField),
/// Toggle the expanded colour palette for a DimStyle colour field. /// Toggle the expanded colour palette for a DimStyle colour field.
DsColorMore(DsField), DsColorMore(DsField),
/// Open the standalone palette window targeting the given field.
OpenColorWindow(ColorPickTarget),
/// A colour was chosen in the standalone palette window.
ColorWindowPick(acadrust::types::Color),
/// Set a block/linetype Handle field on the selected dim style from a /// Set a block/linetype Handle field on the selected dim style from a
/// dropdown of available block-records / linetypes (by name). /// dropdown of available block-records / linetypes (by name).
DsSetHandle { DsSetHandle {
@ -1377,6 +1392,8 @@ impl OpenCADStudio {
layout_manager_window: None, layout_manager_window: None,
plotstyle_window: None, plotstyle_window: None,
dimstyle_window: None, dimstyle_window: None,
color_pick_window: None,
color_pick_target: None,
shortcuts_window: None, shortcuts_window: None,
about_window: None, about_window: None,
update_notice_window: None, update_notice_window: None,
@ -1656,6 +1673,9 @@ pub fn run() -> iced::Result {
if Some(window_id) == state.layer_window { if Some(window_id) == state.layer_window {
return "Layer Properties Manager".into(); return "Layer Properties Manager".into();
} }
if Some(window_id) == state.color_pick_window {
return "Select Color".into();
}
if Some(window_id) == state.page_setup_window { if Some(window_id) == state.page_setup_window {
return "Page Setup".into(); return "Page Setup".into();
} }

View file

@ -1624,6 +1624,10 @@ impl OpenCADStudio {
self.dimstyle_window = None; self.dimstyle_window = None;
self.ribbon.deactivate_tool_if("DIMSTYLE"); self.ribbon.deactivate_tool_if("DIMSTYLE");
} }
if self.color_pick_window == Some(id) {
self.color_pick_window = None;
self.color_pick_target = None;
}
if self.shortcuts_window == Some(id) { if self.shortcuts_window == Some(id) {
self.shortcuts_window = None; self.shortcuts_window = None;
self.ribbon.deactivate_tool_if("SHORTCUTS"); self.ribbon.deactivate_tool_if("SHORTCUTS");
@ -7321,6 +7325,48 @@ impl OpenCADStudio {
}; };
Task::none() Task::none()
} }
Message::OpenColorWindow(target) => {
self.color_pick_target = Some(target);
self.ds_color_open = None;
self.mls_color_open = None;
self.ts_color_open = None;
if let Some(id) = self.color_pick_window {
return window::gain_focus(id);
}
let (id, task) = window::open(window::Settings {
size: iced::Size::new(420.0, 470.0),
resizable: true,
level: window::Level::AlwaysOnTop,
..Default::default()
});
self.color_pick_window = Some(id);
task.map(|_| Message::Noop)
}
Message::ColorWindowPick(color) => {
let s = crate::ui::color_select::color_to_aci_string(color);
let edit = match self.color_pick_target.take() {
Some(crate::app::ColorPickTarget::DimStyle(f)) => Some(Message::DsEdit(f, s)),
Some(crate::app::ColorPickTarget::MLeader(f)) => {
Some(Message::MLeaderStyleEdit { field: f, value: s })
}
Some(crate::app::ColorPickTarget::Table(r, f)) => {
Some(Message::TableStyleCellEdit {
row: r,
field: f,
value: s,
})
}
None => None,
};
let mut tasks = Vec::new();
if let Some(m) = edit {
tasks.push(self.update(m));
}
if let Some(id) = self.color_pick_window.take() {
tasks.push(window::close(id));
}
Task::batch(tasks)
}
Message::DsSetHandle { field, value } => { Message::DsSetHandle { field, value } => {
let i = self.active_tab; let i = self.active_tab;
let name = self.dimstyle_selected.clone(); let name = self.dimstyle_selected.clone();

View file

@ -465,6 +465,9 @@ impl OpenCADStudio {
&self.style_rename_buf, &self.style_rename_buf,
); );
} }
if Some(window_id) == self.color_pick_window {
return crate::ui::color_select::color_grid_window(Message::ColorWindowPick);
}
if Some(window_id) == self.shortcuts_window { if Some(window_id) == self.shortcuts_window {
return crate::ui::shortcuts::view_window(&self.shortcut_overrides); return crate::ui::shortcuts::view_window(&self.shortcut_overrides);
} }

View file

@ -95,6 +95,7 @@ pub fn color_selector<'a>(
extras: ColorExtras, extras: ColorExtras,
on_select: impl Fn(AcadColor) -> Message + 'a, on_select: impl Fn(AcadColor) -> Message + 'a,
on_toggle: Message, on_toggle: Message,
on_more: Message,
) -> Element<'a, Message> { ) -> Element<'a, Message> {
let (cur_bg, cur_name) = acad_color_display(current); let (cur_bg, cur_name) = acad_color_display(current);
@ -150,13 +151,65 @@ pub fn color_selector<'a>(
for i in 1u8..=9 { for i in 1u8..=9 {
list = list.push(named_row(AcadColor::Index(i))); list = list.push(named_row(AcadColor::Index(i)));
} }
// "More…" opens the full ACI palette in a separate window.
list = list.push(
button(text("More…").size(11).color(TEXT))
.on_press(on_more)
.style(|_: &Theme, status| button::Style {
background: matches!(status, button::Status::Hovered)
.then_some(Background::Color(Color {
r: 0.25,
g: 0.25,
b: 0.30,
a: 1.0,
})),
..Default::default()
})
.padding([2, 4])
.width(Length::Fill),
);
let popup = container(list)
.style(|_: &Theme| container::Style {
background: Some(Background::Color(PICKER_BG)),
border: Border {
color: BORDER,
width: 1.0,
radius: 2.0.into(),
},
..Default::default()
})
.padding(5)
.width(220);
// The popup is shown as a floating overlay (anchored below the button) so
// it doesn't push the surrounding form down.
Element::new(Floating {
base: head.into(),
popup: popup.into(),
})
}
/// Full ACI palette as a standalone window body: ByLayer / ByBlock plus the
/// 256-colour grid. `on_pick` is called with the chosen colour.
pub fn color_grid_window(on_pick: impl Fn(AcadColor) -> Message) -> Element<'static, Message> {
let chip = |color: AcadColor, label: &'static str| -> Element<'static, Message> {
let (bg, _) = acad_color_display(color);
button(
row![swatch(bg), text(label).size(11).color(TEXT)]
.spacing(5)
.align_y(iced::Center),
)
.on_press(on_pick(color))
.padding([3, 6])
.into()
};
// Full ACI palette grid (10-255) below the named colours.
const COLS: u16 = 16; const COLS: u16 = 16;
let mut grid = column![].spacing(1); let mut grid = column![].spacing(2);
let mut idx: u16 = 1; let mut idx: u16 = 1;
while idx <= 255 { while idx <= 255 {
let mut r = row![].spacing(1); let mut r = row![].spacing(2);
for _ in 0..COLS { for _ in 0..COLS {
if idx > 255 { if idx > 255 {
break; break;
@ -164,8 +217,8 @@ pub fn color_selector<'a>(
let ci = idx as u8; let ci = idx as u8;
let (bg, _) = acad_color_display(AcadColor::Index(ci)); let (bg, _) = acad_color_display(AcadColor::Index(ci));
r = r.push( r = r.push(
button(text("").width(12).height(12)) button(text("").width(18).height(18))
.on_press(on_select(AcadColor::Index(ci))) .on_press(on_pick(AcadColor::Index(ci)))
.style(move |_: &Theme, status| button::Style { .style(move |_: &Theme, status| button::Style {
background: Some(Background::Color(bg)), background: Some(Background::Color(bg)),
border: Border { border: Border {
@ -195,25 +248,22 @@ pub fn color_selector<'a>(
grid = grid.push(r); grid = grid.push(r);
} }
let popup = container(column![list, scrollable(grid).height(120)].spacing(4)) container(
.style(|_: &Theme| container::Style { column![
background: Some(Background::Color(PICKER_BG)), text("Select Color").size(13).color(TEXT),
border: Border { row![chip(AcadColor::ByLayer, "ByLayer"), chip(AcadColor::ByBlock, "ByBlock")].spacing(6),
color: BORDER, scrollable(grid).height(Length::Fill),
width: 1.0, ]
radius: 2.0.into(), .spacing(8),
}, )
..Default::default() .style(|_: &Theme| container::Style {
}) background: Some(Background::Color(PICKER_BG)),
.padding(5) ..Default::default()
.width(220);
// The popup is shown as a floating overlay (anchored below the button) so
// it doesn't push the surrounding form down.
Element::new(Floating {
base: head.into(),
popup: popup.into(),
}) })
.padding(10)
.width(Length::Fill)
.height(Length::Fill)
.into()
} }
/// A widget that renders `base` inline and `popup` as a floating overlay /// A widget that renders `base` inline and `popup` as a floating overlay

View file

@ -1,6 +1,6 @@
//! Dimension Style Manager window — fills the entire OS window. //! Dimension Style Manager window — fills the entire OS window.
use crate::app::{DsField, Message}; use crate::app::{ColorPickTarget, DsField, Message};
use iced::widget::{ 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, Space,
}; };
@ -308,6 +308,7 @@ pub fn view_window<'a>(
}, },
move |c| Message::DsEdit(f_sel.clone(), crate::ui::color_select::color_to_aci_string(c)), move |c| Message::DsEdit(f_sel.clone(), crate::ui::color_select::color_to_aci_string(c)),
Message::DsColorMore(fld.clone()), Message::DsColorMore(fld.clone()),
Message::OpenColorWindow(ColorPickTarget::DimStyle(fld.clone())),
); );
row![lbl(label), selector] row![lbl(label), selector]
.spacing(8) .spacing(8)

View file

@ -149,6 +149,7 @@ fn color_row<'a>(
value: crate::ui::color_select::color_to_aci_string(c), value: crate::ui::color_select::color_to_aci_string(c),
}, },
Message::MLeaderColorMore(field), Message::MLeaderColorMore(field),
Message::OpenColorWindow(crate::app::ColorPickTarget::MLeader(field)),
); );
row![text(label).size(11).color(DIM).width(150), selector] row![text(label).size(11).color(DIM).width(150), selector]
.spacing(8) .spacing(8)

View file

@ -140,6 +140,7 @@ pub fn view_window<'a>(
value: crate::ui::color_select::color_to_aci_string(c), value: crate::ui::color_select::color_to_aci_string(c),
}, },
Message::TableColorMore(row, field), Message::TableColorMore(row, field),
Message::OpenColorWindow(crate::app::ColorPickTarget::Table(row, field)),
); );
row![text(label).size(11).color(DIM).width(150), selector] row![text(label).size(11).color(DIM).width(150), selector]
.spacing(8) .spacing(8)