M3: All tools expands to a full, searchable view

An expand button at the top-left of the All tools sidebar opens a view that
fills the workspace, like Acrobat's tools page: every tool as a tile, filed
under five categories (Create & Edit, Review & Compare, Forms & Signatures,
Protect & Standardize, Customize & Automate), with a search field that matches
tool names and the items inside each tool, in English or the UI language.
Choosing a tile opens that tool's panel; Collapse, Esc or another workspace
puts the view away. `--tools full` opens it from the command line and the
control channel.

The categories live in the engine catalogue (TOOL_CATEGORIES), with a test
that every tool group is in exactly one. Six new strings are translated in
all 22 catalogues.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
alehel
2026-10-15 10:41:14 +02:00
co-authored by Claude Opus 5.5
parent afa8e1843c
commit 3ec7ce8aae
30 changed files with 417 additions and 26 deletions
+22 -22
View File
@@ -7,7 +7,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Greek UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "cefa955aa92790d6773cb1d1e15e19019b3165a00528d57d6617362e1d1988ab"
sha256 = "d779dbb265c576d68cd804d0a0505ac5821d74f2d4e5afb663516d8801e0fbbc"
# ATTRIBUTION.toml: one entry per asset (AGENTS.md §1). The source of truth for provenance and licences.
# ATTRIBUTION.md is generated from this file: `cargo xtask assets --write`. Checked by `cargo xtask assets` (part of `xtask ci`).
@@ -30,7 +30,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 = "1de29a6cc089be31cb6206bf41e0715f8ddcf74ef887b45ccee235d743bed223"
sha256 = "69a7a36b9b8fbe6cd04d7f287a77deb01c243cc0f1a438511cf35748dde6b3df"
[[asset]]
path = "crates/ui-egui/src/i18n/zh-hant.tsv"
@@ -41,7 +41,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 = "8c6d5b1505d76bc7ba606c98660afa60b1152c5b4c2e7d345104a6ef3d280ed2"
sha256 = "12a568bf7a626ff3e34009150894eabe3c0927589756d8635fc9bae19638a512"
[[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 = "Czech UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "8a8aed18bd8fceabd1389580361ba75b0dccf141d845650a353d8dfe19c95536"
sha256 = "0fd03fafffc2d75bf484186a7dd6ab15ab95d79448e0e798f57519515d73ac4e"
[[asset]]
path = "crates/ui-egui/src/i18n/pt-br.tsv"
@@ -63,7 +63,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 = "1c197ac045810950aceb733152a296f2224f38857b1169032fdc7092ab4aa3f7"
sha256 = "d3292e186768f75b9cb87097688ff4909c26c9810a7e6542c127aad9fbce5af3"
[[asset]]
path = "crates/ui-egui/src/i18n/es.tsv"
@@ -74,7 +74,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 = "a9cf0e062b006b40f549ce2316b88d4b0196d70bd9cecd66c12d980d699bf679"
sha256 = "14c9f2ba61ffc925b01e6bf31c7f7761fb5e7adcda1751f6768829341a8e3757"
[[asset]]
path = "crates/ui-egui/src/i18n/fr.tsv"
@@ -85,7 +85,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 = "ef5c84bd13ef8318f7deab7f42136cfe3c14cbef6fd5231014b94167baaecd2a"
sha256 = "d2dc6bd5de197eff60a440a05c58c6b7307009ecb08ddf8fbb31ff616633e36e"
[[asset]]
path = "crates/ui-egui/src/i18n/de.tsv"
@@ -96,7 +96,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 = "be689af8bb22537879269ad9ab9bdcc4b730665cf0b27c6bf4bad560cacf3b69"
sha256 = "b6ffc5eda71ea13eaea79f5f3368811cb317f0a43e6f6be167bb4742d8e1ae77"
[[asset]]
path = "crates/ui-egui/src/i18n/te.tsv"
@@ -107,7 +107,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 = "989d2e5f57cf22c21a85951835ca9fbd05e314da67eb4c17332df1f03679fa00"
sha256 = "37b60dda614ebc0e2b141cb27c9b2b7d13b49b347c1a5acb4d395147fbbdd246"
[[asset]]
path = "crates/ui-egui/src/i18n/it.tsv"
@@ -118,7 +118,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 = "425c42acb8d98ac31b216b13c05a285e0676318dd379dcfb9d57cc7329e9d7a5"
sha256 = "0055dfb04a35abd2c8da01bf3836855074e17bbd2b6332b2d1cecd16fd816938"
[[asset]]
path = "crates/ui-egui/src/i18n/pl.tsv"
@@ -129,7 +129,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 = "988bcf2e0322a46c0464ff7b91ab5d90193b0ea7f454812d12595eb843de6fc7"
sha256 = "e1c4801f9633f5873f5dc07fa6de01375369b636282fa22e8cd223adb9737b42"
[[asset]]
path = "crates/ui-egui/src/i18n/ru.tsv"
@@ -140,7 +140,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 = "ebe7aa9152baff38ae7e36aeb4f6a7ed2198166ab93425a612a66d53e43cb363"
sha256 = "36bd183e5bb36e76e2eb40ac68d06f2d0dcd962b42998917ba3ad5e7d8ca2b19"
[[asset]]
path = "crates/ui-egui/src/i18n/hu.tsv"
@@ -151,7 +151,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 = "69d2da21949f02c3c40f71159e377eef2fdd1a87f749d6c504e1d151ef33f643"
sha256 = "c10dab015d7cabc571dd2a462a0ca22a1b1461b9b747c4998e7a7e4e1f9b6b71"
[[asset]]
path = "crates/ui-egui/src/i18n/bg.tsv"
@@ -162,7 +162,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 = "98359685aa555f391846f2259ac302ce1234c7a180fb4abe07c36e0c1c62211e"
sha256 = "7b1db4f5f3ce50c8b995037d84cce86088e3af17942b2e1d0197234a2454ecdd"
[[asset]]
path = "crates/ui-egui/src/i18n/kk.tsv"
@@ -173,7 +173,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Kazakh UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "9f6296d9c79c848167fc80bb4cfaacc9e60f72edb70cc1d70f7b6088ada83578"
sha256 = "3ef5f6e5c28c8060017b1219d0ae3fe6308eaa4f69baf821816da1ac2b4468cd"
[[asset]]
path = "crates/ui-egui/src/i18n/ja.tsv"
@@ -184,7 +184,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 = "3528e85afd4117de2c22b9f37d32a380a073a49834fa5a3063d21b803606ffbf"
sha256 = "d2b91bcf778610861b515865339a487f279f68188617fd2729e0d8d8455d7a2b"
[[asset]]
path = "crates/ui-egui/src/i18n/uk.tsv"
@@ -195,7 +195,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 = "4db7e23074d44305f4584e2cd8b9a0fc35b433cb187dad493c8f7d78a8cd4ad2"
sha256 = "b3a0c1563ba9c67ffbebf805769bae9c5a809cd14fd37a0441a30e19cacb28c8"
[[asset]]
path = "crates/ui-egui/src/i18n/ar.tsv"
@@ -206,7 +206,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 = "2af01de9509db4b4ae61193f41ff0f7f3b70ca5f91cd2afda667e90d34980b25"
sha256 = "15dde8ef0f0d20a6e99b82c7f5297b41130e9f0d8f11494fcdc09c69bcbfb43c"
[[asset]]
path = "crates/ui-egui/src/i18n/tr.tsv"
@@ -217,7 +217,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Turkish UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "de988b6959572931c4878a0588109c236bf8c5d46ef57c7a406b6ab42de51339"
sha256 = "9d879f455313d528d2d8c91e080a1fa199f50d901c7691f7b6dc3f15be73fd1a"
[[asset]]
path = "crates/ui-egui/src/i18n/lt.tsv"
@@ -228,7 +228,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Lithuanian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "63955758227bd57329b22dc7c16ed9cee8b0a15df9453477755849c886424dd5"
sha256 = "b300a148c46c122b84ee9b978979ad41c3b4fbb220a488989bb991e0062d9885"
[[asset]]
@@ -240,7 +240,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Vietnamese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "b54adaa6515dbfb0de3859b30989054e5fcc4f56e66e9cc713c7bf509bdbbac3"
sha256 = "4c09041b54acbdb7c5fe16409b2fa699d260200fec8600000d668c9af7636f8c"
[[asset]]
path = "assets/icons/accessibility.svg"
@@ -3363,7 +3363,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Slovene UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "5881cc7fbfe9da5f5f611309dacf267f95db3a143b916a113b649839019b292c"
sha256 = "bb3a8708b1a2f72782ab41b0ae43e902f089caffd545004f5d1b2112d20df78c"
[[asset]]
path = "vendor/vello_common/assets/probe.png"
+29
View File
@@ -547,6 +547,23 @@ pub fn group(id: &str) -> Option<&'static ToolGroup> {
TOOL_GROUPS.iter().find(|g| g.id == id)
}
/// A heading of the expanded All tools view, as Acrobat files its tools under "Create & Edit",
/// "Forms & Signatures" and so on. Every group belongs to exactly one category.
#[derive(Clone, Copy, Debug)]
pub struct ToolCategory {
pub label: &'static str,
/// [`ToolGroup::id`]s, in display order.
pub groups: &'static [&'static str],
}
pub static TOOL_CATEGORIES: &[ToolCategory] = &[
ToolCategory { label: "Create & Edit", groups: &["create", "edit", "export", "combine", "organize", "scan", "compress"] },
ToolCategory { label: "Review & Compare", groups: &["comment", "stamp", "compare", "measure"] },
ToolCategory { label: "Forms & Signatures", groups: &["fill_sign", "form", "certificate"] },
ToolCategory { label: "Protect & Standardize", groups: &["protect", "redact", "accessibility", "standards", "print_production"] },
ToolCategory { label: "Customize & Automate", groups: &["actions", "javascript", "search_index", "ai"] },
];
#[cfg(test)]
mod tests {
use super::*;
@@ -561,6 +578,18 @@ mod tests {
}
}
#[test]
fn every_group_is_in_exactly_one_category() {
let mut seen = HashSet::new();
for c in TOOL_CATEGORIES {
for id in c.groups {
assert!(group(id).is_some(), "{} lists unknown group {id}", c.label);
assert!(seen.insert(*id), "{id} is in two categories");
}
}
assert_eq!(seen.len(), TOOL_GROUPS.len(), "a group is in no category");
}
#[test]
fn commands_are_namespaced() {
for g in TOOL_GROUPS {
+158
View File
@@ -0,0 +1,158 @@
//! The expanded All tools view: every tool, filed under Acrobat-style categories, with a search
//! field, filling the workspace instead of the sidebar. The sidebar's expand button opens it;
//! its collapse button, Esc or choosing a tool closes it again.
use egui::{Align2, Color32, CornerRadius, Rect, Sense, Stroke, vec2};
use pdfcraft_engine::catalog::{self, Availability, TOOL_CATEGORIES, ToolGroup};
use crate::theme::{self, Tokens};
use crate::{PdfCraftApp, icons, widgets};
const TILE: egui::Vec2 = vec2(148.0, 118.0);
/// Item names a tile lists when the search found the tool by them.
const MATCHES_SHOWN: usize = 2;
/// Whether `group` matches `query` (already trimmed and lower-cased), in English or the UI
/// language. `None` is no match; otherwise the items that matched, empty when the tool's own
/// name did.
fn matches(query: &str, group: &ToolGroup) -> Option<Vec<&'static str>> {
let hit = |label: &str| label.to_lowercase().contains(query) || tl!(label).to_lowercase().contains(query);
if query.is_empty() || hit(group.label) {
return Some(Vec::new());
}
let items: Vec<_> = group.sections.iter().flat_map(|s| s.items.iter()).map(|i| i.label).filter(|l| hit(l)).collect();
(!items.is_empty()).then_some(items)
}
pub fn show(app: &mut PdfCraftApp, ui: &mut egui::Ui) {
let t = Tokens::get(ui.ctx());
if ui.input(|i| i.key_pressed(egui::Key::Escape)) {
app.all_tools_full = false;
return;
}
let mut chosen = None;
egui::Frame::NONE.fill(t.panel).show(ui, |ui| {
ui.set_min_size(ui.available_size());
egui::Frame::NONE.inner_margin(egui::Margin { left: 36, right: 28, top: 20, bottom: 12 }).show(ui, |ui| {
ui.horizontal(|ui| {
ui.label(egui::RichText::new(tl!("All tools")).font(theme::semibold(22.0)).color(t.text));
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
if icons::button(ui, "minimize-2", 28.0, false, tl!("Collapse")).clicked() {
app.all_tools_full = false;
}
});
});
ui.add_space(10.0);
let field = ui.add(
widgets::line(&mut app.all_tools_query).hint_text(tl!("Search tools and commands…")).desired_width(ui.available_width().min(520.0)),
);
// Typing goes straight to the search, as in Acrobat's tools view.
if !ui.memory(|m| m.focused().is_some()) {
field.request_focus();
}
});
ui.separator();
let query = app.all_tools_query.trim().to_lowercase();
egui::ScrollArea::vertical().auto_shrink([false, false]).show(ui, |ui| {
egui::Frame::NONE.inner_margin(egui::Margin { left: 36, right: 28, top: 8, bottom: 28 }).show(ui, |ui| {
ui.set_width(ui.available_width());
let mut any = false;
for category in TOOL_CATEGORIES {
let found: Vec<_> =
category.groups.iter().filter_map(|id| catalog::group(id)).filter_map(|g| matches(&query, g).map(|m| (g, m))).collect();
if found.is_empty() {
continue;
}
any = true;
ui.add_space(12.0);
ui.label(egui::RichText::new(tl!(category.label)).font(theme::semibold(16.0)).color(t.text));
ui.add_space(10.0);
ui.horizontal_wrapped(|ui| {
ui.spacing_mut().item_spacing = vec2(14.0, 14.0);
for (g, items) in found {
if tile(ui, &t, g, &items).clicked() {
chosen = Some(g.id);
}
}
});
ui.add_space(8.0);
}
if !any {
ui.add_space(24.0);
ui.label(egui::RichText::new(tl!("No tools match your search.")).color(t.text_muted));
}
});
});
});
if let Some(id) = chosen {
app.all_tools_full = false;
app.left_open = true;
app.select_tool(id);
}
}
fn tile(ui: &mut egui::Ui, t: &Tokens, g: &ToolGroup, items: &[&'static str]) -> egui::Response {
let (rect, resp) = ui.allocate_exact_size(TILE, Sense::click());
resp.widget_info(|| egui::WidgetInfo::labeled(egui::WidgetType::Button, true, tl!(g.label)));
let press = theme::Press::track(ui, &resp);
crate::home::card_lift(ui, t, rect, press);
ui.painter().rect(rect, CornerRadius::same(10), press.fill(t.card, t.hover, t.pressed), Stroke::new(1.0, t.divider), egui::StrokeKind::Inside);
let body = rect.translate(press.offset());
let ready = g.availability == Availability::Ready;
let hue = Color32::from_rgb(g.hue[0], g.hue[1], g.hue[2]);
icons::paint(ui, Rect::from_center_size(body.center_top() + vec2(0.0, 34.0), vec2(32.0, 32.0)), g.icon, 30.0, hue);
let mut job =
egui::text::LayoutJob::simple(tl!(g.label).to_owned(), theme::medium(13.0), if ready { t.text } else { t.text_muted }, body.width() - 16.0);
job.halign = egui::Align::Center;
job.wrap.max_rows = 2;
let title = ui.fonts_mut(|f| f.layout_job(job));
let mut elided = title.elided;
let title_h = title.size().y;
ui.painter().galley(body.center_top() + vec2(0.0, 60.0), title, t.text);
// What found the tool, or else whether it works today.
let note = if items.is_empty() {
match g.availability {
Availability::Ready => String::new(),
Availability::Planned(m) => format!("{} · {m}", tl!("Planned")),
Availability::Provider => g.badge.map(|b| tl!(b).to_owned()).unwrap_or_default(),
}
} else {
items.iter().take(MATCHES_SHOWN).map(|l| tl!(l)).collect::<Vec<_>>().join(" · ")
};
if !note.is_empty() {
let color = if items.is_empty() { t.text_faint } else { t.accent_text };
let mut job = egui::text::LayoutJob::simple_singleline(note.clone(), theme::regular(11.0), color);
job.halign = egui::Align::Center;
job.wrap = egui::text::TextWrapping { max_width: body.width() - 16.0, max_rows: 1, break_anywhere: true, ..Default::default() };
let galley = ui.fonts_mut(|f| f.layout_job(job));
elided |= galley.elided;
ui.painter().galley(body.center_top() + vec2(0.0, 64.0 + title_h), galley, color);
}
if let Some(b) = g.badge.filter(|_| g.availability == Availability::Ready) {
let font = theme::semibold(9.5);
let w = widgets::text_width(ui, tl!(b), &font);
let r = Rect::from_min_size(body.right_top() + vec2(-w - 18.0, 8.0), vec2(w + 10.0, 16.0));
ui.painter().rect_filled(r, CornerRadius::same(4), t.badge_new);
ui.painter().text(r.center(), Align2::CENTER_CENTER, tl!(b), font, Color32::WHITE);
}
let resp = if elided { resp.on_hover_text(format!("{}\n{note}", tl!(g.label))) } else { resp };
theme::hand(resp)
}
#[cfg(test)]
mod tests {
use super::*;
fn group(id: &str) -> &'static ToolGroup {
catalog::group(id).expect("catalogue group")
}
#[test]
fn search_matches_tool_names_and_their_items() {
assert_eq!(matches("", group("organize")), Some(Vec::new()), "an empty query shows everything");
assert_eq!(matches("organize", group("organize")), Some(Vec::new()), "the tool's own name");
let rotate = matches("rotate", group("organize")).expect("Organize pages lists Rotate");
assert!(!rotate.is_empty() && rotate.iter().all(|l| l.to_lowercase().contains("rotate")), "{rotate:?}");
assert_eq!(matches("no such tool anywhere", group("organize")), None);
}
}
+1 -1
View File
@@ -10,7 +10,7 @@ use crate::{PdfCraftApp, icons, panels::human_size, widgets};
const RECOMMENDED: [&str; 5] = ["organize", "comment", "form", "edit", "protect"];
/// A soft shadow that rises on hover and settles when the card is pressed.
fn card_lift(ui: &egui::Ui, t: &Tokens, rect: Rect, press: theme::Press) {
pub(crate) fn card_lift(ui: &egui::Ui, t: &Tokens, rect: Rect, press: theme::Press) {
let lift = press.hover * (1.0 - press.down);
if lift > 0.04 {
let shadow = egui::Shadow { offset: [0, 3], blur: (14.0 * lift) as u8, spread: 0, color: t.page_shadow.gamma_multiply(0.85 * lift) };
+6
View File
@@ -2706,3 +2706,9 @@ certificate Version الإصدار
Make properties default جعل الخصائص افتراضية
Pages selected الصفحات المحددة
Page properties خصائص الصفحة
Create & Edit الإنشاء والتحرير
Review & Compare المراجعة والمقارنة
Forms & Signatures النماذج والتوقيعات
Protect & Standardize الحماية والتوحيد القياسي
Customize & Automate التخصيص والأتمتة
No tools match your search. لا توجد أدوات تطابق بحثك.
Can't render this file because it contains an unexpected character in line 6 and column 47.
+6
View File
@@ -2391,3 +2391,9 @@ animation effects Off Изключени
Make properties default Задаване като свойства по подразбиране
Pages selected Избрани страници
Page properties Свойства на страницата
Create & Edit Създаване и редактиране
Review & Compare Преглед и сравнение
Forms & Signatures Формуляри и подписи
Protect & Standardize Защита и стандартизация
Customize & Automate Персонализиране и автоматизация
No tools match your search. Няма инструменти, отговарящи на търсенето.
Can't render this file because it contains an unexpected character in line 8 and column 16.
+6
View File
@@ -2502,3 +2502,9 @@ animation effects Off Vypnuto
Make properties default Nastavit vlastnosti jako výchozí
Pages selected Vybrané stránky
Page properties Vlastnosti stránky
Create & Edit Vytváření a úpravy
Review & Compare Revize a porovnání
Forms & Signatures Formuláře a podpisy
Protect & Standardize Ochrana a standardizace
Customize & Automate Přizpůsobení a automatizace
No tools match your search. Hledání neodpovídá žádný nástroj.
Can't render this file because it contains an unexpected character in line 6 and column 47.
+6
View File
@@ -2429,3 +2429,9 @@ animation effects Off Aus
Make properties default Eigenschaften als Standard festlegen
Pages selected Ausgewählte Seiten
Page properties Seiteneigenschaften
Create & Edit Erstellen & Bearbeiten
Review & Compare Prüfen & Vergleichen
Forms & Signatures Formulare & Signaturen
Protect & Standardize Schützen & Standardisieren
Customize & Automate Anpassen & Automatisieren
No tools match your search. Keine Werkzeuge entsprechen Ihrer Suche.
Can't render this file because it contains an unexpected character in line 6 and column 51.
+6
View File
@@ -2438,3 +2438,9 @@ animation effects Off Ανενεργά
Make properties default Ορισμός ιδιοτήτων ως προεπιλογή
Pages selected Επιλεγμένες σελίδες
Page properties Ιδιότητες σελίδας
Create & Edit Δημιουργία και επεξεργασία
Review & Compare Αναθεώρηση και σύγκριση
Forms & Signatures Φόρμες και υπογραφές
Protect & Standardize Προστασία και τυποποίηση
Customize & Automate Προσαρμογή και αυτοματοποίηση
No tools match your search. Κανένα εργαλείο δεν ταιριάζει με την αναζήτησή σας.
Can't render this file because it contains an unexpected character in line 476 and column 9.
+6
View File
@@ -2568,3 +2568,9 @@ animation effects Off Desactivados
Make properties default Usar como propiedades predeterminadas
Pages selected Páginas seleccionadas
Page properties Propiedades de página
Create & Edit Crear y editar
Review & Compare Revisar y comparar
Forms & Signatures Formularios y firmas
Protect & Standardize Proteger y estandarizar
Customize & Automate Personalizar y automatizar
No tools match your search. Ninguna herramienta coincide con la búsqueda.
Can't render this file because it contains an unexpected character in line 6 and column 60.
+6
View File
@@ -2428,3 +2428,9 @@ animation effects Off Désactivés
Make properties default Définir comme propriétés par défaut
Pages selected Pages sélectionnées
Page properties Propriétés de la page
Create & Edit Créer et modifier
Review & Compare Réviser et comparer
Forms & Signatures Formulaires et signatures
Protect & Standardize Protéger et normaliser
Customize & Automate Personnaliser et automatiser
No tools match your search. Aucun outil ne correspond à votre recherche.
Can't render this file because it contains an unexpected character in line 6 and column 39.
+6
View File
@@ -2427,3 +2427,9 @@ animation effects Off Ki
Make properties default Tulajdonságok beállítása alapértelmezettként
Pages selected Kijelölt oldalak
Page properties Oldaltulajdonságok
Create & Edit Létrehozás és szerkesztés
Review & Compare Áttekintés és összehasonlítás
Forms & Signatures Űrlapok és aláírások
Protect & Standardize Védelem és szabványosítás
Customize & Automate Testreszabás és automatizálás
No tools match your search. Egy eszköz sem felel meg a keresésnek.
Can't render this file because it contains an unexpected character in line 7 and column 44.
+6
View File
@@ -2640,3 +2640,9 @@ certificate Key usage Utilizzo chiave
certificate Self-signed Autofirmato
certificate SHA-1 digest Impronta SHA-1
certificate SHA-256 digest Impronta SHA-256
Create & Edit Crea e modifica
Review & Compare Revisione e confronto
Forms & Signatures Moduli e firme
Protect & Standardize Proteggi e standardizza
Customize & Automate Personalizza e automatizza
No tools match your search. Nessuno strumento corrisponde alla ricerca.
Can't render this file because it contains an unexpected character in line 6 and column 47.
+6
View File
@@ -2428,3 +2428,9 @@ animation effects Off オフ
Make properties default プロパティをデフォルトに設定
Pages selected 選択したページ
Page properties ページのプロパティ
Create & Edit 作成と編集
Review & Compare レビューと比較
Forms & Signatures フォームと署名
Protect & Standardize 保護と標準化
Customize & Automate カスタマイズと自動化
No tools match your search. 検索に一致するツールはありません。
Can't render this file because it contains an unexpected character in line 6 and column 47.
+6
View File
@@ -2426,3 +2426,9 @@ animation effects Off Өшірулі
Make properties default Қасиеттерді әдепкі ету
Pages selected Таңдалған беттер
Page properties Бет қасиеттері
Create & Edit Жасау және өңдеу
Review & Compare Шолу және салыстыру
Forms & Signatures Пішіндер және қолтаңбалар
Protect & Standardize Қорғау және стандарттау
Customize & Automate Баптау және автоматтандыру
No tools match your search. Іздеуге сәйкес құралдар жоқ.
Can't render this file because it contains an unexpected character in line 825 and column 9.
+6
View File
@@ -2672,3 +2672,9 @@ key Space Tarpas
Make properties default Nustatyti ypatybes kaip numatytąsias
Pages selected Pasirinkti puslapiai
Page properties Puslapio ypatybės
Create & Edit Kūrimas ir redagavimas
Review & Compare Peržiūra ir palyginimas
Forms & Signatures Formos ir parašai
Protect & Standardize Apsauga ir standartizavimas
Customize & Automate Pritaikymas ir automatizavimas
No tools match your search. Paieškos neatitinka joks įrankis.
Can't render this file because it contains an unexpected character in line 404 and column 9.
+3
View File
@@ -811,6 +811,9 @@ mod tests {
for command in pdfcraft_engine::commands::COMMANDS {
assert!(has(JA(), command.label), "missing command: {}", command.label);
}
for category in pdfcraft_engine::catalog::TOOL_CATEGORIES {
assert!(has(JA(), category.label), "missing category: {}", category.label);
}
for group in pdfcraft_engine::catalog::TOOL_GROUPS {
assert!(has(JA(), group.label), "missing group: {}", group.label);
for section in group.sections {
+6
View File
@@ -2408,3 +2408,9 @@ animation effects Off Wyłączone
Make properties default Ustaw właściwości jako domyślne
Pages selected Zaznaczone strony
Page properties Właściwości strony
Create & Edit Tworzenie i edycja
Review & Compare Recenzja i porównanie
Forms & Signatures Formularze i podpisy
Protect & Standardize Ochrona i standaryzacja
Customize & Automate Dostosowanie i automatyzacja
No tools match your search. Żadne narzędzie nie pasuje do wyszukiwania.
Can't render this file because it contains an unexpected character in line 6 and column 47.
+6
View File
@@ -2442,3 +2442,9 @@ animation effects Off Desativados
Make properties default Tornar propriedades padrão
Pages selected Páginas selecionadas
Page properties Propriedades da página
Create & Edit Criar e editar
Review & Compare Revisar e comparar
Forms & Signatures Formulários e assinaturas
Protect & Standardize Proteger e padronizar
Customize & Automate Personalizar e automatizar
No tools match your search. Nenhuma ferramenta corresponde à pesquisa.
Can't render this file because it contains an unexpected character in line 9 and column 16.
+6
View File
@@ -2341,3 +2341,9 @@ animation effects Off Выкл
Make properties default Сделать свойства стандартными
Pages selected Выделенные страницы
Page properties Свойства страницы
Create & Edit Создание и редактирование
Review & Compare Рецензирование и сравнение
Forms & Signatures Формы и подписи
Protect & Standardize Защита и стандартизация
Customize & Automate Настройка и автоматизация
No tools match your search. Нет инструментов, соответствующих запросу.
Can't render this file because it contains an unexpected character in line 9 and column 16.
+6
View File
@@ -2433,3 +2433,9 @@ animation effects Off Izklopljeni
Make properties default Nastavi lastnosti kot privzete
Pages selected Izbrane strani
Page properties Lastnosti strani
Create & Edit Ustvarjanje in urejanje
Review & Compare Pregled in primerjava
Forms & Signatures Obrazci in podpisi
Protect & Standardize Zaščita in standardizacija
Customize & Automate Prilagajanje in avtomatizacija
No tools match your search. Iskanju ne ustreza nobeno orodje.
Can't render this file because it contains an unexpected character in line 9 and column 16.
+6
View File
@@ -2341,3 +2341,9 @@ animation effects Off ఆఫ్
Make properties default లక్షణాలను డిఫాల్ట్‌గా చేయి
Pages selected ఎంచుకున్న పేజీలు
Page properties పేజీ లక్షణాలు
Create & Edit సృష్టించు & సవరించు
Review & Compare సమీక్షించు & పోల్చు
Forms & Signatures ఫారమ్‌లు & సంతకాలు
Protect & Standardize రక్షించు & ప్రామాణీకరించు
Customize & Automate అనుకూలీకరించు & ఆటోమేట్ చేయి
No tools match your search. మీ శోధనకు సరిపోలే సాధనాలు లేవు.
Can't render this file because it contains an unexpected character in line 13 and column 16.
+6
View File
@@ -2424,3 +2424,9 @@ animation effects Off Kapalı
Make properties default Özellikleri varsayılan yap
Pages selected Seçilen sayfalar
Page properties Sayfa özellikleri
Create & Edit Oluştur ve Düzenle
Review & Compare İncele ve Karşılaştır
Forms & Signatures Formlar ve İmzalar
Protect & Standardize Koru ve Standartlaştır
Customize & Automate Özelleştir ve Otomatikleştir
No tools match your search. Aramanızla eşleşen araç yok.
Can't render this file because it contains an unexpected character in line 824 and column 9.
+6
View File
@@ -2450,3 +2450,9 @@ animation effects Off Вимкнено
Make properties default Зробити властивості типовими
Pages selected Вибрані сторінки
Page properties Властивості сторінки
Create & Edit Створення й редагування
Review & Compare Рецензування й порівняння
Forms & Signatures Форми й підписи
Protect & Standardize Захист і стандартизація
Customize & Automate Налаштування й автоматизація
No tools match your search. Немає інструментів, що відповідають пошуку.
Can't render this file because it contains an unexpected character in line 6 and column 47.
+6
View File
@@ -2615,3 +2615,9 @@ key Space Space
Redact Image Xóa nội dung nhạy cảm của hình ảnh
Create Link… Tạo liên kết…
Recognize Text… Nhận dạng văn bản…
Create & Edit Tạo và chỉnh sửa
Review & Compare Xem xét và so sánh
Forms & Signatures Biểu mẫu và chữ ký
Protect & Standardize Bảo vệ và chuẩn hóa
Customize & Automate Tùy chỉnh và tự động hóa
No tools match your search. Không có công cụ nào khớp với tìm kiếm.
Can't render this file because it contains an unexpected character in line 490 and column 12.
+6
View File
@@ -2392,3 +2392,9 @@ animation effects Off 关
Make properties default 将属性设为默认
Pages selected 选定的页面
Page properties 页面属性
Create & Edit 创建和编辑
Review & Compare 审阅和比较
Forms & Signatures 表单和签名
Protect & Standardize 保护和标准化
Customize & Automate 自定义和自动化
No tools match your search. 没有与搜索匹配的工具。
Can't render this file because it contains an unexpected character in line 9 and column 16.
+6
View File
@@ -2384,3 +2384,9 @@ animation effects Off 關
Make properties default 將內容設為預設
Pages selected 選取的頁面
Page properties 頁面屬性
Create & Edit 建立與編輯
Review & Compare 審閱與比較
Forms & Signatures 表單與簽名
Protect & Standardize 保護與標準化
Customize & Automate 自訂與自動化
No tools match your search. 沒有符合搜尋的工具。
Can't render this file because it contains an unexpected character in line 9 and column 16.
+20 -1
View File
@@ -30,6 +30,7 @@ macro_rules! tl_ctx {
mod a11y_ui;
mod actions_ui;
mod all_tools_ui;
mod appearance;
pub mod canvas;
mod chrome;
@@ -563,6 +564,9 @@ pub struct PdfCraftApp {
pub palette_open: bool,
pub palette_query: String,
pub all_tools_expanded: bool,
/// The All tools view fills the workspace (its sidebar's expand button).
pub all_tools_full: bool,
pub all_tools_query: String,
pub recent: Vec<RecentFile>,
/// Preferences: reopen the files that were open when PdfCraft last closed (#442).
pub reopen_last_session: bool,
@@ -929,6 +933,8 @@ impl PdfCraftApp {
palette_open: false,
palette_query: String::new(),
all_tools_expanded: false,
all_tools_full: false,
all_tools_query: String::new(),
recent: Vec::new(),
reopen_last_session: false,
last_session: Default::default(),
@@ -2171,6 +2177,10 @@ impl PdfCraftApp {
/// that panel. The rail stays in Read mode, and its buttons open panels there too (#932).
pub(crate) fn switch_mode(&mut self, mode: Mode, select_tools: bool) {
let was_read = self.mode == Mode::Read;
// Another workspace puts the expanded All tools view away.
if mode != Mode::AllTools {
self.all_tools_full = false;
}
if select_tools {
self.select_mode(mode);
} else {
@@ -2549,7 +2559,14 @@ impl PdfCraftApp {
_ => Some(Dialog::About),
}
}
("tools", _) => self.all_tools_expanded = value != "collapsed",
("tools", _) if value == "full" => {
self.mode = Mode::AllTools;
self.all_tools_full = true;
}
("tools", _) => {
self.all_tools_full = false;
self.all_tools_expanded = value != "collapsed";
}
("palette", _) => {
self.palette_open = true;
self.palette_query = value.to_string();
@@ -3145,6 +3162,8 @@ impl eframe::App for PdfCraftApp {
None => canvas::document_area(self, index, ui),
},
}
} else if self.mode == Mode::AllTools && self.all_tools_full {
all_tools_ui::show(self, ui);
} else {
docking::show(self, ui);
}
+8 -2
View File
@@ -35,11 +35,13 @@ pub fn left_panel(app: &mut PdfCraftApp, ui: &mut egui::Ui) {
);
}
/// Returns whether the leading button (Back, or Expand on All tools) and Close were clicked.
fn panel_header(ui: &mut egui::Ui, t: &Tokens, title: &str, back: bool) -> (bool, bool) {
let mut go_back = false;
let mut close = false;
ui.horizontal(|ui| {
if back && icons::button(ui, "chevron-left", 26.0, false, tl!("Back to all tools")).clicked() {
let (icon, tip) = if back { ("chevron-left", "Back to all tools") } else { ("maximize-2", "Expand") };
if icons::button(ui, icon, 26.0, false, tl!(tip)).clicked() {
go_back = true;
}
// Reserve space for Close before laying out the translated title. Long tool names
@@ -58,7 +60,11 @@ fn panel_header(ui: &mut egui::Ui, t: &Tokens, title: &str, back: bool) -> (bool
}
fn all_tools(app: &mut PdfCraftApp, ui: &mut egui::Ui, t: &Tokens) {
let (_, close) = panel_header(ui, t, "All tools", false);
let (expand, close) = panel_header(ui, t, "All tools", false);
if expand {
app.all_tools_full = true;
app.mode = crate::Mode::AllTools;
}
if close {
app.left_open = false;
}
+44
View File
@@ -125,6 +125,50 @@ fn every_catalog_tool_is_listed_after_view_more() {
}
}
#[test]
fn expanded_all_tools_lists_every_tool_by_category_and_searches() {
let mut h = harness(|_| {});
h.get_by_label("Expand").click();
h.run_steps(3);
assert!(h.state().all_tools_full);
for c in pdfcraft_engine::catalog::TOOL_CATEGORIES {
h.get_by_label(c.label);
}
for g in pdfcraft_engine::catalog::TOOL_GROUPS {
assert!(h.query_all_by_label(g.label).count() >= 1, "tool {} missing from the expanded view", g.label);
}
// An item name finds its tool; the other categories drop out.
h.state_mut().all_tools_query = "rotate".into();
h.run_steps(3);
assert_eq!(h.query_all_by_label("Organize pages").count(), 1, "only the expanded view's tile; the sidebar is gone");
assert!(h.query_by_label("Forms & Signatures").is_none());
h.state_mut().all_tools_query = "no such tool".into();
h.run_steps(3);
h.get_by_label("No tools match your search.");
// Choosing a tool goes back to the workspace with that tool's panel.
h.state_mut().all_tools_query = "organize".into();
h.run_steps(3);
h.get_by_label("Organize pages").click();
h.run_steps(3);
assert!(!h.state().all_tools_full);
assert_eq!(h.state().left, pdfcraft_ui_egui::LeftPanel::Tool("organize"));
h.get_by_label("Back to all tools");
}
#[test]
fn collapse_and_escape_close_the_expanded_all_tools() {
let mut h = harness(|app| app.set_option("tools", "full").unwrap());
h.get_by_label("Collapse").click();
h.run_steps(3);
assert!(!h.state().all_tools_full);
h.get_by_label("View more");
h.get_by_label("Expand").click();
h.run_steps(3);
h.key_press(egui::Key::Escape);
h.run_steps(3);
assert!(!h.state().all_tools_full);
}
#[test]
fn opening_a_pdf_shows_comments_and_bookmarks() {
let mut h = harness(|app| app.open_bytes("fixture.pdf", None, FIXTURE.to_vec()).expect("fixture opens"));