From 74235d8959969eb25e96305aee1f7ba8a9dd142f Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Thu, 12 Nov 2026 08:10:16 -0800 Subject: [PATCH 1/2] Color: a double-click right of an RGB field switches between percent and 0 to 255 In RGB mode a double-click in the cell right of the R, G or B field switches all three fields and sliders between 0 to 255 and percent (0 to 100 with a % suffix, as the CMYK fields show). The color does not change; a typed percent converts back (50% is 127.5 of 255) and a slider click picks a whole percent. Web Safe RGB, the hex field and the other modes keep their units. Only RGB rows have the cell. It is blank, is highlighted under the pointer, and takes no keyboard focus, so Tab does not stop on it. The RGB sliders are narrower by the cell's width and spacing, so an RGB row is as wide as a row in the other modes and Tint, in a narrow panel too. While a mixed selection disables the rows, a double-click on the cell does nothing, as the fields beside it take no input. The double-click runs ui.colorPanel {rgbPercent}, which takes a bool and answers it. The setting is off by default and is kept with the preferences (ui.color_rgb_percent); a saved value that is not true or false reads as off. The color mixer in the Fill and Stroke chips' popovers (Control bar and Properties panel) and the gradient stop dialog show the same rows and follow the same setting. Docs: docs/mcp.md, docs/control-protocol.md, docs/target-app-parity.md. --- crates/mcp/src/ui_catalog.rs | 2 +- crates/ui-egui/src/menus/ui_commands.rs | 2 +- crates/ui-egui/src/panels/color.rs | 74 ++++++++-- crates/ui-egui/src/panels/color/tests.rs | 180 ++++++++++++++++++++++- crates/ui-egui/src/state.rs | 5 + docs/control-protocol.md | 23 +-- docs/mcp.md | 3 +- docs/target-app-parity.md | 1 + 8 files changed, 266 insertions(+), 24 deletions(-) diff --git a/crates/mcp/src/ui_catalog.rs b/crates/mcp/src/ui_catalog.rs index 4f3ddeb86d..b869f511c1 100644 --- a/crates/mcp/src/ui_catalog.rs +++ b/crates/mcp/src/ui_catalog.rs @@ -1236,7 +1236,7 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[ "ui.colorPanel", "Color Panel Options", "", - "{mode?: grayscale|rgb|hsb|cmyk|websafe, options?: bool, recentColors?: bool} pick the Color panel's mode as its panel menu does (the active color converts to the mode's model as one undo step), Show Options (true) or Hide Options (false), and check (true) or uncheck (false) Show Recent Colors, which shows or hides the panel's Recent Colors row (applied colors still join the list while it is hidden; the Swatches panel keeps its row); all three are kept with the preferences; RGB, HSB, Web Safe RGB and hex values use the working RGB space (ui.inspect: ui.color_mode, ui.color_hide_options, ui.color_show_recent_colors) → {mode (null: none picked yet), options, recentColors, shown: the mode the panel shows the color in, or tint for a tint of a global or spot swatch}", + "{mode?: grayscale|rgb|hsb|cmyk|websafe, options?: bool, recentColors?: bool, rgbPercent?: bool} pick the Color panel's mode as its panel menu does (the active color converts to the mode's model as one undo step), Show Options (true) or Hide Options (false), check (true) or uncheck (false) Show Recent Colors, which shows or hides the panel's Recent Colors row (applied colors still join the list while it is hidden; the Swatches panel keeps its row), and show the RGB mode's R, G and B in percent (true) or from 0 to 255 (false), as a double-click right of an RGB field switches them (the color does not change; Web Safe RGB and the hex field stay in 0 to 255); all four are kept with the preferences; RGB, HSB, Web Safe RGB and hex values use the working RGB space (ui.inspect: ui.color_mode, ui.color_hide_options, ui.color_show_recent_colors, ui.color_rgb_percent) → {mode (null: none picked yet), options, recentColors, rgbPercent, shown: the mode the panel shows the color in, or tint for a tint of a global or spot swatch}", ), ( "ui.savePdfDialog", diff --git a/crates/ui-egui/src/menus/ui_commands.rs b/crates/ui-egui/src/menus/ui_commands.rs index b05da18e12..e975167a5a 100644 --- a/crates/ui-egui/src/menus/ui_commands.rs +++ b/crates/ui-egui/src/menus/ui_commands.rs @@ -1237,7 +1237,7 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[ "ui.colorPanel", "Color Panel Options", "", - "{mode?: grayscale|rgb|hsb|cmyk|websafe, options?: bool, recentColors?: bool} pick the Color panel's mode as its panel menu does (the active color converts to the mode's model as one undo step), Show Options (true) or Hide Options (false), and check (true) or uncheck (false) Show Recent Colors, which shows or hides the panel's Recent Colors row (applied colors still join the list while it is hidden; the Swatches panel keeps its row); all three are kept with the preferences; RGB, HSB, Web Safe RGB and hex values use the working RGB space (ui.inspect: ui.color_mode, ui.color_hide_options, ui.color_show_recent_colors) → {mode (null: none picked yet), options, recentColors, shown: the mode the panel shows the color in, or tint for a tint of a global or spot swatch}", + "{mode?: grayscale|rgb|hsb|cmyk|websafe, options?: bool, recentColors?: bool, rgbPercent?: bool} pick the Color panel's mode as its panel menu does (the active color converts to the mode's model as one undo step), Show Options (true) or Hide Options (false), check (true) or uncheck (false) Show Recent Colors, which shows or hides the panel's Recent Colors row (applied colors still join the list while it is hidden; the Swatches panel keeps its row), and show the RGB mode's R, G and B in percent (true) or from 0 to 255 (false), as a double-click right of an RGB field switches them (the color does not change; Web Safe RGB and the hex field stay in 0 to 255); all four are kept with the preferences; RGB, HSB, Web Safe RGB and hex values use the working RGB space (ui.inspect: ui.color_mode, ui.color_hide_options, ui.color_show_recent_colors, ui.color_rgb_percent) → {mode (null: none picked yet), options, recentColors, rgbPercent, shown: the mode the panel shows the color in, or tint for a tint of a global or spot swatch}", ), ( "ui.savePdfDialog", diff --git a/crates/ui-egui/src/panels/color.rs b/crates/ui-egui/src/panels/color.rs index 4e13f629a5..4aee419bd1 100644 --- a/crates/ui-egui/src/panels/color.rs +++ b/crates/ui-egui/src/panels/color.rs @@ -14,8 +14,10 @@ //! Hide Options leaves just the proxy and the spectrum (the app keeps it between sessions too). //! Unchecking Show Recent Colors hides the Recent Colors row while applied colors still join the //! list (the app keeps it between sessions too). +//! A double-click right of an RGB field switches R, G and B between 0 to 255 and percent (the app +//! keeps it between sessions too); Web Safe RGB and the hex field stay in 0 to 255. //! `ui.colorPanel` picks a mode, shows or hides the options and checks or unchecks Show Recent -//! Colors as the panel menu does. +//! Colors as the panel menu does, and switches RGB to percent and back. use egui::{Color32, Rect, Sense, Stroke, StrokeKind, Ui, vec2}; use serde_json::{Value, json}; @@ -298,6 +300,24 @@ pub fn deserialize_show_recent_colors<'de, D: serde::Deserializer<'de>>(d: D) -> Ok(v.as_bool().unwrap_or(true)) } +/// Serde reader for the saved RGB in percent ([`crate::UiState::color_rgb_percent`]): `true` shows +/// percent and any other value 0 to 255, so an unknown value doesn't lose the rest of the +/// preferences. +pub fn deserialize_rgb_percent<'de, D: serde::Deserializer<'de>>(d: D) -> Result { + let v = ::deserialize(d)?; + Ok(v.as_bool().unwrap_or_default()) +} + +/// An RGB component (0 to 255) in percent, as the field shows it while RGB shows in percent. +fn to_percent(v: f32) -> f32 { + v * 100.0 / 255.0 +} + +/// A percent typed or picked while RGB shows in percent, as an RGB component (0 to 255). +fn from_percent(p: f32) -> f32 { + p * 255.0 / 100.0 +} + /// The mode the panel shows `color` in: with nothing selected (the defaults for new art) the mode /// picked last, which the app keeps between sessions, else [`display_mode`]. fn shown_mode(app: &VectorcraftApp, color: Option<&Color>) -> Mode { @@ -522,11 +542,12 @@ fn recolor(app: &mut VectorcraftApp, ui: &Ui, tgt: &Target, invert: bool, behind } } -/// `ui.colorPanel {mode?, options?, recentColors?}`: pick a mode as the panel menu does -/// ([`set_mode`]), Show Options (true) or Hide Options (false), and check (true) or uncheck -/// (false) Show Recent Colors. → {mode, options, recentColors, shown}: the mode picked last (null: -/// none yet), whether the options show, whether Show Recent Colors is checked, and the mode the -/// panel shows the color in (`tint` for a tint of a global or spot swatch). +/// `ui.colorPanel {mode?, options?, recentColors?, rgbPercent?}`: pick a mode as the panel menu +/// does ([`set_mode`]), Show Options (true) or Hide Options (false), check (true) or uncheck +/// (false) Show Recent Colors, and show RGB in percent (true) or 0 to 255 (false). → {mode, +/// options, recentColors, rgbPercent, shown}: the mode picked last (null: none yet), whether the +/// options show, whether Show Recent Colors is checked, whether RGB shows in percent, and the mode +/// the panel shows the color in (`tint` for a tint of a global or spot swatch). pub(crate) fn command(app: &mut VectorcraftApp, p: &Value) -> Result { let mode = match p.get("mode") { None | Some(Value::Null) => None, @@ -534,6 +555,7 @@ pub(crate) fn command(app: &mut VectorcraftApp, p: &Value) -> Result Result json!("tint"), @@ -554,6 +579,7 @@ pub(crate) fn command(app: &mut VectorcraftApp, p: &Value) -> Result)>| { diff --git a/crates/ui-egui/src/panels/color/tests.rs b/crates/ui-egui/src/panels/color/tests.rs index 36acc60e4a..4cdd50f7fc 100644 --- a/crates/ui-egui/src/panels/color/tests.rs +++ b/crates/ui-egui/src/panels/color/tests.rs @@ -676,6 +676,179 @@ fn show_recent_colors_comes_back_after_a_restart() { assert_eq!((app.ui.color_mode, app.ui.color_hide_options, app.ui.color_show_recent_colors), (None, false, false)); } +/// The fill's R, G and B from 0 to 1. +fn fill_rgb(app: &VectorcraftApp) -> [f32; 3] { + working_rgb(&crate::panels::current_paints(app).0.color().expect("a solid fill")) +} + +/// One frame of the panel at `time` with `events`; returns the rect of the cell right of the field +/// of slider row `i` (None: the row has no cell). +fn units_cell(app: &mut VectorcraftApp, ctx: &egui::Context, i: usize, time: f64, events: Vec) -> Option { + let input = egui::RawInput { time: Some(time), events, ..Default::default() }; + let mut rect = None; + let mut out = ctx.run_ui(input, |ui| { + show(app, ui); + rect = ctx.read_response(ui.scope_id().with("color-units").with(i)).map(|r| r.rect); + }); + out.textures_delta.clear(); + rect +} + +/// The rect of the slider that screen readers name `label`, in a frame of `ctx` (`width`: the +/// screen's width; None: the screen of the frame before). +fn slider_rect(app: &mut VectorcraftApp, ctx: &egui::Context, label: &str, width: Option) -> Rect { + ctx.enable_accesskit(); + let screen_rect = width.map(|w| Rect::from_min_size(egui::Pos2::ZERO, vec2(w, 800.0))); + let mut nodes = vec![]; + for _ in 0..2 { + let mut out = ctx.run_ui(egui::RawInput { screen_rect, ..Default::default() }, |ui| show(app, ui)); + out.textures_delta.clear(); + nodes = out.platform_output.accesskit_update.map(|u| u.nodes).unwrap_or_default(); + } + let slider = nodes.iter().find(|(_, n)| n.role() == egui::accesskit::Role::Slider && n.label() == Some(label)); + let b = slider.and_then(|(_, n)| n.bounds()).unwrap_or_else(|| panic!("no slider is named {label:?}")); + Rect::from_min_max(egui::pos2(b.x0 as f32, b.y0 as f32), egui::pos2(b.x1 as f32, b.y1 as f32)) +} + +/// Double-click the cell right of the field of slider row `i`, at `time` and a moment later. +fn double_click_units(app: &mut VectorcraftApp, ctx: &egui::Context, i: usize, time: f64) { + let run = |app: &mut VectorcraftApp, time: f64, events: Vec| units_cell(app, ctx, i, time, events); + let pos = run(app, time, vec![]).expect("the cell is drawn").center(); + let button = |pressed| egui::Event::PointerButton { pos, button: egui::PointerButton::Primary, pressed, modifiers: egui::Modifiers::NONE }; + run(app, time + 0.05, vec![egui::Event::PointerMoved(pos), button(true)]); + run(app, time + 0.1, vec![button(false)]); + run(app, time + 0.15, vec![button(true)]); + run(app, time + 0.2, vec![button(false)]); + run(app, time + 0.25, vec![]); +} + +/// In RGB mode a double-click right of an RGB field switches R, G and B between 0 to 255 and +/// percent, as `ui.colorPanel {rgbPercent}` does. The color does not change; a typed percent and a +/// slider click convert back to 0 to 255. Web Safe RGB and the hex field stay in 0 to 255. Only +/// RGB rows have the cell, and Tab does not stop on it. +#[test] +fn rgb_percent_switches_the_rgb_fields_between_percent_and_0_to_255() { + use crate::panels::tests_appearance::{click, frame_events, text_rect}; + use crate::widgets::accessibility_tests::{slider_value, tab_stops, tree}; + let mut app = app(); + let ctx = egui::Context::default(); + app.run("paint.setFill", json!({"color": "#336699"})).unwrap(); + let texts = |app: &mut VectorcraftApp| frame_events(&ctx, app, vec![], show).into_iter().map(|(t, _)| t).collect::>(); + let has = |texts: &[String], all: &[&str]| all.iter().all(|s| texts.iter().any(|t| t == s)); + assert!(has(&texts(&mut app), &["51", "102", "153"])); + assert_eq!(app.run("ui.colorPanel", json!({"rgbPercent": true})).unwrap()["rgbPercent"], true); + let shown = texts(&mut app); + assert!(has(&shown, &["20%", "40%", "60%", "336699"]), "{shown:?}"); + assert_eq!(paints_hex(&app).0, "#336699", "the color does not change"); + let v = |n: f64, text: &str| (Some(n), Some(0.0), Some(100.0), Some(text.to_string())); + let sliders = tree(|ui| show(&mut app, ui)); + assert_eq!(slider_value(&sliders, "G"), v(40.0, "40%")); + // A typed percent sets the component: 50 % is 127.5 of 255. + let field = text_rect(&frame_events(&ctx, &mut app, vec![], show), "20%").center(); + click(&ctx, &mut app, field, show); + let key = |key, modifiers| egui::Event::Key { key, physical_key: None, pressed: true, repeat: false, modifiers }; + frame_events(&ctx, &mut app, vec![key(egui::Key::A, egui::Modifiers::COMMAND), egui::Event::Text("50".into())], show); + frame_events(&ctx, &mut app, vec![key(egui::Key::Enter, egui::Modifiers::NONE)], show); + let [r, g, b] = fill_rgb(&app); + assert!((r - 0.5).abs() < 1e-6 && (g - 0.4).abs() < 1e-6 && (b - 0.6).abs() < 1e-6, "{r} {g} {b}"); + assert!(has(&texts(&mut app), &["50%", "40%", "60%"])); + // A click a third of the way along the B slider's bar picks 33 %, a whole percent. + let rgb_slider = slider_rect(&mut app, &ctx, "B", None); + let (bar_left, bar_w) = (rgb_slider.left() + 4.0, rgb_slider.width() - 8.0); + click(&ctx, &mut app, egui::pos2(bar_left + bar_w / 3.0, rgb_slider.center().y), show); + let p = fill_rgb(&app)[2] * 100.0; + assert!((p - 33.0).abs() < 1e-3, "{p}"); + // The cells take no keyboard focus: Tab goes from field to slider without stopping on them. + let cells: Vec<_> = (0..3).filter_map(|i| units_cell(&mut app, &ctx, i, 5.0, vec![]).map(|_| i)).collect(); + assert_eq!(cells, [0, 1, 2], "each RGB row has a cell"); + let mut units = egui::Id::NULL; + let stops = tab_stops(&ctx, 24, |events| { + frame_events(&ctx, &mut app, events, |app, ui| { + show(app, ui); + units = ui.scope_id().with("color-units"); + }); + }); + let cell_ids: Vec = (0..3).map(|i| units.with(i)).collect(); + assert!(stops.len() >= 6 && stops.iter().all(|s| !cell_ids.contains(s)), "{stops:?}"); + app.run("paint.setFill", json!({"color": "#803366"})).unwrap(); + assert!((fill_rgb(&app)[0] - 128.0 / 255.0).abs() < 1e-6); + // A double-click right of the G field switches back to 0 to 255, and another to percent. + double_click_units(&mut app, &ctx, 1, 10.0); + assert!(!app.ui.color_rgb_percent); + assert!(has(&texts(&mut app), &["128", "51", "102"])); + assert_eq!(paints_hex(&app).0, "#803366", "the color does not change"); + double_click_units(&mut app, &ctx, 1, 20.0); + assert!(app.ui.color_rgb_percent); + assert!(has(&texts(&mut app), &["50%", "20%", "40%"])); + assert_eq!(app.run("ui.colorPanel", json!({})).unwrap()["rgbPercent"], true); + // RGB sliders are narrower by the cell and its spacing, in a narrow panel too. + let cell_room = 10.0 + ctx.global_style().spacing.item_spacing.x; + let rgb_w = slider_rect(&mut app, &ctx, "B", Some(400.0)).width(); + let narrow_rgb = slider_rect(&mut app, &ctx, "B", Some(150.0)).width(); + // Web Safe RGB shows 0 to 255. It has no cells, and its sliders keep the room the RGB cells + // take. + app.run("paint.setFill", json!({"color": "#336699"})).unwrap(); + app.run("ui.colorPanel", json!({"mode": "websafe"})).unwrap(); + assert!(has(&texts(&mut app), &["51", "102", "153"])); + assert_eq!(units_cell(&mut app, &ctx, 1, 30.0, vec![]), None); + let web_w = slider_rect(&mut app, &ctx, "B", Some(400.0)).width(); + assert!((web_w - rgb_w - cell_room).abs() < 0.5, "{web_w} {rgb_w}"); + let narrow_web = slider_rect(&mut app, &ctx, "B", Some(150.0)).width(); + assert!((narrow_web - narrow_rgb - cell_room).abs() < 0.5, "{narrow_web} {narrow_rgb}"); + // HSB shows its own units, with no cells and sliders as wide as Web Safe RGB's. + app.run("ui.colorPanel", json!({"mode": "hsb"})).unwrap(); + assert!(has(&texts(&mut app), &["210°", "67%", "60%"])); + assert_eq!(units_cell(&mut app, &ctx, 1, 31.0, vec![]), None); + assert!((slider_rect(&mut app, &ctx, "B", Some(400.0)).width() - web_w).abs() < 0.5); + assert!(app.ui.color_rgb_percent); +} + +/// While a mixed selection disables the RGB rows, a double-click right of an RGB field does not +/// switch the units, as the fields beside it take no input. +#[test] +fn rgb_percent_does_not_switch_while_the_rows_are_disabled() { + let mut app = app(); + let ctx = egui::Context::default(); + app.run("ui.colorPanel", json!({"mode": "rgb"})).unwrap(); + let b = app.run("shape.rectangle", json!({"x": 80, "y": 10, "width": 50, "height": 50})).unwrap()["id"].clone(); + app.run("select.all", json!({})).unwrap(); + app.run("paint.setFill", json!({"color": "#ff0000"})).unwrap(); + app.run("paint.setFill", json!({"color": "#00ff00", "ids": [b]})).unwrap(); + units_cell(&mut app, &ctx, 1, 1.0, vec![]); + assert_eq!(crate::panels::mixed_paints(&app, &ctx), (true, false)); + double_click_units(&mut app, &ctx, 1, 2.0); + assert!(!app.ui.color_rgb_percent); + // With one color the rows take input, and the same double-click switches the units. + app.run("paint.setFill", json!({"color": "#336699"})).unwrap(); + double_click_units(&mut app, &ctx, 1, 3.0); + assert!(app.ui.color_rgb_percent); +} + +/// RGB in percent is kept with the UI state, which the app saves with its preferences. A saved +/// state without it, or with a value that isn't true or false, reads as 0 to 255, and the rest of +/// the state still loads. A value that isn't true or false in the command answers an error and +/// changes nothing. +#[test] +fn rgb_percent_comes_back_after_a_restart() { + let mut app = app(); + app.run("ui.colorPanel", json!({"rgbPercent": true})).unwrap(); + let saved: crate::UiState = serde_json::from_value(serde_json::to_value(&app.ui).unwrap()).unwrap(); + assert!(saved.color_rgb_percent); + assert_eq!(crate::control::inspect(&app, &egui::Context::default())["ui"]["color_rgb_percent"], true); + let old: crate::UiState = serde_json::from_value(json!({"place_link": false})).unwrap(); + assert!(!old.color_rgb_percent, "a state saved before the setting existed shows 0 to 255"); + for v in [json!(7), json!("percent"), json!({}), Value::Null] { + let ui: crate::UiState = serde_json::from_value(json!({"color_rgb_percent": v, "place_link": false})).unwrap(); + assert_eq!((ui.color_rgb_percent, ui.place_link), (false, false), "{v}"); + } + for v in [json!("yes"), json!(1), json!([])] { + let e = app.run("ui.colorPanel", json!({"mode": "cmyk", "rgbPercent": v})).unwrap_err(); + assert_eq!(e, "`rgbPercent` must be true or false", "{v}"); + } + assert_eq!((app.ui.color_mode, app.ui.color_rgb_percent), (None, true)); + assert_eq!(app.run("ui.colorPanel", json!({"rgbPercent": false})).unwrap()["rgbPercent"], false); +} + /// `ui.colorPanel {mode}` picks a mode as the panel menu does: HSB shows an RGB fill as H, S and /// B, and CMYK converts it as one undo step. #[test] @@ -684,7 +857,7 @@ fn ui_color_panel_picks_a_mode_as_the_menu_does() { let ctx = egui::Context::default(); app.run("paint.setFill", json!({"color": "#336699"})).unwrap(); let r = app.run("ui.colorPanel", json!({"mode": "hsb"})).unwrap(); - assert_eq!(r, json!({"mode": "hsb", "options": true, "recentColors": true, "shown": "hsb"})); + assert_eq!(r, json!({"mode": "hsb", "options": true, "recentColors": true, "rgbPercent": false, "shown": "hsb"})); assert_eq!(slider_labels(&mut app, &ctx), ["H", "S", "B"]); assert_eq!(crate::control::inspect(&app, &ctx)["ui"]["color_mode"], "hsb"); assert_eq!(paints_hex(&app).0, "#336699", "HSB keeps the RGB color"); @@ -694,7 +867,10 @@ fn ui_color_panel_picks_a_mode_as_the_menu_does() { assert!(matches!(crate::panels::current_paints(&app).0.color(), Some(Color::Rgb { .. })), "undo restores it"); assert_eq!(paints_hex(&app).0, "#336699"); // `{}` reads the settings: CMYK stays picked, and the RGB fill shows in its own model. - assert_eq!(app.run("ui.colorPanel", json!({})).unwrap(), json!({"mode": "cmyk", "options": true, "recentColors": true, "shown": "rgb"})); + assert_eq!( + app.run("ui.colorPanel", json!({})).unwrap(), + json!({"mode": "cmyk", "options": true, "recentColors": true, "rgbPercent": false, "shown": "rgb"}) + ); // A tint of a global swatch shows Tint mode. app.run("swatch.new", json!({"name": "Ink", "color": {"c": 0, "m": 1, "y": 0, "k": 0}, "spot": true})).unwrap(); app.run("paint.setFill", json!({"swatch": "Ink", "tint": 50})).unwrap(); diff --git a/crates/ui-egui/src/state.rs b/crates/ui-egui/src/state.rs index 4a0e73da6b..14d651564f 100644 --- a/crates/ui-egui/src/state.rs +++ b/crates/ui-egui/src/state.rs @@ -670,6 +670,10 @@ pub struct UiState { /// recent colors keeps filling while the row is hidden. #[serde(default = "yes", deserialize_with = "crate::panels::color::deserialize_show_recent_colors")] pub color_show_recent_colors: bool, + /// Color panel: RGB mode shows R, G and B in percent instead of 0 to 255 (a double-click right + /// of an RGB field, `ui.colorPanel`, [`crate::panels::color::deserialize_rgb_percent`]). + #[serde(default, deserialize_with = "crate::panels::color::deserialize_rgb_percent")] + pub color_rgb_percent: bool, /// The SVG Options chosen last (`svg` object of the export/save commands; null: never used). #[serde(default)] pub svg_options: Value, @@ -1116,6 +1120,7 @@ impl Default for UiState { color_mode: None, color_hide_options: false, color_show_recent_colors: true, + color_rgb_percent: false, svg_options: Value::Null, place_link: true, dialog_file: None, diff --git a/docs/control-protocol.md b/docs/control-protocol.md index b3f4e56bde..a0b62da973 100644 --- a/docs/control-protocol.md +++ b/docs/control-protocol.md @@ -238,15 +238,20 @@ Edit or Apply Colors runs `ui.recolorDialog {colors, library?}` with the harmony recolours as usual; with nothing selected the colours themselves are the rows and `ui.dialog.confirm` saves them as a new colour group (`swatch.newGroup`, named by the `groupName` field). -Color panel options: `ui.colorPanel {mode?, options?, recentColors?}` picks the panel's mode as its panel menu does -(`grayscale`, `rgb`, `hsb`, `cmyk` or `websafe`; the active color converts to the mode's model as one undo step), -shows (`options: true`) or hides (`false`) the options, and checks (`recentColors: true`) or unchecks (`false`) Show -Recent Colors, which shows or hides the panel's Recent Colors row. Applied colors still join the list while the row -is hidden, and the Swatches panel keeps its own row. It answers `{mode, options, recentColors, shown}`: `mode` is null -until a mode is picked, and `shown` is the mode the panel shows the color in, or `tint` for a tint of a global or spot -swatch. Another mode, `lab` included, answers an error, as does an `options` or `recentColors` value that is not true -or false. The desktop app keeps the three settings with its preferences, and `ui.inspect` reports them as -`ui.color_mode`, `ui.color_hide_options` and `ui.color_show_recent_colors`. +Color panel options: `ui.colorPanel {mode?, options?, recentColors?, rgbPercent?}` picks the panel's mode as its +panel menu does (`grayscale`, `rgb`, `hsb`, `cmyk` or `websafe`; the active color converts to the mode's model as one +undo step), shows (`options: true`) or hides (`false`) the options, and checks (`recentColors: true`) or unchecks +(`false`) Show Recent Colors, which shows or hides the panel's Recent Colors row. Applied colors still join the list +while the row is hidden, and the Swatches panel keeps its own row. `rgbPercent: true` shows the RGB mode's R, G and B +in percent and `false` shows them from 0 to 255, as a double-click right of an RGB field switches them; the color does +not change, and Web Safe RGB and the hex field stay in 0 to 255. The color mixer in the popovers of the Fill and +Stroke chips (Control bar and Properties panel) and the gradient stop dialog show the Color panel's rows and follow +the same setting. It answers `{mode, options, recentColors, rgbPercent, +shown}`: `mode` is null until a mode is picked, and `shown` is the mode the panel shows the color in, or `tint` for a +tint of a global or spot swatch. Another mode, `lab` included, answers an error, as does an `options`, `recentColors` +or `rgbPercent` value that is not true or false. The desktop app keeps the four settings with its preferences, and +`ui.inspect` reports them as `ui.color_mode`, `ui.color_hide_options`, `ui.color_show_recent_colors` and +`ui.color_rgb_percent`. Color Themes panel (`window.panel {panel: "colorThemes"}`): its Create tab makes a theme on a harmony wheel and its My Themes tab lists the saved themes; every action runs a command (`colorTheme.save`, `colorTheme.delete`, diff --git a/docs/mcp.md b/docs/mcp.md index 996908799f..58fc044512 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -533,7 +533,8 @@ space. RGB edits preserve the other native channels; HSB uses those same working display sRGB retain their native numeric values. `ui.colorPanel` selects the panel mode and `ui.colorPicker` opens the picker through `run_command` with the remote backend. `ui.colorPanel {"recentColors": false}` unchecks the panel menu's Show Recent Colors, which hides the Color panel's Recent Colors row, and `true` shows it again; -applied colors still join the list while it is hidden. +applied colors still join the list while it is hidden. `ui.colorPanel {"rgbPercent": true}` shows the Color panel's +RGB fields in percent, as a double-click right of an RGB field does, and `false` shows them from 0 to 255 again. | Tool | Arguments | Notes | |---|---|---| diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index 2962c63b7e..0921b2ed72 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -1323,6 +1323,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur Recent Colors keeps the last 30 solid colors applied, newest first and each once, and the list stays the same when another document is opened or made active; a 31st color drops the oldest ([Select colors using the Color panel](https://helpx.adobe.com/illustrator/desktop/manage-colors/select-and-adjust-colors/select-colors-using-the-color-panel.html)). The list lasts for the session. In the Color and Swatches panels the chips wrap onto further rows when the panel is too narrow for one row (`paint.recent`). Tests: `recent_colors_keep_thirty_colors_across_documents` in `crates/engine/src/tests/paintproxy.rs`, `all_thirty_recent_colors_wrap_into_a_narrow_panel` in `crates/ui-egui/src/panels/color/tests.rs`. Unchecking Show Recent Colors in the Color panel's menu hides its Recent Colors row, and checking it shows the row again ([Select colors using the Color panel](https://helpx.adobe.com/illustrator/desktop/manage-colors/select-and-adjust-colors/select-colors-using-the-color-panel.html)). In VectorCraft the item is checked by default and kept between sessions, applied colors still join the list while the row is hidden, Hide Options hides the row either way, and the Swatches panel keeps its row (`ui.colorPanel {recentColors}`). Tests: `show_recent_colors_hides_and_shows_the_row` in `crates/ui-egui/src/panels/color/tests.rs`. A Cmd/Ctrl-click on the Color panel's spectrum applies Complement and a Cmd/Ctrl+Shift-click applies Invert to the active Fill or Stroke (`paint.complement`, `paint.invert`); with Alt also held, the click recolors the inactive one, as the reference app's [Default keyboard shortcuts](https://web.archive.org/web/20260726060015/https://helpx.adobe.com/illustrator/using/default-keyboard-shortcuts.html) list them. + A double-click right of an RGB field switches the Color panel's R, G and B between percent and 0 to 255 ([Default keyboard shortcuts](https://helpx.adobe.com/illustrator/using/default-keyboard-shortcuts.html), Color panel). In VectorCraft the color does not change, Web Safe RGB and the hex field stay in 0 to 255, and the panel keeps the choice between sessions (`ui.colorPanel {rgbPercent}`); the color mixer of the Fill and Stroke chips and the gradient stop dialog follow the same setting. Tests: `rgb_percent_switches_the_rgb_fields_between_percent_and_0_to_255`, `rgb_percent_does_not_switch_while_the_rows_are_disabled` and `rgb_percent_comes_back_after_a_restart` in `crates/ui-egui/src/panels/color/tests.rs`. View › Proof Setup › Color blindness – Tritanopia-type turns Proof Colors on and proofs the canvas with the tritanopia simulation of Brettel, Viénot and Mollon (1997). The Separations Preview panel's Proof Setup list offers the same target (`view.proofSetup {target: "tritanopia"}`). Grays keep their value. The Separations Preview panel's Gamut Check button reports one color outside the CMYK gamut in the singular and more in the plural, in English and in each language's plural forms (`gamut_check_status_takes_the_plural_form` in `crates/ui-egui/src/i18n/tests.rs`). - **Document Color Mode outside the layers:** File › Document Color Mode (in one undo step) and `document.open {colorMode}` also convert the colors of graph designs, graphic styles, opacity-mask art, the tile that pattern editing's Cancel puts back and the art of brushes (with a Hue Shift brush's key color); a tint of a global swatch there becomes the converted swatch's color at its tint, as in the art. From d3375c165602e27593fe2376e3c9b4ee470ad6bb Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Thu, 12 Nov 2026 16:04:30 -0800 Subject: [PATCH 2/2] Color: Toggle RGB Percent switches the RGB units from the keyboard The double-click right of an RGB field was the only way to switch R, G and B between percent and 0 to 255 from the UI. ui.colorRgbPercent (Toggle RGB Percent) is listed in the command palette and in Keyboard Shortcuts without a default key, and switches the same setting. A parameter answers an error and changes nothing. The label has a row in every complete catalog. --- crates/mcp/src/ui_catalog.rs | 6 +++++ crates/ui-egui/src/i18n/bg.tsv | 1 + crates/ui-egui/src/i18n/bs.tsv | 1 + crates/ui-egui/src/i18n/ca.tsv | 1 + crates/ui-egui/src/i18n/cs.tsv | 1 + crates/ui-egui/src/i18n/da.tsv | 1 + crates/ui-egui/src/i18n/de.tsv | 1 + crates/ui-egui/src/i18n/el.tsv | 1 + crates/ui-egui/src/i18n/es.tsv | 1 + crates/ui-egui/src/i18n/fi.tsv | 1 + crates/ui-egui/src/i18n/fr.tsv | 1 + crates/ui-egui/src/i18n/hi.tsv | 1 + crates/ui-egui/src/i18n/hu.tsv | 1 + crates/ui-egui/src/i18n/id.tsv | 1 + crates/ui-egui/src/i18n/it.tsv | 1 + crates/ui-egui/src/i18n/ja.tsv | 1 + crates/ui-egui/src/i18n/ka.tsv | 1 + crates/ui-egui/src/i18n/ko.tsv | 1 + crates/ui-egui/src/i18n/nl.tsv | 1 + crates/ui-egui/src/i18n/pl.tsv | 1 + crates/ui-egui/src/i18n/pt-br.tsv | 1 + crates/ui-egui/src/i18n/ru.tsv | 1 + crates/ui-egui/src/i18n/sv.tsv | 1 + crates/ui-egui/src/i18n/tr.tsv | 1 + crates/ui-egui/src/i18n/uk.tsv | 1 + crates/ui-egui/src/i18n/vi.tsv | 1 + crates/ui-egui/src/i18n/zh-hans.tsv | 1 + crates/ui-egui/src/i18n/zh-hant.tsv | 1 + crates/ui-egui/src/menus/run.rs | 1 + crates/ui-egui/src/menus/ui_commands.rs | 6 +++++ crates/ui-egui/src/panels/color.rs | 27 ++++++++++++++++++--- crates/ui-egui/src/panels/color/tests.rs | 31 ++++++++++++++++++++++++ docs/control-protocol.md | 4 ++- docs/mcp.md | 2 ++ docs/target-app-parity.md | 2 +- 35 files changed, 101 insertions(+), 5 deletions(-) diff --git a/crates/mcp/src/ui_catalog.rs b/crates/mcp/src/ui_catalog.rs index 044d4b0098..5c137347c0 100644 --- a/crates/mcp/src/ui_catalog.rs +++ b/crates/mcp/src/ui_catalog.rs @@ -1244,6 +1244,12 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[ "", "{mode?: grayscale|rgb|hsb|cmyk|websafe, options?: bool, recentColors?: bool, rgbPercent?: bool} pick the Color panel's mode as its panel menu does (the active color converts to the mode's model as one undo step), Show Options (true) or Hide Options (false), check (true) or uncheck (false) Show Recent Colors, which shows or hides the panel's Recent Colors row (applied colors still join the list while it is hidden; the Swatches panel keeps its row), and show the RGB mode's R, G and B in percent (true) or from 0 to 255 (false), as a double-click right of an RGB field switches them (the color does not change; Web Safe RGB and the hex field stay in 0 to 255); all four are kept with the preferences; RGB, HSB, Web Safe RGB and hex values use the working RGB space (ui.inspect: ui.color_mode, ui.color_hide_options, ui.color_show_recent_colors, ui.color_rgb_percent) → {mode (null: none picked yet), options, recentColors, rgbPercent, shown: the mode the panel shows the color in, or tint for a tint of a global or spot swatch}", ), + ( + "ui.colorRgbPercent", + "Toggle RGB Percent", + "", + "{} switch the Color panel's R, G and B between percent and 0 to 255, as a double-click right of an RGB field does (ui.colorPanel {rgbPercent}); the color does not change, and the choice is kept with the preferences; a parameter is an error and changes nothing → true when they show in percent", + ), ( "ui.savePdfDialog", "Save PDF Dialog", diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index 582a927f09..59a90fb3eb 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -3209,6 +3209,7 @@ digits Malayalam Малаялам Show Note Показване на бележка Show Options Показване на опциите Show Recent Colors Показване на скорошните цветове + Toggle RGB Percent Превключване на RGB в проценти Show Original Path Показване на оригиналния път Open this dialog with these settings next time Отваряне на този прозорец с тези настройки следващия път Auto-Simplify Автоматично опростяване diff --git a/crates/ui-egui/src/i18n/bs.tsv b/crates/ui-egui/src/i18n/bs.tsv index d92da7ae40..12b0b4296f 100644 --- a/crates/ui-egui/src/i18n/bs.tsv +++ b/crates/ui-egui/src/i18n/bs.tsv @@ -1577,6 +1577,7 @@ digits Malayalam Malajalam Select a text object to edit its character attributes. Odaberite tekstualni objekat da uredite njegove atribute znakova. Show Options Prikaži opcije Show Recent Colors Prikaži nedavne boje + Toggle RGB Percent Uključi/isključi RGB u postocima Color Panel Options Opcije panela boja Small Caps Mala velika slova Small Caps (on the roadmap) Mala velika slova (u planu) diff --git a/crates/ui-egui/src/i18n/ca.tsv b/crates/ui-egui/src/i18n/ca.tsv index 283dae1800..ec47856891 100644 --- a/crates/ui-egui/src/i18n/ca.tsv +++ b/crates/ui-egui/src/i18n/ca.tsv @@ -4172,6 +4172,7 @@ digits Malayalam Malaiàlam Show Note Mostra la nota Show Options Mostra les opcions Show Recent Colors Mostra els colors recents + Toggle RGB Percent Commuta RGB en percentatge Show Original Path Mostra el traçat original Show Package Mostra el paquet Show Panel Mostra el panell diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index 519963c4eb..66a150193f 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -1606,6 +1606,7 @@ digits Malayalam Malajálamské Select a text object to edit its character attributes. Vyberte textový objekt, abyste mohli upravit atributy jeho znaků. Show Options Zobrazit možnosti Show Recent Colors Zobrazit nedávné barvy + Toggle RGB Percent Přepnout RGB v procentech Color Panel Options Možnosti panelu Barva Small Caps Kapitálky Small Caps (on the roadmap) Kapitálky (v plánu) diff --git a/crates/ui-egui/src/i18n/da.tsv b/crates/ui-egui/src/i18n/da.tsv index 3269755003..ba3d8db4ee 100644 --- a/crates/ui-egui/src/i18n/da.tsv +++ b/crates/ui-egui/src/i18n/da.tsv @@ -1513,6 +1513,7 @@ digits Malayalam Malayalam Select a text object to edit its character attributes. Markér et tekstobjekt for at redigere dets tegnattributter. Show Options Vis indstillinger Show Recent Colors Vis seneste farver + Toggle RGB Percent Skift RGB i procent Small Caps Kapitæler Small Caps (on the roadmap) Kapitæler (på vejkortet) Standard Vertical Roman Alignment Standard lodret romersk justering diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index f3858abc1f..fc86077d41 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -3269,6 +3269,7 @@ digits Malayalam Malayalam Show Note Notiz einblenden Show Options Optionen einblenden Show Recent Colors Zuletzt verwendete Farben einblenden + Toggle RGB Percent RGB in Prozent umschalten Show Original Path Originalpfad einblenden Open this dialog with these settings next time Dieses Dialogfeld nächstes Mal mit diesen Einstellungen öffnen Auto-Simplify Automatisch vereinfachen diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index 52a12276f7..22a8fee2e8 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -3763,6 +3763,7 @@ digits Malayalam Μαλαγιάλαμ Show Note Εμφάνιση σημείωσης Show Options Εμφάνιση επιλογών Show Recent Colors Εμφάνιση πρόσφατων χρωμάτων + Toggle RGB Percent Εναλλαγή RGB σε ποσοστό Show Original Path Εμφάνιση αρχικής διαδρομής Open this dialog with these settings next time Άνοιγμα αυτού του παραθύρου με αυτές τις ρυθμίσεις την επόμενη φορά Auto-Simplify Αυτόματη απλοποίηση diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index e6f77cd908..393d090f77 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -3300,6 +3300,7 @@ digits Malayalam Malayálam Show Note Mostrar nota Show Options Mostrar opciones Show Recent Colors Mostrar colores recientes + Toggle RGB Percent Alternar RGB en porcentaje Show Original Path Mostrar trazado original Open this dialog with these settings next time Abrir este cuadro de diálogo con estos ajustes la próxima vez Auto-Simplify Simplificar automáticamente diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv index dc0e432264..ffb8a912e1 100644 --- a/crates/ui-egui/src/i18n/fi.tsv +++ b/crates/ui-egui/src/i18n/fi.tsv @@ -1580,6 +1580,7 @@ digits Malayalam Malajalam Select a text object to edit its character attributes. Valitse tekstiobjekti, jonka merkkimääritteitä haluat muokata. Show Options Näytä asetukset Show Recent Colors Näytä viimeisimmät värit + Toggle RGB Percent Vaihda RGB prosentteina Small Caps Kapiteelit Small Caps (on the roadmap) Kapiteelit (suunnitteilla) Standard Vertical Roman Alignment Tavallinen pystysuuntainen latinalainen tasaus diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index 3c74586ef0..477b697621 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -3292,6 +3292,7 @@ digits Malayalam Malayalam Show Note Afficher la note Show Options Afficher les options Show Recent Colors Afficher les couleurs récentes + Toggle RGB Percent Basculer RVB en pourcentage Show Original Path Afficher le tracé d’origine Open this dialog with these settings next time Ouvrir cette boîte de dialogue avec ces réglages la prochaine fois Auto-Simplify Simplifier automatiquement diff --git a/crates/ui-egui/src/i18n/hi.tsv b/crates/ui-egui/src/i18n/hi.tsv index 8e1ce7cc2a..f526921203 100644 --- a/crates/ui-egui/src/i18n/hi.tsv +++ b/crates/ui-egui/src/i18n/hi.tsv @@ -4504,6 +4504,7 @@ antiAlias Sharp शार्प Show Note नोट दिखाएं Show Options ऑप्शन्स दिखाएँ Show Recent Colors हाल के रंग दिखाएँ + Toggle RGB Percent RGB प्रतिशत में बदलें Show Original Path मूल पाथ दिखाएं Show Package पैकेज करें दिखाएँ Show Panel शो पैनल diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index 15ce573c11..170c552a88 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -3934,6 +3934,7 @@ mark-style Japanese Japán Show Note Megjegyzés megjelenítése Show Options Beállítások megjelenítése Show Recent Colors Legutóbbi színek megjelenítése + Toggle RGB Percent RGB százalékos megjelenítésének váltása Show Original Path Eredeti útvonal megjelenítése Open this dialog with these settings next time Legközelebb ez a párbeszédablak nyíljon meg ezekkel a beállításokkal Auto-Simplify Automatikus egyszerűsítés diff --git a/crates/ui-egui/src/i18n/id.tsv b/crates/ui-egui/src/i18n/id.tsv index f68fad0c1c..7a5d653f38 100644 --- a/crates/ui-egui/src/i18n/id.tsv +++ b/crates/ui-egui/src/i18n/id.tsv @@ -1513,6 +1513,7 @@ digits Malayalam Malayalam Select a text object to edit its character attributes. Pilih objek teks untuk mengedit atribut karakternya. Show Options Tampilkan opsi Show Recent Colors Tampilkan warna terbaru + Toggle RGB Percent Alihkan RGB dalam persen Small Caps Kapital kecil Small Caps (on the roadmap) Kapital kecil (masih dalam roadmap) Standard Vertical Roman Alignment Perataan Romawi vertikal standar diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 480115859b..634db2ff80 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -3345,6 +3345,7 @@ digits Malayalam Malayalam Show Note Mostra nota Show Options Mostra opzioni Show Recent Colors Mostra colori recenti + Toggle RGB Percent Alterna RGB in percentuale Show Original Path Mostra tracciato originale Open this dialog with these settings next time Apri questa finestra di dialogo con queste impostazioni la prossima volta Auto-Simplify Semplifica automaticamente diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index ba3105aec0..2460a2d98d 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -3165,6 +3165,7 @@ mark-style Japanese 日本式 Show Note メモを表示 Show Options オプションを表示 Show Recent Colors 最近使用したカラーを表示 + Toggle RGB Percent RGB のパーセント表示を切り替え Show Original Path 元のパスを表示 Open this dialog with these settings next time 次回はこの設定でこのダイアログを開く Auto-Simplify 自動単純化 diff --git a/crates/ui-egui/src/i18n/ka.tsv b/crates/ui-egui/src/i18n/ka.tsv index 83fadf726a..cd2f49a73b 100644 --- a/crates/ui-egui/src/i18n/ka.tsv +++ b/crates/ui-egui/src/i18n/ka.tsv @@ -1451,6 +1451,7 @@ digits Malayalam მალაიალამური Select a text object to edit its character attributes. სიმბოლოების ატრიბუტების რედაქტირებისთვის მონიშნეთ ტექსტური ობიექტი. Show Options პარამეტრების ჩვენება Show Recent Colors ბოლოდროინდელი ფერების ჩვენება + Toggle RGB Percent RGB-ის პროცენტებში ჩვენების გადართვა Small Caps პატარა მთავრული Small Caps (on the roadmap) პატარა მთავრული (სამომავლო გეგმაშია) Standard Vertical Roman Alignment სტანდარტული ვერტიკალური ლათინური სწორება diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index e6f7478bf3..6bf24cea1d 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -3154,6 +3154,7 @@ mark-style Japanese 일본식 Show Note 메모 표시 Show Options 옵션 표시 Show Recent Colors 최근 색상 표시 + Toggle RGB Percent RGB 백분율 표시 전환 Show Original Path 원래 패스 표시 Open this dialog with these settings next time 다음에 이 설정으로 이 대화상자 열기 Auto-Simplify 자동 단순화 diff --git a/crates/ui-egui/src/i18n/nl.tsv b/crates/ui-egui/src/i18n/nl.tsv index 26baaa8dc5..371158cd66 100644 --- a/crates/ui-egui/src/i18n/nl.tsv +++ b/crates/ui-egui/src/i18n/nl.tsv @@ -1527,6 +1527,7 @@ digits Malayalam Malayalam Select a text object to edit its character attributes. Selecteer een tekstobject om de tekenkenmerken ervan te bewerken. Show Options Opties tonen Show Recent Colors Recente kleuren tonen + Toggle RGB Percent RGB in procenten wisselen Small Caps Kleine kapitalen Small Caps (on the roadmap) Kleine kapitalen (op de roadmap) Standard Vertical Roman Alignment Standaard verticale Romeinse uitlijning diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index 3637bb6f94..50d23b5cf4 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -1518,6 +1518,7 @@ digits Malayalam Malajalam Select a text object to edit its character attributes. Zaznacz obiekt tekstowy, aby edytować atrybuty znaków. Show Options Pokaż opcje Show Recent Colors Pokaż ostatnie kolory + Toggle RGB Percent Przełącz RGB w procentach Color Panel Options Opcje panelu Kolor Small Caps Kapitaliki Small Caps (on the roadmap) Kapitaliki (w planach) diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index 15aa70e244..db371671a4 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -2777,6 +2777,7 @@ digits Malayalam Malaiala Show Note Mostrar Nota Show Options Mostrar Opções Show Recent Colors Mostrar Cores Recentes + Toggle RGB Percent Alternar RGB em Porcentagem Show Original Path Mostrar Caminho Original Open this dialog with these settings next time Abrir este diálogo com estas configurações na próxima vez Auto-Simplify Simplificar Automaticamente diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index 21f270a0fb..1e92af19e3 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -3332,6 +3332,7 @@ digits Malayalam Малаялам Show Note Показать примечание Show Options Показать параметры Show Recent Colors Показать последние цвета + Toggle RGB Percent Переключить RGB в процентах Show Original Path Показать исходный контур Open this dialog with these settings next time Открывать это диалоговое окно с этими параметрами в следующий раз Auto-Simplify Автоматическое упрощение diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv index 7969960880..edde9f6325 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -3224,6 +3224,7 @@ digits Malayalam Malayalam Show Note Visa anteckning Show Options Visa alternativ Show Recent Colors Visa senaste färger + Toggle RGB Percent Växla RGB i procent Show Original Path Visa ursprunglig bana Open this dialog with these settings next time Öppna den här dialogrutan med de här inställningarna nästa gång Auto-Simplify Förenkla automatiskt diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index 639bf692a8..b3a2847d32 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -3127,6 +3127,7 @@ digits Malayalam Malayalam Show Note Notu Göster Show Options Seçenekleri Göster Show Recent Colors Son Renkleri Göster + Toggle RGB Percent RGB Yüzde Gösterimini Değiştir Show Original Path Orijinal Yolu Göster Color Panel Options Renk Paneli Seçenekleri Show Package Paketi Göster diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index 2889757941..ccc02fece9 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -3337,6 +3337,7 @@ digits Malayalam Малаялам Show Note Показати примітку Show Options Показати параметри Show Recent Colors Показати останні кольори + Toggle RGB Percent Переключити RGB у відсотках Show Original Path Показати початковий контур Open this dialog with these settings next time Наступного разу відкривати це діалогове вікно з цими параметрами Auto-Simplify Автоматичне спрощення diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index ca39a2cd16..fd13ef2c19 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -3684,6 +3684,7 @@ mark-style Japanese Kiểu Nhật Show Note Hiện ghi chú Show Options Hiện tùy chọn Show Recent Colors Hiện màu gần đây + Toggle RGB Percent Chuyển đổi RGB theo phần trăm Show Original Path Hiện đường dẫn gốc Open this dialog with these settings next time Lần sau mở hộp thoại này với các cài đặt này Auto-Simplify Tự động đơn giản hóa diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index d4fe4ce4f3..e86d636b7f 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -3165,6 +3165,7 @@ digits Malayalam 马拉雅拉姆数字 Show Note 显示注解 Show Options 显示选项 Show Recent Colors 显示最近使用的颜色 + Toggle RGB Percent 切换 RGB 百分比显示 Show Original Path 显示原始路径 Open this dialog with these settings next time 下次以这些设置打开此对话框 Auto-Simplify 自动简化 diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index 1a81d6d01c..b2cd4f4ab4 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -4312,6 +4312,7 @@ digits Malayalam 馬來亞拉姆數字 Show Note 顯示註解 Show Options 顯示選項 Show Recent Colors 顯示最近使用的顏色 + Toggle RGB Percent 切換 RGB 百分比顯示 Show Original Path 顯示原始路徑 Open this dialog with these settings next time 下次以這些設定開啟此對話方塊 Auto-Simplify 自動簡化 diff --git a/crates/ui-egui/src/menus/run.rs b/crates/ui-egui/src/menus/run.rs index 72e6c25acb..bf5490e4d8 100644 --- a/crates/ui-egui/src/menus/run.rs +++ b/crates/ui-egui/src/menus/run.rs @@ -929,6 +929,7 @@ fn run_ui_part10(app: &mut VectorcraftApp, id: &str, p: &Value) -> Option crate::dialogs::tate_chu_yoko::open(app), "ui.colorGuideLimit" => crate::panels::color_guide::set_limit(app, p), "ui.colorPanel" => crate::panels::color::command(app, p), + "ui.colorRgbPercent" => crate::panels::color::rgb_percent_command(app, p), "ui.savePdfDialog" => crate::dialogs::open_save_pdf(app, p), "file.exportAs" if s("path").is_none() => { crate::dialogs::open_export_as(app, s("format").as_deref(), p); diff --git a/crates/ui-egui/src/menus/ui_commands.rs b/crates/ui-egui/src/menus/ui_commands.rs index 824990d0a8..64ef022334 100644 --- a/crates/ui-egui/src/menus/ui_commands.rs +++ b/crates/ui-egui/src/menus/ui_commands.rs @@ -1245,6 +1245,12 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[ "", "{mode?: grayscale|rgb|hsb|cmyk|websafe, options?: bool, recentColors?: bool, rgbPercent?: bool} pick the Color panel's mode as its panel menu does (the active color converts to the mode's model as one undo step), Show Options (true) or Hide Options (false), check (true) or uncheck (false) Show Recent Colors, which shows or hides the panel's Recent Colors row (applied colors still join the list while it is hidden; the Swatches panel keeps its row), and show the RGB mode's R, G and B in percent (true) or from 0 to 255 (false), as a double-click right of an RGB field switches them (the color does not change; Web Safe RGB and the hex field stay in 0 to 255); all four are kept with the preferences; RGB, HSB, Web Safe RGB and hex values use the working RGB space (ui.inspect: ui.color_mode, ui.color_hide_options, ui.color_show_recent_colors, ui.color_rgb_percent) → {mode (null: none picked yet), options, recentColors, rgbPercent, shown: the mode the panel shows the color in, or tint for a tint of a global or spot swatch}", ), + ( + "ui.colorRgbPercent", + "Toggle RGB Percent", + "", + "{} switch the Color panel's R, G and B between percent and 0 to 255, as a double-click right of an RGB field does (ui.colorPanel {rgbPercent}); the color does not change, and the choice is kept with the preferences; a parameter is an error and changes nothing → true when they show in percent", + ), ( "ui.savePdfDialog", "Save PDF Dialog", diff --git a/crates/ui-egui/src/panels/color.rs b/crates/ui-egui/src/panels/color.rs index 4aee419bd1..a28fba02e5 100644 --- a/crates/ui-egui/src/panels/color.rs +++ b/crates/ui-egui/src/panels/color.rs @@ -17,7 +17,8 @@ //! A double-click right of an RGB field switches R, G and B between 0 to 255 and percent (the app //! keeps it between sessions too); Web Safe RGB and the hex field stay in 0 to 255. //! `ui.colorPanel` picks a mode, shows or hides the options and checks or unchecks Show Recent -//! Colors as the panel menu does, and switches RGB to percent and back. +//! Colors as the panel menu does, and switches RGB to percent and back. `ui.colorRgbPercent` +//! switches RGB between percent and 0 to 255 from the command palette or a keyboard shortcut. use egui::{Color32, Rect, Sense, Stroke, StrokeKind, Ui, vec2}; use serde_json::{Value, json}; @@ -584,6 +585,27 @@ pub(crate) fn command(app: &mut VectorcraftApp, p: &Value) -> Result Result { + match p { + Value::Null => {} + Value::Object(o) => { + if let Some(key) = o.keys().next() { + return Err(format!("unknown parameter `{key}`; the command takes no parameters")); + } + } + got => return Err(format!("the params must be an object, not {got}")), + } + Ok(json!(switch_rgb_units(app))) +} + +/// Switch the RGB mode's R, G and B between percent and 0 to 255, as a double-click right of an +/// RGB field does. → whether they now show in percent. +fn switch_rgb_units(app: &mut VectorcraftApp) -> bool { + app.ui.color_rgb_percent = !app.ui.color_rgb_percent; + app.ui.color_rgb_percent +} + /// Tint mode's rows: the swatch's chip and name, and the T slider and field. Returns a new tint /// (0..1) and its phase. fn tint_rows(ui: &mut Ui, tint: &Tint, slider_w: f32, field_w: f32) -> Option<(f32, Live)> { @@ -783,8 +805,7 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) { }); }); if switch_units { - let percent = app.ui.color_rgb_percent; - command(app, &json!({ "rgbPercent": !percent })).ok(); + switch_rgb_units(app); } ui.add_space(4.0); // A clicked chip or spectrum colour: the active colour, or with Alt the inactive proxy's. diff --git a/crates/ui-egui/src/panels/color/tests.rs b/crates/ui-egui/src/panels/color/tests.rs index 4cdd50f7fc..4538a8e5ce 100644 --- a/crates/ui-egui/src/panels/color/tests.rs +++ b/crates/ui-egui/src/panels/color/tests.rs @@ -849,6 +849,37 @@ fn rgb_percent_comes_back_after_a_restart() { assert_eq!(app.run("ui.colorPanel", json!({"rgbPercent": false})).unwrap()["rgbPercent"], false); } +/// Toggle RGB Percent switches the units from the keyboard: the command palette lists it, Keyboard +/// Shortcuts takes a key for it, and each run switches R, G and B between percent and 0 to 255 as +/// the double-click does, without changing the color. +#[test] +fn toggle_rgb_percent_switches_the_units_from_the_keyboard() { + let mut app = app(); + let ctx = egui::Context::default(); + app.run("paint.setFill", json!({"color": "#336699"})).unwrap(); + app.run("ui.colorPanel", json!({"mode": "rgb"})).unwrap(); + let listed = crate::palette::items().into_iter().any(|(label, id, _)| id == "ui.colorRgbPercent" && label == "Toggle RGB Percent"); + assert!(listed, "the command palette lists it"); + let e = crate::shortcut_editor::entry("ui.colorRgbPercent").expect("listed in Keyboard Shortcuts"); + assert_eq!(e.label, "Toggle RGB Percent"); + assert_eq!(crate::shortcut_editor::default_of("ui.colorRgbPercent"), None, "no default key"); + assert_eq!(app.run("ui.colorRgbPercent", json!({})).unwrap(), json!(true)); + assert!(app.ui.color_rgb_percent); + frame(&mut app, &ctx, vec![], egui::Modifiers::NONE); + assert_eq!(app.run("ui.colorPanel", json!({})).unwrap()["rgbPercent"], true); + for p in [json!({"rgbPercent": true}), json!({"on": false})] { + let e = app.run("ui.colorRgbPercent", p.clone()).unwrap_err(); + assert!(e.ends_with("; the command takes no parameters"), "{p}: {e}"); + } + for p in [json!(false), json!([]), json!("on")] { + assert_eq!(app.run("ui.colorRgbPercent", p.clone()).unwrap_err(), format!("the params must be an object, not {p}")); + } + assert!(app.ui.color_rgb_percent, "a parameter changes nothing"); + assert_eq!(app.run("ui.colorRgbPercent", json!({})).unwrap(), json!(false)); + assert!(!app.ui.color_rgb_percent); + assert_eq!(paints_hex(&app).0, "#336699", "the color does not change"); +} + /// `ui.colorPanel {mode}` picks a mode as the panel menu does: HSB shows an RGB fill as H, S and /// B, and CMYK converts it as one undo step. #[test] diff --git a/docs/control-protocol.md b/docs/control-protocol.md index 2288a160bd..10c8f5ab47 100644 --- a/docs/control-protocol.md +++ b/docs/control-protocol.md @@ -251,7 +251,9 @@ shown}`: `mode` is null until a mode is picked, and `shown` is the mode the pane tint of a global or spot swatch. Another mode, `lab` included, answers an error, as does an `options`, `recentColors` or `rgbPercent` value that is not true or false. The desktop app keeps the four settings with its preferences, and `ui.inspect` reports them as `ui.color_mode`, `ui.color_hide_options`, `ui.color_show_recent_colors` and -`ui.color_rgb_percent`. +`ui.color_rgb_percent`. `ui.colorRgbPercent {}` (Toggle RGB Percent in the command palette and Keyboard Shortcuts) +switches R, G and B between percent and 0 to 255 and answers true when they show in percent; a parameter answers an +error and changes nothing. Color Themes panel (`window.panel {panel: "colorThemes"}`): its Create tab makes a theme on a harmony wheel and its My Themes tab lists the saved themes; every action runs a command (`colorTheme.save`, `colorTheme.delete`, diff --git a/docs/mcp.md b/docs/mcp.md index 1baefe0ea3..c94732b99e 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -591,6 +591,8 @@ opens the picker through `run_command` with the remote backend. `ui.colorPanel { the panel menu's Show Recent Colors, which hides the Color panel's Recent Colors row, and `true` shows it again; applied colors still join the list while it is hidden. `ui.colorPanel {"rgbPercent": true}` shows the Color panel's RGB fields in percent, as a double-click right of an RGB field does, and `false` shows them from 0 to 255 again. +`ui.colorRgbPercent` (Toggle RGB Percent in the command palette and Keyboard Shortcuts) switches between the two +and answers true when the fields show in percent. In remote desktop mode, `invoke_menu` with `{"command":"ui.sampleScreenColor","params":{}}` shows the system's screen color sampler and returns `null` before a color is picked: the system's sampler on macOS, the XDG desktop diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index 3ef2425203..56c28edcb8 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -1353,7 +1353,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur Unchecking Show Recent Colors in the Color panel's menu hides its Recent Colors row, and checking it shows the row again ([Select colors using the Color panel](https://helpx.adobe.com/illustrator/desktop/manage-colors/select-and-adjust-colors/select-colors-using-the-color-panel.html)). In VectorCraft the item is checked by default and kept between sessions, applied colors still join the list while the row is hidden, Hide Options hides the row either way, and the Swatches panel keeps its row (`ui.colorPanel {recentColors}`). Tests: `show_recent_colors_hides_and_shows_the_row` in `crates/ui-egui/src/panels/color/tests.rs`. A Recent Colors chip in the Color or Swatches panel drags into the Swatches panel's list to become a swatch of its color, in its color model, as one undo step ([Select colors using the Color panel](https://helpx.adobe.com/illustrator/desktop/manage-colors/select-and-adjust-colors/select-colors-using-the-color-panel.html)). In VectorCraft a chip dropped on a color group or one of its swatches joins that group, anywhere else in the list it joins the ungrouped swatches at the end, a color that is already a swatch is added again under another name, and dragging a chip does not paint the selected art (`swatch.new`). Tests: `a_recent_color_dragged_onto_the_list_becomes_a_swatch` and `a_recent_color_dragged_from_the_color_panel_becomes_a_swatch` in `crates/ui-egui/src/panels/swatches/tests.rs`. A Cmd/Ctrl-click on the Color panel's spectrum applies Complement and a Cmd/Ctrl+Shift-click applies Invert to the active Fill or Stroke (`paint.complement`, `paint.invert`); with Alt also held, the click recolors the inactive one, as the reference app's [Default keyboard shortcuts](https://web.archive.org/web/20260726060015/https://helpx.adobe.com/illustrator/using/default-keyboard-shortcuts.html) list them. - A double-click right of an RGB field switches the Color panel's R, G and B between percent and 0 to 255 ([Default keyboard shortcuts](https://helpx.adobe.com/illustrator/using/default-keyboard-shortcuts.html), Color panel). In VectorCraft the color does not change, Web Safe RGB and the hex field stay in 0 to 255, and the panel keeps the choice between sessions (`ui.colorPanel {rgbPercent}`); the color mixer of the Fill and Stroke chips and the gradient stop dialog follow the same setting. Tests: `rgb_percent_switches_the_rgb_fields_between_percent_and_0_to_255`, `rgb_percent_does_not_switch_while_the_rows_are_disabled` and `rgb_percent_comes_back_after_a_restart` in `crates/ui-egui/src/panels/color/tests.rs`. + A double-click right of an RGB field switches the Color panel's R, G and B between percent and 0 to 255 ([Default keyboard shortcuts](https://helpx.adobe.com/illustrator/using/default-keyboard-shortcuts.html), Color panel). In VectorCraft the color does not change, Web Safe RGB and the hex field stay in 0 to 255, and the panel keeps the choice between sessions (`ui.colorPanel {rgbPercent}`); the color mixer of the Fill and Stroke chips and the gradient stop dialog follow the same setting. Toggle RGB Percent (`ui.colorRgbPercent`) in the command palette and Keyboard Shortcuts switches the units from the keyboard. Tests: `rgb_percent_switches_the_rgb_fields_between_percent_and_0_to_255`, `rgb_percent_does_not_switch_while_the_rows_are_disabled`, `rgb_percent_comes_back_after_a_restart` and `toggle_rgb_percent_switches_the_units_from_the_keyboard` in `crates/ui-egui/src/panels/color/tests.rs`. View › Proof Setup › Color blindness – Tritanopia-type turns Proof Colors on and proofs the canvas with the tritanopia simulation of Brettel, Viénot and Mollon (1997). The Separations Preview panel's Proof Setup list offers the same target (`view.proofSetup {target: "tritanopia"}`). Grays keep their value. The Separations Preview panel's Gamut Check button reports one color outside the CMYK gamut in the singular and more in the plural, in English and in each language's plural forms (`gamut_check_status_takes_the_plural_form` in `crates/ui-egui/src/i18n/tests.rs`). - **Document Color Mode outside the layers:** File › Document Color Mode (in one undo step) and `document.open {colorMode}` also convert the colors of graph designs, graphic styles, opacity-mask art, the tile that pattern editing's Cancel puts back and the art of brushes (with a Hue Shift brush's key color); a tint of a global swatch there becomes the converted swatch's color at its tint, as in the art.