Merge pull request #6943 from dotCooCoo/feat/color-rgb-percent

Color: a double-click right of an RGB field switches between percent and 0 to 255
This commit is contained in:
BFlat
2026-11-13 08:31:01 +07:00
committed by GitHub
36 changed files with 362 additions and 24 deletions
+7 -1
View File
@@ -1242,7 +1242,13 @@ 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.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",
+1
View File
@@ -3212,6 +3212,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 Автоматично опростяване
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -1579,6 +1579,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)
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -4175,6 +4175,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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -1608,6 +1608,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)
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -1515,6 +1515,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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -3272,6 +3272,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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -3845,6 +3845,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 Αυτόματη απλοποίηση
Can't render this file because it is too large.
+1
View File
@@ -3303,6 +3303,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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -1582,6 +1582,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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -3295,6 +3295,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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -4507,6 +4507,7 @@ antiAlias Sharp शार्प
Show Note नोट दिखाएं
Show Options ऑप्शन्स दिखाएँ
Show Recent Colors हाल के रंग दिखाएँ
Toggle RGB Percent RGB प्रतिशत में बदलें
Show Original Path मूल पाथ दिखाएं
Show Package पैकेज करें दिखाएँ
Show Panel शो पैनल
Can't render this file because it is too large.
+1
View File
@@ -3937,6 +3937,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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -1515,6 +1515,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
1 # Indonesian (id) catalog. Same format as zh-hant.tsv: context <TAB> source <TAB> translation
1515
1516
1517
1518
1519
1520
1521
+1
View File
@@ -3348,6 +3348,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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -3168,6 +3168,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 自動単純化
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -1453,6 +1453,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 სტანდარტული ვერტიკალური ლათინური სწორება
Can't render this file because it is too large.
+1
View File
@@ -3157,6 +3157,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 자동 단순화
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -1529,6 +1529,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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -1520,6 +1520,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)
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -2780,6 +2780,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
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1
View File
@@ -3335,6 +3335,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 Автоматическое упрощение
Can't render this file because it is too large.
+1
View File
@@ -3227,6 +3227,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
Can't render this file because it contains an unexpected character in line 5 and column 25.
+1
View File
@@ -3129,6 +3129,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
Can't render this file because it contains an unexpected character in line 5 and column 25.
+1
View File
@@ -3340,6 +3340,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 Автоматичне спрощення
Can't render this file because it is too large.
+1
View File
@@ -3687,6 +3687,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
Can't render this file because it contains an unexpected character in line 204 and column 5.
+1
View File
@@ -3168,6 +3168,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 自动简化
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -4394,6 +4394,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 自動簡化
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1
View File
@@ -929,6 +929,7 @@ fn run_ui_part10(app: &mut VectorcraftApp, id: &str, p: &Value) -> Option<Result
"ui.tateChuYokoSettings" => 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);
+7 -1
View File
@@ -1243,7 +1243,13 @@ 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.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",
+85 -10
View File
@@ -14,8 +14,11 @@
//! 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. `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};
@@ -298,6 +301,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<bool, D::Error> {
let v = <Value as serde::Deserialize>::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 +543,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<Value, String> {
let mode = match p.get("mode") {
None | Some(Value::Null) => None,
@@ -534,6 +556,7 @@ pub(crate) fn command(app: &mut VectorcraftApp, p: &Value) -> Result<Value, Stri
};
let options = crate::menus::opt_bool(p, "options")?;
let recent = crate::menus::opt_bool(p, "recentColors")?;
let percent = crate::menus::opt_bool(p, "rgbPercent")?;
let (fill, stroke) = app.session.proxy_mixed();
let mixed = if app.session.fill_active { fill } else { stroke };
if let Some(m) = mode {
@@ -545,6 +568,9 @@ pub(crate) fn command(app: &mut VectorcraftApp, p: &Value) -> Result<Value, Stri
if let Some(on) = recent {
app.ui.color_show_recent_colors = on;
}
if let Some(on) = percent {
app.ui.color_rgb_percent = on;
}
let tgt = target_with(app, mixed);
let shown = match tgt.tint() {
Some(_) => json!("tint"),
@@ -554,10 +580,32 @@ pub(crate) fn command(app: &mut VectorcraftApp, p: &Value) -> Result<Value, Stri
"mode": app.ui.color_mode,
"options": !app.ui.color_hide_options,
"recentColors": app.ui.color_show_recent_colors,
"rgbPercent": app.ui.color_rgb_percent,
"shown": shown,
}))
}
/// `ui.colorRgbPercent {}`: [`switch_rgb_units`]. A parameter is an error and changes nothing.
pub(crate) fn rgb_percent_command(app: &mut VectorcraftApp, p: &Value) -> Result<Value, String> {
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)> {
@@ -662,6 +710,11 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
// RGB and Grayscale.
let (shows_gamut, shows_web) = (matches!(mode, Mode::Rgb | Mode::Hsb), !matches!(mode, Mode::WebSafe | Mode::Grayscale));
let gamut = color.filter(|_| shows_gamut).and_then(|c| gamut_fix(app, ui.ctx(), "color-gamut", &c));
// RGB in percent: the fields and sliders show R, G and B from 0 to 100 %, and a double-click
// right of an RGB field (`switch_units`) switches them between percent and 0 to 255.
let percent = mode == Mode::Rgb && app.ui.color_rgb_percent;
let mut switch_units = false;
let units_id = ui.scope_id().with("color-units");
ui.horizontal(|ui| {
// Left column: proxy, out-of-gamut and out-of-web warnings.
ui.vertical(|ui| {
@@ -695,6 +748,10 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
tinted = tint_rows(ui, tint, slider_w, field_w);
return;
}
// An RGB slider is narrower by the width and spacing of the cell right of its field, and
// an RGB row is as wide as a row in the other modes.
let unit_w = 10.0;
let slider_w = if mode == Mode::Rgb { slider_w - unit_w - ui.spacing().item_spacing.x } else { slider_w };
let enabled = color.is_some();
for (i, lbl) in mode.labels().iter().enumerate() {
ui.horizontal(|ui| {
@@ -702,7 +759,8 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
let v = comps.get(i).copied().unwrap_or(0.0);
let track = |x: f32| to32(&track_color(mode, &comps, i, x));
let grey = |_x: f32| t.input;
let shown = widgets::SliderValue::field(v as f64, mode.min(i) as f64..=mode.max(i) as f64, mode.suffix(i), 0);
let (field_v, field_max, suffix) = if percent { (to_percent(v), 100.0, "%") } else { (v, mode.max(i), mode.suffix(i)) };
let shown = widgets::SliderValue::field(field_v as f64, mode.min(i) as f64..=field_max as f64, suffix, 0);
let (nv, phase) = if enabled {
widgets::color_slider_valued(ui, ("color-slider", i), lbl, Some(shown), mode.unit(i, v), slider_w, &track)
} else {
@@ -711,7 +769,8 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
if let Some(nv) = nv
&& enabled
{
let nv = mode.value_at(i, nv);
// In percent the slider picks whole percents.
let nv = if percent { from_percent((nv * 100.0).round()) } else { mode.value_at(i, nv) };
let c2 = if shift && mode.tandem() {
tandem(mode, &comps, i, nv)
} else {
@@ -721,17 +780,33 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
};
new = Some((from_components(mode, &c2), phase, c2));
}
if let Some(fv) = widgets::plain_field_named(ui, ("color-field", i), Some(lbl), v as f64, mode.suffix(i), 0, field_w)
if let Some(fv) = widgets::plain_field_named(ui, ("color-field", i), Some(lbl), field_v as f64, suffix, 0, field_w)
&& enabled
{
let mut c2 = comps.clone();
c2[i] = (fv as f32).clamp(mode.min(i), mode.max(i));
let fv = fv as f32;
c2[i] = (if percent { from_percent(fv) } else { fv }).clamp(mode.min(i), mode.max(i));
new = Some((from_components(mode, &c2), Live::Released, c2));
}
// The cell takes no keyboard focus and is highlighted under the pointer. Like
// the fields, it does nothing while the rows are disabled.
if mode == Mode::Rgb {
let (r, _) = ui.allocate_exact_size(vec2(unit_w, 22.0), Sense::hover());
let cell = ui.interact(r, units_id.with(i), Sense::CLICK);
if cell.hovered() && enabled {
ui.painter().rect_filled(r, 2.0, t.hover);
}
if cell.double_clicked() && enabled {
switch_units = true;
}
}
});
}
});
});
if switch_units {
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.
let mut pick = |c: Color, phase: Live, new: &mut Option<(Color, Live, Vec<f32>)>| {
+209 -2
View File
@@ -676,6 +676,210 @@ 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<egui::Event>) -> Option<Rect> {
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<f32>) -> 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<egui::Event>| 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::<Vec<_>>();
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<egui::Id> = (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);
}
/// 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]
@@ -684,7 +888,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 +898,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();
+5
View File
@@ -699,6 +699,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,
/// Swatches panel: Show Recent Colors in its panel menu, which draws the panel's Recent Colors
/// row (#6617; read as [`Self::color_show_recent_colors`] is). Separate from the Color panel's.
#[serde(default = "yes", deserialize_with = "crate::panels::color::deserialize_show_recent_colors")]
@@ -1152,6 +1156,7 @@ impl Default for UiState {
color_mode: None,
color_hide_options: false,
color_show_recent_colors: true,
color_rgb_percent: false,
swatches_show_recent_colors: true,
svg_options: Value::Null,
place_link: true,