mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-12 00:46:57 -06:00
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:
co-authored by
Brandon Thomas
parent
7f1b632519
commit
046df9dfe4
+2
-1
@@ -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
@@ -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"
|
||||
|
||||
@@ -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 |
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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")),
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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"));
|
||||
|
||||
@@ -24,6 +24,7 @@ macro_rules! tl_ctx {
|
||||
|
||||
mod a11y_ui;
|
||||
mod actions_ui;
|
||||
mod appearance;
|
||||
pub mod canvas;
|
||||
mod chrome;
|
||||
mod combine_grid;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
Reference in New Issue
Block a user