UI: theme picker with light and dark theme cards (#1082)

* UI: theme picker with light and dark theme cards

* UI: update the icon count and the theme test for the cycling theme button

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
This commit is contained in:
Luniiya
2026-10-11 13:12:47 -04:00
committed by GitHub
co-authored by Brandon Thomas
parent 7f1b632519
commit 046df9dfe4
26 changed files with 392 additions and 33 deletions
+2 -1
View File
@@ -4,7 +4,7 @@
Every asset PdfCraft includes, bundles or uses to build its published material, with its author, source and licence. The policy is in [AGENTS.md](AGENTS.md) §1. The machine-readable list, with SHA-256 hashes, is [ATTRIBUTION.toml](ATTRIBUTION.toml). Licence texts are kept beside the assets and summarised in [NOTICE](NOTICE).
## In this repository (264)
## In this repository (265)
| Asset | Title | Author | Licence | Source | Used for |
|---|---|---|---|---|---|
@@ -272,6 +272,7 @@ Every asset PdfCraft includes, bundles or uses to build its published material,
| `packaging/macos/dmg/background.svg` | PdfCraft macOS DMG window background | @XusBadia | MIT OR Apache-2.0 | Original work for this repository, derived from the PdfCraft app icon (assets/app-icon/pdfcraft.svg, referenced via an SVG image element, not copied) | Finder window of the macOS DMG; rendered to background.tiff by packaging/macos/dmg/generate.py |
| `packaging/macos/dmg/background.tiff` | PdfCraft macOS DMG window background (rendered) | @XusBadia | MIT OR Apache-2.0 | Rendered from packaging/macos/dmg/background.svg by packaging/macos/dmg/generate.py | 1x + 2x HiDPI 16-colour palette TIFF copied into the macOS DMG by packaging/macos/package.sh; includes the app icon and the free LittleCMS built-in sRGB profile |
| `crates/sign/tests/data/x509-rsa-sha1.pdf` | One-page PDF with a legacy adbe.x509.rsa_sha1 signature by the RSA test key | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: hand-written PDF objects; signature made with `openssl dgst -sha1 -sign` over its byte ranges (see crates/sign/tests/data/README.md) | Signature validation tests (crates/sign/tests/pdf.rs): the legacy adbe.x509.rsa_sha1 format |
| `assets/icons/monitor.svg` | Lucide icon "monitor" | Lucide Contributors (portions Cole Bemis, Feather) | ISC | https://github.com/lucide-icons/lucide (npm lucide-static 1.49.0, icons/monitor.svg) | UI icon, embedded via crates/ui-egui/src/icon_data.rs |
## Compiled in through dependencies (6)
+26 -15
View File
@@ -19,7 +19,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Simplified Chinese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "1cc32f906351568117e0bf64d67cc4aac364772b1fb1cb884bff4b552f7c545d"
sha256 = "eb5bd161938e18015a635d1079fd2b6e4abb77b2f50de2a89ee402cd7ed4020c"
[[asset]]
path = "crates/ui-egui/src/i18n/zh-hant.tsv"
@@ -30,7 +30,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Traditional Chinese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "472f3ebfa5e9831a92aabf469acab5cdb6ae8af6add95611f50f3b74c420575c"
sha256 = "e1a04a188a6b1a9bff4301916bd202bcd46718e22206f86598e923b791364c79"
[[asset]]
path = "crates/ui-egui/src/i18n/cs.tsv"
@@ -52,7 +52,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Brazilian Portuguese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "e0ac6eaa707d1e52505d55f401a442af5674336596f27a4a271b48b91337551e"
sha256 = "d944c1ac98f8cc50de8878686c9145c7995945a567c7f7ebce3fe365fc37037c"
[[asset]]
path = "crates/ui-egui/src/i18n/es.tsv"
@@ -63,7 +63,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Spanish UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "db87ab4ba9b7cd2e8547b24cc334ba01af6dbb2a5b1fbb33dd51a8fb44157198"
sha256 = "ed63bb07b8cf1eea833572066a0dcb0cfc5ae2a54eac206b09661fb8743bedfe"
[[asset]]
path = "crates/ui-egui/src/i18n/fr.tsv"
@@ -74,7 +74,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "French UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "6f1cd0b54493510fb22566b0659476f98f5937bffe9742de91092edc3053f68f"
sha256 = "7ae1cb58826e2eb7c2fd381173d232d7f4e5017f6ed557ddd65d4bacf2d69624"
[[asset]]
path = "crates/ui-egui/src/i18n/de.tsv"
@@ -85,7 +85,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "German UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "d95feff5318f449d7a438a3100f4190d0d570afc1ba40b55ecebd0e7edfc3288"
sha256 = "d8b5578203cb43a175cc24bbfe1ae931717f01499a05c381823891f8b88a21e7"
[[asset]]
path = "crates/ui-egui/src/i18n/te.tsv"
@@ -96,7 +96,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Telugu UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "41c9cc2664abb82149d88460b35494022dd72f8f10b074c7972154c556cf5e16"
sha256 = "926c6c6ca1c376b159779588d77c5d033e2ddab105ad97d097417c1b7c598690"
[[asset]]
path = "crates/ui-egui/src/i18n/it.tsv"
@@ -107,7 +107,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Italian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "15736e8b0b22c34d151d3998fb342f0dc1e3d0ebda6baefd56c27581018ad07e"
sha256 = "3831aaeda30436a69ac35cc279726d4f1ba006013cf38e4d94304a7b492d1245"
[[asset]]
path = "crates/ui-egui/src/i18n/pl.tsv"
@@ -118,7 +118,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Polish UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "2e36c61bb681d1d357604254d7b38c86564f8b3b52d72382f050529ac224285e"
sha256 = "fefa6bc66dd019254fdd9aa9ad85a861d58f1e4c4ab55e01bf7dbacd7fd054e8"
[[asset]]
path = "crates/ui-egui/src/i18n/ru.tsv"
@@ -129,7 +129,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Russian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "4b253cfe16540a7f2a24f50be28edf3ceef8a4e2db092ebecb97cc325be5a50a"
sha256 = "d82b74ebc99a82963d9f056643aa08b640115bbab0daf76569704b2984e28800"
[[asset]]
path = "crates/ui-egui/src/i18n/hu.tsv"
@@ -140,7 +140,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Hungarian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "1bebc7f5441412596124e46f304f446419e55cb8468e18f7ab52dc5562950484"
sha256 = "feb5ae147ab9647d5233a80d42c1e9a2e6c486813db9989969da75ea093b2c1b"
[[asset]]
path = "crates/ui-egui/src/i18n/bg.tsv"
@@ -151,7 +151,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Bulgarian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "3f8908a3577fa118300e47cbfe25b3d792775365cbfae0aa07e6178137d4beee"
sha256 = "706c0a4dfbbc6eda15ddde838ccf2ebac3f9af00d8a402c4a59450c660172e10"
[[asset]]
path = "crates/ui-egui/src/i18n/ja.tsv"
@@ -162,7 +162,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Japanese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "0c522c8292bc55c2ba8065378202feda081f02f03029dfe06a9b65aba625c677"
sha256 = "27aa500c5b9daef63a0fdf3cbff60cc31242195f28100b5b20fd96c88b838765"
[[asset]]
path = "crates/ui-egui/src/i18n/uk.tsv"
@@ -173,7 +173,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Ukrainian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "9cc716897f6d3e6a4da91eaadb0aff82117d5c6145575c5f33fb943b557fffe2"
sha256 = "2ded225e2bc1cb210c90a1e76ca4ec4a5613b0f4ac1ed3a3c1cbe1fafeccb34d"
[[asset]]
path = "crates/ui-egui/src/i18n/ar.tsv"
@@ -184,7 +184,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Arabic UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "c6c85ed20d1135f0ff605d28e2fe0c9b0ecf03bde84753d184f2401965bc9bdb"
sha256 = "65ba0e77aec38b0717ff146a6aff1a6b40f14b4de1f3b216ba0babceb9075376"
[[asset]]
path = "assets/icons/accessibility.svg"
@@ -3285,3 +3285,14 @@ licence_file = "LICENSE-MIT"
kind = "document"
usage = "Signature validation tests (crates/sign/tests/pdf.rs): the legacy adbe.x509.rsa_sha1 format"
sha256 = "b77bfb21166f4bd043597e45a1ea28b9d9f4ba6df0142b3a0fc8b7f8e0fc111b"
[[asset]]
path = "assets/icons/monitor.svg"
title = "Lucide icon \"monitor\""
author = "Lucide Contributors (portions Cole Bemis, Feather)"
source = "https://github.com/lucide-icons/lucide (npm lucide-static 1.49.0, icons/monitor.svg)"
licence = "ISC"
licence_file = "assets/icons/LICENSE-lucide.txt"
kind = "icon"
usage = "UI icon, embedded via crates/ui-egui/src/icon_data.rs"
sha256 = "d1a443233345724859de8e4fb48968ba91e885e12ddbd710701548bd91b39428"
+15
View File
@@ -0,0 +1,15 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<rect width="20" height="14" x="2" y="3" rx="2" />
<line x1="8" x2="16" y1="21" y2="21" />
<line x1="12" x2="12" y1="17" y2="21" />
</svg>

After

Width:  |  Height:  |  Size: 346 B

+181
View File
@@ -0,0 +1,181 @@
//! Preferences › Appearance: the appearance mode (follow the system, light or dark) above a light
//! and a dark theme card, each with a small picture of PdfCraft in that theme.
use egui::{Rect, RichText, pos2, vec2};
use crate::PdfCraftApp;
use crate::theme::{self, ThemeKind, ThemePreference, Tokens};
/// The themes each card offers. One of each today; a new theme is one more row here.
pub const LIGHT_THEMES: &[(ThemeKind, &str)] = &[(ThemeKind::Light, "Light gray")];
pub const DARK_THEMES: &[(ThemeKind, &str)] = &[(ThemeKind::Dark, "Dark gray")];
fn mode_label(preference: ThemePreference) -> &'static str {
match preference {
ThemePreference::System => "Use system setting",
ThemePreference::Light => "Light",
ThemePreference::Dark => "Dark",
}
}
fn mode_command(preference: ThemePreference) -> &'static str {
match preference {
ThemePreference::System => "view.theme.system",
ThemePreference::Light => "view.theme.light",
ThemePreference::Dark => "view.theme.dark",
}
}
/// A miniature PdfCraft window in `kind`'s colours: title bar and tabs, the tool bar, the page
/// thumbnails, a page on the pasteboard and the right-hand panel.
fn preview(ui: &mut egui::Ui, kind: ThemeKind, width: f32) {
let p = Tokens::for_kind(kind);
let (rect, _) = ui.allocate_exact_size(vec2(width, 118.0), egui::Sense::hover());
let painter = ui.painter_at(rect);
painter.rect_filled(rect, 3.0, p.chrome);
let title = Rect::from_min_size(rect.min, vec2(rect.width(), 12.0));
painter.rect_filled(title, 0.0, p.titlebar);
// The active tab and the theme button.
painter.rect_filled(Rect::from_min_size(title.min + vec2(6.0, 3.0), vec2(36.0, 9.0)), 2.0, p.panel);
painter.rect_filled(Rect::from_min_size(title.min + vec2(9.0, 6.0), vec2(20.0, 2.0)), 1.0, p.text);
painter.circle_filled(pos2(title.right() - 7.0, title.center().y), 2.5, p.icon);
// The tool bar, with its first tool active.
let tools = Rect::from_min_size(pos2(rect.left(), title.bottom()), vec2(rect.width(), 12.0));
painter.rect_filled(tools, 0.0, p.panel);
for i in 0..8 {
let c = if i == 0 { p.accent } else { p.icon };
painter.rect_filled(Rect::from_min_size(tools.min + vec2(8.0 + i as f32 * 9.0, 3.5), vec2(5.0, 5.0)), 1.0, c);
}
let body = Rect::from_min_max(pos2(rect.left(), tools.bottom() + 1.0), rect.max);
// Page thumbnails on the left, the second one selected.
let rail = Rect::from_min_size(body.min, vec2((body.width() * 0.16).round(), body.height()));
painter.rect_filled(rail, 0.0, p.panel);
for i in 0..3 {
let thumb = Rect::from_min_size(rail.min + vec2(rail.width() * 0.25, 6.0 + i as f32 * 26.0), vec2(rail.width() * 0.5, 20.0));
if i == 1 {
painter.rect_filled(thumb.expand(2.0), 2.0, p.accent_soft);
}
painter.rect_filled(thumb, 1.0, egui::Color32::WHITE);
painter.rect_stroke(thumb, 1.0, egui::Stroke::new(0.5, p.border), egui::StrokeKind::Outside);
}
// The right-hand panel: a heading and a few rows.
let side = Rect::from_min_max(pos2(body.right() - (body.width() * 0.22).round(), body.top()), body.max);
painter.rect_filled(side, 0.0, p.panel);
painter.rect_filled(Rect::from_min_size(side.min + vec2(5.0, 5.0), vec2(side.width() * 0.5, 2.5)), 1.0, p.text);
for i in 0..4 {
let y = side.top() + 14.0 + i as f32 * 8.0;
painter.rect_filled(Rect::from_min_size(pos2(side.left() + 5.0, y), vec2(side.width() * 0.7, 2.0)), 1.0, p.text_muted);
}
// The page, white in both themes, with a heading and lines of text.
let paste = Rect::from_min_max(pos2(rail.right(), body.top()), pos2(side.left(), body.bottom()));
painter.rect_filled(paste, 0.0, p.pasteboard);
let page_h = paste.height() - 10.0;
let page = Rect::from_center_size(paste.center(), vec2(page_h * 0.77, page_h));
painter.rect_filled(page.translate(vec2(1.0, 1.0)), 1.0, p.page_shadow);
painter.rect_filled(page, 1.0, egui::Color32::WHITE);
let ink = egui::Color32::from_gray(0x60);
painter.rect_filled(Rect::from_min_size(page.min + vec2(6.0, 7.0), vec2(page.width() * 0.5, 3.0)), 1.0, egui::Color32::from_gray(0x30));
for i in 0..8 {
let w = if i % 4 == 3 { 0.45 } else { 0.8 };
painter.rect_filled(Rect::from_min_size(page.min + vec2(6.0, 15.0 + i as f32 * 6.0), vec2(page.width() * w - 6.0, 1.5)), 0.5, ink);
}
}
/// One theme card: its title, Active when it is what shows now, a preview of the chosen theme
/// and a radio row per theme.
fn card(ui: &mut egui::Ui, t: &Tokens, title: &str, active: bool, themes: &[(ThemeKind, &str)]) {
egui::Frame::new()
.fill(t.card)
.stroke(egui::Stroke::new(1.0, if active { t.accent } else { t.border }))
.corner_radius(6.0)
.inner_margin(egui::Margin::same(8))
.show(ui, |ui| {
ui.set_width(ui.available_width());
ui.horizontal(|ui| {
ui.label(RichText::new(title).font(theme::semibold(12.5)).color(t.text));
if active {
ui.label(RichText::new(tl!("Active")).color(t.accent_text).small());
}
});
ui.add_space(4.0);
let shown = themes.first().map_or(ThemeKind::Light, |(kind, _)| *kind);
preview(ui, shown, ui.available_width());
ui.add_space(4.0);
for (kind, label) in themes {
// One theme per card today, so it is always the chosen one.
let _ = ui.radio(*kind == shown, tl!(label));
}
});
}
/// The Appearance section of Preferences.
pub fn preferences_section(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tokens) {
ui.label(RichText::new(tl!("Appearance")).font(theme::semibold(13.0)));
ui.horizontal(|ui| {
ui.label(tl!("Appearance mode"));
egui::ComboBox::from_id_salt("appearance-mode").selected_text(tl!(mode_label(app.theme_preference))).show_ui(ui, |ui| {
for preference in [ThemePreference::System, ThemePreference::Light, ThemePreference::Dark] {
if ui.selectable_label(app.theme_preference == preference, tl!(mode_label(preference))).clicked() {
app.execute(mode_command(preference));
ui.close();
}
}
});
});
ui.add_space(4.0);
let light_active = app.theme == ThemeKind::Light;
ui.columns(2, |cols| {
let [light, dark] = cols else { return };
card(light, t, tl!("Light theme"), light_active, LIGHT_THEMES);
card(dark, t, tl!("Dark theme"), !light_active, DARK_THEMES);
});
}
#[cfg(test)]
mod tests {
use egui_kittest::Harness;
use egui_kittest::kittest::Queryable;
use super::*;
/// The mode is chosen in Preferences and both theme cards show, the one on screen marked
/// Active.
#[test]
fn preferences_choose_the_appearance_mode_and_show_both_themes() {
let mut app = PdfCraftApp::new();
app.set_option("language", "en").unwrap();
let mut fonts = false;
let mut h = Harness::builder().with_size(egui::vec2(640.0, 400.0)).build_ui_state(
move |ui, app: &mut PdfCraftApp| {
// The app's fonts (the semibold headings) take effect from the next frame.
if !fonts {
ui.ctx().set_fonts(theme::font_definitions());
fonts = true;
return;
}
let t = Tokens::get(ui.ctx());
preferences_section(ui, app, &t);
},
app,
);
h.run();
assert!(h.query_by_label("Light theme").is_some() && h.query_by_label("Dark theme").is_some());
assert!(h.query_by_label("Light gray").is_some() && h.query_by_label("Dark gray").is_some());
assert_eq!(h.query_all_by_label("Active").count(), 1);
if let Ok(path) = std::env::var("PDFCRAFT_APPEARANCE_SHOT") {
h.render().unwrap().save(path).unwrap();
}
let combo = egui::accesskit::Role::ComboBox;
h.get_by_role(combo).click();
h.run();
h.get_by_label("Dark").click();
h.run();
assert_eq!(h.state().theme_preference, ThemePreference::Dark);
h.get_by_role(combo).click();
h.run();
h.get_by_label("Use system setting").click();
h.run();
assert_eq!(h.state().theme_preference, ThemePreference::System);
}
}
+23 -2
View File
@@ -172,13 +172,21 @@ pub fn tab_strip(app: &mut PdfCraftApp, ui: &mut egui::Ui) {
}
ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
let (icon, label) = match app.theme_preference {
ThemePreference::System => ("settings", tl!("Use system setting")),
ThemePreference::System => ("monitor", tl!("Use system setting")),
ThemePreference::Light => ("sun", tl!("Light gray")),
ThemePreference::Dark => ("moon", tl!("Dark gray")),
};
let tip = format!("{}: {label}", tl!("Display theme"));
let response = icons::button(ui, icon, button, false, &tip);
egui::Popup::menu(&response).show(|ui| theme_menu(app, ui));
// A click cycles System, Light, Dark; a right-click opens the menu.
if response.clicked() {
app.execute(match app.theme_preference {
ThemePreference::System => "view.theme.light",
ThemePreference::Light => "view.theme.dark",
ThemePreference::Dark => "view.theme.system",
});
}
egui::Popup::context_menu(&response).id(response.id.with("theme_menu")).show(|ui| appearance_menu(app, ui));
if icons::button(ui, "circle-help", button, false, tl!("Keyboard shortcuts")).clicked() {
app.dialog = Some(Dialog::Shortcuts);
}
@@ -203,6 +211,19 @@ fn theme_menu(app: &mut PdfCraftApp, ui: &mut egui::Ui) {
ui.close();
}
}
ui.separator();
if ui.button(tl!("Appearance settings…")).clicked() {
app.dialog = Some(Dialog::Preferences);
ui.close();
}
}
/// The right-click menu on the title bar's theme button.
fn appearance_menu(app: &mut PdfCraftApp, ui: &mut egui::Ui) {
if ui.button(tl!("Appearance settings…")).clicked() {
app.dialog = Some(Dialog::Preferences);
ui.close();
}
}
/// A tab's chrome around its name: the icon on the left, the close button on the right.
+6
View File
@@ -111,6 +111,12 @@
Open a PDF ({key}) فتح ملف PDF ({key})
Dark gray theme سمة رمادية داكنة
Light theme سمة فاتحة
Appearance mode وضع المظهر
Light فاتح
Dark داكن
Active نشط
Dark theme سمة داكنة
Appearance settings… إعدادات المظهر…
All tools كل الأدوات
Read قراءة
Convert تحويل
Can't render this file because it contains an unexpected character in line 6 and column 47.
+6
View File
@@ -1090,6 +1090,12 @@
Open a PDF ({key}) Отвори PDF файл ({key})
Dark gray theme Тъмносива тема
Light theme Светла тема
Appearance mode Режим на облика
Light Светъл
Dark Тъмен
Active Активен
Dark theme Тъмна тема
Appearance settings… Настройки на облика…
Print ({key}) Печат ({key})
Save ({key}) Запиши ({key})
Document properties ({key}) Свойства на документа ({key})
Can't render this file because it contains an unexpected character in line 8 and column 16.
+6
View File
@@ -83,6 +83,12 @@
Open a PDF ({key}) PDF öffnen ({key})
Dark gray theme Dunkelgraues Design
Light theme Helles Design
Appearance mode Darstellungsmodus
Light Hell
Dark Dunkel
Active Aktiv
Dark theme Dunkles Design
Appearance settings… Darstellungseinstellungen…
All tools Alle Werkzeuge
Read Lesen
Convert Konvertieren
Can't render this file because it contains an unexpected character in line 6 and column 51.
+6
View File
@@ -104,6 +104,12 @@
Open a PDF ({key}) Abrir un PDF ({key})
Dark gray theme Tema gris oscuro
Light theme Tema claro
Appearance mode Modo de apariencia
Light Claro
Dark Oscuro
Active Activo
Dark theme Tema oscuro
Appearance settings… Ajustes de apariencia…
All tools Todas las herramientas
Read Leer
Convert Convertir
Can't render this file because it contains an unexpected character in line 6 and column 60.
+6
View File
@@ -83,6 +83,12 @@
Open a PDF ({key}) Ouvrir un PDF ({key})
Dark gray theme Thème gris foncé
Light theme Thème clair
Appearance mode Mode d'apparence
Light Clair
Dark Sombre
Active Actif
Dark theme Thème sombre
Appearance settings… Réglages d'apparence…
All tools Tous les outils
Read Lire
Convert Convertir
Can't render this file because it contains an unexpected character in line 6 and column 39.
+6
View File
@@ -81,6 +81,12 @@
Open a PDF ({key}) PDF megnyitása ({key})
Dark gray theme Sötétszürke téma
Light theme Világos téma
Appearance mode Megjelenési mód
Light Világos
Dark Sötét
Active Aktív
Dark theme Sötét téma
Appearance settings… Megjelenési beállítások…
All tools Minden eszköz
Read Olvasás
Convert Konvertálás
Can't render this file because it contains an unexpected character in line 7 and column 44.
+6
View File
@@ -107,6 +107,12 @@
Open a PDF ({key}) Apri un PDF ({key})
Dark gray theme Tema grigio scuro
Light theme Tema chiaro
Appearance mode Modalità aspetto
Light Chiaro
Dark Scuro
Active Attivo
Dark theme Tema scuro
Appearance settings… Impostazioni aspetto…
All tools Tutti gli strumenti
Read Lettura
Convert Converti
Can't render this file because it contains an unexpected character in line 6 and column 47.
+6
View File
@@ -110,6 +110,12 @@
Open a PDF ({key}) PDF を開く({key})
Dark gray theme ダークグレーのテーマ
Light theme ライトテーマ
Appearance mode 外観モード
Light ライト
Dark ダーク
Active 使用中
Dark theme ダークテーマ
Appearance settings… 外観の設定…
All tools すべてのツール
Read 閲覧
Convert 変換
Can't render this file because it contains an unexpected character in line 6 and column 47.
+6
View File
@@ -106,6 +106,12 @@
Open a PDF (⌘O) Otwórz PDF (⌘O)
Dark gray theme Ciemnoszary motyw
Light theme Jasny motyw
Appearance mode Tryb wyglądu
Light Jasny
Dark Ciemny
Active Aktywny
Dark theme Ciemny motyw
Appearance settings… Ustawienia wyglądu…
All tools Wszystkie narzędzia
Read Czytaj
Convert Konwertuj
Can't render this file because it contains an unexpected character in line 6 and column 47.
+6
View File
@@ -1032,6 +1032,12 @@
Open a PDF ({key}) Abrir um PDF ({key})
Dark gray theme Tema cinza escuro
Light theme Tema claro
Appearance mode Modo de aparência
Light Claro
Dark Escuro
Active Ativo
Dark theme Tema escuro
Appearance settings… Configurações de aparência…
Print ({key}) Imprimir ({key})
Save ({key}) Salvar ({key})
Document properties ({key}) Propriedades do documento ({key})
Can't render this file because it contains an unexpected character in line 9 and column 16.
+6
View File
@@ -104,6 +104,12 @@
Open a PDF ({key}) Открыть PDF ({key})
Dark gray theme Тёмно-серая тема
Light theme Светлая тема
Appearance mode Режим оформления
Light Светлое
Dark Тёмное
Active Активно
Dark theme Тёмная тема
Appearance settings… Настройки оформления…
All tools Все инструменты
Read Чтение
Convert Конвертация
Can't render this file because it contains an unexpected character in line 9 and column 16.
+6
View File
@@ -87,6 +87,12 @@ comment menu Edit సవరించు
Open a PDF ({key}) PDFను తెరువు ({key})
Dark gray theme ముదురు బూడిద రంగు థీమ్
Light theme లేత థీమ్
Appearance mode రూపం మోడ్
Light లైట్
Dark డార్క్
Active సక్రియం
Dark theme డార్క్ థీమ్
Appearance settings… రూపం సెట్టింగ్‌లు…
All tools అన్ని సాధనాలు
Read చదవడం
Convert మార్పిడి
Can't render this file because it contains an unexpected character in line 13 and column 16.
+6
View File
@@ -109,6 +109,12 @@
Open a PDF ({key}) Відкрити PDF ({key})
Dark gray theme Темно-сіра тема
Light theme Світла тема
Appearance mode Режим вигляду
Light Світлий
Dark Темний
Active Активний
Dark theme Темна тема
Appearance settings… Налаштування вигляду…
All tools Усі інструменти
Read Читання
Convert Перетворення
Can't render this file because it contains an unexpected character in line 6 and column 47.
+6
View File
@@ -1097,6 +1097,12 @@
Open a PDF ({key}) 打开 PDF ({key})
Dark gray theme 深灰色主题
Light theme 浅色主题
Appearance mode 外观模式
Light 浅色
Dark 深色
Active 当前
Dark theme 深色主题
Appearance settings… 外观设置…
Print ({key}) 打印 ({key})
Save ({key}) 保存 ({key})
Document properties ({key}) 文档属性 ({key})
Can't render this file because it contains an unexpected character in line 9 and column 16.
+6
View File
@@ -1045,6 +1045,12 @@
Open a PDF ({key}) 開啟 PDF({key})
Dark gray theme 深灰色主題
Light theme 淺色主題
Appearance mode 外觀模式
Light 淺色
Dark 深色
Active 使用中
Dark theme 深色主題
Appearance settings… 外觀設定…
Print ({key}) 列印({key})
Save ({key}) 儲存({key})
Document properties ({key}) 文件內容({key})
Can't render this file because it contains an unexpected character in line 9 and column 16.
+1
View File
@@ -118,6 +118,7 @@ pub static ICONS: &[(&str, &[u8])] = &[
("minimize", include_bytes!("../../../assets/icons/minimize.svg")),
("minimize-2", include_bytes!("../../../assets/icons/minimize-2.svg")),
("minus", include_bytes!("../../../assets/icons/minus.svg")),
("monitor", include_bytes!("../../../assets/icons/monitor.svg")),
("moon", include_bytes!("../../../assets/icons/moon.svg")),
("mouse-pointer-2", include_bytes!("../../../assets/icons/mouse-pointer-2.svg")),
("move", include_bytes!("../../../assets/icons/move.svg")),
+1 -1
View File
@@ -75,7 +75,7 @@ mod tests {
#[test]
fn shared_catalog_preserves_existing_icons_and_fallback_uri() {
assert_eq!(ICONS.len(), 185);
assert_eq!(ICONS.len(), 186);
assert!(exists("minimize-2"));
for (name, original) in ICONS {
let expected =
+3 -1
View File
@@ -230,7 +230,7 @@ pub(crate) fn document_js_body(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tok
close
}
/// Preferences: interface language, identity and JavaScript. Returns `true` to close.
/// Preferences: interface language and size, appearance, identity and JavaScript. Returns `true` to close.
pub(crate) fn preferences_body(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tokens) -> bool {
ui.label(egui::RichText::new(tl!("Preferences")).font(theme::semibold(18.0)));
ui.horizontal(|ui| {
@@ -277,6 +277,8 @@ pub(crate) fn preferences_body(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tok
.color(t.text_muted),
);
ui.add_space(8.0);
crate::appearance::preferences_section(ui, app, t);
ui.add_space(8.0);
ui.label(egui::RichText::new(tl!("Documents and view")).font(theme::semibold(13.0)));
ui.horizontal(|ui| {
ui.label(tl!("Default workspace mode"));
+1
View File
@@ -24,6 +24,7 @@ macro_rules! tl_ctx {
mod a11y_ui;
mod actions_ui;
mod appearance;
pub mod canvas;
mod chrome;
mod combine_grid;
+8 -13
View File
@@ -24,12 +24,10 @@ fn assert_theme(h: &Harness<'static, PdfCraftApp>, preference: ThemePreference,
assert_eq!(h.ctx.theme(), if kind == ThemeKind::Dark { egui::Theme::Dark } else { egui::Theme::Light });
}
fn open_toolbar(h: &mut Harness<'static, PdfCraftApp>, label: &str) {
/// A click on the title bar's theme button cycles Light, Dark, System.
fn click_toolbar(h: &mut Harness<'static, PdfCraftApp>, label: &str) {
h.get_by_label(&format!("Display theme: {label}")).click();
h.run_steps(3);
for choice in ["Use system setting", "Light gray", "Dark gray"] {
h.get_by_label(choice);
}
}
fn open_view_menu(h: &mut Harness<'static, PdfCraftApp>) {
@@ -50,18 +48,16 @@ fn toolbar_and_view_menu_share_three_state_preferences() {
let mut h = harness();
// Explicit light wins even on a dark desktop, including egui's popup style.
assert_theme(&h, ThemePreference::Light, ThemeKind::Light);
open_toolbar(&mut h, "Light gray");
h.get_by_label("Use system setting").click();
h.run_steps(3);
click_toolbar(&mut h, "Light gray");
assert_theme(&h, ThemePreference::Dark, ThemeKind::Dark);
click_toolbar(&mut h, "Dark gray");
assert_theme(&h, ThemePreference::System, ThemeKind::Dark);
open_view_menu(&mut h);
h.get_by_label("Light gray").click();
h.run_steps(3);
assert_theme(&h, ThemePreference::Light, ThemeKind::Light);
open_toolbar(&mut h, "Light gray");
h.get_by_label("Dark gray").click();
h.run_steps(3);
click_toolbar(&mut h, "Light gray");
assert_theme(&h, ThemePreference::Dark, ThemeKind::Dark);
open_view_menu(&mut h);
@@ -71,9 +67,8 @@ fn toolbar_and_view_menu_share_three_state_preferences() {
h.input_mut().system_theme = Some(egui::Theme::Light);
h.run_steps(3);
assert_theme(&h, ThemePreference::System, ThemeKind::Light);
open_toolbar(&mut h, "Use system setting");
h.get_by_label("Dark gray").click();
h.run_steps(3);
click_toolbar(&mut h, "Use system setting");
click_toolbar(&mut h, "Light gray");
// A manual choice exits system mode and survives subsequent OS changes.
h.input_mut().system_theme = Some(egui::Theme::Dark);
h.run_steps(2);
+41
View File
@@ -0,0 +1,41 @@
//! The title bar's theme button: a click cycles System, Light and Dark; a right-click opens the
//! menu with the appearance settings.
use egui_kittest::Harness;
use egui_kittest::kittest::Queryable;
use pdfcraft_ui_egui::PdfCraftApp;
fn harness() -> Harness<'static, PdfCraftApp> {
let mut h = Harness::builder().with_size(egui::vec2(1400.0, 900.0)).build_eframe(|_cc| {
let mut app = PdfCraftApp::new();
app.set_option("language", "en").unwrap();
app
});
h.run_steps(6);
h
}
fn preference(h: &Harness<'static, PdfCraftApp>) -> String {
format!("{:?}", h.state().theme_preference)
}
#[test]
fn a_click_cycles_the_theme_and_a_right_click_opens_the_menu() {
let mut h = harness();
assert_eq!(preference(&h), "Light", "new installs start Light");
for (label, next) in
[("Display theme: Light gray", "Dark"), ("Display theme: Dark gray", "System"), ("Display theme: Use system setting", "Light")]
{
h.get_by_label(label).click();
h.run_steps(4);
assert_eq!(preference(&h), next);
}
assert!(h.query_by_label("Appearance settings…").is_none(), "a plain click opens no menu");
h.get_by_label("Display theme: Light gray").click_secondary();
h.run_steps(4);
h.get_by_label("Appearance settings…").click();
h.run_steps(4);
h.run_steps(10);
assert!(h.query_by_label("Light theme").is_some(), "Preferences opens on Appearance");
}