Merge pull request #1795 from MarcelP102/Select-Color-Redesign

[UX] Select Color redesign and feature parity

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Hakan SevenandClaude Opus 5.5 committed 2026-10-15 00:21:10 +03:00
commit 843cbcd607
56 files changed
+4716 -606

No files matched your search

+10 -2
View File
@@ -358,7 +358,11 @@ impl OpenCADStudio {
if layer != "0" || entity.as_entity().layer().is_empty() {
entity.as_entity_mut().set_layer(layer);
}
crate::scene::view::dispatch::apply_color(&mut entity, self.ribbon.active_color);
crate::scene::view::dispatch::apply_named_color(
&mut entity,
self.ribbon.active_color,
self.ribbon.active_color_name(),
);
crate::scene::view::dispatch::apply_common_prop(
&mut entity,
"linetype",
@@ -476,7 +480,11 @@ impl OpenCADStudio {
if layer != "0" || entity.as_entity().layer().is_empty() {
entity.as_entity_mut().set_layer(layer);
}
crate::scene::view::dispatch::apply_color(&mut entity, self.ribbon.active_color);
crate::scene::view::dispatch::apply_named_color(
&mut entity,
self.ribbon.active_color,
self.ribbon.active_color_name(),
);
crate::scene::view::dispatch::apply_common_prop(
&mut entity,
"linetype",
+2 -2
View File
@@ -391,8 +391,8 @@ impl OpenCADStudio {
.location(id)
.map_or(crate::app::config::DockSide::Right, |(side, _)| side),
matches!(
self.color_pick_target,
Some((crate::app::ColorPickTarget::Gradient(_), _))
self.color_picker.as_ref().map(|picker| &picker.target),
Some(crate::app::ColorPickTarget::Gradient(_))
),
),
crate::ui::dock::PanelId::BlockPalette => {
+3
View File
@@ -290,6 +290,8 @@ pub(super) struct DocumentTab {
/// Model-space corners of the last crossing window drawn with no command
/// running: STRETCH on that selection stretches through it (#1068).
pub(super) last_crossing_window: Option<([f64; 2], [f64; 2])>,
/// Colours picked in this drawing this session, newest first. Not saved.
pub(super) recent_colors: Vec<codec::types::Color>,
}
impl DocumentTab {
@@ -697,6 +699,7 @@ impl DocumentTab {
suspended_cmd: None,
transparent_resume: false,
last_crossing_window: None,
recent_colors: Vec::new(),
}
}
+29 -36
View File
@@ -952,9 +952,6 @@ pub(super) struct OpenCADStudio {
pub(crate) sheet_set_pending_layout: Option<(String, String)>,
/// The subset / set Import Layout as Sheet adds to, while its file picker is open.
pub(crate) sheet_set_import_parent: Option<String>,
/// Which viewport background the colour wheel is editing, or `None` when
/// it is closed. One slot, because only one wheel can be open at a time.
pub(crate) bg_picker: Option<BgTarget>,
/// General edge-stack dock layout for the side panels.
pub(crate) dock: crate::ui::dock::DockState,
/// Pallet revealed although it auto-hides (its edge's group, or its
@@ -1074,13 +1071,14 @@ pub(super) struct OpenCADStudio {
#[cfg(target_arch = "wasm32")]
web_autosave_clear_after: Vec<String>,
// ── Floating panel windows ────────────────────────────────────────────
/// Active `iced_aw` colour picker: destination plus its initial colour.
color_pick_target: Option<(ColorPickTarget, AcadColor)>,
/// Visible page in the shared CAD colour picker.
color_picker_tab: ColorPickerTab,
/// The open Select Color panel, if any.
color_picker: Option<crate::ui::color_select::ColorPicker>,
/// The built-in colour book and those in the colour book folder, read
/// lazily when the Select Color panel first opens.
color_books: std::sync::Arc<Vec<crate::ui::color_select::book::ColorBook>>,
color_books_read: bool,
/// Recently committed real colours, newest first.
/// ACI and True Color remain semantically distinct even when their RGB matches.
recent_colors: Vec<AcadColor>,
/// The open in-canvas modal dialog, if any (Plan B: shared overlay instead
/// of OS windows).
active_modal: Option<ModalKind>,
@@ -1788,21 +1786,19 @@ pub struct SaveOutcome {
refreshed_preview: Option<Option<codec::Preview>>,
result: Result<(), crate::io::SaveFailure>,
}
/// Which viewport background a colour-wheel session is editing.
/// Which Options colour the interface colour picker is editing.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BgTarget {
Model,
Paper,
Desk,
pub enum UiColor {
ModelBackground,
PaperBackground,
DeskBackground,
/// One of the six theme colours, by its row in Options.
Theme(usize),
Crosshair,
SnapMarker,
CommandText,
}
/// Active page in the shared CAD colour picker.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ColorPickerTab {
#[default]
Index,
TrueColor,
}
/// Where a colour chosen in the standalone palette window should be applied.
#[derive(Debug, Clone)]
pub enum ColorPickTarget {
@@ -1828,6 +1824,8 @@ pub enum ColorPickTarget {
LayerState(usize),
/// The MText editor's selection (or global) colour.
MText,
/// An Options interface colour (true colour only).
Interface(UiColor),
/// A colour of the view Background dialog: 0 solid, 1 top, 2 middle, 3 bottom.
ViewBackground(u8),
}
@@ -2596,12 +2594,6 @@ pub enum Message {
ModelSpaceModeChanged(config::ModelSpaceMode),
/// Change Model Space custom background color as hex or empty for default.
ModelSpaceBgChanged(String),
/// Open the colour wheel on one of the viewport backgrounds.
BgPickerOpen(BgTarget),
/// Dismiss the wheel, changing nothing.
BgPickerCancel,
/// Accept the wheel's colour for whichever background it was opened on.
BgPickerSubmit(iced::Color),
/// Change Paper Space custom sheet background color as hex or empty for default.
PaperSpaceBgChanged(String),
/// Change Paper Space desk surround background (#RRGGBB).
@@ -4344,14 +4336,16 @@ pub enum Message {
DsColorMore(DsField),
/// Open the shared CAD colour picker for a field and its current colour.
OpenColorWindow(ColorPickTarget, AcadColor),
/// Switch between Index Color and True Color.
ColorPickerTabChanged(ColorPickerTab),
/// Change the pending colour without committing it yet.
ColorPickerColorChanged(AcadColor),
/// Interaction inside the Select Color panel.
ColorPicker(crate::ui::color_select::ColorPickerMsg),
/// Close the colour picker without choosing.
CloseColorPicker,
/// Commit the colour chosen in the shared picker.
ColorWindowPick(codec::types::Color),
/// Commit the colour chosen in the shared picker, with its `BOOK$NAME`
/// when it comes from a colour book.
ColorWindowPick(codec::types::Color, Option<String>),
/// An `.acb` file was read (and, on desktop, copied into the first
/// colour book folder).
ColorBookLoaded(Result<crate::ui::color_select::book::ColorBook, String>),
/// Set a block/linetype Handle field on the selected dim style from a
/// dropdown of available block-records / linetypes (by name).
DsSetHandle {
@@ -4756,7 +4750,6 @@ impl OpenCADStudio {
sheet_set: Default::default(),
sheet_set_pending_layout: None,
sheet_set_import_parent: None,
bg_picker: None,
block_palette: Default::default(),
xref_manager: Default::default(),
dock: Default::default(),
@@ -4805,9 +4798,9 @@ impl OpenCADStudio {
web_autosave_consumed: None,
#[cfg(target_arch = "wasm32")]
web_autosave_clear_after: Vec::new(),
color_pick_target: None,
color_picker_tab: ColorPickerTab::Index,
recent_colors: Vec::new(),
color_picker: None,
color_books: Default::default(),
color_books_read: false,
active_modal: None,
hyperlink_editor_handles: Vec::new(),
hyperlink_editor_url: String::new(),
+5 -1
View File
@@ -3160,7 +3160,11 @@ handles={handles_ms:.1} panel={:.1} ribbon={ribbon_ms:.1} tail={:.1} selected={}
}
if !preserve_entity_style {
crate::scene::view::dispatch::apply_color(&mut entity, self.ribbon.active_color);
crate::scene::view::dispatch::apply_named_color(
&mut entity,
self.ribbon.active_color,
self.ribbon.active_color_name(),
);
crate::scene::view::dispatch::apply_common_prop(
&mut entity,
"linetype",
+267 -102
View File
@@ -436,6 +436,41 @@ impl OpenCADStudio {
let tab = &self.tabs[self.active_tab];
(started, tab.id, tab.scene.geometry_epoch)
});
// The Select Color panel can open on top of another dialog, so it
// takes the keyboard first: Esc cancels, Enter confirms, the arrows
// walk the index palette, and every other keystroke is swallowed.
if self.color_picker.is_some() {
use crate::ui::color_select::ColorPickerMsg;
match &msg {
Message::CommandEscape => {
return self.update(Message::ColorPicker(ColorPickerMsg::Cancel));
}
Message::CommandFinalize => {
return self.update(Message::ColorPicker(ColorPickerMsg::Ok));
}
Message::ShortcutPressed(key) => match key.rsplit('+').next() {
Some("ESCAPE") => {
return self.update(Message::ColorPicker(ColorPickerMsg::Cancel));
}
Some("ENTER" | "RETURN") => {
return self.update(Message::ColorPicker(ColorPickerMsg::Ok));
}
Some("TAB") => {
return crate::ui::color_select::picker::focus_step(
!key.contains("SHIFT+"),
);
}
_ => {}
},
Message::ArrowKeyPressed { direction, .. } => {
return self.update(Message::ColorPicker(ColorPickerMsg::Arrow(*direction)));
}
_ => {}
}
if is_modal_blocked_key_msg(&msg) {
return Task::none();
}
}
// A modal dialog must capture the keyboard the same way it already
// captures the mouse. Otherwise keystrokes from the global key
// subscription leak past the modal into the command line and fire as
@@ -4330,38 +4365,7 @@ impl OpenCADStudio {
Task::none()
}
Message::LayerColorSet(color) => {
let i = self.active_tab;
// Apply to every selected layer (multi-select), not just one.
let names = self.selected_layer_names(i);
if !names.is_empty() {
let undo = self.begin_layer_undo(i, "LAYER COLOR", &names);
for name in &names {
if let Some(dl) = self.tabs[i].scene.document.layers.get_mut(name) {
dl.color = color;
dl.color_name = None;
dl.book_name = None;
}
}
for pl in self.tabs[i].layers.layers.iter_mut() {
if names.contains(&pl.name) {
pl.color = color;
pl.color_name = None;
pl.book_name = None;
}
}
self.tabs[i].dirty = true;
self.commit_layer_undo(i, undo);
// ByLayer color is baked into the cached wires at
// tessellation time, so bump the geometry epoch to
// invalidate the wire cache and repaint with the new color.
self.tabs[i].scene.invalidate_layer_dependencies(&names);
self.tabs[i].layers.color_picker_row = None;
self.tabs[i].layers.color_full_palette = false;
self.sync_ribbon_layers();
}
Task::none()
}
Message::LayerColorSet(color) => self.set_layer_color(color, None),
Message::LayerLinetypeSet(lt) => {
let i = self.active_tab;
@@ -4473,7 +4477,7 @@ impl OpenCADStudio {
Message::PaneClicked(pane) => self.on_pane_clicked(pane),
Message::PaneDragged(ev) => self.on_pane_dragged(ev),
Message::PaneMove(idx, local) => {
if self.color_pick_target.is_some() {
if self.color_picker.is_some() {
return Task::none();
}
let p = self.pane_canvas_point(idx, local);
@@ -7432,22 +7436,7 @@ impl OpenCADStudio {
Task::none()
}
Message::PropColorChanged(color) => {
let i = self.active_tab;
let handles = self.property_target_handles(i);
if handles.is_empty() {
let task = self.on_ribbon_color_changed(color);
self.refresh_properties();
return task;
}
self.apply_property_op(i, "CHPROP", &handles, |app, handle| {
if let Some(entity) = app.tabs[i].scene.document.get_entity_mut(handle) {
crate::scene::view::dispatch::apply_color(entity, color);
}
});
self.tabs[i].properties.color_picker_open = false;
Task::none()
}
Message::PropColorChanged(color) => self.set_property_color(color, None),
Message::PropLwChanged(lw) => {
let i = self.active_tab;
@@ -8958,39 +8947,6 @@ impl OpenCADStudio {
Task::none()
}
Message::BgPickerOpen(target) => {
self.bg_picker = Some(target);
Task::none()
}
Message::BgPickerCancel => {
self.bg_picker = None;
Task::none()
}
Message::BgPickerSubmit(color) => {
// Hand the result to the same handler the typed hex field
// uses, so the wheel and the field cannot drift apart on
// validation, persistence or the MatchTheme fallback.
let hex = crate::app::config::rgb_to_hex([
(color.r * 255.0).round() as u8,
(color.g * 255.0).round() as u8,
(color.b * 255.0).round() as u8,
]);
match self.bg_picker.take() {
Some(crate::app::BgTarget::Model) => {
Task::done(Message::ModelSpaceBgChanged(hex))
}
Some(crate::app::BgTarget::Paper) => {
Task::done(Message::PaperSpaceBgChanged(hex))
}
Some(crate::app::BgTarget::Desk) => {
Task::done(Message::DeskSpaceBgChanged(hex))
}
None => Task::none(),
}
}
Message::ModelSpaceBgChanged(hex) => {
self.model_bg_input = hex.clone();
if hex.trim().is_empty() {
@@ -11922,10 +11878,21 @@ impl OpenCADStudio {
) {
self.graphic_attributes.open_menu = None;
}
self.color_pick_target = Some((target, color));
// Always open the shared CAD colour picker on the indexed ACI page.
self.color_picker_tab = crate::app::ColorPickerTab::Index;
let (current, varies, name) = self.color_picker_current(&target, color);
let mut picker = crate::ui::color_select::ColorPicker::new(target, current, varies);
if !matches!(picker.target, crate::app::ColorPickTarget::Interface(_)) {
self.read_color_books();
let by_layer = self.color_picker_layer_color(&picker.target);
let tab = &self.tabs[self.active_tab];
picker = picker.with_drawing(
crate::ui::color_select::document::collect(&tab.scene.document),
&tab.recent_colors,
std::sync::Arc::clone(&self.color_books),
name,
by_layer,
);
}
self.color_picker = Some(picker);
self.modal_offset = iced::Vector::ZERO;
self.ds_color_open = None;
self.mls_color_open = None;
@@ -11940,29 +11907,226 @@ impl OpenCADStudio {
Task::none()
}
Message::ColorPickerTabChanged(tab) => {
self.color_picker_tab = tab;
Task::none()
}
Message::ColorPickerColorChanged(color) => {
if let Some((_, current)) = self.color_pick_target.as_mut() {
*current = color;
Message::ColorPicker(msg) => {
let Some(picker) = self.color_picker.as_mut() else {
return Task::none();
};
match picker.update(msg) {
Some(crate::ui::color_select::Outcome::Commit(color, name)) => {
self.update(Message::ColorWindowPick(color, name))
}
Some(crate::ui::color_select::Outcome::LoadBook) => Task::perform(
crate::ui::color_select::book::load_from_dialog(),
Message::ColorBookLoaded,
),
Some(crate::ui::color_select::Outcome::Cancel) => {
self.color_picker = None;
Task::none()
}
Some(crate::ui::color_select::Outcome::Screenshot) => {
use crate::ui::color_select::{eyedropper::EyedropperMsg, ColorPickerMsg};
match self.main_window {
Some(id) => iced::window::screenshot(id).map(|shot| {
Message::ColorPicker(ColorPickerMsg::Eyedropper(
EyedropperMsg::Shot(shot),
))
}),
// Nothing to capture: leave the eyedropper instead
// of waiting for a screenshot that never comes.
None => self.update(Message::ColorPicker(ColorPickerMsg::Cancel)),
}
}
None => Task::none(),
}
Task::none()
}
Message::CloseColorPicker => {
self.color_pick_target = None;
self.color_picker = None;
Task::none()
}
Message::ColorWindowPick(color) => {
self.note_recent_color(color);
self.on_color_window_pick(color)
Message::ColorWindowPick(color, name) => {
// Interface colours are not drawing colours: keep them out of
// the recent list.
if !matches!(
self.color_picker.as_ref().map(|picker| &picker.target),
Some(crate::app::ColorPickTarget::Interface(_))
) {
self.note_recent_color(color);
}
self.on_color_window_pick(color, name)
}
Message::ColorBookLoaded(Ok(book)) => {
// A book loaded again replaces the earlier copy.
let index = crate::ui::color_select::book::add_or_replace(
std::sync::Arc::make_mut(&mut self.color_books),
book,
);
if let Some(picker) = self.color_picker.as_mut() {
picker.show_book(std::sync::Arc::clone(&self.color_books), index);
}
Task::none()
}
Message::ColorBookLoaded(Err(error)) => {
if let Some(picker) = self.color_picker.as_mut().filter(|_| error != "Cancelled") {
picker.set_status(error);
}
Task::none()
}
Message::DsSetHandle { field, value } => self.on_ds_set_handle(field, value),
}
}
/// The colour the Select Color panel shows as "Current", and whether the
/// selection it edits has mixed colours. A mixed selection starts from the
/// colour of its most recently drawn object (the highest handle).
/// Also returns the colour-book identity (`BOOK$NAME`) of that colour.
fn color_picker_current(
&self,
target: &crate::app::ColorPickTarget,
color: codec::types::Color,
) -> (codec::types::Color, bool, Option<String>) {
let i = self.active_tab;
let document = &self.tabs[i].scene.document;
let name_of = |handle| {
document
.get_entity(handle)
.and_then(|entity| entity.common().color_name.clone())
};
match target {
crate::app::ColorPickTarget::Properties => {}
crate::app::ColorPickTarget::Ribbon => {
return (color, false, self.ribbon.active_color_name());
}
crate::app::ColorPickTarget::Layer(layer) => {
let name = document
.layers
.get(layer)
.and_then(crate::ui::color_select::document::layer_identity);
return (color, false, name);
}
_ => return (color, false, None),
}
let mut colors = self
.property_target_handles(i)
.into_iter()
.filter_map(|handle| {
document
.get_entity(handle)
.map(|entity| (handle, entity.common().color))
})
.collect::<Vec<_>>();
let Some(&(first_handle, first)) = colors.first() else {
return (color, false, None);
};
if colors.iter().all(|&(_, c)| c == first) {
return (first, false, name_of(first_handle));
}
colors.sort_by_key(|&(handle, _)| handle);
let (newest_handle, newest) = colors.last().copied().unwrap_or((first_handle, color));
(newest, true, name_of(newest_handle))
}
/// The colour ByLayer stands for in the Select Color panel: the layer of
/// the newest selected object, or the current layer for new objects.
fn color_picker_layer_color(
&self,
target: &crate::app::ColorPickTarget,
) -> Option<codec::types::Color> {
let i = self.active_tab;
let document = &self.tabs[i].scene.document;
let layer = match target {
crate::app::ColorPickTarget::Properties => self
.property_target_handles(i)
.into_iter()
.max()
.and_then(|handle| document.get_entity(handle))
.map_or(self.tabs[i].active_layer.as_str(), |entity| {
entity.as_entity().layer()
}),
crate::app::ColorPickTarget::Ribbon => self.tabs[i].active_layer.as_str(),
_ => return None,
};
document.layers.get(layer).map(|layer| layer.color)
}
/// Read the built-in colour book and every `.acb` in the colour book
/// folder, once per session. A book in the folder replaces a built-in
/// one of the same name.
fn read_color_books(&mut self) {
if std::mem::replace(&mut self.color_books_read, true) {
return;
}
self.color_books = std::sync::Arc::new(crate::ui::color_select::book::read_all());
}
/// Colour the selected objects (or set the current colour when nothing
/// is selected). `name` is a colour-book identity, `BOOK$NAME`.
pub(super) fn set_property_color(
&mut self,
color: codec::types::Color,
name: Option<String>,
) -> Task<Message> {
let i = self.active_tab;
let handles = self.property_target_handles(i);
if handles.is_empty() {
let task = self.on_ribbon_color_changed(color);
self.ribbon.active_color_book = name.map(|name| (color, name));
self.refresh_properties();
return task;
}
self.apply_property_op(i, "CHPROP", &handles, |app, handle| {
if let Some(entity) = app.tabs[i].scene.document.get_entity_mut(handle) {
crate::scene::view::dispatch::apply_named_color(entity, color, name.clone());
}
});
self.tabs[i].properties.color_picker_open = false;
Task::none()
}
/// Colour every selected layer (multi-select), not just one. `name` is a
/// colour-book identity, `BOOK$NAME`.
pub(super) fn set_layer_color(
&mut self,
color: codec::types::Color,
name: Option<&str>,
) -> Task<Message> {
let i = self.active_tab;
// Layers keep the book and the colour name apart.
let (book_name, color_name) = match name.map(|name| name.split_once('$')) {
Some(Some((book, name))) => (Some(book), Some(name)),
Some(None) => (None, name),
None => (None, None),
};
let names = self.selected_layer_names(i);
if !names.is_empty() {
let undo = self.begin_layer_undo(i, "LAYER COLOR", &names);
for name in &names {
if let Some(dl) = self.tabs[i].scene.document.layers.get_mut(name) {
dl.color = color;
dl.color_name = color_name.map(str::to_string);
dl.book_name = book_name.map(str::to_string);
}
}
for pl in self.tabs[i].layers.layers.iter_mut() {
if names.contains(&pl.name) {
pl.color = color;
pl.color_name = color_name.map(str::to_string);
pl.book_name = book_name.map(str::to_string);
}
}
self.tabs[i].dirty = true;
self.commit_layer_undo(i, undo);
// ByLayer color is baked into the cached wires at
// tessellation time, so bump the geometry epoch to
// invalidate the wire cache and repaint with the new color.
self.tabs[i].scene.invalidate_layer_dependencies(&names);
self.tabs[i].layers.color_picker_row = None;
self.tabs[i].layers.color_full_palette = false;
self.sync_ribbon_layers();
}
Task::none()
}
pub(crate) fn note_recent_color(&mut self, color: codec::types::Color) {
// Keep only real colours in the recent list. ByLayer / ByBlock / None
// are logical CAD states rather than reusable colours.
@@ -11971,12 +12135,13 @@ impl OpenCADStudio {
codec::types::Color::Index(_) | codec::types::Color::Rgb { .. }
) {
// No duplicates: selecting an existing colour moves it to the front.
if let Some(pos) = self.recent_colors.iter().position(|c| c == &color) {
self.recent_colors.remove(pos);
let recent = &mut self.tabs[self.active_tab].recent_colors;
if let Some(pos) = recent.iter().position(|c| c == &color) {
recent.remove(pos);
}
self.recent_colors.insert(0, color);
self.recent_colors.truncate(12);
recent.insert(0, color);
recent.truncate(12);
}
}
+54 -27
View File
@@ -1136,42 +1136,60 @@ impl OpenCADStudio {
Task::none()
}
pub(super) fn on_color_window_pick(&mut self, color: codec::types::Color) -> Task<Message> {
pub(super) fn on_color_window_pick(
&mut self,
color: codec::types::Color,
name: Option<String>,
) -> Task<Message> {
if let Some(crate::app::ColorPickTarget::Interface(target)) = self
.color_picker
.as_ref()
.map(|picker| picker.target.clone())
{
self.color_picker = None;
let Some((r, g, b)) = crate::ui::color_select::math::color_rgb(color) else {
return Task::none();
};
// Hand the result to the same handler the typed hex field uses, so
// the picker and the field cannot drift apart on validation or
// persistence.
let hex = crate::app::config::rgb_to_hex([r, g, b]);
use crate::app::UiColor;
return Task::done(match target {
UiColor::ModelBackground => Message::ModelSpaceBgChanged(hex),
UiColor::PaperBackground => Message::PaperSpaceBgChanged(hex),
UiColor::DeskBackground => Message::DeskSpaceBgChanged(hex),
UiColor::Theme(index) => Message::OptionsThemeColorChanged(index, hex),
UiColor::Crosshair => Message::CrosshairColorChanged(hex),
UiColor::SnapMarker => Message::SnapMarkerColorChanged(hex),
UiColor::CommandText => Message::CommandTextColorChanged(hex),
});
}
if matches!(
self.color_pick_target.as_ref().map(|(target, _)| target),
self.color_picker.as_ref().map(|picker| &picker.target),
Some(crate::app::ColorPickTarget::PlotStyle)
) {
self.color_pick_target = None;
self.color_picker = None;
let rgb = match color {
codec::types::Color::Rgb { r, g, b } => Some((r, g, b)),
codec::types::Color::Index(index) => codec::types::aci_table::aci_to_rgb(index),
_ => None,
};
if let Some((r, g, b)) = rgb {
self.ps_color_buf = format!("#{r:02X}{g:02X}{b:02X}");
if let Some((r, g, b)) = crate::ui::color_select::math::color_rgb(color) {
self.ps_color_buf = crate::ui::color_select::math::hex_string(r, g, b);
}
return self.on_plot_style_panel_apply();
}
let s = crate::ui::color_select::color_to_aci_string(color);
let edit = match self.color_pick_target.take().map(|(target, _)| target) {
let edit = match self.color_picker.take().map(|picker| picker.target) {
Some(crate::app::ColorPickTarget::Gradient(index)) => {
Some(Message::GraphicAttributes(GraphicAttributesMsg::Gradient(
crate::ui::window::gradient_editor::GradientMsg::Color(index, color),
)))
}
Some(crate::app::ColorPickTarget::ViewBackground(slot)) => {
let rgb = match color {
codec::types::Color::Rgb { r, g, b } => Some([r, g, b]),
codec::types::Color::Index(index) => {
codec::types::aci_table::aci_to_rgb(index).map(|(r, g, b)| [r, g, b])
}
_ => None,
};
rgb.map(|rgb| {
Message::PdfDialog(crate::ui::window::pdf_dialogs::PdfDialogMsg::BgColor(slot, rgb))
crate::ui::color_select::math::color_rgb(color).map(|(r, g, b)| {
Message::PdfDialog(crate::ui::window::pdf_dialogs::PdfDialogMsg::BgColor(
slot,
[r, g, b],
))
})
}
Some(crate::app::ColorPickTarget::GraphicAttributesLine) => Some(
@@ -1189,7 +1207,9 @@ impl OpenCADStudio {
field: f,
value: s,
}),
Some(crate::app::ColorPickTarget::Properties) => Some(Message::PropColorChanged(color)),
Some(crate::app::ColorPickTarget::Properties) => {
return self.set_property_color(color, name);
}
Some(crate::app::ColorPickTarget::PropertiesBg) => {
Some(Message::PropBgColorChanged(color))
}
@@ -1197,13 +1217,17 @@ impl OpenCADStudio {
Some(Message::PropColorFieldChanged { field, color })
}
Some(crate::app::ColorPickTarget::MText) => Some(Message::MTextColorChanged(color)),
Some(crate::app::ColorPickTarget::Ribbon) => Some(Message::RibbonColorChanged(color)),
Some(crate::app::ColorPickTarget::Layer(name)) => {
Some(crate::app::ColorPickTarget::Ribbon) => {
let task = self.update(Message::RibbonColorChanged(color));
self.ribbon.active_color_book = name.map(|name| (color, name));
return task;
}
Some(crate::app::ColorPickTarget::Layer(layer)) => {
let i = self.active_tab;
match self.layer_panel_index(i, &name) {
match self.layer_panel_index(i, &layer) {
Some(idx) => {
self.tabs[i].layers.selected = Some(idx);
Some(Message::LayerColorSet(color))
return self.set_layer_color(color, name.as_deref());
}
None => None,
}
@@ -1211,7 +1235,10 @@ impl OpenCADStudio {
Some(crate::app::ColorPickTarget::LayerState(idx)) => {
Some(Message::LayerStateEditorLayerColor(idx, color))
}
Some(crate::app::ColorPickTarget::PlotStyle) => None,
// Handled before this match.
Some(
crate::app::ColorPickTarget::PlotStyle | crate::app::ColorPickTarget::Interface(_),
) => None,
None => None,
};
if let Some(m) = edit {
+2 -2
View File
@@ -1144,7 +1144,7 @@ impl OpenCADStudio {
p: Point,
expected_viewport: Option<codec::Handle>,
) -> Task<Message> {
if self.color_pick_target.is_some() {
if self.color_picker.is_some() {
return Task::none();
}
@@ -1232,7 +1232,7 @@ impl OpenCADStudio {
// leaks through the stack to the pane mouse_area beneath and
// would track the crosshair over the dropdown's empty areas.
// Drop the move here instead. (#227)
if self.ribbon.open_dropdown.is_some() || self.color_pick_target.is_some() {
if self.ribbon.open_dropdown.is_some() || self.color_picker.is_some() {
return Task::none();
}
let i = self.active_tab;
+14 -36
View File
@@ -2303,7 +2303,7 @@ bg={bg_ms:.1}ms n={view_count}"
&redo_labels,
self.win_size,
self.tabs[self.active_tab].is_start,
&self.recent_colors,
&self.tabs[self.active_tab].recent_colors,
)
.unwrap_or_else(|| iced::widget::Space::new().width(0).height(0).into())
};
@@ -2431,42 +2431,20 @@ bg={bg_ms:.1}ms n={view_count}"
}
}
if let Some((_, current)) = self.color_pick_target.as_ref() {
match self.color_picker_tab {
super::ColorPickerTab::Index => {
let content =
crate::ui::color_select::index_color_page(*current, &self.recent_colors);
crate::ui::modal::modal(
base,
t!("Select Color"),
content,
Message::CloseColorPicker,
self.modal_offset,
crate::ui::modal::ModalOptions::NOTICE,
)
}
super::ColorPickerTab::TrueColor => {
let initial = crate::ui::properties::acad_color_display(*current).0;
let modal_base = crate::ui::modal::backdrop(base, Message::CloseColorPicker);
iced_aw::ColorPicker::new(
true,
initial,
modal_base,
// Cancel inside True Color returns to the indexed page instead
// of closing the whole CAD colour picker.
Message::ColorPickerTabChanged(super::ColorPickerTab::Index),
|color| {
Message::ColorWindowPick(crate::ui::color_select::iced_to_acad_color(
color,
))
},
)
.into()
}
if let Some(picker) = self.color_picker.as_ref() {
// While the eyedropper is picking, the panel steps aside so the
// whole window can be sampled.
if let Some(eyedropper) = picker.eyedropper_view() {
return iced::widget::stack![base, eyedropper].into();
}
crate::ui::modal::modal(
base,
t!("Select Color"),
picker.view(),
Message::CloseColorPicker,
self.modal_offset,
crate::ui::modal::ModalOptions::NOTICE,
)
} else {
base
}
+2 -1
View File
@@ -373,6 +373,8 @@ impl OpenCADStudio {
.map(|_| std::env::temp_dir().display().to_string()),
fonts: crate::io::font_repo::fonts_dir()
.map(|p| p.display().to_string()),
color_books: crate::ui::color_select::book::folder()
.map(|p| p.display().to_string()),
}
}
#[cfg(target_arch = "wasm32")]
@@ -395,7 +397,6 @@ impl OpenCADStudio {
&self.model_bg_input,
&self.paper_bg_input,
&self.desk_bg_input,
self.bg_picker,
dirty,
close_confirm,
flow,