diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index f5263170a..5b9aa8f3f 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -1344,6 +1344,7 @@ lighting Light Světlo Roman Baseline Latinková účaří Select a text object to edit its character attributes. Vyberte textový objekt, abyste mohli upravit atributy jeho znaků. Show Options Zobrazit možnosti + Color Panel Options Možnosti panelu Barva Small Caps Kapitálky Small Caps (on the roadmap) Kapitálky (v plánu) Standard Vertical Roman Alignment Standardní svislé zarovnání latinky @@ -3910,6 +3911,7 @@ imageTrace Outline Obrys @msg `{key}` must be between {min} and {max} (got {x}) `{key}` musí být mezi {min} a {max} (zadáno {x}) @msg `{key}` must be one of {_1} `{key}` musí být jedno z {_1} @msg `{key}` must be true or false `{key}` musí být true nebo false +@msg `mode` must be grayscale, rgb, hsb, cmyk or websafe `mode` musí být grayscale, rgb, hsb, cmyk nebo websafe @msg `{k}` must be [x, y] `{k}` musí být [x, y] @msg `{name}` has no options `{name}` nemá žádné možnosti @msg `{name}` is a built-in workspace; choose another name `{name}` je vestavěný pracovní prostor; zvolte jiný název diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index 37bc57204..a2894b475 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -2694,6 +2694,7 @@ Show Modified Geänderte einblenden Show Note Notiz einblenden Show Options Optionen einblenden + Color Panel Options Optionen für das Farbbedienfeld Show Package Paket anzeigen Show Panel Bedienfeld einblenden Show Pattern Swatches Musterfelder einblenden @@ -3516,6 +3517,7 @@ theme Appearance Erscheinungsbild @msg `{key}` must be between {min} and {max} (got {x}) `{key}` muss zwischen {min} und {max} liegen (erhalten: {x}) @msg `{key}` must be one of {_1} `{key}` muss einer der folgenden Werte sein: {_1} @msg `{key}` must be true or false `{key}` muss true oder false sein +@msg `mode` must be grayscale, rgb, hsb, cmyk or websafe `mode` muss grayscale, rgb, hsb, cmyk oder websafe sein @msg `{k}` must be [x, y] `{k}` muss [x, y] sein @msg `{name}` has no options `{name}` hat keine Optionen @msg `{name}` is a built-in workspace; choose another name `{name}` ist ein integrierter Arbeitsbereich; wählen Sie einen anderen Namen diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index 0b47fbec6..e2d2fa0c8 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -2743,6 +2743,7 @@ Show Modified Mostrar modificados Show Note Mostrar nota Show Options Mostrar opciones + Color Panel Options Opciones del panel Color Show Package Mostrar paquete Show Panel Mostrar panel Show Pattern Swatches Mostrar muestras de motivo @@ -3614,6 +3615,7 @@ axis Both Ambos @msg `{key}` must be between {min} and {max} (got {x}) `{key}` debe estar entre {min} y {max} (se recibió {x}) @msg `{key}` must be one of {_1} `{key}` debe ser uno de estos: {_1} @msg `{key}` must be true or false `{key}` debe ser true o false +@msg `mode` must be grayscale, rgb, hsb, cmyk or websafe `mode` debe ser grayscale, rgb, hsb, cmyk o websafe @msg `{k}` must be [x, y] `{k}` debe ser [x, y] @msg `{name}` has no options `{name}` no tiene opciones @msg `{name}` is a built-in workspace; choose another name `{name}` es un espacio de trabajo integrado; elige otro nombre diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index b25d8ecf6..042023805 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -2740,6 +2740,7 @@ Show Modified Afficher les éléments modifiés Show Note Afficher la note Show Options Afficher les options + Color Panel Options Options du panneau Couleur Show Package Afficher le dossier d’assemblage Show Panel Afficher le panneau Show Pattern Swatches Afficher les nuances de motif @@ -3611,6 +3612,7 @@ axis Both Les deux @msg `{key}` must be between {min} and {max} (got {x}) `{key}` doit être compris entre {min} et {max} (reçu : {x}) @msg `{key}` must be one of {_1} `{key}` doit valoir l’une de ces valeurs : {_1} @msg `{key}` must be true or false `{key}` doit valoir true ou false +@msg `mode` must be grayscale, rgb, hsb, cmyk or websafe `mode` doit valoir grayscale, rgb, hsb, cmyk ou websafe @msg `{k}` must be [x, y] `{k}` doit être [x, y] @msg `{name}` has no options `{name}` n’a pas d’options @msg `{name}` is a built-in workspace; choose another name `{name}` est un espace de travail intégré ; choisissez un autre nom diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index 48b3682c9..45ed86225 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -3325,6 +3325,7 @@ Show Modified Módosultak megjelenítése Show Note Megjegyzés megjelenítése Show Options Beállítások megjelenítése + Color Panel Options Szín panel beállításai Show Package Csomag megjelenítése Show Panel Panel megjelenítése Show Pattern Swatches Mintázat-színminták megjelenítése @@ -4103,6 +4104,7 @@ imageTrace Outline Körvonal @msg `{key}` must be between {min} and {max} (got {x}) `{key}`: {min} és {max} közötti érték kell (kapott érték: {x}) @msg `{key}` must be one of {_1} `{key}`: ezek egyike kell: {_1} @msg `{key}` must be true or false `{key}`: true vagy false kell +@msg `mode` must be grayscale, rgb, hsb, cmyk or websafe `mode`: grayscale, rgb, hsb, cmyk vagy websafe kell @msg `{k}` must be [x, y] `{k}`: [x, y] kell @msg `{name}` has no options ennek nincsenek beállításai: `{name}` @msg `{name}` is a built-in workspace; choose another name beépített munkaterület: `{name}`; válassz másik nevet diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 2a356476a..dfa1b127d 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -2745,6 +2745,7 @@ Show Modified Mostra modificati Show Note Mostra nota Show Options Mostra opzioni + Color Panel Options Opzioni pannello Colore Show Package Mostra pacchetto Show Panel Mostra pannello Show Pattern Swatches Mostra campioni motivo @@ -3616,6 +3617,7 @@ axis Both Entrambi @msg `{key}` must be between {min} and {max} (got {x}) `{key}` deve essere compreso tra {min} e {max} (ricevuto {x}) @msg `{key}` must be one of {_1} `{key}` deve essere uno dei seguenti: {_1} @msg `{key}` must be true or false `{key}` deve essere true o false +@msg `mode` must be grayscale, rgb, hsb, cmyk or websafe `mode` deve essere grayscale, rgb, hsb, cmyk o websafe @msg `{k}` must be [x, y] `{k}` deve essere [x, y] @msg `{name}` has no options `{name}` non ha opzioni @msg `{name}` is a built-in workspace; choose another name `{name}` è un'area di lavoro integrata; scegli un altro nome diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index c9cea608a..70030631b 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -2564,6 +2564,7 @@ Show List View リスト表示 Show Note メモを表示 Show Options オプションを表示 + Color Panel Options カラーパネルオプション Show Package パッケージを表示 Show Panel パネルを表示 Show Perspective Grid 遠近グリッドを表示 @@ -4013,6 +4014,7 @@ theme Appearance 外観 @msg Opened {url} {url} を開きました @msg No saved views (View → New View…) 保存した表示はありません (表示 → 新規表示…) @msg `command` must be a command whose menu item opens a dialog (see ui.menuDialog) `command` には、メニュー項目がダイアログを開くコマンドを指定してください (ui.menuDialog を参照) +@msg `mode` must be grayscale, rgb, hsb, cmyk or websafe `mode` には grayscale、rgb、hsb、cmyk、websafe のいずれかを指定してください @msg `{key}` must be true or false `{key}` には true または false を指定してください @msg brightness must be dark|mediumDark|mediumLight|light brightness には dark|mediumDark|mediumLight|light のいずれかを指定してください @msg index must be a number or first, previous, next or last index には数値、または first、previous、next、last のいずれかを指定してください diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index 99d9927bc..005642690 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -2558,6 +2558,7 @@ Show List View 목록 보기 표시 Show Note 메모 표시 Show Options 옵션 표시 + Color Panel Options 색상 패널 옵션 Show Package 패키지 표시 Show Panel 패널 표시 Show Perspective Grid 원근 격자 표시 @@ -3991,6 +3992,7 @@ theme Appearance 외관 @msg Opened {url} {url}을(를) 열었습니다 @msg No saved views (View → New View…) 저장된 보기가 없습니다(보기 → 새 보기…) @msg `command` must be a command whose menu item opens a dialog (see ui.menuDialog) `command`는 메뉴 항목이 대화상자를 여는 명령이어야 합니다(ui.menuDialog 참조) +@msg `mode` must be grayscale, rgb, hsb, cmyk or websafe `mode`은(는) grayscale, rgb, hsb, cmyk, websafe 중 하나여야 합니다 @msg `{key}` must be true or false `{key}`은(는) true 또는 false여야 합니다 @msg brightness must be dark|mediumDark|mediumLight|light brightness는 dark|mediumDark|mediumLight|light 중 하나여야 합니다 @msg index must be a number or first, previous, next or last index는 숫자이거나 first, previous, next, last 중 하나여야 합니다 diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index 8932bab85..38817fd39 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -1256,6 +1256,7 @@ lighting Light Światło Roman Baseline Linia bazowa łacińska Select a text object to edit its character attributes. Zaznacz obiekt tekstowy, aby edytować atrybuty znaków. Show Options Pokaż opcje + Color Panel Options Opcje panelu Kolor Small Caps Kapitaliki Small Caps (on the roadmap) Kapitaliki (w planach) Standard Vertical Roman Alignment Standardowe pionowe wyrównanie łacińskie @@ -3724,6 +3725,7 @@ imageTrace Outline Kontur @msg `{key}` must be between {min} and {max} (got {x}) `{key}` musi mieścić się w zakresie od {min} do {max} (podano {x}) @msg `{key}` must be one of {_1} `{key}` musi mieć jedną z wartości: {_1} @msg `{key}` must be true or false `{key}` musi mieć wartość true lub false +@msg `mode` must be grayscale, rgb, hsb, cmyk or websafe `mode` musi mieć wartość grayscale, rgb, hsb, cmyk lub websafe @msg `{k}` must be [x, y] `{k}` musi mieć postać [x, y] @msg `{name}` has no options `{name}` nie ma opcji @msg `{name}` is a built-in workspace; choose another name `{name}` to wbudowany obszar roboczy; wybierz inną nazwę diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index df419a722..f116b3222 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -2741,6 +2741,7 @@ Show Modified Показать изменённые Show Note Показать примечание Show Options Показать параметры + Color Panel Options Параметры панели «Цвет» Show Package Показать пакет Show Panel Показать панель Show Pattern Swatches Показать образцы узоров @@ -3612,6 +3613,7 @@ axis Both Оба @msg `{key}` must be between {min} and {max} (got {x}) `{key}` должно быть от {min} до {max} (получено {x}) @msg `{key}` must be one of {_1} `{key}` должно быть одним из: {_1} @msg `{key}` must be true or false `{key}` должно быть true или false +@msg `mode` must be grayscale, rgb, hsb, cmyk or websafe `mode` должно быть grayscale, rgb, hsb, cmyk или websafe @msg `{k}` must be [x, y] `{k}` должно быть [x, y] @msg `{name}` has no options `{name}` не имеет параметров @msg `{name}` is a built-in workspace; choose another name `{name}` — встроенная рабочая среда; выберите другое имя diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index 000bc3612..b866de9f6 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -2737,6 +2737,7 @@ Show Modified Показати змінені Show Note Показати примітку Show Options Показати параметри + Color Panel Options Параметри панелі «Колір» Show Package Показати пакет Show Panel Показати панель Show Pattern Swatches Показати зразки візерунків @@ -3600,6 +3601,7 @@ axis Both Обидві @msg `{key}` must be between {min} and {max} (got {x}) `{key}` має бути від {min} до {max} (отримано {x}) @msg `{key}` must be one of {_1} `{key}` має бути одним з: {_1} @msg `{key}` must be true or false `{key}` має бути true або false +@msg `mode` must be grayscale, rgb, hsb, cmyk or websafe `mode` має бути grayscale, rgb, hsb, cmyk або websafe @msg `{k}` must be [x, y] `{k}` має бути [x, y] @msg `{name}` has no options `{name}` не має параметрів @msg `{name}` is a built-in workspace; choose another name `{name}` - вбудоване робоче середовище; виберіть інше ім’я diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index 8c1afee24..e1e65add7 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -3048,6 +3048,7 @@ Show Modified Hiện đã sửa đổi Show Note Hiện ghi chú Show Options Hiện tùy chọn + Color Panel Options Tùy chọn bảng màu Show Package Hiện gói Show Panel Hiện bảng Show Pattern Swatches Hiện mẫu họa tiết @@ -3733,6 +3734,7 @@ theme Appearance Giao diện @msg `{key}` must be between {min} and {max} (got {x}) `{key}` phải nằm trong khoảng {min} đến {max} (nhận được {x}) @msg `{key}` must be one of {_1} `{key}` phải là một trong {_1} @msg `{key}` must be true or false `{key}` phải là true hoặc false +@msg `mode` must be grayscale, rgb, hsb, cmyk or websafe `mode` phải là grayscale, rgb, hsb, cmyk hoặc websafe @msg `{k}` must be [x, y] `{k}` phải là [x, y] @msg `{name}` has no options `{name}` không có tùy chọn @msg `{name}` is a built-in workspace; choose another name `{name}` là không gian làm việc tích hợp sẵn; hãy chọn tên khác diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index 036061180..137af1836 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -2594,6 +2594,7 @@ Show Modified 显示已修改 Show Note 显示注解 Show Options 显示选项 + Color Panel Options 颜色面板选项 Show Package 显示打包 Show Panel 显示面板 Show Pattern Swatches 显示图案色板 diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index 0d5b3035f..bd8252726 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -2723,6 +2723,7 @@ Show Modified 顯示已修改 Show Note 顯示註解 Show Options 顯示選項 + Color Panel Options 顏色面板選項 Show Package 顯示封裝 Show Panel 顯示面板 Show Pattern Swatches 顯示圖樣色票 diff --git a/crates/ui-egui/src/menus.rs b/crates/ui-egui/src/menus.rs index e43cbeafc..046f43142 100644 --- a/crates/ui-egui/src/menus.rs +++ b/crates/ui-egui/src/menus.rs @@ -620,6 +620,12 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[ "", "{library: swatch library id or name (see swatch.library.list) | \"document\" (the document's swatches) | \"\" or null (no limit)} limit the Color Guide panel's colours to that library: every harmony colour and variation snaps to its nearest colour, as color.harmony {limitTo} answers (ui.inspect: ui.color_guide_limit) → {limitTo, name}", ), + ( + "ui.colorPanel", + "Color Panel Options", + "", + "{mode?: grayscale|rgb|hsb|cmyk|websafe, options?: 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), and Show Options (true) or Hide Options (false); both are kept with the preferences (ui.inspect: ui.color_mode, ui.color_hide_options) → {mode (null: none picked yet), options, shown: the mode the panel shows the color in, or tint for a tint of a global or spot swatch}", + ), ( "ui.savePdfDialog", "Save PDF Dialog", @@ -1425,6 +1431,7 @@ pub fn run_ui_command(app: &mut VectorcraftApp, id: &str, p: &Value) -> Option crate::dialogs::width_point::open(app, p), "ui.corners" => crate::dialogs::corners::open(app, p), "ui.colorGuideLimit" => crate::panels::color_guide::set_limit(app, p), + "ui.colorPanel" => crate::panels::color::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/panels/color.rs b/crates/ui-egui/src/panels/color.rs index 0213231dc..58b915f4b 100644 --- a/crates/ui-egui/src/panels/color.rs +++ b/crates/ui-egui/src/panels/color.rs @@ -9,10 +9,11 @@ //! model and shows the defaults for new art; the app keeps it between sessions). Alt-clicking the //! spectrum or a chip paints the inactive proxy; Shift-clicking the spectrum cycles the modes; //! Shift-dragging a slider moves the others in tandem; RGB and HSB show the out-of-gamut warning; -//! Hide Options leaves just the proxy and the spectrum. +//! Hide Options leaves just the proxy and the spectrum (the app keeps it between sessions too). +//! `ui.colorPanel` picks a mode and shows or hides the options as the panel menu does. use egui::{Color32, Rect, Sense, Stroke, StrokeKind, Ui, vec2}; -use serde_json::json; +use serde_json::{Value, json}; use vectorcraft_color::{Color, GradientKind, Paint}; use super::{active_paint, color_json, live_run, pstate, set_pstate}; @@ -251,8 +252,21 @@ pub(crate) fn gamut_fix(app: &mut VectorcraftApp, ctx: &egui::Context, key: &str /// Serde reader for the saved panel mode ([`crate::UiState::color_mode`]): a menu mode, else none, /// so an unknown value doesn't lose the rest of the preferences. pub fn deserialize_mode<'de, D: serde::Deserializer<'de>>(d: D) -> Result, D::Error> { - let v = ::deserialize(d)?; - Ok(Mode::ALL.into_iter().find(|m| serde_json::to_value(m).is_ok_and(|id| id == v))) + let v = ::deserialize(d)?; + Ok(menu_mode(&v)) +} + +/// The menu mode whose id (`"grayscale"`, `"rgb"`, `"hsb"`, `"cmyk"`, `"websafe"`) is `v`. +fn menu_mode(v: &Value) -> Option { + Mode::ALL.into_iter().find(|m| serde_json::to_value(m).is_ok_and(|id| id == *v)) +} + +/// Serde reader for the saved Hide Options ([`crate::UiState::color_hide_options`]): `true` hides +/// the options and any other value shows them, so an unknown value doesn't lose the rest of the +/// preferences. +pub fn deserialize_hide_options<'de, D: serde::Deserializer<'de>>(d: D) -> Result { + let v = ::deserialize(d)?; + Ok(v.as_bool().unwrap_or_default()) } /// The mode the panel shows `color` in: with nothing selected (the defaults for new art) the mode @@ -366,7 +380,13 @@ impl Target { /// The active proxy's colour or gradient stop; no colour for None, patterns and a "?" proxy. fn target(app: &VectorcraftApp, ui: &Ui) -> Target { - if super::active_mixed(app, ui.ctx()) { + target_with(app, super::active_mixed(app, ui.ctx())) +} + +/// [`target`] with `mixed` for a "?" active proxy: the panel reads it from its per-frame cache, +/// `ui.colorPanel` from the session. +fn target_with(app: &VectorcraftApp, mixed: bool) -> Target { + if mixed { return Target::Paint(None, None); } let p = active_paint(app); @@ -439,6 +459,32 @@ fn set_mode(app: &mut VectorcraftApp, tgt: &Target, mode: Mode) { } } +/// `ui.colorPanel {mode?, options?}`: pick a mode as the panel menu does ([`set_mode`]), and Show +/// Options (true) or Hide Options (false). → {mode, options, shown}: the mode picked last (null: +/// none yet), whether the options show, 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, + Some(v) => Some(menu_mode(v).ok_or("`mode` must be grayscale, rgb, hsb, cmyk or websafe")?), + }; + let options = crate::menus::opt_bool(p, "options")?; + let (fill, stroke) = app.session.proxy_mixed(); + let mixed = if app.session.fill_active { fill } else { stroke }; + if let Some(m) = mode { + set_mode(app, &target_with(app, mixed), m); + } + if let Some(shown) = options { + app.ui.color_hide_options = !shown; + } + let tgt = target_with(app, mixed); + let shown = match tgt.tint() { + Some(_) => json!("tint"), + None => json!(shown_mode(app, tgt.color().as_ref())), + }; + Ok(json!({"mode": app.ui.color_mode, "options": !app.ui.color_hide_options, "shown": shown})) +} + /// 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)> { @@ -494,7 +540,7 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) { let mode = shown_mode(app, color.as_ref()); let alt = super::alt_held(ui); // Hide Options: just the proxy and the spectrum. - if pstate::(ui.ctx(), "color-hide-options") { + if app.ui.color_hide_options { let hit = ui .horizontal(|ui| { super::proxy(app, ui, 40.0); @@ -650,9 +696,9 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) { } pub fn menu(app: &mut VectorcraftApp, ui: &mut Ui) { - let hidden: bool = pstate(ui.ctx(), "color-hide-options"); + let hidden = app.ui.color_hide_options; if menu_item(ui, if hidden { tl!("Show Options") } else { tl!("Hide Options") }, true, false) { - set_pstate(ui.ctx(), "color-hide-options", !hidden); + command(app, &json!({ "options": hidden })).ok(); } ui.separator(); let tgt = target(app, ui); @@ -1047,9 +1093,77 @@ mod tests { let ctx = egui::Context::default(); let (spectrum, full) = frame(&mut app, &ctx, vec![], egui::Modifiers::NONE); assert!(spectrum.is_some()); - set_pstate(&ctx, "color-hide-options", true); + assert_eq!(app.run("ui.colorPanel", json!({"options": false})).unwrap()["options"], false); let (_, hidden) = frame(&mut app, &ctx, vec![], egui::Modifiers::NONE); assert!(hidden <= 44.0 && full > 150.0, "{hidden} / {full}"); + app.run("ui.colorPanel", json!({"options": true})).unwrap(); + assert_eq!(frame(&mut app, &ctx, vec![], egui::Modifiers::NONE).1, full); + } + + /// Hide Options is kept with the UI state, which the app saves with its preferences. A saved + /// value that isn't true or false reads as Show Options, and the rest of the state still loads. + #[test] + fn hide_options_comes_back_after_a_restart() { + let mut app = app(); + app.run("ui.colorPanel", json!({"options": false})).unwrap(); + let saved: crate::UiState = serde_json::from_value(serde_json::to_value(&app.ui).unwrap()).unwrap(); + assert!(saved.color_hide_options); + assert_eq!(crate::control::inspect(&app, &egui::Context::default())["ui"]["color_hide_options"], true); + for v in [json!(7), json!("hide"), json!({}), Value::Null] { + let ui: crate::UiState = serde_json::from_value(json!({"color_hide_options": v, "color_mode": "future", "place_link": false})).unwrap(); + assert_eq!((ui.color_hide_options, ui.color_mode, ui.place_link), (false, None, false), "{v}"); + } + } + + /// `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] + fn ui_color_panel_picks_a_mode_as_the_menu_does() { + let mut app = app(); + 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, "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"); + assert_eq!(app.run("ui.colorPanel", json!({"mode": "cmyk"})).unwrap()["shown"], "cmyk"); + assert!(matches!(crate::panels::current_paints(&app).0.color(), Some(Color::Cmyk { .. })), "CMYK converts the color"); + app.run("edit.undo", json!({})).unwrap(); + 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, "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(); + assert_eq!(app.run("ui.colorPanel", json!({})).unwrap()["shown"], "tint"); + } + + /// A mode the panel menu doesn't list, Lab included, and an options value that isn't true or + /// false answer an error, and the command then changes nothing. + #[test] + fn ui_color_panel_answers_an_error_for_a_mode_off_the_menu() { + let mut app = app(); + for m in [json!("lab"), json!("oklch"), json!("hsl"), json!("future"), json!("RGB"), json!(3)] { + let e = app.run("ui.colorPanel", json!({"mode": m})).unwrap_err(); + assert_eq!(e, "`mode` must be grayscale, rgb, hsb, cmyk or websafe", "{m}"); + } + assert_eq!(app.run("ui.colorPanel", json!({"mode": "cmyk", "options": "no"})).unwrap_err(), "`options` must be true or false"); + assert_eq!((app.ui.color_mode, app.ui.color_hide_options), (None, false)); + } + + /// `ui.colorPanel` answers every junk parameter, with and without a document, without a panic. + #[test] + fn ui_color_panel_answers_junk_params_without_a_panic() { + let doc = crate::menus::UI_COMMANDS.iter().find(|c| c.0 == "ui.colorPanel").expect("registered").3; + let mut app = app(); + app.run("paint.setFill", json!({"color": "#336699"})).unwrap(); + for p in vectorcraft_testkit::strategies::junk_params(doc, "/nonexistent").into_iter().chain(vectorcraft_testkit::strategies::junk_values()) { + let _ = app.run("ui.colorPanel", p); + } + let mut app = VectorcraftApp::new(Session::new(), Default::default()); + assert_eq!(app.run("ui.colorPanel", json!({"mode": "cmyk"})).unwrap()["mode"], "cmyk"); } #[test] diff --git a/crates/ui-egui/src/state.rs b/crates/ui-egui/src/state.rs index 1fc6f089f..12015c988 100644 --- a/crates/ui-egui/src/state.rs +++ b/crates/ui-egui/src/state.rs @@ -380,6 +380,10 @@ pub struct UiState { /// shows the defaults for new art ([`crate::panels::color::deserialize_mode`]). #[serde(default, deserialize_with = "crate::panels::color::deserialize_mode")] pub color_mode: Option, + /// Color panel: Hide Options, which leaves just the proxy and the spectrum (`ui.colorPanel`, + /// [`crate::panels::color::deserialize_hide_options`]). + #[serde(default, deserialize_with = "crate::panels::color::deserialize_hide_options")] + pub color_hide_options: bool, /// The SVG Options chosen last (`svg` object of the export/save commands; null: never used). #[serde(default)] pub svg_options: Value, @@ -715,6 +719,7 @@ impl Default for UiState { color_guide_limit: String::new(), align_to: Default::default(), color_mode: None, + color_hide_options: false, svg_options: Value::Null, place_link: true, dialog_file: None, diff --git a/docs/control-protocol.md b/docs/control-protocol.md index 3a8bcb806..cbb9e3ab3 100644 --- a/docs/control-protocol.md +++ b/docs/control-protocol.md @@ -207,6 +207,13 @@ 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?}` 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) and shows +(`options: true`) or hides (`false`) the options. It answers `{mode, options, 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. The desktop app keeps both settings with its preferences, and +`ui.inspect` reports them as `ui.color_mode` and `ui.color_hide_options`. + 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`, `colorTheme.addToSwatches`, `swatch.newGroup` for the theme being made), and `colorTheme.list` reads the library. diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index f2fa364bd..ae118edeb 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -428,6 +428,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur - Native macOS save sheets offer a localised Format menu (#866), built from the request's existing format filters. It selects the suggested filename's format and updates the suffix when changed, including legacy `.drawcraft` names. `apps/vectorcraft/src/mac_save_format.rs` has filename regressions; an actual native SVG save, selection of PDF and cancellation without changing the saved document were verified. - Save / Don't Save / Cancel before closing or quitting with unsaved documents (tabs, Close, Close All, Quit, window close). - Four brightness themes, persistent preferences. + - The desktop app keeps the Color panel's Show Options or Hide Options between sessions, and `ui.colorPanel` sets its mode and options as its panel menu does. - Preferences that act (#394, in part): Show Tool Tips, Double Click To Isolate, Zoom with Mouse Wheel (the wheel zooms about the pointer, Shift-wheel scrolls up and down, Cmd/Ctrl-wheel sideways; Alt/Option-wheel and a trackpad pinch zoom about the pointer either way), Use Precise Cursors; Selection & Anchor Display › Tolerance (objects, anchors and handles), Object Selection by Path Only, Command Click to Select Objects Behind (cycling down the stack), Zoom to Selection, anchor Size, Highlight anchors on mouse over; Type › Number of Recent Fonts (up to 15); Guides & Grid › guide and grid Color and Style (lines or dots) and Grids In Back. Then Snap to Point (its distance, for moves, drawn points and reference points), Move Locked and Hidden Artwork with Artboard, Enable Rubber Band for the Pen and Curvature tools, Handles style, Show handles when multiple anchors are selected, Hide Corner Widget for angles greater than; General › Transform Pattern Tiles (the transforms' and dialogs' Transform Patterns, Align and Distribute, art moved or duplicated with an artboard, and objects moved whole with direct-selected anchors) and Select Same Tint %; Type › Size/Leading, Tracking and Baseline Shift increments (`type.step`, Cmd+Shift+. and Cmd+Shift+, for the size, the Type tool's Alt+arrows), Enable Missing Glyph Protection and Fill New Type Objects With Placeholder Text. Then Smart Guides › Color, Alignment Guides, Anchor/Path Labels, Measurement Labels, Transform Tools, Object Highlighting (with Smart Guides on) and Snapping Tolerance (4 px by default, for every smart guide snap), then Construction Guides and their Angles (#506: a drawn point near a line at one of those angles through the point it leaves from lands on it). Then Type › Type Object Selection by Path Only (type is picked on the baseline of any of its lines, area type's frame or the path of type on a path, no longer anywhere in its bounds; the Type tools and the Eyedropper still take a click among the characters). Then General › Anti-aliased Artwork (off, the canvas draws the art with hard edges). Then Guides & Grid › Show Pixel Grid (Above 600% Zoom), a line at every document pixel over the art in Pixel Preview, turning with a rotated view as the document grid now does too. Then General › Show The Home Screen When No Documents Are Open (off, closing the last document leaves an empty window; the Home button still opens the screen). Then Units › Numbers Without Units Are Points (on by default: a number typed with no unit into a field in picas is read in points, a typed unit still wins; dimmed unless a unit is Picas). Then User Interface › Auto-Collapse Iconic Panels (off by default: on, a click away from a panel popped out of the icon column puts it away). Then Performance › Animated Zoom (on, with GPU Performance, which it needs and which dims it when off: the Zoom tool dragged sideways zooms about the press, right in and left out, and held still zooms on, Alt out; off, a drag zooms to the area dragged across, now drawn as a marquee), User Interface › Large Tabs (taller document tabs) and Scale Cursor Proportional to UI (off, the tool cursors keep their size whatever the UI Scaling), and File Handling › Display Bitmaps as Anti-aliased Images in Pixel Preview (off by default: Pixel Preview draws images with each pixel taking its nearest image pixel). Type › Auto Size New Area Type works too (new area type gets Auto Size). They reach agents too (`prefs.set`, then `pointer_gesture` or `press_key`; `ui.wheel` on the control channel). Then Smart Guides › Spacing Guides (moves space the selection evenly with its row or column, see Drawing aids). Then Hyphenation › Exceptions (comma-separated words that never break, or break only at their hyphens; `prefs.set` and restore push them into the hyphenator, #394). Then Type › Show Font Names in English (off, font menus label a family by its native-language name when the font has one; documents still use the English family, #394). Then General › Display Print Size at 100% Zoom (off by default: View › Actual Size is one document point per screen point; on, one document inch fills 96 screen points, an inch at the reference density display scaling is set against; `view.actualSize` / `prefs.set`, #394). Then Guides & Grid › Gridline every and Subdivisions (changing either sets the grid of every open document; new and opened documents take that grid too, except a native file and the native document an SVG, PDF, `.ai` or EPS file carries, which open with the grid they were saved with; `file.new`, `document.open`, `file.newFromTemplate`, `file.revert`, #394). Then Appearance of Black › On Screen (Display All Blacks Accurately by default: pure CMYK black shows through the CMYK profile, a dark grey; Display All Blacks as Rich Black shows it on the canvas as RGB black, also in CMYK documents drawn by ink planes, while rich blacks, black tints and images show as before; `RenderOptions::rich_black`, #394). Then Appearance of Black › Printing / Exporting (Output All Blacks Accurately by default; Output All Blacks as Rich Black draws pure CMYK black as RGB black in RGB raster exports and Print as Bitmap; CMYK ink exports and the document stay unchanged; `prefs.blackOutput` / `richBlack`, #394). Then Units › Identify Objects By (Object Name by default: Layers, Variables and Layer Options show and edit free names; XML ID shows and edits AI-style XML ids, `_` for a space and `_xHH_` escapes, and `layer.setProps` / rename refuse invalid ids; the document still stores the object name, #394). About 15 stored options still do nothing yet (the rest of #394: Highlight Alternate Glyphs, East Asian Type units, Open Documents As Tabs, Real-time Drawing and Editing, GPU Performance beyond Animated Zoom, hyphenation language, scratch disks, the legacy clipboard, Touch Workspace). - Dialogs move by dragging their heading (the band down from the top edge, as a title bar), stay on screen and keep where they were moved for the session; the form dialogs' choices are dropdowns (Offset Path's Joins, Average's and Shear's and Reflect's axis), and Offset Path lists Offset, Joins, Miter Limit and previews (#423). - Dialogs open with their first field focused and its value selected: type a value and press Enter (Move, Rotate, Scale…). A dialog opened from a menu takes the keyboard focus every time it opens, not only the first (#1367), and one whose first field starts greyed out (Blend Options on Smooth Color, Layers Panel Options) gives the focus to that field once it is enabled (#1395). Number fields in panels, the Control bar and dialogs (preferences too) step with Up/Down, or with the mouse wheel over the focused field (#485: a notch a step; the panel under it then stays put, and an unfocused field leaves the wheel to scrolling) (Shift: ten, Ctrl/Cmd: a tenth of their unit), applied at once.