mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-14 19:26:56 -04:00
User Interface › System Title Bar: native decorations on Windows/Linux, file in OS title
This commit is contained in:
1 parent
9f659195c3
commit
12f8a77a00
17 files changed
+243
-21
No files matched your search
@@ -400,6 +400,32 @@ fn adapter_summary(info: &eframe::wgpu::AdapterInfo) -> String {
|
||||
/// macOS keeps its traffic lights over the integrated title strip.
|
||||
const CUSTOM_TITLEBAR: bool = !cfg!(target_os = "macos");
|
||||
|
||||
/// Whether this start draws its own title bar: Windows and Linux do, unless Preferences ›
|
||||
/// User Interface › System Title Bar asks for the system's. Read from the saved preferences
|
||||
/// before the window opens; a missing or unreadable value keeps the default.
|
||||
fn custom_titlebar(saved: Option<&vectorcraft_ui_egui::UiState>) -> bool {
|
||||
CUSTOM_TITLEBAR && !saved.map(|ui| ui.engine_prefs.get("systemTitleBar").and_then(serde_json::Value::as_bool).unwrap_or(false)).unwrap_or(false)
|
||||
}
|
||||
|
||||
fn native_options(custom_titlebar: bool) -> eframe::NativeOptions {
|
||||
eframe::NativeOptions {
|
||||
viewport: egui::ViewportBuilder::default()
|
||||
.with_title("VectorCraft")
|
||||
.with_inner_size(window::DEFAULT_SIZE)
|
||||
.with_min_inner_size(window::MIN_SIZE)
|
||||
.with_drag_and_drop(true)
|
||||
.with_decorations(!custom_titlebar)
|
||||
.with_fullsize_content_view(true)
|
||||
.with_titlebar_shown(false)
|
||||
.with_title_shown(false)
|
||||
.with_icon(app_icon())
|
||||
.with_app_id("ai.storyteller.vectorcraft"),
|
||||
#[cfg(feature = "windows7")]
|
||||
renderer: eframe::Renderer::Glow,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> std::process::ExitCode {
|
||||
// First, so every start-up warning is recorded (`logging`).
|
||||
let logger = logging::install();
|
||||
@@ -486,22 +512,8 @@ fn main() -> std::process::ExitCode {
|
||||
}
|
||||
#[cfg(feature = "wgpu")]
|
||||
let startup = std::sync::Arc::new(gpu::Startup::default());
|
||||
let options = eframe::NativeOptions {
|
||||
viewport: egui::ViewportBuilder::default()
|
||||
.with_title("VectorCraft")
|
||||
.with_inner_size(window::DEFAULT_SIZE)
|
||||
.with_min_inner_size(window::MIN_SIZE)
|
||||
.with_drag_and_drop(true)
|
||||
.with_decorations(!CUSTOM_TITLEBAR)
|
||||
.with_fullsize_content_view(true)
|
||||
.with_titlebar_shown(false)
|
||||
.with_title_shown(false)
|
||||
.with_icon(app_icon())
|
||||
.with_app_id("ai.storyteller.vectorcraft"),
|
||||
#[cfg(feature = "windows7")]
|
||||
renderer: eframe::Renderer::Glow,
|
||||
..Default::default()
|
||||
};
|
||||
let custom_titlebar = custom_titlebar(saved.as_ref());
|
||||
let options = native_options(custom_titlebar);
|
||||
#[cfg(feature = "wgpu")]
|
||||
let options = {
|
||||
let mut options = options;
|
||||
@@ -575,7 +587,7 @@ fn main() -> std::process::ExitCode {
|
||||
{
|
||||
app.graphics_adapter = Some("OpenGL (Windows 7 compatibility)".into());
|
||||
}
|
||||
app.custom_titlebar = CUSTOM_TITLEBAR;
|
||||
app.custom_titlebar = custom_titlebar;
|
||||
if let Some(port) = control_port {
|
||||
let rx = control_server::start(port, cc.egui_ctx.clone());
|
||||
app = app.with_control(rx).with_automation_roots(roots.clone());
|
||||
@@ -618,6 +630,27 @@ 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
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_system_title_bar_preference_keeps_the_window_decorations() {
|
||||
// Preferences › User Interface › System Title Bar gives the window back its system
|
||||
// decorations on Windows and Linux; macOS always has them.
|
||||
assert_eq!(super::custom_titlebar(None), super::CUSTOM_TITLEBAR, "no preferences: the default");
|
||||
let off = ui_state_with_prefs(serde_json::json!({"systemTitleBar": false}));
|
||||
assert_eq!(super::custom_titlebar(Some(&off)), super::CUSTOM_TITLEBAR);
|
||||
let on = ui_state_with_prefs(serde_json::json!({"systemTitleBar": true}));
|
||||
assert!(!super::custom_titlebar(Some(&on)));
|
||||
assert_eq!(super::native_options(false).viewport.decorations, Some(true));
|
||||
if super::CUSTOM_TITLEBAR {
|
||||
assert_eq!(super::native_options(true).viewport.decorations, Some(false));
|
||||
}
|
||||
}
|
||||
|
||||
/// The file extensions the macOS bundle declares: its document types and its own exported type.
|
||||
fn plist_extensions(plist: &str) -> Vec<&str> {
|
||||
["<key>CFBundleTypeExtensions</key>", "<key>public.filename-extension</key>"]
|
||||
|
||||
@@ -282,6 +282,7 @@ pub const PREF_SPECS: &[PrefSpec] = &[
|
||||
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),
|
||||
|
||||
@@ -512,6 +512,10 @@ pub struct Prefs {
|
||||
/// UI language: `auto` (follow the system locale) or a language code such as `en`, `zh-hant`.
|
||||
/// The list of languages belongs to the shell (`ui-egui` i18n); an unknown code reads as `auto`.
|
||||
pub interface_language: String,
|
||||
/// Windows and Linux: use the system's title bar and window buttons instead of the app bar
|
||||
/// acting as the title bar (tiling window managers, desktops that draw their own decorations).
|
||||
/// Read when the app starts. macOS always uses the system's.
|
||||
pub system_title_bar: bool,
|
||||
// Performance
|
||||
pub gpu_performance: bool,
|
||||
pub animated_zoom: bool,
|
||||
@@ -726,6 +730,7 @@ impl Default for Prefs {
|
||||
ui_scaling: 1.0,
|
||||
scale_cursor_with_ui: false,
|
||||
interface_language: s("auto"),
|
||||
system_title_bar: false,
|
||||
gpu_performance: true,
|
||||
animated_zoom: true,
|
||||
gpu_preference: s("automatic"),
|
||||
|
||||
@@ -36,6 +36,12 @@ pub fn paint_mark(ui: &Ui, r: Rect) {
|
||||
ui.painter().image(texture(ui.ctx()).id(), r, uv, Color32::WHITE);
|
||||
}
|
||||
|
||||
/// The mark's texture id (tests: find the painted marks among the shapes).
|
||||
#[cfg(test)]
|
||||
pub fn texture_id(ctx: &Context) -> egui::TextureId {
|
||||
texture(ctx).id()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -55,6 +61,9 @@ mod tests {
|
||||
let ctx = Context::default();
|
||||
crate::theme::install_fonts(&ctx);
|
||||
let mut app = VectorcraftApp::new(vectorcraft_engine::Session::new(), Default::default());
|
||||
// The system title bar hides the in-app mark (the OS draws its own icon), so paint the
|
||||
// app-drawn bar here.
|
||||
app.custom_titlebar = true;
|
||||
let mut frame = || {
|
||||
let mut out = ctx.run_ui(egui::RawInput::default(), |ui| crate::chrome::app_bar(&mut app, ui));
|
||||
let uploads = out.textures_delta.set.values().flat_map(|d| d.iter()).filter(|d| d.image.size() == [128, 128]).count();
|
||||
|
||||
@@ -18,9 +18,12 @@ pub const APP_BAR_HEIGHT: f32 = 44.0;
|
||||
/// The application bar: brand mark, Home, menus, then Discord, search and the workspace switcher
|
||||
/// at the right. With [`VectorcraftApp::custom_titlebar`] it is also the window's title bar
|
||||
/// ([`titlebar`]): the caption buttons take the right end and the rest of the bar drags the window.
|
||||
/// With the system title bar (Windows and Linux, `system_title_bar`) the OS draws its own icon and
|
||||
/// title, so the in-app bar shows Home, menus and workspace controls only: no brand mark.
|
||||
pub fn app_bar(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
let t = Tokens::get(ui.ctx());
|
||||
let custom = app.custom_titlebar;
|
||||
let system = system_title_bar(app);
|
||||
let left = app.titlebar_inset.clamp(8.0, f32::from(i8::MAX)) as i8;
|
||||
let frame = egui::Frame::NONE.fill(t.app_bar).inner_margin(egui::Margin { left, right: if custom { 0 } else { 14 }, top: 0, bottom: 0 });
|
||||
let bar = egui::Panel::top("app_bar").exact_size(APP_BAR_HEIGHT).frame(frame.stroke(Stroke::new(1.0, t.border))).show(ui, |ui| {
|
||||
@@ -28,10 +31,12 @@ pub fn app_bar(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
titlebar::drag_area(ui, ui.max_rect());
|
||||
}
|
||||
ui.horizontal_centered(|ui| {
|
||||
// Brand mark: the app icon.
|
||||
let (r, _) = ui.allocate_exact_size(vec2(22.0, 22.0), Sense::hover());
|
||||
crate::brand::paint_mark(ui, r);
|
||||
ui.add_space(4.0);
|
||||
// With the system title bar the OS draws its own icon: no in-app brand mark.
|
||||
if !system {
|
||||
let (r, _) = ui.allocate_exact_size(vec2(22.0, 22.0), Sense::hover());
|
||||
crate::brand::paint_mark(ui, r);
|
||||
ui.add_space(4.0);
|
||||
}
|
||||
let on_home = menus::home_showing(app);
|
||||
if widgets::icon_button(ui, "house", tl!("Home"), on_home, 24.0).clicked() {
|
||||
app.run("app.home", json!({})).ok();
|
||||
@@ -109,6 +114,35 @@ pub fn app_bar(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether the window shows the system's title bar instead of the app drawing its own: Windows
|
||||
/// and Linux with Preferences › User Interface › System Title Bar. macOS always has system
|
||||
/// decorations, but keeps the in-app brand mark, so it never counts as system mode here; nor
|
||||
/// does the web build (the browser tab has no document title), nor an embedder that never
|
||||
/// turned the preference on.
|
||||
pub fn system_title_bar(app: &VectorcraftApp) -> bool {
|
||||
!app.custom_titlebar && app.session.prefs.system_title_bar && !cfg!(any(target_os = "macos", target_arch = "wasm32"))
|
||||
}
|
||||
|
||||
/// The OS window title: the active document's name suffixed with the app name (a `*` prefix
|
||||
/// marks unsaved changes, like the `*` in the document tabs), or just the app name with no
|
||||
/// document open.
|
||||
pub fn window_title(app: &VectorcraftApp) -> String {
|
||||
app.session
|
||||
.active()
|
||||
.map(|d| format!("{}{} \u{2014} VectorCraft", if d.is_dirty() { "*" } else { "" }, d.title()))
|
||||
.unwrap_or_else(|| "VectorCraft".into())
|
||||
}
|
||||
|
||||
/// Keep the OS window title (and the taskbar / Alt-Tab entry) on the active file. Sends
|
||||
/// `ViewportCommand::Title` only when the title changed since the last frame.
|
||||
pub fn sync_window_title(app: &mut VectorcraftApp, ctx: &egui::Context) {
|
||||
let want = window_title(app);
|
||||
if app.last_window_title != want {
|
||||
app.last_window_title = want.clone();
|
||||
ctx.send_viewport_cmd(egui::ViewportCommand::Title(want));
|
||||
}
|
||||
}
|
||||
|
||||
/// An anchor button: icon, tooltip, command and the convert command's `to`.
|
||||
type AnchorButton<'a> = (&'a str, &'a str, &'a str, Option<&'a str>);
|
||||
|
||||
@@ -965,4 +999,81 @@ mod tests {
|
||||
let off = hint(&mut app);
|
||||
assert!(off.contains(" to zoom into an area") && !off.contains("Hold"), "{off}");
|
||||
}
|
||||
|
||||
fn title_test_app(custom_titlebar: bool) -> crate::VectorcraftApp {
|
||||
let mut app = crate::VectorcraftApp::new(Session::new(), Default::default());
|
||||
app.custom_titlebar = custom_titlebar;
|
||||
// The preference that makes the shell launch without its own title bar.
|
||||
app.run("prefs.set", json!({"key": "systemTitleBar", "value": !custom_titlebar})).unwrap();
|
||||
app
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn without_the_preference_the_mark_stays() {
|
||||
// The default (e.g. the web build): no custom bar, no preference.
|
||||
let app = crate::VectorcraftApp::new(Session::new(), Default::default());
|
||||
assert!(!super::system_title_bar(&app));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn window_title_with_no_document_is_the_app_name() {
|
||||
assert_eq!(super::window_title(&title_test_app(true)), "VectorCraft");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn window_title_follows_the_active_file() {
|
||||
let mut app = title_test_app(true);
|
||||
app.run("file.new", json!({"width": 100, "height": 100, "name": "foo.svg"})).unwrap();
|
||||
assert_eq!(super::window_title(&app), "foo.svg \u{2014} VectorCraft");
|
||||
// Unsaved changes gain a `*` prefix, like the `*` in the document tabs.
|
||||
app.run("shape.rectangle", json!({"x": 0, "y": 0, "width": 10, "height": 10})).unwrap();
|
||||
assert_eq!(super::window_title(&app), "*foo.svg \u{2014} VectorCraft");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn system_mode_hides_the_mark() {
|
||||
for custom in [true, false] {
|
||||
let mut app = title_test_app(custom);
|
||||
app.run("file.new", json!({"width": 100, "height": 100, "name": "foo.svg"})).unwrap();
|
||||
let ctx = egui::Context::default();
|
||||
crate::theme::install_fonts(&ctx);
|
||||
let mut out = ctx.run_ui(egui::RawInput::default(), |ui| super::app_bar(&mut app, ui));
|
||||
let uploads = out.textures_delta.set.values().flat_map(|d| d.iter()).filter(|d| d.image.size() == [128, 128]).count();
|
||||
out.textures_delta.clear();
|
||||
let marks: Vec<egui::Rect> = out
|
||||
.shapes
|
||||
.iter()
|
||||
.filter_map(|c| match &c.shape {
|
||||
egui::Shape::Mesh(m) if m.texture_id == crate::brand::texture_id(&ctx) => Some(m.calc_bounds()),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
let system = super::system_title_bar(&app);
|
||||
assert_eq!(system, !custom && !cfg!(target_os = "macos"));
|
||||
assert_eq!(marks.is_empty(), system, "mark in system mode (custom={custom})");
|
||||
assert_eq!(uploads, if system { 0 } else { 1 }, "mark texture in system mode (custom={custom})");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sync_window_title_sends_only_on_change() {
|
||||
let mut app = title_test_app(true);
|
||||
let ctx = egui::Context::default();
|
||||
crate::theme::install_fonts(&ctx);
|
||||
super::sync_window_title(&mut app, &ctx);
|
||||
assert_eq!(app.last_window_title, "VectorCraft");
|
||||
let mut out = ctx.run_ui(egui::RawInput::default(), |_| {});
|
||||
out.textures_delta.clear();
|
||||
let cmds = out.viewport_output.remove(&egui::ViewportId::ROOT).map(|v| v.commands).unwrap_or_default();
|
||||
assert!(cmds.iter().any(|c| matches!(c, egui::ViewportCommand::Title(t) if t == "VectorCraft")), "{cmds:?}");
|
||||
// Same document state again: the cached title stops a repeat command.
|
||||
super::sync_window_title(&mut app, &ctx);
|
||||
let mut out = ctx.run_ui(egui::RawInput::default(), |_| {});
|
||||
out.textures_delta.clear();
|
||||
let cmds = out.viewport_output.remove(&egui::ViewportId::ROOT).map(|v| v.commands).unwrap_or_default();
|
||||
assert!(!cmds.iter().any(|c| matches!(c, egui::ViewportCommand::Title(_))), "repeat Title: {cmds:?}");
|
||||
app.run("file.new", json!({"width": 100, "height": 100, "name": "foo.svg"})).unwrap();
|
||||
super::sync_window_title(&mut app, &ctx);
|
||||
assert_eq!(app.last_window_title, "foo.svg \u{2014} VectorCraft");
|
||||
}
|
||||
}
|
||||
@@ -673,3 +673,5 @@
|
||||
Patchwork… Patchwork…
|
||||
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í.
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3975,3 +3975,5 @@ axis Both Ambos
|
||||
Top Right Arriba a la derecha
|
||||
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.
|
@@ -3987,3 +3987,5 @@ axis Both Les deux
|
||||
Bottom Right En bas à droite
|
||||
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.
|
@@ -3977,3 +3977,5 @@ axis Both Entrambi
|
||||
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.
|
@@ -3970,3 +3970,5 @@ effect Soft ソフト
|
||||
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.
|
@@ -2733,3 +2733,5 @@
|
||||
Patchwork… Colcha de retalhos…
|
||||
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.
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -3973,3 +3973,5 @@ axis Both Оба
|
||||
Top Right Сверху справа
|
||||
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.
|
@@ -3958,3 +3958,5 @@ axis Both Обидві
|
||||
Top Right Вгорі праворуч
|
||||
Bottom Right Знизу праворуч
|
||||
Edge Край
|
||||
System Title Bar Системний рядок заголовка
|
||||
Applies at next launch. Набуде чинності після перезапуску.
|
||||
|
Can't render this file because it contains an unexpected character in line 659 and column 40.
|
@@ -3606,3 +3606,5 @@ effect Soft 柔和
|
||||
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.
|
@@ -3387,3 +3387,5 @@ axis Both 兩者
|
||||
Bottom Right 右下
|
||||
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.
|
@@ -353,6 +353,9 @@ pub struct VectorcraftApp {
|
||||
/// Windows and Linux: the window has no OS decorations, so the app bar is the title bar (drag,
|
||||
/// double-click to maximize, caption buttons) and invisible edge zones resize the window.
|
||||
pub custom_titlebar: bool,
|
||||
/// Last window title sent to the OS (`ViewportCommand::Title`, see `chrome::sync_window_title`):
|
||||
/// sent again only when it changes, so idle frames don't spam the backend.
|
||||
pub(crate) last_window_title: String,
|
||||
/// The graphics adapter the window renders with ("name (backend)"), as the host reports it:
|
||||
/// shown in Help › About and `ui.inspect` for GPU bug reports. `None` when unknown.
|
||||
pub graphics_adapter: Option<String>,
|
||||
@@ -462,6 +465,7 @@ impl VectorcraftApp {
|
||||
canvas_rect: None,
|
||||
hover_doc: None,
|
||||
custom_titlebar: false,
|
||||
last_window_title: String::new(),
|
||||
graphics_adapter: None,
|
||||
place: Default::default(),
|
||||
system_paste: false,
|
||||
@@ -958,6 +962,9 @@ impl VectorcraftApp {
|
||||
}
|
||||
shortcut_editor::sync(&self.ui);
|
||||
prefs_dialog::apply_runtime(self, ctx);
|
||||
// The OS title bar (and the taskbar / Alt-Tab entry) follows the active file; with the
|
||||
// system title bar this is where the document name lives, as the in-app mark is hidden.
|
||||
crate::chrome::sync_window_title(self, ctx);
|
||||
self.drain_control(ctx);
|
||||
if !self.synthetic.is_empty() {
|
||||
ctx.request_repaint();
|
||||
|
||||
@@ -261,6 +261,11 @@ 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));
|
||||
}
|
||||
PrefKind::Bool => bool_row(ui, d, sp.key, sp.label),
|
||||
PrefKind::Num { min, max, unit } => {
|
||||
labeled(ui, sp.label, |ui| {
|
||||
@@ -530,6 +535,37 @@ mod tests {
|
||||
assert_eq!(saved["engine_prefs"]["gpuPreference"], json!("highPerformance"));
|
||||
}
|
||||
|
||||
/// User Interface › System Title Bar: shown with its next-launch note, applied by OK and
|
||||
/// saved with the UI state under the key the desktop app reads before the window opens.
|
||||
#[test]
|
||||
fn system_title_bar_preference_shows_and_persists() {
|
||||
fn texts(s: &egui::Shape, out: &mut Vec<String>) {
|
||||
match s {
|
||||
egui::Shape::Text(t) => out.push(t.galley.text().to_string()),
|
||||
egui::Shape::Vec(v) => v.iter().for_each(|s| texts(s, out)),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
let mut a = app();
|
||||
open(&mut a, Some("User Interface"));
|
||||
let ctx = egui::Context::default();
|
||||
crate::theme::install_fonts(&ctx);
|
||||
// The window sizes itself on the first frame and draws on the second.
|
||||
let mut out = ctx.run_ui(egui::RawInput::default(), |ui| show(&mut a, ui.ctx()));
|
||||
out.textures_delta.clear();
|
||||
let mut out = ctx.run_ui(egui::RawInput::default(), |ui| show(&mut a, ui.ctx()));
|
||||
out.textures_delta.clear();
|
||||
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:?}");
|
||||
a.ui.dialog.as_mut().unwrap().fields.insert("systemTitleBar".into(), json!(true));
|
||||
confirm(&mut a).unwrap();
|
||||
assert!(a.session.prefs.system_title_bar);
|
||||
let saved: Value = serde_json::from_slice(&serde_json::to_vec(&a.ui).unwrap()).unwrap();
|
||||
assert_eq!(saved["engine_prefs"]["systemTitleBar"], json!(true));
|
||||
}
|
||||
|
||||
/// Units › Numbers Without Units Are Points (#394): the preference reaches the fields when it
|
||||
/// changes, and its checkbox is dimmed unless a unit is Picas.
|
||||
#[test]
|
||||
|
||||
Reference in new issue
Block a user