5 Commits

Author SHA1 Message Date
Marcos Paulo ebf018177d Release: VectorCraft v0.9.1 2026-10-12 00:52:43 -03:00
Marcos Paulo 4170143744 feat(ui): Catppuccin themes and a Window > Theme menu
Adds Latte, Frappé, Macchiato and Mocha to the UI brightness themes. The theme ids
in the command docs, error message and Preferences list now come from one place,
with a test that keeps the engine and shell lists equal.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-12 00:37:34 -03:00
Marcos Paulo bff0cd0dd5 build: use the masi/craft-libs fork
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-12 00:37:34 -03:00
Marcos Paulo f401ee6832 build(windows): set working directory to repo root in package.ps1 2026-10-11 22:38:52 -03:00
Marcos Paulo df08b1f153 build(windows): package standalone vectorcraft-cli zip and auto-detect rc.exe 2026-10-11 22:37:50 -03:00
19 changed files with 291 additions and 55 deletions
Generated
+28 -28
View File
@@ -809,7 +809,7 @@ dependencies = [
[[package]]
name = "craft-ui"
version = "0.1.0"
source = "git+https://github.com/storytold/craft-libs?rev=5c874d51eeb3de70e735c8ce87a59a7dedd47675#5c874d51eeb3de70e735c8ce87a59a7dedd47675"
source = "git+https://git.marcospaulo.dev.br/masi/craft-libs?rev=22544b6d619d4f16779b8bdfa3f0b642a86fc065#22544b6d619d4f16779b8bdfa3f0b642a86fc065"
dependencies = [
"egui",
"serde",
@@ -4482,7 +4482,7 @@ dependencies = [
[[package]]
name = "vectorcraft"
version = "0.9.0"
version = "0.9.1"
dependencies = [
"arboard",
"block2 0.6.2",
@@ -4515,7 +4515,7 @@ dependencies = [
[[package]]
name = "vectorcraft-affinity"
version = "0.9.0"
version = "0.9.1"
dependencies = [
"crc32fast",
"flate2",
@@ -4526,7 +4526,7 @@ dependencies = [
[[package]]
name = "vectorcraft-brush"
version = "0.9.0"
version = "0.9.1"
dependencies = [
"kurbo 0.13.1",
"serde",
@@ -4538,7 +4538,7 @@ dependencies = [
[[package]]
name = "vectorcraft-cad"
version = "0.9.0"
version = "0.9.1"
dependencies = [
"image",
"kurbo 0.13.1",
@@ -4554,7 +4554,7 @@ dependencies = [
[[package]]
name = "vectorcraft-cli"
version = "0.9.0"
version = "0.9.1"
dependencies = [
"log",
"objc2-core-foundation",
@@ -4575,7 +4575,7 @@ dependencies = [
[[package]]
name = "vectorcraft-color"
version = "0.9.0"
version = "0.9.1"
dependencies = [
"kurbo 0.13.1",
"moxcms",
@@ -4587,7 +4587,7 @@ dependencies = [
[[package]]
name = "vectorcraft-doc"
version = "0.9.0"
version = "0.9.1"
dependencies = [
"image",
"jpeg-encoder",
@@ -4604,7 +4604,7 @@ dependencies = [
[[package]]
name = "vectorcraft-effects"
version = "0.9.0"
version = "0.9.1"
dependencies = [
"kurbo 0.13.1",
"serde_json",
@@ -4619,7 +4619,7 @@ dependencies = [
[[package]]
name = "vectorcraft-engine"
version = "0.9.0"
version = "0.9.1"
dependencies = [
"crc32fast",
"flate2",
@@ -4654,7 +4654,7 @@ dependencies = [
[[package]]
name = "vectorcraft-eps"
version = "0.9.0"
version = "0.9.1"
dependencies = [
"flate2",
"image",
@@ -4674,7 +4674,7 @@ dependencies = [
[[package]]
name = "vectorcraft-format"
version = "0.9.0"
version = "0.9.1"
dependencies = [
"flate2",
"proptest",
@@ -4688,7 +4688,7 @@ dependencies = [
[[package]]
name = "vectorcraft-geom"
version = "0.9.0"
version = "0.9.1"
dependencies = [
"kurbo 0.13.1",
"proptest",
@@ -4700,7 +4700,7 @@ dependencies = [
[[package]]
name = "vectorcraft-mcp"
version = "0.9.0"
version = "0.9.1"
dependencies = [
"image",
"log",
@@ -4719,7 +4719,7 @@ dependencies = [
[[package]]
name = "vectorcraft-metafile"
version = "0.9.0"
version = "0.9.1"
dependencies = [
"image",
"kurbo 0.13.1",
@@ -4733,7 +4733,7 @@ dependencies = [
[[package]]
name = "vectorcraft-pathops"
version = "0.9.0"
version = "0.9.1"
dependencies = [
"kurbo 0.13.1",
"linesweeper",
@@ -4745,7 +4745,7 @@ dependencies = [
[[package]]
name = "vectorcraft-pdf"
version = "0.9.0"
version = "0.9.1"
dependencies = [
"aes",
"flate2",
@@ -4774,7 +4774,7 @@ dependencies = [
[[package]]
name = "vectorcraft-plugins"
version = "0.9.0"
version = "0.9.1"
dependencies = [
"serde",
"serde_json",
@@ -4788,7 +4788,7 @@ dependencies = [
[[package]]
name = "vectorcraft-render"
version = "0.9.0"
version = "0.9.1"
dependencies = [
"flate2",
"gif",
@@ -4813,7 +4813,7 @@ dependencies = [
[[package]]
name = "vectorcraft-svg"
version = "0.9.0"
version = "0.9.1"
dependencies = [
"flate2",
"image",
@@ -4837,7 +4837,7 @@ dependencies = [
[[package]]
name = "vectorcraft-testkit"
version = "0.9.0"
version = "0.9.1"
dependencies = [
"image",
"kurbo 0.13.1",
@@ -4854,7 +4854,7 @@ dependencies = [
[[package]]
name = "vectorcraft-text"
version = "0.9.0"
version = "0.9.1"
dependencies = [
"harfrust 0.13.3",
"icu_segmenter",
@@ -4874,14 +4874,14 @@ dependencies = [
[[package]]
name = "vectorcraft-three-d"
version = "0.9.0"
version = "0.9.1"
dependencies = [
"vectorcraft-geom",
]
[[package]]
name = "vectorcraft-tools"
version = "0.9.0"
version = "0.9.1"
dependencies = [
"image",
"kurbo 0.13.1",
@@ -4896,7 +4896,7 @@ dependencies = [
[[package]]
name = "vectorcraft-trace"
version = "0.9.0"
version = "0.9.1"
dependencies = [
"image",
"kurbo 0.13.1",
@@ -4909,7 +4909,7 @@ dependencies = [
[[package]]
name = "vectorcraft-ui-egui"
version = "0.9.0"
version = "0.9.1"
dependencies = [
"craft-ui",
"egui",
@@ -4942,7 +4942,7 @@ dependencies = [
[[package]]
name = "vectorcraft-web"
version = "0.9.0"
version = "0.9.1"
dependencies = [
"eframe",
"egui",
@@ -6052,7 +6052,7 @@ checksum = "9440ea3e5aeabb0ac63af70daf835274065238cdd0cec83418f417eae38bacee"
[[package]]
name = "xtask"
version = "0.9.0"
version = "0.9.1"
dependencies = [
"serde_json",
]
+2 -2
View File
@@ -4,14 +4,14 @@ members = ["crates/*", "apps/*", "xtask"]
default-members = ["crates/*", "apps/vectorcraft", "apps/vectorcraft-cli", "xtask"]
[workspace.package]
version = "0.9.0"
version = "0.9.1"
edition = "2024"
license = "MIT OR Apache-2.0"
rust-version = "1.95"
repository = "https://github.com/storytold/vectorcraft"
[workspace.dependencies]
craft-ui = { git = "https://github.com/storytold/craft-libs", rev = "5c874d51eeb3de70e735c8ce87a59a7dedd47675", features = ["serde"] }
craft-ui = { git = "https://git.marcospaulo.dev.br/masi/craft-libs", rev = "22544b6d619d4f16779b8bdfa3f0b642a86fc065", features = ["serde"] }
# internal
vectorcraft-geom = { path = "crates/geom" }
vectorcraft-color = { path = "crates/color" }
+13 -7
View File
@@ -81,6 +81,18 @@ pub const UNITS: &[(&str, &str)] = &[
("yards", "Yards"),
("feet", "Feet"),
];
/// User Interface › Brightness (`uiBrightness`): the UI's themes. `vectorcraft_ui_egui::theme::Brightness`
/// draws them; a test there keeps the two lists equal.
pub const UI_THEMES: &[(&str, &str)] = &[
("dark", "Dark"),
("mediumDark", "Medium Dark"),
("mediumLight", "Medium Light"),
("light", "Light"),
("catppuccinLatte", "Catppuccin Latte"),
("catppuccinFrappe", "Catppuccin Frappé"),
("catppuccinMacchiato", "Catppuccin Macchiato"),
("catppuccinMocha", "Catppuccin Mocha"),
];
const LINE_STYLE: &[(&str, &str)] = &[("lines", "Lines"), ("dots", "Dots")];
/// Performance › Graphics Processor (`gpuPreference`), read by the desktop app at startup. The
/// values are WebGPU's power preferences plus `automatic`; 0.5.0's default, `powerSaving`, reads as
@@ -258,13 +270,7 @@ pub const PREF_SPECS: &[PrefSpec] = &[
p!("scratchPrimary", "Performance & Storage", "Scratch Disks", "Primary", text),
p!("scratchSecondary", "Performance & Storage", "Scratch Disks", "Secondary", text),
// User Interface
p!(
"uiBrightness",
"User Interface",
"",
"Brightness",
choice(&[("dark", "Dark"), ("mediumDark", "Medium Dark"), ("mediumLight", "Medium Light"), ("light", "Light")])
),
p!("uiBrightness", "User Interface", "", "Brightness", choice(UI_THEMES)),
p!(
"canvasColor",
"User Interface",
+6 -1
View File
@@ -3700,7 +3700,7 @@ effect Regular Pravidelný
@msg bad trailer chybný trailer
@msg balance must be between 0 and 100 vyvážení musí být mezi 0 a 100
@msg batch step {_1} (`{id}`) failed: {e} krok dávky {_1} (`{id}`) selhal: {e}
@msg brightness must be dark|mediumDark|mediumLight|light jas musí být dark|mediumDark|mediumLight|light
@msg brightness must be {_1} jas musí být {_1}
@msg can't encode the document: {0} dokument nelze zakódovat: {0}
@msg can't parse shortcut `{c}` klávesovou zkratku `{c}` nelze analyzovat
@msg can't parse shortcut `{s}` for `{k}` klávesovou zkratku `{s}` pro `{k}` nelze analyzovat
@@ -4439,3 +4439,8 @@ effect Regular Pravidelný
24 bits/pixel 24 bitů na pixel
32 bits/pixel 32 bitů na pixel
DWG (use DXF) DWG (použijte DXF)
Theme Motiv
Catppuccin Latte Catppuccin Latte
Catppuccin Frappé Catppuccin Frappé
Catppuccin Macchiato Catppuccin Macchiato
Catppuccin Mocha Catppuccin Mocha
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6 -1
View File
@@ -3403,7 +3403,7 @@ theme Appearance Erscheinungsbild
@msg bad trailer fehlerhafter Trailer
@msg balance must be between 0 and 100 balance muss zwischen 0 und 100 liegen
@msg batch step {_1} (`{id}`) failed: {e} Stapelschritt {_1} (`{id}`) ist fehlgeschlagen: {e}
@msg brightness must be dark|mediumDark|mediumLight|light brightness muss dark|mediumDark|mediumLight|light sein
@msg brightness must be {_1} brightness muss {_1} sein
@msg can't encode the document: {0} das Dokument kann nicht kodiert werden: {0}
@msg can't parse shortcut `{c}` Tastaturbefehl `{c}` kann nicht ausgewertet werden
@msg can't parse shortcut `{s}` for `{k}` Tastaturbefehl `{s}` für `{k}` kann nicht ausgewertet werden
@@ -4104,3 +4104,8 @@ theme Appearance Erscheinungsbild
@msg unsupported native docking operation Nicht unterstützte native Docking-Operation
Linux Display Server Linux-Anzeigeserver
X11 (XWayland) X11 (XWayland)
Theme Thema
Catppuccin Latte Catppuccin Latte
Catppuccin Frappé Catppuccin Frappé
Catppuccin Macchiato Catppuccin Macchiato
Catppuccin Mocha Catppuccin Mocha
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6 -1
View File
@@ -3503,7 +3503,7 @@ axis Both Ambos
@msg bad trailer trailer no válido
@msg balance must be between 0 and 100 el equilibrio debe estar entre 0 y 100
@msg batch step {_1} (`{id}`) failed: {e} falló el paso {_1} del lote (`{id}`): {e}
@msg brightness must be dark|mediumDark|mediumLight|light el brillo debe ser dark|mediumDark|mediumLight|light
@msg brightness must be {_1} el brillo debe ser {_1}
@msg can't encode the document: {0} no se puede codificar el documento: {0}
@msg can't parse shortcut `{c}` no se puede interpretar el atajo `{c}`
@msg can't parse shortcut `{s}` for `{k}` no se puede interpretar el atajo `{s}` de `{k}`
@@ -4148,3 +4148,8 @@ lighting Light Luz
@msg unsupported native docking operation Operación nativa de anclaje no admitida
Linux Display Server Servidor de pantalla de Linux
X11 (XWayland) X11 (XWayland)
Theme Tema
Catppuccin Latte Catppuccin Latte
Catppuccin Frappé Catppuccin Frappé
Catppuccin Macchiato Catppuccin Macchiato
Catppuccin Mocha Catppuccin Mocha
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6 -1
View File
@@ -3500,7 +3500,7 @@ axis Both Les deux
@msg bad trailer trailer non valide
@msg balance must be between 0 and 100 l’équilibre doit être compris entre 0 et 100
@msg batch step {_1} (`{id}`) failed: {e} l’étape {_1} du lot (`{id}`) a échoué : {e}
@msg brightness must be dark|mediumDark|mediumLight|light la luminosité doit valoir dark|mediumDark|mediumLight|light
@msg brightness must be {_1} la luminosité doit valoir {_1}
@msg can't encode the document: {0} impossible d’encoder le document : {0}
@msg can't parse shortcut `{c}` impossible d’interpréter le raccourci `{c}`
@msg can't parse shortcut `{s}` for `{k}` impossible d’interpréter le raccourci `{s}` pour `{k}`
@@ -4158,3 +4158,8 @@ lighting Light Lumière
@msg unsupported native docking operation Opération native d’ancrage non prise en charge
Linux Display Server Serveur d’affichage Linux
X11 (XWayland) X11 (XWayland)
Theme Thème
Catppuccin Latte Catppuccin Latte
Catppuccin Frappé Catppuccin Frappé
Catppuccin Macchiato Catppuccin Macchiato
Catppuccin Mocha Catppuccin Mocha
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6 -1
View File
@@ -3503,7 +3503,7 @@ axis Both Entrambi
@msg bad trailer trailer non valido
@msg balance must be between 0 and 100 il bilanciamento deve essere compreso tra 0 e 100
@msg batch step {_1} (`{id}`) failed: {e} il passaggio {_1} del batch (`{id}`) non è riuscito: {e}
@msg brightness must be dark|mediumDark|mediumLight|light la luminosità deve essere dark|mediumDark|mediumLight|light
@msg brightness must be {_1} la luminosità deve essere {_1}
@msg can't encode the document: {0} impossibile codificare il documento: {0}
@msg can't parse shortcut `{c}` impossibile interpretare la scelta rapida `{c}`
@msg can't parse shortcut `{s}` for `{k}` impossibile interpretare la scelta rapida `{s}` per `{k}`
@@ -4150,3 +4150,8 @@ lighting Light Luce
@msg unsupported native docking operation Operazione nativa di ancoraggio non supportata
Linux Display Server Server di visualizzazione Linux
X11 (XWayland) X11 (XWayland)
Theme Tema
Catppuccin Latte Catppuccin Latte
Catppuccin Frappé Catppuccin Frappé
Catppuccin Macchiato Catppuccin Macchiato
Catppuccin Mocha Catppuccin Mocha
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6 -1
View File
@@ -3843,7 +3843,7 @@ theme Appearance 外観
@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 `{key}` must be true or false `{key}` には true または false を指定してください
@msg brightness must be dark|mediumDark|mediumLight|light brightness には dark|mediumDark|mediumLight|light のいずれかを指定してください
@msg brightness must be {_1} brightness には {_1} のいずれかを指定してください
@msg index must be a number or first, previous, next or last index には数値、または first、previous、next、last のいずれかを指定してください
@msg index must be a number or first, previous, next or last, not `{other}` index には数値、または first、previous、next、last のいずれかを指定してください (`{other}` は指定できません)
@msg lang must be one of {_1} lang には {_1} のいずれかを指定してください
@@ -4144,3 +4144,8 @@ lighting Light ライト
@msg unsupported native docking operation ネイティブドッキング操作は未対応です
Linux Display Server Linux ディスプレイサーバー
X11 (XWayland) X11 (XWayland)
Theme テーマ
Catppuccin Latte Catppuccin Latte
Catppuccin Frappé Catppuccin Frappé
Catppuccin Macchiato Catppuccin Macchiato
Catppuccin Mocha Catppuccin Mocha
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6 -1
View File
@@ -3183,7 +3183,7 @@ axis Both Ambos
@msg bad trailer trailer inválido
@msg balance must be between 0 and 100 o equilíbrio deve estar entre 0 e 100
@msg batch step {_1} (`{id}`) failed: {e} a etapa {_1} do lote (`{id}`) falhou: {e}
@msg brightness must be dark|mediumDark|mediumLight|light brightness deve ser dark|mediumDark|mediumLight|light
@msg brightness must be {_1} brightness deve ser {_1}
@msg can't encode the document: {0} não foi possível codificar o documento: {0}
@msg can't parse shortcut `{c}` não foi possível interpretar o atalho `{c}`
@msg can't parse shortcut `{s}` for `{k}` não foi possível interpretar o atalho `{s}` de `{k}`
@@ -3658,3 +3658,8 @@ axis Both Ambos
@msg invalid native panel set conjunto de painéis nativos inválido
@msg missing panel layout layout de painéis ausente
@msg unsupported native docking operation operação de encaixe nativa não suportada
Theme Tema
Catppuccin Latte Catppuccin Latte
Catppuccin Frappé Catppuccin Frappé
Catppuccin Macchiato Catppuccin Macchiato
Catppuccin Mocha Catppuccin Mocha
Can't render this file because it contains an unexpected character in line 6 and column 47.
+6 -1
View File
@@ -3499,7 +3499,7 @@ axis Both Оба
@msg bad trailer недопустимый трейлер
@msg balance must be between 0 and 100 баланс должен быть от 0 до 100
@msg batch step {_1} (`{id}`) failed: {e} шаг пакета {_1} (`{id}`) не выполнен: {e}
@msg brightness must be dark|mediumDark|mediumLight|light яркость должна быть dark|mediumDark|mediumLight|light
@msg brightness must be {_1} яркость должна быть {_1}
@msg can't encode the document: {0} не удалось закодировать документ: {0}
@msg can't parse shortcut `{c}` не удалось разобрать сочетание `{c}`
@msg can't parse shortcut `{s}` for `{k}` не удалось разобрать сочетание `{s}` для `{k}`
@@ -4146,3 +4146,8 @@ lighting Light Свет
@msg unsupported native docking operation Неподдерживаемая собственная операция прикрепления
Linux Display Server Дисплейный сервер Linux
X11 (XWayland) X11 (XWayland)
Theme Тема
Catppuccin Latte Catppuccin Latte
Catppuccin Frappé Catppuccin Frappé
Catppuccin Macchiato Catppuccin Macchiato
Catppuccin Mocha Catppuccin Mocha
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6 -1
View File
@@ -3487,7 +3487,7 @@ axis Both Обидві
@msg bad trailer неприпустимий трейлер
@msg balance must be between 0 and 100 баланс має бути від 0 до 100
@msg batch step {_1} (`{id}`) failed: {e} крок пакету {_1} (`{id}`) не виконано: {e}
@msg brightness must be dark|mediumDark|mediumLight|light яскравість має бути dark|mediumDark|mediumLight|light
@msg brightness must be {_1} яскравість має бути {_1}
@msg can't encode the document: {0} не вдалося закодувати документ: {0}
@msg can't parse shortcut `{c}` не вдалося розібрати поєднання `{c}`
@msg can't parse shortcut `{s}` for `{k}` не вдалося розібрати поєднання `{s}` для `{k}`
@@ -4130,3 +4130,8 @@ lighting Light Світло
@msg unsupported native docking operation Непідтримувана власна операція прикріплення
Linux Display Server Дисплейний сервер Linux
X11 (XWayland) X11 (XWayland)
Theme Тема
Catppuccin Latte Catppuccin Latte
Catppuccin Frappé Catppuccin Frappé
Catppuccin Macchiato Catppuccin Macchiato
Catppuccin Mocha Catppuccin Mocha
Can't render this file because it contains an unexpected character in line 657 and column 40.
+5
View File
@@ -3748,3 +3748,8 @@ lighting Light 光源
Move Guide to Layer 将参考线移至图层
Linux Display Server Linux 显示服务器
X11 (XWayland) X11 (XWayland)
Theme 主题
Catppuccin Latte Catppuccin Latte
Catppuccin Frappé Catppuccin Frappé
Catppuccin Macchiato Catppuccin Macchiato
Catppuccin Mocha Catppuccin Mocha
Can't render this file because it contains an unexpected character in line 2 and column 73.
+5
View File
@@ -3529,3 +3529,8 @@ lighting Light 光源
Move Guide to Layer 將參考線移至圖層
Linux Display Server Linux 顯示伺服器
X11 (XWayland) X11 (XWayland)
Theme 主題
Catppuccin Latte Catppuccin Latte
Catppuccin Frappé Catppuccin Frappé
Catppuccin Macchiato Catppuccin Macchiato
Catppuccin Mocha Catppuccin Mocha
Can't render this file because it contains an unexpected character in line 6 and column 47.
+20 -2
View File
@@ -34,6 +34,18 @@ fn c(label: &'static str, id: &'static str) -> Item {
fn cp(label: &'static str, id: &'static str, p: Value) -> Item {
Item::Cmd(label, id, p)
}
/// Window → Theme: the UI brightness levels, then the Catppuccin flavours.
pub fn theme_items() -> Vec<Item> {
let mut out = Vec::new();
let mut flavours = false;
for b in Brightness::ALL {
if b.flavor().is_some() && !std::mem::replace(&mut flavours, true) {
out.push(Item::Sep);
}
out.push(cp(b.label(), "window.brightness", json!({"brightness": b.id()})));
}
out
}
fn todo(label: &'static str) -> Item {
Item::Todo(label, "")
}
@@ -322,7 +334,12 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[
"",
"{collapsed?: bool} collapse the dock's Properties | Layers | Libraries group to icons (true), expand it (false) or toggle (omitted), as the double arrow at the top of the dock does; returns the new state",
),
("window.brightness", "UI Brightness", "", "{brightness: dark|mediumDark|mediumLight|light}"),
(
"window.brightness",
"UI Brightness",
"",
"{brightness: dark|mediumDark|mediumLight|light|catppuccinLatte|catppuccinFrappe|catppuccinMacchiato|catppuccinMocha}",
),
("window.workspace", "Workspace", "", "{name} switch workspace (Essentials, Essentials Classic, Painting, …)"),
("window.workspace.reset", "Reset Essentials", "", "{} reset the current workspace"),
("window.workspace.new", "New Workspace…", "", "{name?} save the current layout"),
@@ -1214,7 +1231,7 @@ pub fn run_ui_command(app: &mut VectorcraftApp, id: &str, p: &Value) -> Option<R
app.canvas.key = None;
Ok(json!(b.id()))
}
None => Err("brightness must be dark|mediumDark|mediumLight|light".into()),
None => Err(format!("brightness must be {}", Brightness::id_list())),
},
"window.newWindow" => Err("multiple windows land with M11.5".into()),
"tool.select" => match s("tool") {
@@ -2572,6 +2589,7 @@ pub fn menu_tree_named(english_names: bool) -> Vec<(&'static str, Vec<Item>)> {
c("New Window", "window.newWindow"),
sub("Arrange", vec![todo("Cascade"), todo("Tile"), todo("Float in Window"), todo("Consolidate All Windows")]),
sub("Workspace", crate::workspaces::menu_items()),
sub("Theme", theme_items()),
Sep,
c("Control", "window.control"),
c("Contextual Task Bar", "window.taskBar"),
+8 -2
View File
@@ -371,8 +371,14 @@ fn language_node(app: &VectorcraftApp, lang: Lang) -> Node {
/// Appearance in the app menu (the UI-brightness theme, as Mac apps call it).
fn appearance_node(app: &VectorcraftApp, lang: Lang) -> Node {
let children =
Brightness::ALL.iter().map(|b| Node::Item(native_item(app, b.label(), "window.brightness", json!({"brightness": b.id()})))).collect();
let mut children = Vec::new();
let mut flavours = false;
for b in Brightness::ALL {
if b.flavor().is_some() && !std::mem::replace(&mut flavours, true) {
children.push(Node::Separator);
}
children.push(Node::Item(native_item(app, b.label(), "window.brightness", json!({"brightness": b.id()}))));
}
Node::Submenu { label: tr_ctx(lang, "theme", "Appearance").into(), children }
}
+2 -1
View File
@@ -144,7 +144,8 @@ fn language_and_appearance_are_in_the_app_menu() {
}
}
let appearance: Vec<(&'static str, Option<bool>)> = items(submenu(app_menu, "Appearance")).iter().map(|it| (it.source, it.checked)).collect();
assert_eq!(appearance.len(), crate::theme::Brightness::ALL.len());
assert_eq!(appearance.len(), crate::theme::Brightness::ALL.len(), "every theme, with a separator before the Catppuccin ones");
assert_eq!(appearance.iter().map(|(s, _)| *s).collect::<Vec<_>>(), crate::theme::Brightness::ALL.map(|b| b.label()));
assert_eq!(appearance.iter().filter(|(_, c)| *c == Some(true)).count(), 1, "{appearance:?}");
assert!(appearance.iter().all(|(_, c)| c.is_some()));
assert_eq!(crate::i18n::tr_ctx(Lang::from_code("ja").unwrap(), "theme", "Appearance"), "外観", "the theme, not the Appearance panel");
+127 -3
View File
@@ -1,7 +1,8 @@
//! Design tokens (Illustrator's four UI brightness levels), fonts and egui style.
//! Design tokens (Illustrator's four UI brightness levels plus the Catppuccin flavours), fonts and egui style.
use std::sync::Arc;
use craft_ui::theme::catppuccin::Flavor;
use egui::{Color32, CornerRadius, FontData, FontDefinitions, FontFamily, FontId, Stroke, TextStyle, Visuals};
use serde::{Deserialize, Serialize};
@@ -12,16 +13,45 @@ pub enum Brightness {
MediumDark,
MediumLight,
Light,
CatppuccinLatte,
CatppuccinFrappe,
CatppuccinMacchiato,
CatppuccinMocha,
}
impl Brightness {
pub const ALL: [Brightness; 4] = [Brightness::Dark, Brightness::MediumDark, Brightness::MediumLight, Brightness::Light];
pub const ALL: [Brightness; 8] = [
Brightness::Dark,
Brightness::MediumDark,
Brightness::MediumLight,
Brightness::Light,
Brightness::CatppuccinLatte,
Brightness::CatppuccinFrappe,
Brightness::CatppuccinMacchiato,
Brightness::CatppuccinMocha,
];
/// The Catppuccin flavour behind this theme, if it is one.
pub fn flavor(self) -> Option<Flavor> {
match self {
Brightness::CatppuccinLatte => Some(Flavor::Latte),
Brightness::CatppuccinFrappe => Some(Flavor::Frappe),
Brightness::CatppuccinMacchiato => Some(Flavor::Macchiato),
Brightness::CatppuccinMocha => Some(Flavor::Mocha),
_ => None,
}
}
pub fn label(self) -> &'static str {
match self {
Brightness::Dark => "Dark",
Brightness::MediumDark => "Medium Dark",
Brightness::MediumLight => "Medium Light",
Brightness::Light => "Light",
Brightness::CatppuccinLatte => "Catppuccin Latte",
Brightness::CatppuccinFrappe => "Catppuccin Frappé",
Brightness::CatppuccinMacchiato => "Catppuccin Macchiato",
Brightness::CatppuccinMocha => "Catppuccin Mocha",
}
}
pub fn id(self) -> &'static str {
@@ -30,11 +60,19 @@ impl Brightness {
Brightness::MediumDark => "mediumDark",
Brightness::MediumLight => "mediumLight",
Brightness::Light => "light",
Brightness::CatppuccinLatte => "catppuccinLatte",
Brightness::CatppuccinFrappe => "catppuccinFrappe",
Brightness::CatppuccinMacchiato => "catppuccinMacchiato",
Brightness::CatppuccinMocha => "catppuccinMocha",
}
}
pub fn parse(s: &str) -> Option<Self> {
Self::ALL.into_iter().find(|b| b.id().eq_ignore_ascii_case(s) || b.label().eq_ignore_ascii_case(s))
}
/// Every id, `|`-separated: for error messages and command docs.
pub fn id_list() -> String {
Self::ALL.map(Brightness::id).join("|")
}
}
/// All colours and metrics the UI uses. Never hard-code a colour in a widget.
@@ -141,8 +179,15 @@ impl Tokens {
axis_y: hex(0x45af63),
axis_z: hex(0x378ef0),
};
if let Some(f) = b.flavor() {
return catppuccin(base, f);
}
match b {
Brightness::Dark => base,
Brightness::Dark
| Brightness::CatppuccinLatte
| Brightness::CatppuccinFrappe
| Brightness::CatppuccinMacchiato
| Brightness::CatppuccinMocha => base,
// Measured from Illustrator 2026 (plan/illustrator/10-observed-ui.md §1).
Brightness::MediumDark => Tokens {
app_bar: hex(0x535353),
@@ -229,6 +274,54 @@ impl Tokens {
}
}
/// A Catppuccin flavour on VectorCraft's panel layout (accent Mauve, selection Blue), over `base`'s metrics.
fn catppuccin(base: Tokens, f: Flavor) -> Tokens {
let p = f.palette();
let dark = f.is_dark();
Tokens {
dark,
app_bar: p.mantle,
panel: p.base,
panel_darker: p.mantle,
input: if dark { p.crust } else { p.base },
input_border: p.surface1,
border: if dark { p.crust } else { p.surface1 },
divider: p.surface0,
text: p.text,
text_dim: p.subtext0,
text_disabled: p.overlay0,
icon: p.subtext1,
hover: p.surface0,
tool_active: p.surface1,
accent: p.mauve,
accent_strong: p.mauve,
row_selected: p.surface1,
pasteboard: p.crust,
ruler: p.mantle,
ruler_tick: p.overlay1,
smart_guide: p.pink,
guide: p.sky,
measure_bg: Color32::from_rgba_unmultiplied(p.surface1.r(), p.surface1.g(), p.surface1.b(), 235),
button: p.surface0,
text_strong: p.text,
tab_strip: p.mantle,
button_border: p.surface2,
selection: p.blue,
anchor_selected: p.blue,
flyout_active: p.sapphire,
caption_close: p.red,
caption_close_text: p.crust,
highlight: p.red,
bleed: p.red,
error: p.red,
warning: p.yellow,
axis_x: p.red,
axis_y: p.green,
axis_z: p.blue,
..base
}
}
pub const FONT_UI: &str = "ui";
pub const FONT_UI_SEMIBOLD: &str = "ui-semibold";
pub const FONT_MONO: &str = "mono";
@@ -342,4 +435,35 @@ mod tests {
}
assert!(!Tokens::for_brightness(Brightness::Light).dark);
}
/// The Preferences dropdown (engine) and the menus (shell) list the same themes, and the
/// `window.brightness` docs and error name every id.
#[test]
fn theme_lists_agree() {
let engine: Vec<_> = vectorcraft_engine::cmd::prefscmds::UI_THEMES.to_vec();
let shell: Vec<_> = Brightness::ALL.iter().map(|b| (b.id(), b.label())).collect();
assert_eq!(engine, shell);
let doc = crate::menus::UI_COMMANDS.iter().find(|c| c.0 == "window.brightness").unwrap().3;
for b in Brightness::ALL {
assert!(doc.contains(b.id()), "{} missing from the window.brightness docs", b.id());
}
assert_eq!(Brightness::id_list().split('|').count(), Brightness::ALL.len());
}
#[test]
fn catppuccin_themes_use_the_flavour_palette() {
for b in Brightness::ALL {
let t = Tokens::for_brightness(b);
match b.flavor() {
Some(f) => {
assert_eq!(t.dark, f.is_dark(), "{}", b.id());
assert_eq!((t.panel, t.text, t.accent), (f.palette().base, f.palette().text, f.palette().mauve), "{}", b.id());
}
None => assert!(b.id().eq_ignore_ascii_case(b.label().replace(' ', "").as_str()), "{}", b.id()),
}
}
assert_eq!(Brightness::ALL.iter().filter(|b| b.flavor().is_some()).count(), Flavor::ALL.len());
assert!(!Tokens::for_brightness(Brightness::CatppuccinLatte).dark);
assert!(Tokens::for_brightness(Brightness::CatppuccinMocha).dark);
}
}
+27 -1
View File
@@ -6,6 +6,7 @@
Produces, in $env:DIST (default: dist/release):
vectorcraft-<version>-windows-<arch>.msi per-machine installer (WiX v5)
vectorcraft-<version>-windows-<arch>-portable.zip vectorcraft.exe + vectorcraft-cli.exe
vectorcraft-cli-<version>-windows-<arch>.zip vectorcraft-cli.exe standalone
The binaries link the C runtime statically (+crt-static), so neither the MSI nor the portable
zip needs the Visual C++ redistributable. Signing is delegated to sign.ps1 (skipped with a
@@ -26,6 +27,7 @@ param(
)
$ErrorActionPreference = 'Stop'
$Root = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path
Set-Location $Root
function Invoke-Native([string] $What, [scriptblock] $Block) {
Write-Output "==> $What"
@@ -57,6 +59,17 @@ if (-not $env:VECTORCRAFT_BUILD_DATE) { $env:VECTORCRAFT_BUILD_DATE = (Get-Date)
Write-Output "VectorCraft $Version for Windows $Arch ($Target)"
if (-not $SkipBuild) {
# Locate rc.exe if not in PATH
if (-not (Get-Command rc.exe -ErrorAction SilentlyContinue)) {
$kits = Join-Path ${env:ProgramFiles(x86)} 'Windows Kits\10\bin'
$foundRc = Get-ChildItem -Path $kits -Recurse -Filter rc.exe -ErrorAction SilentlyContinue |
Where-Object { $_.FullName -match '\\x64\\rc\.exe$' } |
Sort-Object FullName -Descending | Select-Object -First 1
if ($foundRc) {
$env:PATH = "$($foundRc.DirectoryName);$env:PATH"
}
}
# Static CRT: no VC++ redistributable needed. Scoped to the target so host build scripts and
# proc-macros are unaffected.
$flagVar = 'CARGO_TARGET_' + ($Target.ToUpper() -replace '-', '_') + '_RUSTFLAGS'
@@ -161,6 +174,19 @@ $Zip = Join-Path $Dist "vectorcraft-$Version-windows-$Arch-portable.zip"
Remove-Item -Force $Zip -ErrorAction SilentlyContinue
Compress-Archive -Path $Portable -DestinationPath $Zip
# ---- cli zip -----------------------------------------------------------------------------------
$CliPortable = Join-Path $TargetDir "windows-package\vectorcraft-cli-$Version-windows-$Arch"
Remove-Item -Recurse -Force $CliPortable -ErrorAction SilentlyContinue
New-Item -ItemType Directory -Force -Path $CliPortable | Out-Null
Copy-Item (Join-Path $Stage 'vectorcraft-cli.exe') $CliPortable
foreach ($f in 'README.md', 'LICENSE', 'LICENSE-MIT', 'LICENSE-APACHE') {
$p = Join-Path $Root $f
if (Test-Path $p) { Copy-Item $p $CliPortable }
}
$CliZip = Join-Path $Dist "vectorcraft-cli-$Version-windows-$Arch.zip"
Remove-Item -Force $CliZip -ErrorAction SilentlyContinue
Compress-Archive -Path $CliPortable -DestinationPath $CliZip
# Smoke-test the CLI when this machine can run it. An ARM64 build made on an x64 runner can't run
# here; .github/workflows/windows-arm64.yml installs and runs it on ARM64 instead.
$HostArch = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString().ToLowerInvariant()
@@ -169,4 +195,4 @@ if ($Arch -ne 'arm64' -or $HostArch -eq 'arm64') {
} else {
Write-Output "skipping vectorcraft-cli --version: an $Arch build doesn't run on this $HostArch machine"
}
Get-Item $Msi, $Zip | Format-Table Name, Length
Get-Item $Msi, $Zip, $CliZip | Format-Table Name, Length