UI: Classic colour scheme (User Interface › Color Scheme)

A second colour scheme next to Modern: the neutral greys and blue accent of
earlier versions, on the same cards, corners and layout. Picked in
Preferences › User Interface › Color Scheme (pref uiColorScheme:
modern|classic); Accent Color is dimmed while Classic is chosen, which has
its own blue.
This commit is contained in:
bflatastic
2026-10-16 14:29:33 +07:00
parent ae6dc17ffe
commit a64a5a9911
23 changed files with 274 additions and 18 deletions
+1
View File
@@ -265,6 +265,7 @@ pub const PREF_SPECS: &[PrefSpec] = &[
"Brightness",
choice(&[("dark", "Dark"), ("mediumDark", "Medium Dark"), ("mediumLight", "Medium Light"), ("light", "Light")])
),
p!("uiColorScheme", "User Interface", "", "Color Scheme", choice(&[("modern", "Modern"), ("classic", "Classic")])),
p!(
"uiAccent",
"User Interface",
+4
View File
@@ -746,6 +746,9 @@ pub struct Prefs {
pub ui_brightness: String,
/// User Interface › Accent Color: the colour of what is on, chosen or focused.
pub ui_accent: String,
/// User Interface › Color Scheme: "modern" (with the accent above) or "classic" (neutral
/// greys and a blue accent).
pub ui_color_scheme: String,
pub canvas_color: String,
pub auto_collapse_icon_panels: bool,
/// User Interface › Show Tool Group Labels: the toolbar's group names (Select, Shapes, Draw…);
@@ -979,6 +982,7 @@ impl Default for Prefs {
scratch_secondary: s("None"),
ui_brightness: s("mediumDark"),
ui_accent: s("coral"),
ui_color_scheme: s("modern"),
canvas_color: s("matchUi"),
auto_collapse_icon_panels: false,
tool_group_labels: false,
+4
View File
@@ -4920,3 +4920,7 @@ theme Appearance Външен вид
Amber Кехлибарен
Coral Коралов
Violet Виолетов
# User Interface › Color Scheme
Color Scheme Цветова схема
Modern Модерна
Can't render this file because it contains an unexpected character in line 2 and column 73.
+4
View File
@@ -4938,3 +4938,7 @@ mark-style Japanese Japonské
Amber Jantarová
Coral Korálová
Violet Fialová
# User Interface › Color Scheme
Color Scheme Barevné schéma
Modern Moderní
Can't render this file because it contains an unexpected character in line 2 and column 73.
+4
View File
@@ -4475,3 +4475,7 @@ theme Appearance Erscheinungsbild
Coral Koralle
Violet Violett
Pink Pink
# User Interface › Color Scheme
Color Scheme Farbschema
Modern Modern
Can't render this file because it contains an unexpected character in line 2 and column 73.
+4
View File
@@ -4890,3 +4890,7 @@ effect Points Σημεία
Amber Κεχριμπαρί
Coral Κοραλλί
Violet Βιολετί
# User Interface › Color Scheme
Color Scheme Χρωματικός συνδυασμός
Modern Σύγχρονο
Can't render this file because it contains an unexpected character in line 1857 and column 19.
+4
View File
@@ -4880,3 +4880,7 @@ mark-style Japanese Japonesas
Amber Ámbar
Coral Coral
Violet Violeta
# User Interface › Color Scheme
Color Scheme Esquema de color
Modern Moderno
Can't render this file because it contains an unexpected character in line 2 and column 73.
+4
View File
@@ -4902,3 +4902,7 @@ mark-style Japanese Japanilainen
Amber Meripihka
Coral Koralli
Violet Violetti
# User Interface › Color Scheme
Color Scheme Värimalli
Modern Moderni
Can't render this file because it contains an unexpected character in line 2 and column 73.
+4
View File
@@ -4937,3 +4937,7 @@ theme Appearance Apparence
Amber Ambre
Coral Corail
Violet Violet
# User Interface › Color Scheme
Color Scheme Jeu de couleurs
Modern Moderne
Can't render this file because it contains an unexpected character in line 2 and column 73.
+4
View File
@@ -4905,3 +4905,7 @@ imageTrace Outline Körvonal
Amber Borostyán
Coral Korall
Violet Ibolya
# User Interface › Color Scheme
Color Scheme Színséma
Modern Modern
Can't render this file because it contains an unexpected character in line 2 and column 73.
+4
View File
@@ -4515,3 +4515,7 @@ lighting Light Luce
Coral Corallo
Violet Viola
Pink Rosa
# User Interface › Color Scheme
Color Scheme Combinazione colori
Modern Moderno
Can't render this file because it contains an unexpected character in line 2 and column 73.
+4
View File
@@ -4556,3 +4556,7 @@ lighting Light ライト
Amber アンバー
Violet バイオレット
Pink ピンク
# User Interface › Color Scheme
Color Scheme 配色
Modern モダン
Can't render this file because it contains an unexpected character in line 2 and column 73.
+4
View File
@@ -4479,3 +4479,7 @@ lighting Light 광원
Coral 코럴
Violet 보라
Pink 분홍
# User Interface › Color Scheme
Color Scheme 색 구성표
Modern 모던
Can't render this file because it contains an unexpected character in line 2 and column 73.
+4
View File
@@ -4783,3 +4783,7 @@ mark-style Japanese Japońskie
Coral Koralowy
Violet Fioletowy
Pink Różowy
# User Interface › Color Scheme
Color Scheme Schemat kolorów
Modern Nowoczesny
Can't render this file because it contains an unexpected character in line 2 and column 73.
+4
View File
@@ -4855,3 +4855,7 @@ mark-style Japanese Japonesas
Amber Âmbar
Coral Coral
Violet Violeta
# User Interface › Color Scheme
Color Scheme Esquema de cores
Modern Moderno
Can't render this file because it contains an unexpected character in line 6 and column 47.
+4
View File
@@ -4511,3 +4511,7 @@ lighting Light Свет
Coral Коралловый
Violet Фиолетовый
Pink Розовый
# User Interface › Color Scheme
Color Scheme Цветовая схема
Modern Современная
Can't render this file because it contains an unexpected character in line 2 and column 73.
+4
View File
@@ -4895,3 +4895,7 @@ theme Appearance Utseende
Amber Bärnsten
Coral Korall
Violet Violett
# User Interface › Color Scheme
Color Scheme Färgschema
Modern Modern
Can't render this file because it contains an unexpected character in line 5 and column 25.
+4
View File
@@ -4495,3 +4495,7 @@ lighting Light Світло
Coral Кораловий
Violet Фіолетовий
Pink Рожевий
# User Interface › Color Scheme
Color Scheme Колірна схема
Modern Сучасна
Can't render this file because it contains an unexpected character in line 718 and column 40.
+4
View File
@@ -4495,3 +4495,7 @@ theme Appearance Giao diện
Coral San hô
Violet Tím
Pink Hồng
# User Interface › Color Scheme
Color Scheme Bảng phối màu
Modern Hiện đại
Can't render this file because it contains an unexpected character in line 3729 and column 10.
+4
View File
@@ -4046,3 +4046,7 @@ lighting Light 光源
Amber 琥珀色
Coral 珊瑚色
Violet 紫罗兰色
# User Interface › Color Scheme
Color Scheme 配色方案
Modern 现代
Can't render this file because it contains an unexpected character in line 2 and column 73.
+4
View File
@@ -3834,3 +3834,7 @@ lighting Light 光源
Coral 珊瑚色
Violet 紫羅蘭色
Pink 粉紅色
# User Interface › Color Scheme
Color Scheme 配色方案
Modern 現代
Can't render this file because it contains an unexpected character in line 6 and column 47.
+23 -10
View File
@@ -9,7 +9,7 @@ use vectorcraft_engine::Prefs;
use vectorcraft_engine::cmd::prefscmds::{GPU_PREFERENCES, PREF_CATEGORIES, PREF_GROUPS, PREF_SPECS, PrefKind};
use crate::state::Dialog;
use crate::theme::{self, Accent, Brightness, Tokens};
use crate::theme::{self, Accent, Brightness, Scheme, Tokens};
use crate::{VectorcraftApp, widgets};
/// UI-only fields shown in the dialog (backed by `UiState`, not engine prefs).
@@ -82,6 +82,7 @@ pub fn restore(app: &mut VectorcraftApp) {
struct Applied {
brightness: Brightness,
accent: Accent,
scheme: Scheme,
/// User Interface › Canvas Color, unless it matches the interface brightness.
canvas: Option<egui::Color32>,
threads: i32,
@@ -113,6 +114,7 @@ pub fn apply_runtime(app: &mut VectorcraftApp, ctx: &egui::Context) {
let want = Applied {
brightness: app.ui.brightness,
accent: Accent::parse(&p.ui_accent).unwrap_or_default(),
scheme: Scheme::parse(&p.ui_color_scheme).unwrap_or_default(),
canvas: canvas_color(&p.canvas_color),
threads: p.render_threads,
tool_tips: p.show_tool_tips,
@@ -122,7 +124,7 @@ pub fn apply_runtime(app: &mut VectorcraftApp, ctx: &egui::Context) {
let id = egui::Id::new("dc-applied-prefs");
let prev: Option<Applied> = ctx.data(|d| d.get_temp::<Option<Applied>>(id)).flatten();
if prev != Some(want) {
theme::apply_with(ctx, want.brightness, want.accent);
theme::apply_with(ctx, want.brightness, want.accent, want.scheme);
// General › Show Tool Tips: off, no button or field shows its tool tip (they never come
// due), whichever widget asks for one.
let delay = if want.tool_tips { egui::style::Interaction::default().tooltip_delay } else { f32::INFINITY };
@@ -311,15 +313,13 @@ fn category_fields(ui: &mut egui::Ui, d: &mut Dialog, cat: &str) {
}
// Linux only: the other platforms have a single display server.
PrefKind::Choice(_) if sp.key == "linuxDisplayServer" && !cfg!(target_os = "linux") => {}
// The Classic colour scheme has its own accent: dimmed while it is chosen.
PrefKind::Choice(opts) if sp.key == "uiAccent" => {
let modern = Scheme::parse(&d.str("uiColorScheme")).unwrap_or_default() == Scheme::Modern;
ui.add_enabled_ui(modern, |ui| choice_row(ui, d, sp.key, sp.label, opts, &v));
}
PrefKind::Choice(opts) => {
labeled(ui, sp.label, |ui| {
let cur = v.as_str().unwrap_or("");
let cur_label = opts.iter().find(|o| o.0 == cur).map(|o| o.1).unwrap_or(cur);
let labels: Vec<&str> = opts.iter().map(|o| o.1).collect();
if let Some(i) = widgets::dropdown(ui, sp.key, cur_label, &labels, 260.0) {
d.fields.insert(sp.key.into(), json!(opts[i].0));
}
});
choice_row(ui, d, sp.key, sp.label, opts, &v);
if sp.key == "gpuPreference" || sp.key == "linuxDisplayServer" {
// The window's graphics device and display server are chosen once, when the app starts.
ui.label(egui::RichText::new(tl!("Applies the next time VectorCraft starts.")).color(t.text_dim).size(11.0));
@@ -367,6 +367,19 @@ fn picas_in_use(d: &Dialog) -> bool {
["unitsGeneral", "unitsStroke", "unitsType", "unitsAsianType"].iter().any(|k| d.str(k) == vectorcraft_doc::Unit::Picas.key())
}
fn choice_row(ui: &mut egui::Ui, d: &mut Dialog, key: &str, label: &str, opts: &[(&str, &str)], v: &Value) {
labeled(ui, label, |ui| {
let cur = v.as_str().unwrap_or("");
let cur_label = opts.iter().find(|o| o.0 == cur).map(|o| o.1).unwrap_or(cur);
let labels: Vec<&str> = opts.iter().map(|o| o.1).collect();
if let Some(i) = widgets::dropdown(ui, key, cur_label, &labels, 260.0)
&& let Some(o) = opts.get(i)
{
d.fields.insert(key.into(), json!(o.0));
}
});
}
fn bool_row(ui: &mut egui::Ui, d: &mut Dialog, key: &str, label: &str) {
// The app's outlined checkbox (it translates `label`): egui's own has no border at rest.
let on = d.bool(key);
+170 -8
View File
@@ -85,6 +85,29 @@ impl Accent {
}
}
/// User Interface › Color Scheme: Modern (the default: slate greys and the chosen accent) or
/// Classic (the neutral greys and blue accent of earlier versions). Both keep the same layout,
/// cards and corners; only the colours differ.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum Scheme {
#[default]
Modern,
Classic,
}
impl Scheme {
pub const ALL: [Scheme; 2] = [Scheme::Modern, Scheme::Classic];
pub fn id(self) -> &'static str {
match self {
Scheme::Modern => "modern",
Scheme::Classic => "classic",
}
}
pub fn parse(s: &str) -> Option<Self> {
Self::ALL.into_iter().find(|c| c.id().eq_ignore_ascii_case(s))
}
}
/// All colours and metrics the UI uses. Never hard-code a colour in a widget.
#[derive(Clone, Copy, Debug)]
pub struct Tokens {
@@ -280,6 +303,129 @@ impl Tokens {
}
}
/// The Classic colour scheme at brightness `b`: neutral greys with a blue accent, a dark
/// pressed tool and blue-grey selected rows, on the Modern metrics (corners, sizes).
pub fn classic(b: Brightness) -> Self {
let hex = |s: u32| Color32::from_rgb((s >> 16) as u8, (s >> 8) as u8, s as u8);
let base = Tokens {
app_bar: hex(0x262626),
panel: hex(0x323232),
panel_darker: hex(0x282828),
input: hex(0x1f1f1f),
input_border: hex(0x464646),
border: hex(0x1b1b1b),
divider: hex(0x3f3f3f),
text: hex(0xd8d8d8),
text_dim: hex(0xa8a8a8),
text_disabled: hex(0x6e6e6e),
icon: hex(0xcfcfcf),
hover: hex(0x444444),
tool_active: hex(0x1d1d1d),
accent: hex(0x378ef0),
accent_strong: hex(0x1473e6),
row_selected: hex(0x484b50),
pasteboard: hex(0x1e1e1e),
ruler: hex(0x2f2f2f),
ruler_tick: hex(0x8a8a8a),
measure_bg: Color32::from_rgba_unmultiplied(92, 92, 92, 235),
button: hex(0x444444),
text_strong: hex(0xf5f5f5),
tab_strip: hex(0x282828),
button_border: hex(0x6a6a6a),
flyout_active: hex(0x54b2f7),
..Tokens::for_brightness(Brightness::Dark)
};
match b {
Brightness::Dark => base,
Brightness::MediumDark => Tokens {
// A shade below the panels, so the cards stand out from the window behind them.
app_bar: hex(0x464646),
panel: hex(0x535353),
panel_darker: hex(0x424242),
tab_strip: hex(0x424242),
input: hex(0x454545),
input_border: hex(0x5f5f5f),
border: hex(0x383838),
divider: hex(0x4b4b4b),
text: hex(0xd1d1d1),
text_strong: hex(0xffffff),
text_dim: hex(0xb0b0b0),
text_disabled: hex(0x7a7a7a),
icon: hex(0xc2c2c2),
hover: hex(0x606060),
tool_active: hex(0x303030),
row_selected: hex(0x52677c),
pasteboard: hex(0x606060),
ruler: hex(0x333333),
button: hex(0x535353),
button_border: hex(0x747474),
..base
},
Brightness::MediumLight => Tokens {
dark: false,
app_bar: hex(0xa9a9a9),
panel: hex(0xb8b8b8),
panel_darker: hex(0xaaaaaa),
tab_strip: hex(0xa6a6a6),
input: hex(0xd6d6d6),
input_border: hex(0x8c8c8c),
border: hex(0x969696),
divider: hex(0x9e9e9e),
text: hex(0x1f1f1f),
text_strong: hex(0x000000),
text_dim: hex(0x3c3c3c),
text_disabled: hex(0x7c7c7c),
icon: hex(0x2a2a2a),
hover: hex(0xc8c8c8),
tool_active: hex(0x969696),
row_selected: hex(0x9fb4d6),
pasteboard: hex(0xa0a0a0),
ruler: hex(0xc2c2c2),
ruler_tick: hex(0x4a4a4a),
measure_bg: Color32::from_rgba_unmultiplied(184, 184, 184, 235),
button: hex(0xcacaca),
button_border: hex(0x7a7a7a),
flyout_active: hex(0x1473e6),
..base
},
Brightness::Light => Tokens {
dark: false,
app_bar: hex(0xe4e4e4),
panel: hex(0xf0f0f0),
panel_darker: hex(0xe2e2e2),
tab_strip: hex(0xdddddd),
input: hex(0xffffff),
input_border: hex(0xb4b4b4),
border: hex(0xcfcfcf),
divider: hex(0xd6d6d6),
text: hex(0x1f1f1f),
text_strong: hex(0x000000),
text_dim: hex(0x4b4b4b),
text_disabled: hex(0x9a9a9a),
icon: hex(0x303030),
hover: hex(0xdddddd),
tool_active: hex(0xcdcdcd),
row_selected: hex(0xc8daf7),
pasteboard: hex(0xdcdcdc),
ruler: hex(0xeaeaea),
ruler_tick: hex(0x5a5a5a),
measure_bg: Color32::from_rgba_unmultiplied(240, 240, 240, 235),
button: hex(0xe0e0e0),
button_border: hex(0x9a9a9a),
flyout_active: hex(0x1473e6),
..base
},
}
}
/// The tokens for `b` in `scheme`; Modern takes `accent`, Classic has its own blue.
pub fn for_scheme(scheme: Scheme, b: Brightness, accent: Accent) -> Self {
match scheme {
Scheme::Modern => Self::for_brightness(b).with_accent(accent),
Scheme::Classic => Self::classic(b),
}
}
/// These tokens with `accent`: the accent, buttons filled with it, the active item in flyouts and
/// selected rows (the panel tinted towards it).
pub fn with_accent(self, accent: Accent) -> Self {
@@ -400,12 +546,12 @@ fn add_craft_fonts(fonts: &mut FontDefinitions) {
/// Apply tokens to egui's global style.
pub fn apply(ctx: &egui::Context, b: Brightness) {
apply_with(ctx, b, Accent::default());
apply_with(ctx, b, Accent::default(), Scheme::default());
}
/// [`apply`] with an accent colour.
pub fn apply_with(ctx: &egui::Context, b: Brightness, accent: Accent) {
let t = Tokens::for_brightness(b).with_accent(accent);
/// [`apply`] with an accent colour and colour scheme.
pub fn apply_with(ctx: &egui::Context, b: Brightness, accent: Accent, scheme: Scheme) {
let t = Tokens::for_scheme(scheme, b, accent);
ctx.data_mut(|d| d.insert_temp(egui::Id::NULL, t));
let mut v = if t.dark { Visuals::dark() } else { Visuals::light() };
v.panel_fill = t.panel;
@@ -467,10 +613,26 @@ mod tests {
/// in every brightness.
#[test]
fn slider_rails_show_on_every_panel() {
for b in [Brightness::Dark, Brightness::MediumDark, Brightness::MediumLight, Brightness::Light] {
let t = Tokens::for_brightness(b);
let gap = (i32::from(t.button_border.r()) - i32::from(t.panel.r())).abs();
assert!(gap >= 24, "{b:?}: rail {:?} on panel {:?}", t.button_border, t.panel);
for b in Brightness::ALL {
for scheme in Scheme::ALL {
let t = Tokens::for_scheme(scheme, b, Accent::default());
let gap = (i32::from(t.button_border.r()) - i32::from(t.panel.r())).abs();
assert!(gap >= 24, "{scheme:?} {b:?}: rail {:?} on panel {:?}", t.button_border, t.panel);
}
}
}
/// Classic keeps the Modern corners and the cards' frame, and has its own blue accent
/// whatever accent is chosen.
#[test]
fn classic_scheme_keeps_the_corners_and_its_blue() {
for b in Brightness::ALL {
let (modern, classic) = (Tokens::for_brightness(b), Tokens::for_scheme(Scheme::Classic, b, Accent::Pink));
assert_eq!(classic.radius, modern.radius);
assert_eq!(classic.dark, modern.dark, "{b:?}");
assert_ne!(classic.app_bar, classic.panel, "{b:?}: the cards need a frame");
assert_eq!(classic.accent, Color32::from_rgb(0x37, 0x8e, 0xf0));
assert_eq!(Scheme::parse(Scheme::Classic.id()), Some(Scheme::Classic));
}
}
}