Merge pull request #4750 from dotCooCoo/feat/paint-mode-angle-bracket-keys

Shortcuts: Shift+Comma and Shift+Period also apply the last color and gradient
This commit is contained in:
BFlat
2026-10-31 04:01:44 +07:00
committed by GitHub
32 changed files with 272 additions and 73 deletions
+2 -2
View File
@@ -495,7 +495,7 @@ font-technology Variable Променливи
Collect in New Layer Събиране в нов слой
Collect Links in a Separate Folder Събиране на връзките в отделна папка
Color Цвят
Color (,) Цвят (,)
Color (, or <) Цвят (, или <)
Color Adjustments Цветови корекции
Color bars Цветови скали
Color blindness – Deuteranopia-type Цветна слепота – тип девтеранопия
@@ -1236,7 +1236,7 @@ font-technology Variable Променливи
GPL palettes keep solid colours only, as RGB. Палитрите GPL запазват само плътни цветове като RGB.
GPU Performance Производителност на графичния процесор
Gradient Градиент
Gradient (.) Градиент (.)
Gradient (. or >) Градиент (. или >)
Gradient across stroke Градиент напречно на щриха
Gradient along stroke Градиент по дължината на щриха
Gradient and Mesh Resolution: Разделителна способност на градиент и мрежа:
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2 -2
View File
@@ -708,7 +708,7 @@
Collect in New Layer Recull en una capa nova
Collect Links in a Separate Folder Recull els enllaços en una carpeta a part
Color Color
Color (,) Color (,)
Color (, or <) Color (, o <)
Color Adjustments Ajustos de color
Color bars Barres de color
Color blindness – Deuteranopia-type Daltonisme – tipus deuteranòpsia
@@ -1691,7 +1691,7 @@
GPU Performance Rendiment de la GPU
Grab bag Calaix de sastre
Gradient Degradat
Gradient (.) Degradat (.)
Gradient (. or >) Degradat (. o >)
Gradient across stroke Degradat transversal al traç
Gradient along stroke Degradat al llarg del traç
Gradient and Mesh Resolution: Resolució de degradat i malla:
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2 -2
View File
@@ -1209,12 +1209,12 @@ lighting Light Světlo
to set spacing and orientation nastaví rozestupy a orientaci
Apply Použít
Change Screen Mode (F) Změnit režim obrazovky (F)
Color (,) Barva (,)
Color (, or <) Barva (, nebo <)
Draw Behind Kreslit za
Draw Inside Kreslit dovnitř
Draw Normal Kreslit normálně
Expand: Rozložit:
Gradient (.) Přechod (.)
Gradient (. or >) Přechod (. nebo >)
H: V:
Modify Úpravy
Navigate Pohled
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2 -2
View File
@@ -1127,12 +1127,12 @@ lighting Light Lys
to set spacing and orientation for at angive afstand og orientering
Apply Anvend
Change Screen Mode (F) Skift skærmtilstand (F)
Color (,) Farve (,)
Color (, or <) Farve (, eller <)
Draw Behind Tegn bagved
Draw Inside Tegn indeni
Draw Normal Tegn normalt
Expand: Udvid:
Gradient (.) Gradient (.)
Gradient (. or >) Gradient (. eller >)
H: H:
Modify Ændr
Navigate Navigér
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2 -2
View File
@@ -487,7 +487,7 @@ font-technology Variable Variabel
Collect in New Layer In neuer Ebene sammeln
Collect Links in a Separate Folder Verknüpfungen in separatem Ordner sammeln
Color Farbe
Color (,) Farbe (,)
Color (, or <) Farbe (, oder <)
Color Adjustments Farbkorrekturen
Color bars Farbkontrollstreifen
Color blindness – Deuteranopia-type Farbenblindheit – Deuteranopie
@@ -1260,7 +1260,7 @@ file-filter Templates Vorlagen
GPL palettes keep solid colours only, as RGB. GPL-Paletten enthalten nur einfarbige Farben, als RGB.
GPU Performance GPU-Leistung
Gradient Verlauf
Gradient (.) Verlauf (.)
Gradient (. or >) Verlauf (. oder >)
Gradient across stroke Verlauf quer zur Kontur
Gradient along stroke Verlauf entlang der Kontur
Gradient and Mesh Resolution: Auflösung für Verläufe und Gitter:
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2 -2
View File
@@ -470,7 +470,7 @@ font-technology Variable Μεταβλητές
Collect in New Layer Συγκέντρωση σε νέο επίπεδο
Collect Links in a Separate Folder Συγκέντρωση συνδεδεμένων αρχείων σε ξεχωριστό φάκελο
Color Χρώμα
Color (,) Χρώμα (,)
Color (, or <) Χρώμα (, ή <)
Color Adjustments Ρυθμίσεις χρώματος
Color bars Χρωματικές μπάρες
Color blindness – Deuteranopia-type Αχρωματοψία – τύπου δευτερανοπίας
@@ -1200,7 +1200,7 @@ font-technology Variable Μεταβλητές
GPL palettes keep solid colours only, as RGB. Οι παλέτες GPL διατηρούν μόνο συμπαγή χρώματα σε RGB.
GPU Performance Απόδοση GPU
Gradient Ντεγκραντέ
Gradient (.) Ντεγκραντέ (.)
Gradient (. or >) Ντεγκραντέ (. ή >)
Gradient across stroke Ντεγκραντέ εγκάρσια στο περίγραμμα
Gradient along stroke Ντεγκραντέ κατά μήκος του περιγράμματος
Gradient and Mesh Resolution: Ανάλυση ντεγκραντέ και πλέγματος:
Can't render this file because it contains an unexpected character in line 164 and column 5.
+2 -2
View File
@@ -509,7 +509,7 @@ font-technology Variable Variables
Collect in New Layer Reunir en capa nueva
Collect Links in a Separate Folder Reunir vínculos en una carpeta aparte
Color Color
Color (,) Color (,)
Color (, or <) Color (, o <)
Color Adjustments Ajustes de color
Color bars Barras de color
Color blindness – Deuteranopia-type Daltonismo – tipo deuteranopía
@@ -1273,7 +1273,7 @@ font-technology Variable Variables
GPL palettes keep solid colours only, as RGB. Las paletas GPL solo conservan colores sólidos, en RGB.
GPU Performance Rendimiento de GPU
Gradient Degradado
Gradient (.) Degradado (.)
Gradient (. or >) Degradado (. o >)
Gradient across stroke Degradado a lo ancho del trazo
Gradient along stroke Degradado a lo largo del trazo
Gradient and Mesh Resolution: Resolución de degradados y mallas:
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2 -2
View File
@@ -1187,12 +1187,12 @@ lighting Light Valo
to set spacing and orientation asettaaksesi välit ja suunnan
Apply Käytä
Change Screen Mode (F) Vaihda näyttötilaa (F)
Color (,) Väri (,)
Color (, or <) Väri (, tai <)
Draw Behind Piirrä taakse
Draw Inside Piirrä sisään
Draw Normal Piirrä normaalisti
Expand: Laajenna:
Gradient (.) Liukuväri (.)
Gradient (. or >) Liukuväri (. tai >)
H: K:
Modify Muokkaa
Navigate Siirry
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2 -2
View File
@@ -510,7 +510,7 @@ font-technology Variable Variables
Collect in New Layer Rassembler dans un nouveau calque
Collect Links in a Separate Folder Rassembler les liens dans un dossier distinct
Color Couleur
Color (,) Couleur (,)
Color (, or <) Couleur (, ou <)
Color Adjustments Réglages de couleur
Color bars Barres de couleurs
Color blindness – Deuteranopia-type Daltonisme – type deutéranopie
@@ -1272,7 +1272,7 @@ font-technology Variable Variables
GPL palettes keep solid colours only, as RGB. Les palettes GPL ne conservent que les couleurs unies, en RVB.
GPU Performance Performances GPU
Gradient Dégradé
Gradient (.) Dégradé (.)
Gradient (. or >) Dégradé (. ou >)
Gradient across stroke Dégradé sur la largeur du contour
Gradient along stroke Dégradé le long du contour
Gradient and Mesh Resolution: Résolution des dégradés et des filets :
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2 -2
View File
@@ -679,7 +679,7 @@ font-technology Variable Változó
Collect in New Layer Összegyűjtés új rétegbe
Collect Links in a Separate Folder Csatolások gyűjtése külön mappába
Color Szín
Color (,) Szín (,)
Color (, or <) Szín (, vagy <)
Color Adjustments Színkorrekciók
Color bars Színsávok
Color blindness – Deuteranopia-type Színvakság – deuteranópia
@@ -1591,7 +1591,7 @@ file-filter Templates Sablonok
GPU Performance GPU-teljesítmény
Grab bag Vegyes
Gradient Színátmenet
Gradient (.) Színátmenet (.)
Gradient (. or >) Színátmenet (. vagy >)
Gradient across stroke Színátmenet a körvonalon keresztben
Gradient along stroke Színátmenet a körvonal mentén
Gradient and Mesh Resolution: Színátmenet és háló felbontása:
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2 -2
View File
@@ -1127,12 +1127,12 @@ lighting Light Cahaya
to set spacing and orientation untuk mengatur jarak dan orientasi
Apply Terapkan
Change Screen Mode (F) Ganti mode layar (F)
Color (,) Warna (,)
Color (, or <) Warna (, atau <)
Draw Behind Gambar di belakang
Draw Inside Gambar di dalam
Draw Normal Gambar normal
Expand: Perluas:
Gradient (.) Gradien (.)
Gradient (. or >) Gradien (. atau >)
H: H:
Modify Ubah
Navigate Navigasi
1 # Indonesian (id) catalog. Same format as zh-hant.tsv: context <TAB> source <TAB> translation
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
+2 -2
View File
@@ -509,7 +509,7 @@ font-technology Variable Variabili
Collect in New Layer Raccogli in un nuovo livello
Collect Links in a Separate Folder Raccogli i collegamenti in una cartella separata
Color Colore
Color (,) Colore (,)
Color (, or <) Colore (, o <)
Color Adjustments Regolazioni colore
Color bars Barre colore
Color blindness – Deuteranopia-type Daltonismo – tipo deuteranopia
@@ -1283,7 +1283,7 @@ file-filter Templates Modelli
GPL palettes keep solid colours only, as RGB. Le palette GPL conservano solo i colori pieni, in RGB.
GPU Performance Prestazioni GPU
Gradient Sfumatura
Gradient (.) Sfumatura (.)
Gradient (. or >) Sfumatura (. o >)
Gradient across stroke Sfumatura trasversale alla traccia
Gradient along stroke Sfumatura lungo la traccia
Gradient and Mesh Resolution: Risoluzione sfumature e trame:
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2 -2
View File
@@ -3368,7 +3368,7 @@ gradient-kind Radial 円形
Client-side (.html) クライアントサイド (.html)
Cmd Cmd
Collect Links in a Separate Folder リンクを別フォルダーに収集
Color (,) カラー (,)
Color (, or <) カラー (, または <)
Color Group カラーグループ
Color Library カラーライブラリ
Color Management カラーマネジメント
@@ -3463,7 +3463,7 @@ file-filter Templates テンプレート
General › Placement › X 一般 › 位置 › X
General › Thumbnails 一般 › サムネール
Global Process Color, CMYK グローバルプロセスカラー、CMYK
Gradient (.) グラデーション (.)
Gradient (. or >) グラデーション (. または >)
Graph Data グラフデータ
Graph Type グラフの種類
Graph Design グラフのデザイン
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2 -2
View File
@@ -1061,12 +1061,12 @@ lighting Light სინათლე
to set spacing and orientation — ინტერვალისა და ორიენტაციის დასაყენებლად
Apply გამოყენება
Change Screen Mode (F) ეკრანის რეჟიმის შეცვლა (F)
Color (,) ფერი (,)
Color (, or <) ფერი (, ან <)
Draw Behind უკან ხატვა
Draw Inside შიგნით ხატვა
Draw Normal ჩვეულებრივი ხატვა
Expand: გაშლა:
Gradient (.) გრადიენტი (.)
Gradient (. or >) გრადიენტი (. ან >)
H: სიმ.:
Modify შეცვლა
Navigate ნავიგაცია
Can't render this file because it is too large.
+2 -2
View File
@@ -3354,7 +3354,7 @@ gradient-kind Radial 방사형
Client-side (.html) 클라이언트 측 (.html)
Cmd Cmd
Collect Links in a Separate Folder 링크를 별도 폴더에 모으기
Color (,) 색상 (,)
Color (, or <) 색상 (, 또는 <)
Color Group 색상 그룹
Color Library 색상 라이브러리
Color Management 색상 관리
@@ -3449,7 +3449,7 @@ file-filter Templates 템플릿
General › Placement › X 일반 › 배치 › X
General › Thumbnails 일반 › 축소판
Global Process Color, CMYK 글로벌 프로세스 색상, CMYK
Gradient (.) 그레이디언트 (.)
Gradient (. or >) 그레이디언트 (. 또는 >)
Graph Data 그래프 데이터
Graph Type 그래프 유형
Graph Design 그래프 디자인
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2 -2
View File
@@ -1141,12 +1141,12 @@ lighting Light Licht
to set spacing and orientation om de tussenruimte en de oriëntatie in te stellen
Apply Toepassen
Change Screen Mode (F) Schermmodus wisselen (F)
Color (,) Kleur (,)
Color (, or <) Kleur (, of <)
Draw Behind Achter tekenen
Draw Inside Binnen tekenen
Draw Normal Normaal tekenen
Expand: Uitvouwen:
Gradient (.) Verloop (.)
Gradient (. or >) Verloop (. of >)
H: H:
Modify Wijzigen
Navigate Navigeren
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2 -2
View File
@@ -1140,12 +1140,12 @@ lighting Light Światło
to set spacing and orientation aby ustawić odstęp i orientację
Apply Zastosuj
Change Screen Mode (F) Zmień tryb ekranu (F)
Color (,) Kolor (,)
Color (, or <) Kolor (, lub <)
Draw Behind Rysuj pod spodem
Draw Inside Rysuj wewnątrz
Draw Normal Rysuj normalnie
Expand: Rozszerz:
Gradient (.) Gradient (.)
Gradient (. or >) Gradient (. lub >)
H: W:
Modify Modyfikuj
Navigate Nawigacja
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2 -2
View File
@@ -4329,7 +4329,7 @@ axis Both Ambos
Client-side (.html) Lado do Cliente (.html)
Cmd Cmd
Collect Links in a Separate Folder Reunir Links em uma Pasta Separada
Color (,) Cor (,)
Color (, or <) Cor (, ou <)
Color bars Barras de cores
Color Group Grupo de Cores
Color Library Biblioteca de Cores
@@ -4402,7 +4402,7 @@ axis Both Ambos
General › Placement › X Geral › Posicionamento › X
General › Thumbnails Geral › Miniaturas
Global Process Color, CMYK Cor de Processo Global, CMYK
Gradient (.) Gradiente (.)
Gradient (. or >) Gradiente (. ou >)
Graph Data Dados do Gráfico
Graph Type Tipo de Gráfico
New Design Novo Design
Can't render this file because it contains an unexpected character in line 6 and column 47.
+2 -2
View File
@@ -507,7 +507,7 @@ font-technology Variable Вариативные
Collect in New Layer Собрать в новый слой
Collect Links in a Separate Folder Собрать связи в отдельную папку
Color Цвет
Color (,) Цвет (,)
Color (, or <) Цвет (, или <)
Color Adjustments Настройки цвета
Color bars Цветные полосы
Color blindness – Deuteranopia-type Дальтонизм — дейтеранопия
@@ -1281,7 +1281,7 @@ file-filter Templates Шаблоны
GPL palettes keep solid colours only, as RGB. Палитры GPL сохраняют только сплошные цвета в RGB.
GPU Performance Производительность GPU
Gradient Градиент
Gradient (.) Градиент (.)
Gradient (. or >) Градиент (. или >)
Gradient across stroke Градиент поперёк обводки
Gradient along stroke Градиент вдоль обводки
Gradient and Mesh Resolution: Разрешение градиентов и сеток:
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2 -2
View File
@@ -497,7 +497,7 @@ font-technology Variable Variabla
Collect in New Layer Samla i nytt lager
Collect Links in a Separate Folder Samla länkar i en separat mapp
Color Färg
Color (,) Färg (,)
Color (, or <) Färg (, eller <)
Color Adjustments Färgjusteringar
Color bars Färgstaplar
Color blindness – Deuteranopia-type Färgblindhet – deuteranopi
@@ -1238,7 +1238,7 @@ font-technology Variable Variabla
GPL palettes keep solid colours only, as RGB. GPL-paletter sparar bara enfärgade färger, som RGB.
GPU Performance GPU-prestanda
Gradient Gradient
Gradient (.) Gradient (.)
Gradient (. or >) Gradient (. eller >)
Gradient across stroke Gradient tvärs över konturen
Gradient along stroke Gradient längs konturen
Gradient and Mesh Resolution: Upplösning för gradienter och nät:
Can't render this file because it contains an unexpected character in line 5 and column 25.
+2 -2
View File
@@ -491,7 +491,7 @@ font-technology Variable Değişken
Collect in New Layer Yeni Katmanda Topla
Collect Links in a Separate Folder Bağlantıları Ayrı Bir Klasörde Toplayın
Color Renk
Color (,) Renk (,)
Color (, or <) Renk (, veya <)
Color Adjustments Renk Ayarlamaları
Color bars Renk çubukları
Color blindness – Deuteranopia-type Renk körlüğü – Döteranopi tipi
@@ -1215,7 +1215,7 @@ font-technology Variable Değişken
GPL palettes keep solid colours only, as RGB. GPL paletleri yalnızca düz renkleri RGB olarak tutar.
GPU Performance GPU Performansı
Gradient Degrade
Gradient (.) Gradyan (.)
Gradient (. or >) Gradyan (. veya >)
Gradient across stroke Kontur boyunca gradyan
Gradient along stroke Kontur boyunca gradyan
Gradient and Mesh Resolution: Gradyan ve Mesh Çözünürlüğü:
Can't render this file because it contains an unexpected character in line 5 and column 25.
+2 -2
View File
@@ -503,7 +503,7 @@ font-technology Variable Варіативні
Collect in New Layer Зібрати у новий шар
Collect Links in a Separate Folder Зібрати зв’язки в окрему папку
Color Колір
Color (,) Колір (,)
Color (, or <) Колір (, або <)
Color Adjustments Налаштування кольору
Color bars Кольорові смуги
Color blindness – Deuteranopia-type Дальтонізм - дейтеранопія
@@ -1277,7 +1277,7 @@ file-filter Templates Шаблони
GPL palettes keep solid colours only, as RGB. Палітри GPL зберігають лише суцільні кольори RGB.
GPU Performance Продуктивність GPU
Gradient Градієнт
Gradient (.) Градієнт (.)
Gradient (. or >) Градієнт (. або >)
Gradient across stroke Градієнт упоперек обведення
Gradient along stroke Градієнт вздовж обведення
Gradient and Mesh Resolution: Роздільна здатність градієнтів і сіток:
Can't render this file because it contains an unexpected character in line 178 and column 5.
+2 -2
View File
@@ -591,7 +591,7 @@ font-technology Variable Biến thiên
Collect in New Layer Thu thập vào lớp mới
Collect Links in a Separate Folder Thu thập liên kết vào thư mục riêng
Color Màu
Color (,) Màu (,)
Color (, or <) Màu (, hoặc <)
Color Adjustments Điều chỉnh màu sắc
Color bars Thanh màu
Color blindness – Deuteranopia-type Mù màu – loại Deuteranopia
@@ -1491,7 +1491,7 @@ file-filter Templates Mẫu
GPU Performance Hiệu suất GPU
Grab bag Tổng hợp
Gradient Chuyển sắc
Gradient (.) Chuyển sắc (.)
Gradient (. or >) Chuyển sắc (. hoặc >)
Gradient across stroke Chuyển sắc ngang qua nét
Gradient along stroke Chuyển sắc dọc theo nét
Gradient and Mesh Resolution: Độ phân giải chuyển sắc và lưới:
Can't render this file because it contains an unexpected character in line 200 and column 5.
+2 -2
View File
@@ -455,7 +455,7 @@
Collect in New Layer 收集到新图层
Collect Links in a Separate Folder 将链接收集到单独的文件夹
Color 颜色
Color (,) 颜色 (,)
Color (, or <) 颜色 (, 或 <)
Color Adjustments 色彩调整
Color bars 色彩条
Color blindness – Deuteranopia-type 色盲 – 绿色盲型
@@ -1221,7 +1221,7 @@ file-filter Templates 模板
GPL palettes keep solid colours only, as RGB. GPL 调板仅保留为 RGB 的纯色。
GPU Performance GPU 性能
Gradient 渐变
Gradient (.) 渐变 (.)
Gradient (. or >) 渐变 (. 或 >)
Gradient across stroke 横跨描边的渐变
Gradient along stroke 沿描边的渐变
Gradient and Mesh Resolution: 渐变和网格分辨率:
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2 -2
View File
@@ -471,7 +471,7 @@ font-technology Variable 可變
Collect in New Layer 收集至新圖層
Collect Links in a Separate Folder 將連結收集到獨立的資料夾
Color 顏色
Color (,) 顏色 (,)
Color (, or <) 顏色 (, 或 <)
Color Adjustments 色彩調整
Color bars 色彩條
Color blindness – Deuteranopia-type 色盲 – 綠色盲型
@@ -1247,7 +1247,7 @@ file-filter Templates 範本
GPL palettes keep solid colours only, as RGB. GPL 調色盤僅保留純色,並以 RGB 儲存。
GPU Performance GPU 效能
Gradient 漸層
Gradient (.) 漸層 (.)
Gradient (. or >) 漸層 (. 或 >)
Gradient across stroke 橫跨筆畫的漸層
Gradient along stroke 沿筆畫的漸層
Gradient and Mesh Resolution: 漸層與網格解析度:
Can't render this file because it contains an unexpected character in line 6 and column 47.
+42 -4
View File
@@ -146,14 +146,28 @@ fn catalogue() -> Vec<Bindable> {
if TOGGLES.contains(&e.key.as_str()) {
b = b.toggle();
}
match shortcut_editor::default_of(&e.key).and_then(vc_binding) {
Some(d) => b.with_default(d),
None => b,
if let Some(d) = shortcut_editor::default_of(&e.key).and_then(vc_binding) {
b = b.with_default(d);
}
for alias in default_aliases(&e.key) {
if let Some(d) = vc_binding(alias) {
b = b.with_default(d);
}
}
b
})
.collect()
}
/// Additional defaults for commands whose primary shortcut remains in their command spec.
fn default_aliases(key: &str) -> &'static [&'static str] {
match key {
"paint.lastColor" => &["Shift+,"],
"paint.lastGradient" => &["Shift+."],
_ => &[],
}
}
/// On the web, the browser's operating system once egui reports it ([`note_os`]).
static WEB_PLATFORM: RwLock<Option<Platform>> = RwLock::new(None);
@@ -398,11 +412,35 @@ mod tests {
let unbound: Vec<(&str, &str)> = shortcut_editor::entries()
.iter()
.filter_map(|e| shortcut_editor::default_of(&e.key).map(|d| (e.key.as_str(), d)))
.filter(|(key, d)| vc_binding(d).map(|b| vec![b]) != Some(template().default_bindings(&command_of(key))))
.filter(|(key, d)| {
let mut expected: Vec<Binding> = vc_binding(d).into_iter().collect();
expected.extend(default_aliases(key).iter().filter_map(|alias| vc_binding(alias)));
expected != template().default_bindings(&command_of(key))
})
.collect();
assert_eq!(unbound, vec![], "every default text becomes the entry's binding");
}
#[test]
fn paint_defaults_have_two_bindings_and_reset_restores_both() {
for (key, plain, shifted) in [("paint.lastColor", ",", "Shift+,"), ("paint.lastGradient", ".", "Shift+.")] {
let command = command_of(key);
let defaults = template().default_bindings(&command);
assert_eq!(defaults, vec![vc_binding(plain).unwrap(), vc_binding(shifted).unwrap()], "{key}");
assert_eq!(
shortcut_editor::entry(key).map(|e| e.label.as_str()),
Some(if key == "paint.lastColor" { "Apply Last Color" } else { "Apply Last Gradient" })
);
assert_eq!(effective_with(template(), key).as_deref(), Some(plain));
let mut overrides = Overrides::new();
set_override(&mut overrides, key, "Shift+;");
assert_eq!(keymap_with(&overrides).bindings(&command), vec![vc_binding("Shift+;").unwrap()]);
shortcut_editor::reset_one(&mut overrides, key);
assert_eq!(keymap_with(&overrides).bindings(&command), defaults);
}
assert!(template().conflicts().is_empty());
}
/// The chords the operating system takes are refused, and no default uses one.
#[test]
fn system_shortcuts_are_refused() {
+26 -7
View File
@@ -506,6 +506,17 @@ pub fn reset_one(overrides: &mut Overrides, key: &str) {
overrides.remove(&crate::keymap::stored_key(key));
}
/// Text shown for all active chords in a dialog row.
fn dialog_shortcuts_with(km: &Keymap, key: &str) -> Option<String> {
let bindings = km.bindings(&command_of(key));
if bindings.len() > 1 {
let primary = effective_with(km, key).map(|s| menus::pretty_shortcut(&s))?;
let additional = bindings.iter().skip(1).map(|b| menus::pretty_shortcut(&crate::keymap::binding_text(b)));
return Some(std::iter::once(primary).chain(additional).collect::<Vec<_>>().join(" · "));
}
effective_with(km, key).map(|s| menus::pretty_shortcut(&s))
}
/// Overrides of a preset (both presets are the registry defaults for now).
pub fn preset(name: &str) -> Option<Overrides> {
PRESETS.contains(&set_name(name)).then(Overrides::new)
@@ -695,7 +706,7 @@ struct DialogView {
impl DialogView {
fn new(overrides: Overrides) -> DialogView {
let keymap = keymap_with(&overrides);
let texts = entries().iter().map(|e| effective_with(&keymap, &e.key)).collect();
let texts = entries().iter().map(|e| dialog_shortcuts_with(&keymap, &e.key)).collect();
let conflicts = conflicts_in(&keymap).len();
DialogView { source: stored::to_value(keymap.overrides()), keymap, texts, conflicts }
}
@@ -812,7 +823,7 @@ pub fn show(app: &mut VectorcraftApp, ctx: &egui::Context) {
for (i, e) in entries().iter().enumerate().filter(|(_, e)| e.is_tool == tools && listed(km.overrides(), e)) {
let sc = match &km {
Cow::Borrowed(_) => view.texts.get(i).cloned().flatten(),
Cow::Owned(km) => effective_with(km, &e.key),
Cow::Owned(km) => dialog_shortcuts_with(km, &e.key),
};
let matches = e.label.to_lowercase().contains(&q)
|| tl!(&e.label).to_lowercase().contains(&q)
@@ -837,11 +848,7 @@ pub fn show(app: &mut VectorcraftApp, ctx: &egui::Context) {
if r.clicked() {
d.fields.insert("__selected".into(), json!(e.key));
}
let label = if rec == e.key {
tl!("Press keys…").to_string()
} else {
sc.as_deref().map(menus::pretty_shortcut).unwrap_or_else(|| "—".into())
};
let label = if rec == e.key { tl!("Press keys…").to_string() } else { sc.unwrap_or_else(|| "—".into()) };
let changed = crate::keymap::has_override(km.overrides(), &e.key);
let txt = egui::RichText::new(label).color(if changed { t.accent } else { t.text });
let b = ui
@@ -1224,6 +1231,18 @@ mod tests {
assert!(preset("Nope").is_none());
}
#[test]
fn paint_dialog_rows_show_both_default_keys() {
let view = DialogView::new(Overrides::new());
for (key, plain, shifted) in [("paint.lastColor", ",", "Shift+Comma"), ("paint.lastGradient", ".", "Shift+Period")] {
let at = entries().iter().position(|e| e.key == key).unwrap();
let shown = view.texts[at].as_deref().unwrap();
let shifted = menus::pretty_shortcut(shifted);
assert!(shown.contains(plain) && shown.contains(&shifted), "{key}: {shown}");
}
assert_eq!(view.conflicts, 0);
}
#[test]
fn entries_cover_tools_and_menu_commands() {
assert!(entries().iter().any(|e| e.key == "tool:selection"));
+26 -3
View File
@@ -323,9 +323,13 @@ pub fn handle(app: &mut VectorcraftApp, ctx: &egui::Context) {
if claimed_tool_keys(app, ctx, view) {
return;
}
// Command, panel and tool shortcuts. Keys that type a character without Cmd/Alt/Ctrl are
// handled below as text (tool shortcuts, X, D, /, `,` and `.`).
let hits = dispatch(app, ctx, |hit| !(hit.trigger.chord.is_plain() && types_text(hit.trigger.chord.key)));
// Command, panel and tool shortcuts. A bound shifted punctuation chord uses its key press;
// other text keys use the typed character below.
let hits = dispatch(app, ctx, |hit| {
let chord = hit.trigger.chord;
let shifted_punctuation = chord.mods.shift && chord.key.name().chars().all(|c| !c.is_alphanumeric());
!(chord.is_plain() && types_text(chord.key) && !shifted_punctuation)
});
let ran = !hits.is_empty();
for c in hits {
run_hit(app, ctx, &c);
@@ -715,6 +719,25 @@ fn dispatch(app: &mut VectorcraftApp, ctx: &egui::Context, accept: impl FnMut(&H
let mirror = crate::shortcut_editor::current();
let dispatcher = &mut app.dispatcher;
ctx.input_mut(|i| {
// Shift+Comma and Shift+Period follow their key positions when the logical character
// differs. An explicitly bound logical shifted chord takes precedence.
for e in &mut i.events {
if let egui::Event::Key { key, physical_key: Some(physical), modifiers, pressed: true, .. } = e
&& modifiers.shift
&& matches!(*physical, Key::Comma | Key::Period)
&& *key != *physical
&& let (Some(logical), Some(position)) = (craft_keymap::Key::from_egui(*key), craft_keymap::Key::from_egui(*physical))
{
let mods = craft_keymap::Mods::from_egui(modifiers, crate::keymap::platform());
let logical_chord = craft_keymap::Chord::new(mods, logical);
let position_chord = craft_keymap::Chord::new(mods, position);
if crate::keymap::lookup(&mirror.keymap, &logical_chord).is_some() {
*physical = *key;
} else if crate::keymap::lookup(&mirror.keymap, &position_chord).is_some() {
*key = *physical;
}
}
}
// Shift+`=` types `+`: as the Plus key it also reaches a `=` chord held with Shift
// (Cmd+Shift+= zooms in as Cmd+= does).
for e in &mut i.events {
+125 -7
View File
@@ -1120,18 +1120,136 @@ fn brackets_size_the_blob_brush_and_the_eraser() {
assert_eq!(app.session.paint.stroke_width, 3.0);
}
#[test]
fn comma_and_period_reapply_the_last_colour_and_gradient() {
fn remembered_paint_app() -> VectorcraftApp {
let mut app = VectorcraftApp::new(vectorcraft_engine::Session::new(), Default::default());
app.session.execute("file.new", &json!({"width": 200, "height": 200})).unwrap();
app.session.execute("shape.rectangle", &json!({"x": 0, "y": 0, "width": 10, "height": 10})).unwrap();
app.session.execute("paint.setFill", &json!({"gradient": {"kind": "linear"}})).unwrap();
app.session.execute("paint.setFill", &json!({"color": "#336699"})).unwrap();
let fill = |app: &VectorcraftApp| crate::panels::current_paints(app).0;
frame(&mut app, vec![egui::Event::Text(".".into())]);
assert!(matches!(fill(&app), vectorcraft_color::Paint::Gradient(_)), "`.` applies the last gradient");
frame(&mut app, vec![egui::Event::Text(",".into())]);
assert_eq!(fill(&app).color().unwrap().to_hex(), "#336699", "`,` applies the last colour");
app
}
#[test]
fn plain_and_shifted_paint_keys_run_once_on_us_and_german_like_input() {
let mut app = remembered_paint_app();
let paint = |app: &VectorcraftApp| crate::panels::current_paints(app).0;
let press = |key, physical_key, modifiers, typed: &str| {
vec![
egui::Event::ModifiersChanged(modifiers),
egui::Event::Key { key, physical_key: Some(physical_key), pressed: true, repeat: false, modifiers },
egui::Event::Text(typed.into()),
]
};
for (key, physical_key, modifiers, typed) in [
(Key::Period, Key::Period, Modifiers::NONE, "."),
(Key::Period, Key::Period, Modifiers::SHIFT, ">"),
(Key::Colon, Key::Period, Modifiers::SHIFT, ":"),
] {
let before = app.session.active().unwrap().history.undo.len();
frame(&mut app, press(key, physical_key, modifiers, typed));
assert!(matches!(paint(&app), vectorcraft_color::Paint::Gradient(_)), "{typed} applies gradient");
assert_eq!(app.session.active().unwrap().history.undo.len(), before + 1, "{typed} runs once");
app.session.execute("paint.setFill", &json!({"color": "#336699"})).unwrap();
}
for (key, physical_key, modifiers, typed) in [
(Key::Comma, Key::Comma, Modifiers::NONE, ","),
(Key::Comma, Key::Comma, Modifiers::SHIFT, "<"),
(Key::Semicolon, Key::Comma, Modifiers::SHIFT, ";"),
] {
app.session.execute("paint.setFill", &json!({"gradient": {"kind": "linear"}})).unwrap();
let before = app.session.active().unwrap().history.undo.len();
frame(&mut app, press(key, physical_key, modifiers, typed));
assert_eq!(paint(&app).color().map(|c| c.to_hex()), Some("#336699".into()), "{typed} applies color");
assert_eq!(app.session.active().unwrap().history.undo.len(), before + 1, "{typed} runs once");
}
let before = app.session.active().unwrap().history.undo.len();
for typed in [">", "<"] {
frame(&mut app, press(Key::Backslash, Key::Backslash, Modifiers::NONE, typed));
}
assert_eq!(app.session.active().unwrap().history.undo.len(), before, "another key typing a bracket runs no paint command");
let id = app.session.execute("text.create", &json!({"x": 20, "y": 50, "text": "Hi"})).unwrap()["id"].as_u64().unwrap();
app.select_tool("type");
app.session.apply_actions(vec![vectorcraft_tools::Action::Notify("text.editNew".into())]).unwrap();
app.session.set_tool_option("select", &json!({"start": 2, "end": 2}));
assert!(app.session.tool_wants_text());
app.session.execute("paint.setFill", &json!({"gradient": {"kind": "linear"}})).unwrap();
let paints_before = crate::panels::current_paints(&app);
assert!(matches!(paints_before.0, vectorcraft_color::Paint::Gradient(_)));
for (key, physical_key, mods, typed) in [
(Key::Comma, Key::Comma, Modifiers::NONE, ","),
(Key::Period, Key::Period, Modifiers::NONE, "."),
(Key::Comma, Key::Comma, Modifiers::SHIFT, "<"),
(Key::Period, Key::Period, Modifiers::SHIFT, ">"),
(Key::Colon, Key::Period, Modifiers::SHIFT, ":"),
(Key::Semicolon, Key::Comma, Modifiers::SHIFT, ";"),
] {
frame(&mut app, press(key, physical_key, mods, typed));
assert_eq!(crate::panels::current_paints(&app), paints_before, "typing {typed} keeps fill and stroke");
}
assert_eq!(text_and_layers(&mut app, id).0, "Hi,.<>:;");
}
#[test]
fn explicit_logical_shifted_binding_wins_over_position_alias() {
let mut app = remembered_paint_app();
crate::keymap::set_override(&mut app.ui.shortcut_overrides, "paint.lastGradient", "Shift+;");
let before = app.session.active().unwrap().history.undo.len();
frame(
&mut app,
vec![
egui::Event::ModifiersChanged(Modifiers::SHIFT),
egui::Event::Key { key: Key::Semicolon, physical_key: Some(Key::Comma), pressed: true, repeat: false, modifiers: Modifiers::SHIFT },
egui::Event::Text(";".into()),
],
);
assert!(matches!(crate::panels::current_paints(&app).0, vectorcraft_color::Paint::Gradient(_)));
assert_eq!(app.session.active().unwrap().history.undo.len(), before + 1);
}
#[test]
fn user_shifted_punctuation_binding_runs_once() {
let mut app = remembered_paint_app();
crate::keymap::set_override(&mut app.ui.shortcut_overrides, "paint.lastGradient", "Shift+;");
let undo_before = app.session.active().unwrap().history.undo.len();
let shifted = Modifiers::SHIFT;
frame(
&mut app,
vec![
egui::Event::ModifiersChanged(shifted),
egui::Event::Key { key: Key::Semicolon, physical_key: Some(Key::Semicolon), pressed: true, repeat: false, modifiers: shifted },
egui::Event::Text(":".into()),
],
);
assert!(matches!(crate::panels::current_paints(&app).0, vectorcraft_color::Paint::Gradient(_)));
assert_eq!(app.session.active().unwrap().history.undo.len(), undo_before + 1);
}
#[test]
fn saved_plain_paint_override_runs_its_new_command() {
let mut app = remembered_paint_app();
let saved = serde_json::json!({"paint.lastGradient": ",", "paint.lastColor": "."});
app.ui.shortcut_overrides = crate::keymap::stored::from_value(saved);
let press = |key, typed: &str| {
vec![
egui::Event::Key { key, physical_key: Some(key), pressed: true, repeat: false, modifiers: Modifiers::NONE },
egui::Event::Text(typed.into()),
]
};
frame(&mut app, press(Key::Comma, ","));
assert!(matches!(crate::panels::current_paints(&app).0, vectorcraft_color::Paint::Gradient(_)));
frame(&mut app, press(Key::Period, "."));
assert_eq!(crate::panels::current_paints(&app).0.color().map(|c| c.to_hex()), Some("#336699".into()));
crate::shortcut_editor::reset_one(&mut app.ui.shortcut_overrides, "paint.lastGradient");
frame(
&mut app,
vec![
egui::Event::ModifiersChanged(Modifiers::SHIFT),
egui::Event::Key { key: Key::Period, physical_key: Some(Key::Period), pressed: true, repeat: false, modifiers: Modifiers::SHIFT },
egui::Event::Text(">".into()),
],
);
assert!(matches!(crate::panels::current_paints(&app).0, vectorcraft_color::Paint::Gradient(_)), "reset restores the extra default");
}
#[test]
+1 -1
View File
@@ -486,7 +486,7 @@ fn bottom_controls(app: &mut VectorcraftApp, ui: &mut Ui, t: &Tokens, cols: usiz
ui.add_space((ui.available_width() - 3.0 * chip - 2.0 * gap) / 2.0);
ui.spacing_mut().item_spacing.x = gap;
let mut clicked = None;
for (tip, cmd) in [("Color (,)", "paint.lastColor"), ("Gradient (.)", "paint.lastGradient"), ("None (/)", "paint.none")] {
for (tip, cmd) in [("Color (, or <)", "paint.lastColor"), ("Gradient (. or >)", "paint.lastGradient"), ("None (/)", "paint.none")] {
let (r, resp) = ui.allocate_exact_size(vec2(chip, chip), Sense::click());
match cmd {
"paint.lastColor" => widgets::paint_chip(ui, r, &Paint::solid(app.session.last_solid)),
+1 -1
View File
@@ -109,7 +109,7 @@ fn screen_readers_name_the_tool_buttons_and_the_active_tool() {
let name = |id| tool_info(id).map(tip).unwrap_or_default();
assert_eq!(role_and_state(&tree, &name("pen")), (Role::Button, Some(Toggled::True)));
assert_eq!(role_and_state(&tree, &name("selection")), (Role::Button, Some(Toggled::False)));
for label in ["Toggle single/double column", "Drag to float the toolbar", "Color (,)", "Gradient (.)", "None (/)"] {
for label in ["Toggle single/double column", "Drag to float the toolbar", "Color (, or <)", "Gradient (. or >)", "None (/)"] {
assert_eq!(role_and_state(&tree, label), (Role::Button, None), "{label}");
}
}
+1
View File
@@ -911,6 +911,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
- **Missing library files:** a persistent swatch or graphic style library panel whose file is gone at startup reopens and stays in the preferences, showing the file's path with Locate Library… (`ui.libraryPanel.locate`), which reads the file from where it is now into the same panel.
- **Swatch library and Recolor Artwork parameters:** the `swatch.library.*` commands, `swatch.resetDefaults`, the graphic style library save, load and add, the `recolor.*` commands and `color.harmony`'s `limitTo` refuse parameters of the wrong form, such as `names: "Grays"`, `limitTo: 5` or `includeImages: "false"`. Errors name the parameter, except that a `limitTo` that is not a string, such as `5`, gets an unknown-library error. A `null` counts as left out except for `recolor.apply`'s `model`, which must be a string when given.
- **Colour:** a Color Picker (colour field, H/S/B/R/G/B channel slider, HSB/RGB/Lab/CMYK fields, hex, web-only snapping, out-of-gamut correction); the Color panel shows a selected colour in its own model and, with nothing selected, the new-art defaults in the mode picked last, which it keeps between sessions; Alt-click paints the other proxy; proxies look through groups and show "?" for mixed paint; Invert, Complement, Apply Last Color (`,`) and Apply Last Gradient (`.`) keep the colour model; every paint command feeds the recent colours. The Color Guide has 21 harmony rules, Steps and Variation options and Limit to Library. Adjust Color Balance (its Global mode shifts tints) and Saturate preview live; Edit Colors and Recolor also reach meshes, images and patterns. Recolor Artwork has colour reduction, five recolour methods, presets, Limit to Library, a colour-wheel Edit tab and Edit Color Group; a local Color Themes panel saves five-colour themes. Fill/Stroke chips open swatch and mixer popovers (the Properties panel has the Control bar's Fill and Stroke chips, Stroke link and weight spinner, with nothing selected too), panels have Illustrator's Window menu keys (Align Shift+F7, Transform Shift+F8, Pathfinder Cmd+Shift+F9, Layers F7, Character Cmd+T…; every panel can be given one in Edit › Keyboard Shortcuts), and Eyedropper Options pick up and apply chosen attributes (Alt and Shift+Alt, image pixels). An ICC CMYK profile converts straight into the RGB working space (not through sRGB, so a wider space such as Wide Gamut RGB keeps colours sRGB can't hold) with the colour settings' black-point compensation, on the canvas and in RGB exports alike (the built-in Generic CMYK compensates both ways, so RGB black separated to a rich black displays as black again). Fills and strokes can overprint (Attributes panel, Overprint Black options), and CMYK documents blend in inks on screen, in PDF and when flattening. Document Color Mode converts the art, symbols, pattern tiles and swatches through the colour settings: Gray stays Gray, RGB greys separate into four-colour greys and a rich black as Illustrator does, or go on the black plate only with `grays: "black"` (also for `document.open {colorMode}`).
Plain Comma and Period, and also Shift+Comma and Shift+Period, apply the last solid color and the last gradient to the active Fill or Stroke proxy in VectorCraft ([Default keyboard shortcuts](https://web.archive.org/web/20260726060015/https://helpx.adobe.com/illustrator/using/default-keyboard-shortcuts.html), "Select color fill mode" and "Select gradient fill mode").
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.
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.
- **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.