mirror of
https://github.com/storytold/wordcraft.git
synced 2026-10-30 05:17:25 -06:00
UI: draw icons instead of symbols the interface fonts lack (✕ ✎ 💬 ➕ 📂 ⋯ ➢) (#305)
Several buttons put symbol characters in their text that neither Inter nor egui's emoji fallbacks contain, so they drew as empty boxes: the panes' close button (✕), the title-row Editing/Reviewing (✎) and Comments (💬) buttons, the Comments pane's New comment (➕), File › Open's Browse… (📂), the Equation tab's symbol-set button (⋯) and the ➢ tile in the Bullet Library. - Pane close buttons paint the existing `close` icon, with a Close tooltip. - New `widgets::icon_text_button` paints an icon from icons.rs next to text; Editing/Reviewing (pencil), Comments and New Comment use it. The Editing/Reviewing menu is now an egui Popup on that button. - Browse… drops the emoji; ⋯ becomes …; the ➢ bullet stays a bullet choice and only its menu tile draws the arrowhead. - Catalogs: `📂 Browse…` is renamed `Browse…` (translation without the emoji) and `➕ New comment` is removed in favour of the existing `New Comment`, in every language. theme::tests::interface_symbols_have_glyphs scans the UI sources' string literals for non-letter, non-ASCII characters and checks each against the real interface font setup, with a short commented exception list (equation templates drawn by the math renderer, the ➢ bullet value). Claude-Session: https://claude.ai/code/session_01WtHKQ7wrZm3uBuaG1Ye8po Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Dmitrii Khlapov
Claude Opus 5.5
parent
fc0c11a440
commit
82b31efd67
@@ -257,7 +257,7 @@ fn open_list(app: &mut WordApp, ui: &mut Ui) {
|
||||
|
||||
fn open_page(app: &mut WordApp, ui: &mut Ui) {
|
||||
heading(ui, "Open");
|
||||
if ui.button(egui::RichText::new(tl!("📂 Browse…")).font(medium(14.0))).clicked() {
|
||||
if ui.button(egui::RichText::new(tl!("Browse…")).font(medium(14.0))).clicked() {
|
||||
let _ = app.run("ui.openFileDialog", json!({}));
|
||||
}
|
||||
ui.add_space(18.0);
|
||||
|
||||
@@ -210,7 +210,7 @@ fn symbols(ui: &mut Ui, app: &mut WordApp) {
|
||||
if ui.add_enabled(start + page < all.len(), egui::Button::new("▼").min_size(vec2(16.0, 22.0))).clicked() {
|
||||
ui.data_mut(|d| d.insert_temp(start_id, start + per_row));
|
||||
}
|
||||
let resp = ui.add(egui::Button::new("⋯").min_size(vec2(16.0, 22.0))).on_hover_text(tl!("All symbol sets"));
|
||||
let resp = ui.add(egui::Button::new("…").min_size(vec2(16.0, 22.0))).on_hover_text(tl!("All symbol sets"));
|
||||
egui::Popup::menu(&resp).show(|ui| {
|
||||
ui.set_width(420.0);
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
|
||||
@@ -819,8 +819,7 @@
|
||||
{key}+Click to follow link {key}+clic para seguir el vínculo
|
||||
{selected} of {words} words {selected} de {words} palabras
|
||||
{words} words {words} palabras
|
||||
➕ New comment ➕ Nuevo comentario
|
||||
📂 Browse… 📂 Examinar…
|
||||
Browse… Examinar…
|
||||
Equation Ecuación
|
||||
Review Revisar
|
||||
Track\nChanges Control de\ncambios
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 25.
|
@@ -790,8 +790,7 @@
|
||||
Zoom Out 縮小
|
||||
{group} settings {group} の設定
|
||||
{key}+Click to follow link {key} キーを押しながらクリックしてリンク先を表示
|
||||
➕ New comment ➕ 新しいコメント
|
||||
📂 Browse… 📂 参照…
|
||||
Browse… 参照…
|
||||
{words} words {words} 語
|
||||
{selected} of {words} words {selected}/{words} 語
|
||||
Direction: 方向:
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 416 and column 13.
|
@@ -790,8 +790,7 @@
|
||||
Zoom Out Reduzir
|
||||
{group} settings Configurações de {group}
|
||||
{key}+Click to follow link {key}+clique para seguir o link
|
||||
➕ New comment ➕ Novo comentário
|
||||
📂 Browse… 📂 Procurar…
|
||||
Browse… Procurar…
|
||||
{words} words Palavras: {words}
|
||||
{selected} of {words} words Palavras: {words}; seleção: {selected}
|
||||
Dark page (white text on black) Página escura (texto branco sobre preto)
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 25.
|
@@ -798,8 +798,7 @@
|
||||
Zoom Out Umanji
|
||||
{group} settings Podešavanja za {group}
|
||||
{key}+Click to follow link {key}+klik za otvaranje veze
|
||||
➕ New comment ➕ Novi komentar
|
||||
📂 Browse… 📂 Potraži…
|
||||
Browse… Potraži…
|
||||
{words} words {words} reči
|
||||
{selected} of {words} words {selected} od {words} reči
|
||||
Direction: Smer:
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 25.
|
@@ -802,8 +802,7 @@
|
||||
Zoom Out Умањи
|
||||
{group} settings Подешавања за {group}
|
||||
{key}+Click to follow link {key}+клик за отварање везе
|
||||
➕ New comment ➕ Нови коментар
|
||||
📂 Browse… 📂 Потражи…
|
||||
Browse… Потражи…
|
||||
{words} words {words} речи
|
||||
{selected} of {words} words {selected} од {words} речи
|
||||
Direction: Смер:
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 11 and column 25.
|
@@ -817,8 +817,7 @@
|
||||
Zoom Out Зменшити масштаб
|
||||
{group} settings Параметри групи «{group}»
|
||||
{key}+Click to follow link {key}+клацання, щоб перейти за посиланням
|
||||
➕ New comment ➕ Новий коментар
|
||||
📂 Browse… 📂 Огляд…
|
||||
Browse… Огляд…
|
||||
{words} words Слів: {words}
|
||||
{selected} of {words} words Слів: {words}; виділено: {selected}
|
||||
Apply Styles Застосувати стилі
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 31.
|
@@ -801,8 +801,7 @@
|
||||
Zoom Out 缩小
|
||||
{group} settings {group}设置
|
||||
{key}+Click to follow link 按住 {key} 并单击可访问链接
|
||||
➕ New comment ➕ 新建批注
|
||||
📂 Browse… 📂 浏览…
|
||||
Browse… 浏览…
|
||||
{words} words {words} 个字
|
||||
{selected} of {words} words {selected}/{words} 个字
|
||||
Direction: 方向:
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 25.
|
@@ -791,8 +791,7 @@
|
||||
Zoom Out 縮小
|
||||
{group} settings {group}設定
|
||||
{key}+Click to follow link 按住 {key} 並按一下以追蹤連結
|
||||
➕ New comment ➕ 新增註解
|
||||
📂 Browse… 📂 瀏覽…
|
||||
Browse… 瀏覽…
|
||||
{words} words {words} 個字
|
||||
{selected} of {words} words {selected}/{words} 個字
|
||||
Direction: 方向:
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 417 and column 13.
|
@@ -44,7 +44,14 @@ fn header(ui: &mut Ui, title: &str) -> bool {
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(egui::RichText::new(tl!(title)).font(semibold(15.0)));
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||
if ui.small_button("✕").clicked() {
|
||||
// Drawn, not the "✕" character: the interface fonts have no glyph for it.
|
||||
let t = crate::theme::Tokens::get(ui.ctx());
|
||||
let (r, resp) = ui.allocate_exact_size(egui::vec2(22.0, 22.0), egui::Sense::click());
|
||||
if resp.hovered() {
|
||||
ui.painter().rect_filled(r, 4.0, t.hover);
|
||||
}
|
||||
crate::icons::paint(ui.painter(), egui::Rect::from_center_size(r.center(), egui::vec2(14.0, 14.0)), "close", t.icon, t.accent);
|
||||
if resp.on_hover_text(tl!("Close")).clicked() {
|
||||
close = true;
|
||||
}
|
||||
});
|
||||
@@ -216,7 +223,7 @@ fn comments(app: &mut WordApp, ui: &mut Ui) {
|
||||
let _ = app.run("view.commentsPane", json!({"value": false}));
|
||||
return;
|
||||
}
|
||||
if ui.button(tl!("➕ New comment")).clicked() {
|
||||
if crate::widgets::icon_text_button(ui, "newComment", tl!("New Comment"), regular(12.5)).clicked() {
|
||||
let _ = app.run("review.newComment", json!({"text": ""}));
|
||||
}
|
||||
ui.separator();
|
||||
|
||||
@@ -120,21 +120,24 @@ pub fn show(app: &mut WordApp, ui: &mut Ui) {
|
||||
}
|
||||
ui.add_space(6.0);
|
||||
let track = app.session.doc.settings.track_changes;
|
||||
ui.menu_button(
|
||||
egui::RichText::new(format!("✎ {} ▾", if track { tl!("Reviewing") } else { tl!("Editing") })).font(regular(12.0)),
|
||||
|ui| {
|
||||
if ui.selectable_label(!track, tl!(tl!("Editing — edit the document directly"))).clicked() {
|
||||
let _ = app.run("review.trackChanges", json!({"value": false}));
|
||||
ui.close();
|
||||
}
|
||||
if ui.selectable_label(track, tl!(tl!("Reviewing — edits become suggestions"))).clicked() {
|
||||
let _ = app.run("review.trackChanges", json!({"value": true}));
|
||||
ui.close();
|
||||
}
|
||||
},
|
||||
let mode = crate::widgets::icon_text_button(
|
||||
ui,
|
||||
"pencil",
|
||||
&format!("{} ▾", if track { tl!("Reviewing") } else { tl!("Editing") }),
|
||||
regular(12.0),
|
||||
);
|
||||
egui::Popup::menu(&mode).show(|ui| {
|
||||
if ui.selectable_label(!track, tl!(tl!("Editing — edit the document directly"))).clicked() {
|
||||
let _ = app.run("review.trackChanges", json!({"value": false}));
|
||||
ui.close();
|
||||
}
|
||||
if ui.selectable_label(track, tl!(tl!("Reviewing — edits become suggestions"))).clicked() {
|
||||
let _ = app.run("review.trackChanges", json!({"value": true}));
|
||||
ui.close();
|
||||
}
|
||||
});
|
||||
ui.add_space(4.0);
|
||||
if ui.button(egui::RichText::new(format!("💬 {}", tl!("Comments"))).font(regular(12.0))).clicked() {
|
||||
if crate::widgets::icon_text_button(ui, "comment", tl!("Comments"), regular(12.0)).clicked() {
|
||||
let _ = app.run("view.commentsPane", json!({}));
|
||||
}
|
||||
});
|
||||
@@ -366,7 +369,16 @@ fn home(app: &mut WordApp, ui: &mut Ui) {
|
||||
ui.label(egui::RichText::new(tl!("Bullet Library")).small().weak());
|
||||
ui.horizontal(|ui| {
|
||||
for c in ["•", "○", "▪", "◆", "➢", "✓", "–"] {
|
||||
if ui.button(egui::RichText::new(c).size(16.0)).clicked() {
|
||||
// The interface fonts have no ➢, so its tile draws the arrowhead.
|
||||
let label = if c == "➢" { " " } else { c };
|
||||
let resp = ui.button(egui::RichText::new(label).size(16.0));
|
||||
if c == "➢" {
|
||||
let m = resp.rect.center();
|
||||
let pts = [(-4.0, -5.0), (5.0, 0.0), (-4.0, 5.0), (-1.5, 0.0)].map(|(x, y)| m + vec2(x, y));
|
||||
ui.painter().add(egui::Shape::convex_polygon(vec![pts[0], pts[1], pts[3]], Tokens::get(ui.ctx()).text, Stroke::NONE));
|
||||
ui.painter().add(egui::Shape::convex_polygon(vec![pts[3], pts[1], pts[2]], Tokens::get(ui.ctx()).text, Stroke::NONE));
|
||||
}
|
||||
if resp.clicked() {
|
||||
let _ = app.run("para.bullets", json!({"kind": c}));
|
||||
ui.close();
|
||||
}
|
||||
|
||||
@@ -418,3 +418,53 @@ pub fn apply(ctx: &egui::Context, t: &Tokens) {
|
||||
pub fn c32(c: wordcraft_doc::Rgb) -> Color32 {
|
||||
Color32::from_rgb(c.0, c.1, c.2)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
/// Symbols written as text in the interface code must exist in the interface fonts (Inter and
|
||||
/// egui's emoji fallbacks); a missing glyph draws as an empty box, as the panes' "✕" close
|
||||
/// button once did. Letters are the catalogs' business (see the i18n font tests).
|
||||
#[test]
|
||||
fn interface_symbols_have_glyphs() {
|
||||
let ctx = egui::Context::default();
|
||||
ctx.set_fonts(super::font_definitions(false, false));
|
||||
ctx.run_ui(egui::RawInput::default(), |_| {}).drop_without_applying_deltas();
|
||||
let dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("src");
|
||||
let mut symbols = BTreeSet::new();
|
||||
for entry in std::fs::read_dir(&dir).unwrap() {
|
||||
let path = entry.unwrap().path();
|
||||
if path.extension().is_none_or(|e| e != "rs") {
|
||||
continue;
|
||||
}
|
||||
let code = std::fs::read_to_string(&path).unwrap();
|
||||
// Characters inside string literals, line by line (good enough for this code base).
|
||||
for line in code.lines().filter(|l| !l.trim_start().starts_with("//")) {
|
||||
for (i, part) in line.split('"').enumerate() {
|
||||
if i % 2 == 1 {
|
||||
symbols.extend(
|
||||
part.chars()
|
||||
.filter(|c| !c.is_ascii() && !c.is_alphabetic() && !c.is_whitespace())
|
||||
.map(|c| (c, path.file_name().unwrap().to_string_lossy().to_string())),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Equation gallery templates are UnicodeMath input (`lim┬(n→∞)`, `≜`) that the math
|
||||
// renderer draws with document fonts, not interface text.
|
||||
// The ➢ bullet is a value for `para.bullets`; its menu tile draws the shape instead.
|
||||
let exempt = |c: char, file: &str| match file {
|
||||
"equation_tab.rs" => matches!(c, '≜' | '┬'),
|
||||
"ribbon.rs" => c == '➢',
|
||||
_ => false,
|
||||
};
|
||||
symbols.retain(|(c, file)| !exempt(*c, file));
|
||||
let font = super::regular(12.5);
|
||||
let missing: Vec<String> = ctx.fonts_mut(|f| {
|
||||
symbols.iter().filter(|(c, _)| !f.has_glyph(&font, *c)).map(|(c, file)| format!("{c} U+{:04X} in {file}", *c as u32)).collect()
|
||||
});
|
||||
assert!(missing.is_empty(), "symbols without a glyph: {missing:#?}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -390,6 +390,28 @@ pub fn combo_list(ui: &mut Ui, width: f32, current: &str, items: &[String], prev
|
||||
out
|
||||
}
|
||||
|
||||
/// A text button with a drawn icon in front. Symbols such as ✎ or 💬 aren't in the interface
|
||||
/// fonts and would draw as empty boxes, so icons are painted (`icons.rs`) rather than typed.
|
||||
pub fn icon_text_button(ui: &mut Ui, icon: &str, text: &str, font: egui::FontId) -> Response {
|
||||
let t = Tokens::get(ui.ctx());
|
||||
let galley = ui.painter().layout_no_wrap(text.to_string(), font, t.text);
|
||||
let pad = ui.spacing().button_padding;
|
||||
let icon_w = 16.0;
|
||||
let size = vec2(pad.x + icon_w + 4.0 + galley.size().x + pad.x, galley.size().y.max(icon_w) + 2.0 * pad.y);
|
||||
let (r, resp) = ui.allocate_exact_size(size, Sense::click());
|
||||
let v = ui.style().interact(&resp);
|
||||
ui.painter().rect(r, v.corner_radius, v.weak_bg_fill, v.bg_stroke, egui::StrokeKind::Inside);
|
||||
icons::paint(
|
||||
ui.painter(),
|
||||
Rect::from_center_size(pos2(r.min.x + pad.x + icon_w / 2.0, r.center().y), vec2(icon_w, icon_w)),
|
||||
icon,
|
||||
t.icon,
|
||||
t.accent,
|
||||
);
|
||||
ui.painter().galley(pos2(r.min.x + pad.x + icon_w + 4.0, r.center().y - galley.size().y / 2.0), galley, t.text);
|
||||
resp
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
Reference in New Issue
Block a user