Review: System Title Bar only where it applies, docs, title repaint

- Preferences hides System Title Bar on macOS and the web, where it did
  nothing (it fell through to the plain checkbox row), and lists it with
  the window options instead of under Language.
- The next-launch note reuses "Applies the next time VectorCraft starts."
  instead of a second wording in every catalog.
- A new window title asks for one more frame: AppKit lays the title bar
  out again for it, and the next frame centres the window buttons (#968).
- The menu bar drops the bold branch for the VectorCraft menu it no
  longer shows; theme is imported where the tests use it.
- Docs: Language is Preferences › User Interface › Language (VectorCraft
  › Language on macOS); the control protocol describes System Title Bar;
  ROADMAP Last updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
bflatastic 2026-10-10 21:01:01 +07:00
parent 2e643b0e76
commit 6315086f96
19 changed files with 28 additions and 35 deletions

File diff suppressed because one or more lines are too long

View File

@ -630,10 +630,8 @@ fn main() -> std::process::ExitCode {
mod tests {
use super::*;
fn ui_state_with_prefs(prefs: serde_json::Value) -> vectorcraft_ui_egui::UiState {
let mut ui = vectorcraft_ui_egui::UiState::default();
ui.engine_prefs = prefs;
ui
fn ui_state_with_prefs(engine_prefs: serde_json::Value) -> vectorcraft_ui_egui::UiState {
vectorcraft_ui_egui::UiState { engine_prefs, ..Default::default() }
}
#[test]

View File

@ -278,11 +278,11 @@ pub const PREF_SPECS: &[PrefSpec] = &[
p!("toolGroupLabels", "User Interface", "", "Show Tool Group Labels", bool),
p!("openDocumentsAsTabs", "User Interface", "", "Open Documents As Tabs", bool),
p!("largeTabs", "User Interface", "", "Large Tabs", bool),
p!("systemTitleBar", "User Interface", "", "System Title Bar", bool),
p!("uiScaling", "User Interface", "UI Scaling", "Scale", num(0.75, 2.0, "×")),
p!("scaleCursorWithUi", "User Interface", "UI Scaling", "Scale Cursor Proportional to UI", bool),
// `auto` or a language code the shell registers (`zh-hant`); the shell shows it as a dropdown.
p!("interfaceLanguage", "User Interface", "Language", "Language", text),
p!("systemTitleBar", "User Interface", "", "System Title Bar", bool),
// Performance
p!("gpuPerformance", "Performance", "GPU Performance", "GPU Performance", bool),
p!("animatedZoom", "Performance", "GPU Performance", "Animated Zoom", bool),

View File

@ -130,6 +130,9 @@ pub fn sync_window_title(app: &mut VectorcraftApp, ctx: &egui::Context) {
if app.last_window_title != want {
app.last_window_title = want.clone();
ctx.send_viewport_cmd(egui::ViewportCommand::Title(want));
// One more frame: a new title makes AppKit lay its title bar out again, putting the window
// buttons back where it keeps them until the next frame centres them on the bar (#968).
ctx.request_repaint();
}
}

View File

@ -673,5 +673,4 @@
Stained Glass… Vitráž…
Texturizer… Texturovač…
System Title Bar Systémové záhlaví okna
Applies at next launch. Projeví se při příštím spuštění.
Exit Ukončit

Can't render this file because it contains an unexpected character in line 2 and column 73.

View File

@ -3976,4 +3976,3 @@ axis Both Ambos
Bottom Right Abajo a la derecha
Edge Borde
System Title Bar Barra de título del sistema
Applies at next launch. Se aplicará en el próximo inicio.

Can't render this file because it contains an unexpected character in line 2 and column 73.

View File

@ -3988,4 +3988,3 @@ axis Both Les deux
effect Soft Doux
Edge Bord
System Title Bar Barre de titre du système
Applies at next launch. Prend effet au prochain démarrage.

Can't render this file because it contains an unexpected character in line 2 and column 73.

View File

@ -3978,4 +3978,3 @@ effect Soft Morbido
effect Regular Normale
Edge Bordo
System Title Bar Barra del titolo di sistema
Applies at next launch. Si applica al prossimo avvio.

Can't render this file because it contains an unexpected character in line 2 and column 73.

View File

@ -3971,4 +3971,3 @@ effect Horizontal 水平
effect Vertical 垂直
Edge エッジ
System Title Bar システムのタイトルバー
Applies at next launch. 次回の起動時に適用されます。

Can't render this file because it contains an unexpected character in line 2 and column 73.

View File

@ -2733,5 +2733,4 @@
Stained Glass… Vitral…
Texturizer… Texturizador…
System Title Bar Barra de título do sistema
Applies at next launch. Aplica-se na próxima inicialização.
Exit Sair

Can't render this file because it contains an unexpected character in line 6 and column 47.

View File

@ -3974,4 +3974,3 @@ axis Both Оба
Bottom Right Снизу справа
Edge Край
System Title Bar Системная строка заголовка
Applies at next launch. Применится при следующем запуске.

Can't render this file because it contains an unexpected character in line 2 and column 73.

View File

@ -3959,4 +3959,3 @@ axis Both Обидві
Bottom Right Знизу праворуч
Edge Край
System Title Bar Системний рядок заголовка
Applies at next launch. Набуде чинності після перезапуску.

Can't render this file because it has a wrong number of fields in line 13.

View File

@ -3607,4 +3607,3 @@ effect Horizontal 水平
effect Vertical 垂直
Edge 边缘
System Title Bar 系统标题栏
Applies at next launch. 下次启动时生效。

Can't render this file because it contains an unexpected character in line 2 and column 73.

View File

@ -3388,4 +3388,3 @@ axis Both 兩者
effect Soft 柔和
Edge 邊緣
System Title Bar 系統標題列
Applies at next launch. 下次啟動時套用。

Can't render this file because it contains an unexpected character in line 6 and column 47.

View File

@ -13,7 +13,7 @@ use crate::VectorcraftApp;
use crate::io;
use crate::panels::character::Face;
use crate::state::{DockTab, next_zoom};
use crate::theme::{self, Brightness, Tokens};
use crate::theme::{Brightness, Tokens};
use crate::widgets;
#[derive(Clone, Debug)]
@ -2754,11 +2754,7 @@ pub fn menu_bar(app: &mut VectorcraftApp, ui: &mut egui::Ui) -> f32 {
.ui(ui, |ui| {
let mut titles = Vec::with_capacity(tree.len());
for (title, items) in tree.iter() {
let text = if *title == "VectorCraft" {
egui::RichText::new(tl!(title)).font(theme::semibold(13.0)).color(t.text)
} else {
egui::RichText::new(tl!(title)).size(13.0).color(t.text)
};
let text = egui::RichText::new(tl!(title)).size(13.0).color(t.text);
titles.push(ui.menu_button(text, |ui| menu_body(app, ui, items, &mut clicked)).response);
}
(ui.cursor().min.x, switch_on_hover(ui.ctx(), &titles))
@ -3520,6 +3516,7 @@ pub fn menu_strings() -> std::collections::BTreeSet<String> {
#[cfg(test)]
mod tests {
use super::*;
use crate::theme;
/// One headless frame of the in-window menu bar; returns its titles (left to right) as
/// (rect, id of the title's popup).

View File

@ -261,10 +261,13 @@ fn category_fields(ui: &mut egui::Ui, d: &mut Dialog, cat: &str) {
PrefKind::Bool if sp.key == "animatedZoom" => {
ui.add_enabled_ui(d.bool("gpuPerformance"), |ui| bool_row(ui, d, sp.key, sp.label));
}
PrefKind::Bool if sp.key == "systemTitleBar" && cfg!(all(not(target_arch = "wasm32"), not(target_os = "macos"))) => {
bool_row(ui, d, sp.key, sp.label);
// The window's decorations are chosen once, when the app starts.
ui.label(egui::RichText::new(tl!("Applies at next launch.")).color(t.text_dim).size(11.0));
// Windows and Linux only: macOS always has the system's title bar, the web has none.
PrefKind::Bool if sp.key == "systemTitleBar" => {
if cfg!(all(not(target_arch = "wasm32"), not(target_os = "macos"))) {
bool_row(ui, d, sp.key, sp.label);
// The window's decorations 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));
}
}
PrefKind::Bool => bool_row(ui, d, sp.key, sp.label),
PrefKind::Num { min, max, unit } => {
@ -558,7 +561,7 @@ mod tests {
let mut shown = vec![];
out.shapes.iter().for_each(|c| texts(&c.shape, &mut shown));
assert!(shown.iter().any(|t| t == "System Title Bar"), "{shown:?}");
assert!(shown.iter().any(|t| t == "Applies at next launch."), "{shown:?}");
assert!(shown.iter().any(|t| t == "Applies the next time VectorCraft starts."), "{shown:?}");
a.ui.dialog.as_mut().unwrap().fields.insert("systemTitleBar".into(), json!(true));
confirm(&mut a).unwrap();
assert!(a.session.prefs.system_title_bar);

View File

@ -208,8 +208,10 @@ Tile Edge Color: Object → Pattern → Tile Edge Color… (`ui.tileEdgeColor`)
Window title bar: on Windows and Linux the window has no OS decorations and the app bar is the title bar. Its
caption buttons (Minimize, Maximize/Restore, Close) sit at the bar's right end, 46 pt each; they are window chrome,
not commands, so drive them with `ui.click` if needed. Close runs `app.quit` (the same `saveChanges` questions for
modified documents), and empty bar space and the 5 pt window edges move and resize the window. macOS and the web
build are unchanged.
modified documents), and empty bar space and the 5 pt window edges move and resize the window. Preferences › User
Interface › System Title Bar (`systemTitleBar`, read when the app starts) gives the window its OS decorations back
instead: the OS title then shows the active document (`*name — VectorCraft` with unsaved changes) and the app bar
drops its brand mark. On macOS the window buttons are centred on the app bar; the web build is unchanged.
Fill/Stroke chips and panel shortcuts: the Control bar's and Properties' Fill and Stroke chips bring their proxy
forward (`paint.toggleActive {fill}`) and open a popover with the Swatches panel (Shift-click: the Color panel's mixer);

View File

@ -86,7 +86,7 @@ By default VectorCraft's own crates log at `info` and everything else at `warn`.
## macOS: the menu bar
On macOS the menus live in the system menu bar, not in the window's app bar (which keeps the brand mark, Home, the search box and the workspace switcher next to the traffic lights). `--in-window-menus` or `VECTORCRAFT_IN_WINDOW_MENUS=1` keeps them in the window, as on Windows, Linux and the web; so does a menu bar AppKit refuses to build (logged). `ui.inspect` says which with `nativeMenuBar`.
On macOS the menus live in the system menu bar, not in the window's app bar (which keeps the brand mark, Home, the search icon and the workspace switcher next to the traffic lights, centred on the bar by `apps/vectorcraft/src/mac_window.rs`). `--in-window-menus` or `VECTORCRAFT_IN_WINDOW_MENUS=1` keeps them in the window, as on Windows, Linux and the web; so does a menu bar AppKit refuses to build (logged). `ui.inspect` says which with `nativeMenuBar`.
- **One menu model.** `crates/ui-egui/src/native_menu.rs` (platform-free, tested on every platform) builds the menu bar from the in-window menus (`menus::menu_tree`, each item through `menus::entry`, which the in-window menus draw too) and lays it out the Mac way: the VectorCraft menu holds About, Settings ▸ (one item per Preferences page, General… ⌘K), Language ▸, Appearance ▸ (UI brightness), Services, Hide VectorCraft ⌃⌘H (⌘H stays View › Hide Edges), Hide Others ⌥⌘H, Show All and Quit ⌘Q (the app's own `app.quit`, so unsaved documents are asked about); those leave Edit and Help. The in-window bar itself never shows the VectorCraft menu (it starts at File, as in Illustrator, on every platform): Language and Appearance have no in-window menu and are synthesized in `mac_layout`, and Quit is File › Exit in the window, relabelled to Quit in the App menu. Window starts with Minimize ⌃⌘M and Zoom and ends with Bring All to Front, and Help is the system's Help menu (its search field). A system key a command (or a user's shortcut) already has stays the command's, and the log says so.
- **The native side** is `apps/vectorcraft/src/mac_menu.rs`: muda (`=0.21.1`, default features off, macOS only, no `unsafe`) builds the `NSMenu`s. Labels, enabled and checked are updated in place when anything they show may have changed (a command ran, the document, its history or selection changed, a click or a key press); a change of structure (a recent file, the language, a shortcut) rebuilds the menus.
@ -182,8 +182,8 @@ locale such as `ru-RU`, `ru-BY` or `ru-KZ` resolves to it), Ukrainian (`uk`, com
- The shared widgets (`widgets::check`, `dropdown`, `menu_item`, the buttons, `label_row`, tooltips of
`icon_button`…), the menus, the dock, the toolbar, the dialog frame and `panels::empty_state` translate
the text they are given, so a panel mostly needs its literals wrapped in `tl!` to be covered by the tests.
- The language is VectorCraft › Language (the `app.language` UI command, `{lang: auto|<code>}`) or Edit ›
Preferences › User Interface › Language; both set the `interfaceLanguage` preference (`auto` or a language
- The language is Edit › Preferences › User Interface › Language or, on macOS, VectorCraft › Language (the
`app.language` UI command, `{lang: auto|<code>}`); both set the `interfaceLanguage` preference (`auto` or a language
code; `auto` follows the system locale: `VECTORCRAFT_LOCALE`, then `LC_ALL`/`LC_MESSAGES`/`LANG`/`LANGUAGE`,
the macOS preferred languages, the Windows user locale; on the web, `?lang=<code>` in the address, then
the browser's `navigator.languages`). The Preferences dialog previews the chosen language before OK.

View File

@ -2601,7 +2601,7 @@ without ideographs uses its em box. One undo step.
## New type in a Japanese interface
While the interface is in Japanese (VectorCraft › Language, or `auto` on a Japanese system), new type starts with em
While the interface is in Japanese (Preferences › User Interface › Language, or `auto` on a Japanese system), new type starts with em
box top-to-top leading (`leadingModel: "emBoxTop"`) and em box centre character alignment (`charAlign: "emBoxCenter"`);
in other languages it starts on the Roman baseline (#432). This covers the Type tools,
`text.create` and `text.createInPath`, which also take `leadingModel` and `charAlign` to choose, and `paraStyle.new` /