From acde2ab30e473b03b09e6ab8a8811b1739ba7b1e Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Thu, 29 Oct 2026 06:52:38 -0700 Subject: [PATCH] Color Wheel: the remembered hue resets when the color settings change the color's display The wheel kept the HSB picked last while the objects and the color were unchanged, even after a change of the working RGB space or the document's profile made the same color display differently. It now also stores the display sRGB of the color it picked, and shows the color's own HSB when that no longer matches. --- crates/ui-egui/src/panels/color_wheel.rs | 17 ++++++++++++----- crates/ui-egui/src/panels/tests_color_wheel.rs | 17 ++++++++++++++++- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/crates/ui-egui/src/panels/color_wheel.rs b/crates/ui-egui/src/panels/color_wheel.rs index f466584c0..7011c6c70 100644 --- a/crates/ui-egui/src/panels/color_wheel.rs +++ b/crates/ui-egui/src/panels/color_wheel.rs @@ -5,7 +5,7 @@ //! proxy and Shift steps the ring by 45°. A pick is display sRGB, converted into the working RGB //! space: an RGB, CMYK or Lab color keeps its model, and a Gray color or a tint of a global swatch //! becomes a process color in the document's color mode. The HSB picked last stays while the -//! objects, the proxy and the color are unchanged, so a gray keeps its hue. +//! objects, the proxy, the color and the color settings are unchanged, so a gray keeps its hue. use egui::{Ui, UiBuilder}; use vectorcraft_color::cms::{self, Cms}; @@ -20,7 +20,7 @@ use crate::widgets::{self, FieldAxis, Live}; pub const ID: &str = "colorWheel"; /// The widest the wheel is drawn. const MAX_WHEEL: f32 = 200.0; -/// Panel state: the HSB picked last and its [`Owner`]. +/// Panel state: the HSB picked last, its [`Owner`] and the display sRGB of the color it stored. const MEMORY: &str = "color-wheel-hsb"; /// What the HSB picked last belongs to: the document, the selected objects, the proxy, the @@ -59,6 +59,13 @@ pub(crate) fn display_hsb(cms: &Cms, c: &Color) -> [f32; 3] { rgb_to_hsb(cms.display_rgb(c)) } +/// The HSB the wheel shows for `c` through `cms` when `hsb` was picked last and `c` then displayed +/// as the sRGB `shown`: `hsb` while `c` still displays as `shown`, else `c`'s own display HSB (after +/// a change of the color settings or the document's profile). +pub(crate) fn remembered_hsb(cms: &Cms, c: &Color, shown: [f32; 3], hsb: [f32; 3]) -> [f32; 3] { + if cms.display_rgb(c) == shown { hsb } else { display_hsb(cms, c) } +} + /// The color a pick of display HSB `hsb` stores in place of `was`, through `cms`. The picked sRGB /// goes into the working RGB space. An RGB color stays RGB, and a CMYK or Lab color converts to its /// model with the color settings. Anything else (no color, a Gray color, a tint of a global swatch) @@ -104,8 +111,8 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) { let cms = cms::active(); // With None, a pattern or a "?" proxy the wheel shows the last solid color without markers, // and a click, an arrow key or a screen reader's action applies the picked color. - let hsb = match (color, pstate::>(ui.ctx(), MEMORY)) { - (Some(c), Some((owner, hsb))) if owner == Owner::of(app, &tgt, c) => hsb, + let hsb = match (color, pstate::>(ui.ctx(), MEMORY)) { + (Some(c), Some((owner, shown, hsb))) if owner == Owner::of(app, &tgt, c) => remembered_hsb(&cms, &c, shown, hsb), (c, _) => display_hsb(&cms, &c.unwrap_or(app.session.last_solid)), }; let marked = color.is_some(); @@ -144,7 +151,7 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) { let cmyk_doc = app.session.active().is_some_and(|d| d.doc.color_mode == ColorMode::Cmyk); let c = stored(&cms, hsb, replaced(app, &tgt, behind).as_ref(), cmyk_doc); if !behind { - set_pstate(ui.ctx(), MEMORY, Some((Owner::of(app, &tgt, c), hsb))); + set_pstate(ui.ctx(), MEMORY, Some((Owner::of(app, &tgt, c), cms.display_rgb(&c), hsb))); } apply(app, &tgt, c, phase, behind); } diff --git a/crates/ui-egui/src/panels/tests_color_wheel.rs b/crates/ui-egui/src/panels/tests_color_wheel.rs index a3dfa3fba..83d78a5e3 100644 --- a/crates/ui-egui/src/panels/tests_color_wheel.rs +++ b/crates/ui-egui/src/panels/tests_color_wheel.rs @@ -11,7 +11,7 @@ use vectorcraft_color::cms::{self, Cms, ColorSettings, delta_e2000}; use vectorcraft_color::{Color, Paint}; use vectorcraft_engine::Session; -use super::color_wheel::{ID, display_hsb, show, stored}; +use super::color_wheel::{ID, display_hsb, remembered_hsb, show, stored}; use crate::VectorcraftApp; /// An app with a selected rectangle filled with `fill`. @@ -308,6 +308,21 @@ fn the_remembered_hue_stays_with_its_object() { assert!(!(1.0..359.0).contains(&hue), "the white object's own hue is 0°, not 120°: {hue}"); } +/// The remembered HSB holds only while the color displays as it did when picked: in another +/// working RGB space the wheel shows the color's own HSB there. +#[test] +fn the_remembered_hsb_follows_a_change_of_working_space() { + let srgb = Cms::default(); + let p3 = Cms::new(&ColorSettings { rgb: cms::DISPLAY_P3.into(), ..ColorSettings::default() }).unwrap(); + let c = Color::rgb(0.5, 0.3, 0.2); + let picked = [25.0, 0.6, 0.5]; + let shown = srgb.display_rgb(&c); + assert_eq!(remembered_hsb(&srgb, &c, shown, picked), picked); + let own = display_hsb(&p3, &c); + assert_ne!(own, picked); + assert_eq!(remembered_hsb(&p3, &c, shown, picked), own); +} + /// A CMYK fill stays CMYK and keeps its look (ΔE 2000 under 2) when its marker is clicked; the /// working profile separates the picked color again, so its inks can move. #[test]