forked from 1b-agent/opencadstudio-upstream
Redesign Select Color panel: index and true color tabs
Replace the old index page and the iced_aw ColorPicker popup with one Select Color panel: - Index Color tab: standard colors, ACI 10-249 grid, grays; arrow keys move through the palette. - True Color tab: HSB, RGB and gradient-field modes built on two new widgets (GradientSlider, ColorArea) and iced_aw NumberInput boxes, an editable hex field and a nearest/exact index color button. - Current/New swatch: clicking Current reverts. A mixed selection opens on the color of the most recently drawn object with *VARIES* on top. - Color field accepts an index, a color name, r,g,b or hex. - Enter confirms, Esc cancels, other keys stay in the panel. - New labels translated in all 22 locales. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_013rGmDUNr1hUf4zo1X3dak8
This commit is contained in:
36 files changed
+2173
-276
No files matched your search
+83
-14
@@ -377,6 +377,37 @@ impl OpenCADStudio {
|
||||
// closes it and every other keystroke-derived message is swallowed;
|
||||
// the modal's own text fields keep working because they emit their own
|
||||
// (non-blocked) messages. (#126)
|
||||
// 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;
|
||||
let last_key = |key: &str| key.rsplit('+').next().map(str::to_owned);
|
||||
match &msg {
|
||||
Message::CommandEscape => {
|
||||
return self.update(Message::ColorPicker(ColorPickerMsg::Cancel));
|
||||
}
|
||||
Message::CommandFinalize => {
|
||||
return self.update(Message::ColorPicker(ColorPickerMsg::Ok));
|
||||
}
|
||||
Message::ShortcutPressed(key) => match last_key(key).as_deref() {
|
||||
Some("ESCAPE") => {
|
||||
return self.update(Message::ColorPicker(ColorPickerMsg::Cancel));
|
||||
}
|
||||
Some("ENTER" | "RETURN") => {
|
||||
return self.update(Message::ColorPicker(ColorPickerMsg::Ok));
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
Message::ArrowKeyPressed { direction, .. } => {
|
||||
return self.update(Message::ColorPicker(ColorPickerMsg::Arrow(*direction)));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
if is_modal_blocked_key_msg(&msg) {
|
||||
return Task::none();
|
||||
}
|
||||
}
|
||||
if self.active_modal.is_some() {
|
||||
if matches!(msg, Message::CommandEscape)
|
||||
|| matches!(&msg, Message::ShortcutPressed(key) if key.rsplit('+').next() == Some("ESCAPE"))
|
||||
@@ -3806,7 +3837,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);
|
||||
@@ -11005,10 +11036,10 @@ impl OpenCADStudio {
|
||||
Task::none()
|
||||
}
|
||||
Message::OpenColorWindow(target, color) => {
|
||||
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) = self.color_picker_current(&target, color);
|
||||
self.color_picker = Some(crate::ui::color_select::ColorPicker::new(
|
||||
target, current, varies,
|
||||
));
|
||||
self.modal_offset = iced::Vector::ZERO;
|
||||
self.ds_color_open = None;
|
||||
self.mls_color_open = None;
|
||||
@@ -11023,18 +11054,23 @@ 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)) => {
|
||||
self.update(Message::ColorWindowPick(color))
|
||||
}
|
||||
Some(crate::ui::color_select::Outcome::Cancel) => {
|
||||
self.color_picker = None;
|
||||
Task::none()
|
||||
}
|
||||
None => Task::none(),
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::CloseColorPicker => {
|
||||
self.color_pick_target = None;
|
||||
self.color_picker = None;
|
||||
Task::none()
|
||||
}
|
||||
|
||||
@@ -11046,6 +11082,39 @@ impl OpenCADStudio {
|
||||
}
|
||||
}
|
||||
|
||||
/// 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).
|
||||
fn color_picker_current(
|
||||
&self,
|
||||
target: &crate::app::ColorPickTarget,
|
||||
color: codec::types::Color,
|
||||
) -> (codec::types::Color, bool) {
|
||||
if !matches!(target, crate::app::ColorPickTarget::Properties) {
|
||||
return (color, false);
|
||||
}
|
||||
let i = self.active_tab;
|
||||
let document = &self.tabs[i].scene.document;
|
||||
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)) = colors.first() else {
|
||||
return (color, false);
|
||||
};
|
||||
if colors.iter().all(|&(_, c)| c == first) {
|
||||
return (first, false);
|
||||
}
|
||||
colors.sort_by_key(|&(handle, _)| handle);
|
||||
let newest = colors.last().map_or(color, |&(_, c)| c);
|
||||
(newest, true)
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
@@ -1137,10 +1137,10 @@ impl OpenCADStudio {
|
||||
|
||||
pub(super) fn on_color_window_pick(&mut self, color: codec::types::Color) -> Task<Message> {
|
||||
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)),
|
||||
@@ -1155,7 +1155,7 @@ impl OpenCADStudio {
|
||||
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::DimStyle(f)) => Some(Message::DsEdit(f, s)),
|
||||
Some(crate::app::ColorPickTarget::MLeader(f)) => {
|
||||
Some(Message::MLeaderStyleEdit { field: f, value: s })
|
||||
|
||||
@@ -1119,7 +1119,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();
|
||||
}
|
||||
|
||||
@@ -1207,7 +1207,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;
|
||||
|
||||
Reference in new issue
Block a user