Integration: M3: All tools expands to a full, searchable view (#2460)

This commit is contained in:
aiongg
2026-10-24 22:48:11 +00:00
41 changed files with 725 additions and 11 deletions
+29
View File
@@ -550,6 +550,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::*;
@@ -564,6 +581,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 {
+196
View File
@@ -0,0 +1,196 @@
//! 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 `query` (already trimmed and lower-cased) is in `label` or its translation.
/// A right-to-left language's translations are in display order (their words run the other way)
/// while the query is in typing order, so there several words match one by one, as in the
/// command palette.
fn label_hit(label: &str, translated: &str, query: &str, rtl: bool) -> bool {
let translated = translated.to_lowercase();
label.to_lowercase().contains(query)
|| translated.contains(query)
|| (rtl && query.contains(char::is_whitespace) && query.split_whitespace().all(|word| translated.contains(word)))
}
/// 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. An entry with a menu (Watermark: Add, Update, Remove) also matches by its commands'
/// names, so "remove watermark" finds it.
fn matches(query: &str, group: &ToolGroup) -> Option<Vec<&'static str>> {
let rtl = crate::i18n::current().rtl();
let hit = |label: &str| label_hit(label, tl!(label), query, rtl);
if query.is_empty() || hit(group.label) {
return Some(Vec::new());
}
let action_hit = |id: &&str| {
pdfcraft_engine::commands::command(id).is_some_and(|spec| label_hit(spec.label, &crate::i18n::menu_label(spec.id, spec.label), query, rtl))
};
let items: Vec<_> =
group.sections.iter().flat_map(|s| s.items.iter()).filter(|i| hit(i.label) || i.actions.iter().any(action_hit)).map(|i| i.label).collect();
(!items.is_empty()).then_some(items)
}
/// Draw the view over the workspace: the title with Collapse, the search field, and each
/// category's tiles that match the search. Choosing a tile opens that tool's panel.
pub fn show(app: &mut PdfCraftApp, ui: &mut egui::Ui) {
let t = Tokens::get(ui.ctx());
// A dialog, prompt or the command palette over the view owns Escape.
if !app.modal_open() && !app.palette_open && 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 all tools")).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.left_open = true;
app.select_tool(id);
}
}
/// One tool's tile: its icon and name, then the `items` the search found it by or, when there are
/// none, its availability (a planned tool is greyed out with its milestone). A name or note cut
/// short shows in full on hover.
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);
}
#[test]
fn search_finds_an_entry_by_its_menus_commands() {
let found = matches("remove watermark", group("edit")).expect("Watermark's menu has Remove watermark");
assert!(found.contains(&"Watermark"), "{found:?}");
}
#[test]
fn right_to_left_labels_match_a_query_typed_in_logical_order() {
let shown = crate::bidi::display_rtl("تنظيم الصفحات");
assert_ne!(shown, "تنظيم الصفحات", "the catalogue reverses the words for display");
assert!(label_hit("Organize pages", &shown, "تنظيم الصفحات", true));
assert!(label_hit("Organize pages", &shown, "الصفحات", true), "one word");
assert!(!label_hit("Organize pages", &shown, "تنظيم الملفات", true), "every word must be there");
assert!(!label_hit("Organize pages", &shown, "تنظيم الصفحات", false), "left-to-right labels stay exact");
}
}
+1 -1
View File
@@ -494,7 +494,7 @@ fn explained(issues: &[CalculationIssue]) -> String {
/// The Calculation order page; Back returns to Prepare a form.
pub(crate) fn panel(app: &mut PdfCraftApp, ui: &mut egui::Ui, t: &Tokens) {
let (back, close) = crate::panels::panel_header(ui, t, "Calculation order", Some(tl!("Back to Prepare a form")));
let (back, close) = crate::panels::panel_header(ui, t, "Calculation order", Some(("chevron-left", tl!("Back to Prepare a form"))));
if back {
app.left = LeftPanel::Tool("form");
}
+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) };
+8
View File
@@ -2766,6 +2766,14 @@ certificate Version الإصدار
Make properties default جعل الخصائص افتراضية
Pages selected الصفحات المحددة
Page properties خصائص الصفحة
Create & Edit الإنشاء والتحرير
Review & Compare المراجعة والمقارنة
Forms & Signatures النماذج والتوقيعات
Protect & Standardize الحماية والتوحيد القياسي
Customize & Automate التخصيص والأتمتة
No tools match your search. لا توجد أدوات تطابق بحثك.
Expand all tools توسيع كل الأدوات
Collapse all tools طي كل الأدوات
Attach a file… إرفاق ملف…
Delete attachment حذف المرفق
Attaching files is not available on the web yet إرفاق الملفات غير متاح على الويب بعد
Can't render this file because it contains an unexpected character in line 6 and column 47.
+8
View File
@@ -2452,6 +2452,14 @@ 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. Няма инструменти, отговарящи на търсенето.
Expand all tools Разгъване на всички инструменти
Collapse all tools Свиване на всички инструменти
Attach a file… Прикачване на файл…
Delete attachment Изтриване на прикачения файл
Attaching files is not available on the web yet Прикачването на файлове още не е налично в уеб версията
Can't render this file because it contains an unexpected character in line 8 and column 16.
+8
View File
@@ -2654,6 +2654,14 @@ certificate SHA-256 digest Resum SHA-256
Optimizing… merging image fragments S'està optimitzant… fusionant fragments d'imatge
Outline colour: Color del contorn:
Page properties Propietats de la pàgina
Create & Edit Crea i edita
Review & Compare Revisa i compara
Forms & Signatures Formularis i signatures
Protect & Standardize Protegeix i estandarditza
Customize & Automate Personalitza i automatitza
No tools match your search. Cap eina coincideix amb la cerca.
Expand all tools Desplega totes les eines
Collapse all tools Replega totes les eines
Pages counted Pàgines comptades
Pages selected Pàgines seleccionades
Print as image Imprimeix com a imatge
Can't render this file because it contains an unexpected character in line 7 and column 3.
+8
View File
@@ -2720,6 +2720,14 @@ certificate Version وەشان
Make properties default کردنی تایبەتمەندییەکان بە بنەڕەتی
Pages selected پەڕە هەڵبژێردراوەکان
Page properties تایبەتمەندییەکانی پەڕە
Create & Edit دروستکردن و دەستکاریکردن
Review & Compare پێداچوونەوە و بەراوردکردن
Forms & Signatures فۆڕمەکان و واژووەکان
Protect & Standardize پاراستن و ستانداردکردن
Customize & Automate تایبەتکردن و خۆکارکردن
No tools match your search. هیچ ئامرازێک لەگەڵ گەڕانەکەت ناگونجێت.
Expand all tools فراوانکردنی هەموو ئامرازەکان
Collapse all tools کۆکردنەوەی هەموو ئامرازەکان
# Date picker: Today button (#4274) (AI-assisted, not separately native-reviewed)
Today ئەمڕۆ
Can't render this file because it contains an unexpected character in line 6 and column 47.
+8
View File
@@ -2563,6 +2563,14 @@ 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.
Expand all tools Rozbalit všechny nástroje
Collapse all tools Sbalit všechny nástroje
Attach a file… Připojit soubor…
Delete attachment Odstranit přílohu
Attaching files is not available on the web yet Připojování souborů zatím na webu není k dispozici
Can't render this file because it contains an unexpected character in line 6 and column 47.
+8
View File
@@ -2490,6 +2490,14 @@ 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.
Expand all tools Alle Werkzeuge ausklappen
Collapse all tools Alle Werkzeuge einklappen
Attach a file… Datei anhängen…
Delete attachment Anhang löschen
Attaching files is not available on the web yet Dateien anhängen ist im Web noch nicht verfügbar
Can't render this file because it contains an unexpected character in line 6 and column 51.
+8
View File
@@ -2495,6 +2495,14 @@ 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. Κανένα εργαλείο δεν ταιριάζει με την αναζήτησή σας.
Expand all tools Ανάπτυξη όλων των εργαλείων
Collapse all tools Σύμπτυξη όλων των εργαλείων
Attach a file… Επισύναψη αρχείου…
Delete attachment Διαγραφή συνημμένου
Attaching files is not available on the web yet Η επισύναψη αρχείων δεν είναι ακόμη διαθέσιμη στον ιστό
Can't render this file because it contains an unexpected character in line 482 and column 9.
+8
View File
@@ -2632,6 +2632,14 @@ 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.
Expand all tools Expandir todas las herramientas
Collapse all tools Contraer todas las herramientas
Font Fuente
Bulleted list Lista con viñetas
Numbered list Lista numerada
Can't render this file because it contains an unexpected character in line 6 and column 60.
+8
View File
@@ -2698,6 +2698,14 @@ certificate SHA-256 digest SHA-256-tiiviste
Make properties default Aseta ominaisuudet oletuksiksi
Pages selected Valitut sivut
Page properties Sivun ominaisuudet
Create & Edit Luo ja muokkaa
Review & Compare Tarkasta ja vertaa
Forms & Signatures Lomakkeet ja allekirjoitukset
Protect & Standardize Suojaa ja standardoi
Customize & Automate Mukauta ja automatisoi
No tools match your search. Hakuasi vastaavia työkaluja ei löytynyt.
Expand all tools Laajenna kaikki työkalut
Collapse all tools Kutista kaikki työkalut
The password must have at least 8 characters. Salasanassa on oltava vähintään 8 merkkiä.
theme Appearance Teema
# Labels added upstream 2026-10-16…18.
Can't render this file because it contains an unexpected character in line 5 and column 47.
+8
View File
@@ -2489,6 +2489,14 @@ 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.
Expand all tools Développer tous les outils
Collapse all tools Réduire tous les outils
Attach a file… Joindre un fichier…
Delete attachment Supprimer la pièce jointe
Attaching files is not available on the web yet Joindre des fichiers n'est pas encore disponible sur le web
Can't render this file because it contains an unexpected character in line 6 and column 39.
+8
View File
@@ -2682,6 +2682,14 @@ certificate SHA-256 digest SHA-256 डाइजेस्ट
Make properties default गुण डिफ़ॉल्ट बनाएँ
Pages selected चयनित पृष्ठ
Page properties पृष्ठ गुण
Create & Edit बनाएँ और संपादित करें
Review & Compare समीक्षा और तुलना करें
Forms & Signatures फ़ॉर्म और हस्ताक्षर
Protect & Standardize सुरक्षित और मानकीकृत करें
Customize & Automate अनुकूलित और स्वचालित करें
No tools match your search. आपकी खोज से कोई टूल मेल नहीं खाता।
Expand all tools सभी टूल विस्तृत करें
Collapse all tools सभी टूल समेटें
The password must have at least 8 characters. पासवर्ड में कम से कम 8 वर्ण होने चाहिए।
theme Appearance स्वरूप
Zoom — type a percentage and press Enter ज़ूम — प्रतिशत टाइप करें और Enter दबाएँ
Can't render this file because it contains an unexpected character in line 855 and column 12.
+8
View File
@@ -2488,6 +2488,14 @@ 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.
Expand all tools Az összes eszköz kibontása
Collapse all tools Az összes eszköz összecsukása
Attach a file… Fájl csatolása…
Delete attachment Melléklet törlése
Attaching files is not available on the web yet Fájlok csatolása a weben még nem érhető el
Can't render this file because it contains an unexpected character in line 7 and column 44.
+8
View File
@@ -2683,6 +2683,14 @@ key Space Space
Make properties default Jadikan properti default
Pages selected Halaman yang dipilih
Page properties Properti halaman
Create & Edit Buat & Edit
Review & Compare Tinjau & Bandingkan
Forms & Signatures Formulir & Tanda Tangan
Protect & Standardize Lindungi & Standarkan
Customize & Automate Sesuaikan & Otomatiskan
No tools match your search. Tidak ada alat yang cocok dengan pencarian Anda.
Expand all tools Perluas semua alat
Collapse all tools Ciutkan semua alat
The password must have at least 8 characters. Kata sandi harus memiliki minimal 8 karakter.
theme Appearance Tampilan
Zoom — type a percentage and press Enter Zoom — ketik persentase dan tekan Enter
Can't render this file because it contains an unexpected character in line 483 and column 12.
+8
View File
@@ -2701,6 +2701,14 @@ 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.
Expand all tools Espandi tutti gli strumenti
Collapse all tools Comprimi tutti gli strumenti
Attach a file… Allega un file…
Delete attachment Elimina allegato
Attaching files is not available on the web yet Allegare file non è ancora disponibile sul web
Can't render this file because it contains an unexpected character in line 6 and column 47.
+8
View File
@@ -2489,6 +2489,14 @@ 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. 検索に一致するツールはありません。
Expand all tools すべてのツールを展開
Collapse all tools すべてのツールを折りたたむ
Attach a file… ファイルを添付…
Delete attachment 添付ファイルを削除
Attaching files is not available on the web yet Web 版ではまだファイルを添付できません
Can't render this file because it contains an unexpected character in line 6 and column 47.
+8
View File
@@ -2487,6 +2487,14 @@ 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. Іздеуге сәйкес құралдар жоқ.
Expand all tools Барлық құралдарды жаю
Collapse all tools Барлық құралдарды жию
Attach a file… Файл тіркеу…
Delete attachment Тіркемені жою
Attaching files is not available on the web yet Вебте файл тіркеу әзірге қолжетімсіз
Can't render this file because it contains an unexpected character in line 860 and column 9.
+8
View File
@@ -2682,6 +2682,14 @@ certificate SHA-256 digest SHA-256 다이제스트
Make properties default 속성을 기본값으로 설정
Pages selected 선택한 페이지
Page properties 페이지 속성
Create & Edit 만들기 및 편집
Review & Compare 검토 및 비교
Forms & Signatures 양식 및 서명
Protect & Standardize 보호 및 표준화
Customize & Automate 사용자 지정 및 자동화
No tools match your search. 검색과 일치하는 도구가 없습니다.
Expand all tools 모든 도구 펼치기
Collapse all tools 모든 도구 접기
The password must have at least 8 characters. 암호는 8자 이상이어야 합니다.
theme Appearance 모양
Zoom — type a percentage and press Enter 줌 — 백분율을 입력하고 Enter를 누르세요
Can't render this file because it contains an unexpected character in line 855 and column 12.
+8
View File
@@ -2436,6 +2436,14 @@ 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. ບໍ່ມີເຄື່ອງມືທີ່ກົງກັບການຄົ້ນຫາຂອງທ່ານ.
Expand all tools ຂະຫຍາຍເຄື່ອງມືທັງໝົດ
Collapse all tools ຫຍໍ້ເຄື່ອງມືທັງໝົດເຂົ້າ
# Date picker: Today button (#4274) (AI-assisted, not separately native-reviewed)
Today ມື້ນີ້
Can't render this file because it contains an unexpected character in line 6 and column 32.
+8
View File
@@ -2739,6 +2739,14 @@ 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.
Expand all tools Išskleisti visus įrankius
Collapse all tools Suskleisti visus įrankius
# AI-assisted additions for concurrently merged UI features (2026-10-15).
# These additions have not had separate native-speaker review.
Can't render this file because it contains an unexpected character in line 406 and column 9.
+1
View File
@@ -1794,6 +1794,7 @@ mod tests {
section.items.iter().for_each(|item| add(item.label));
}
}
pdfcraft_engine::catalog::TOOL_CATEGORIES.iter().for_each(|category| add(category.label));
let engine: [&[&str]; 9] = [
pdfcraft_engine::a11y::WORDINGS,
pdfcraft_engine::a11y::REPORT_WORDS,
+8
View File
@@ -2614,6 +2614,14 @@
Make properties default Bruk egenskapene som standard
Pages selected Valgte sider
Page properties Sideegenskaper
Create & Edit Opprett og rediger
Review & Compare Gjennomgå og sammenlign
Forms & Signatures Skjemaer og signaturer
Protect & Standardize Beskytt og standardiser
Customize & Automate Tilpass og automatiser
No tools match your search. Ingen verktøy samsvarer med søket.
Expand all tools Utvid alle verktøy
Collapse all tools Fold sammen alle verktøy
Cut Image Klipp ut bilde
Redact Image Sladd bilde
Create Link… Opprett lenke…
Can't render this file because it contains an unexpected character in line 8 and column 16.
+8
View File
@@ -2465,6 +2465,14 @@ 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.
Expand all tools Rozwiń wszystkie narzędzia
Collapse all tools Zwiń wszystkie narzędzia
Attach a file… Dołącz plik…
Delete attachment Usuń załącznik
Attaching files is not available on the web yet Dołączanie plików nie jest jeszcze dostępne w wersji webowej
Can't render this file because it contains an unexpected character in line 6 and column 47.
+8
View File
@@ -2503,6 +2503,14 @@ 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.
Expand all tools Expandir todas as ferramentas
Collapse all tools Recolher todas as ferramentas
Attach a file… Anexar um arquivo…
Delete attachment Excluir anexo
Attaching files is not available on the web yet Anexar arquivos ainda não está disponível na web
Can't render this file because it contains an unexpected character in line 9 and column 16.
+8
View File
@@ -2654,6 +2654,14 @@ key Space Spațiu
Optimizing… merging image fragments Se optimizează… unificare fragmente de imagine
Outline colour: Culoare contur:
Page properties Proprietăți pagină
Create & Edit Creare și editare
Review & Compare Revizuire și comparare
Forms & Signatures Formulare și semnături
Protect & Standardize Protejare și standardizare
Customize & Automate Personalizare și automatizare
No tools match your search. Niciun instrument nu corespunde căutării.
Expand all tools Extinderea tuturor instrumentelor
Collapse all tools Restrângerea tuturor instrumentelor
Pages counted Pagini numărate
Pages selected Pagini selectate
Print as image Imprimare ca imagine
Can't render this file because it contains an unexpected character in line 3 and column 110.
+8
View File
@@ -2402,6 +2402,14 @@ 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. Нет инструментов, соответствующих запросу.
Expand all tools Развернуть все инструменты
Collapse all tools Свернуть все инструменты
Attach a file… Прикрепить файл…
Delete attachment Удалить вложение
Attaching files is not available on the web yet Прикрепление файлов в веб-версии пока недоступно
Can't render this file because it contains an unexpected character in line 9 and column 16.
+8
View File
@@ -2494,6 +2494,14 @@ 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.
Expand all tools Razširi vsa orodja
Collapse all tools Strni vsa orodja
Attach a file… Priloži datoteko…
Delete attachment Izbriši prilogo
Attaching files is not available on the web yet Prilaganje datotek na spletu še ni na voljo
Can't render this file because it contains an unexpected character in line 9 and column 16.
+8
View File
@@ -2420,6 +2420,14 @@ animation effects Off Joaktiv
Other options Other options
Outline colour: Outline colour:
Page properties Page properties
Create & Edit Krijo dhe redakto
Review & Compare Rishiko dhe krahaso
Forms & Signatures Formularë dhe nënshkrime
Protect & Standardize Mbro dhe standardizo
Customize & Automate Personalizo dhe automatizo
No tools match your search. Asnjë mjet nuk përputhet me kërkimin tuaj.
Expand all tools Zgjero të gjitha mjetet
Collapse all tools Palos të gjitha mjetet
Pages counted Pages counted
Pages selected Pages selected
Paragraph spacing Paragraph spacing
Can't render this file because it contains an unexpected character in line 9 and column 16.
+8
View File
@@ -2398,6 +2398,14 @@ 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. మీ శోధనకు సరిపోలే సాధనాలు లేవు.
Expand all tools అన్ని సాధనాలను విస్తరించు
Collapse all tools అన్ని సాధనాలను కుదించు
Attach a file… ఫైల్‌ను జోడించు…
Delete attachment జోడింపును తొలగించు
Attaching files is not available on the web yet వెబ్‌లో ఫైల్‌లను జోడించడం ఇంకా అందుబాటులో లేదు
Can't render this file because it contains an unexpected character in line 13 and column 16.
+8
View File
@@ -2686,6 +2686,14 @@ key Space Space
Make properties default ตั้งคุณสมบัติเป็นค่าเริ่มต้น
Pages selected หน้าที่เลือก
Page properties คุณสมบัติหน้า
Create & Edit สร้างและแก้ไข
Review & Compare ตรวจทานและเปรียบเทียบ
Forms & Signatures แบบฟอร์มและลายเซ็น
Protect & Standardize ป้องกันและกำหนดมาตรฐาน
Customize & Automate ปรับแต่งและทำงานอัตโนมัติ
No tools match your search. ไม่มีเครื่องมือที่ตรงกับการค้นหาของคุณ
Expand all tools ขยายเครื่องมือทั้งหมด
Collapse all tools ยุบเครื่องมือทั้งหมด
The password must have at least 8 characters. รหัสผ่านต้องมีอย่างน้อย 8 ตัวอักษร
theme Appearance ลักษณะที่ปรากฏ
Can't render this file because it contains an unexpected character in line 866 and column 12.
+8
View File
@@ -2485,6 +2485,14 @@ 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.
Expand all tools Tüm araçları genişlet
Collapse all tools Tüm araçları daralt
Attach a file… Dosya ekle…
Delete attachment Eki sil
Attaching files is not available on the web yet Dosya eklemek web'de henüz kullanılamıyor
Can't render this file because it contains an unexpected character in line 859 and column 9.
+8
View File
@@ -2511,6 +2511,14 @@ 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. Немає інструментів, що відповідають пошуку.
Expand all tools Розгорнути всі інструменти
Collapse all tools Згорнути всі інструменти
Attach a file… Прикріпити файл…
Delete attachment Видалити вкладення
Attaching files is not available on the web yet Прикріплення файлів у веб-версії поки недоступне
Can't render this file because it contains an unexpected character in line 6 and column 47.
+8
View File
@@ -2676,6 +2676,14 @@ 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.
Expand all tools Mở rộng tất cả công cụ
Collapse all tools Thu gọn tất cả công cụ
# AI-assisted additions for concurrently merged UI features (2026-10-15).
# These additions have not had separate native-speaker review.
Can't render this file because it contains an unexpected character in line 493 and column 12.
+8
View File
@@ -2453,6 +2453,14 @@ 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. 没有与搜索匹配的工具。
Expand all tools 展开所有工具
Collapse all tools 折叠所有工具
Attach a file… 附加文件…
Delete attachment 删除附件
Attaching files is not available on the web yet 网页版尚不支持附加文件
Can't render this file because it contains an unexpected character in line 9 and column 16.
+8
View File
@@ -2439,6 +2439,14 @@ 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. 沒有符合搜尋的工具。
Expand all tools 展開所有工具
Collapse all tools 摺疊所有工具
Attach a file… 附加檔案…
Delete attachment 刪除附件
Attaching files is not available on the web yet 網頁版尚不支援附加檔案
Can't render this file because it contains an unexpected character in line 9 and column 16.
+56 -2
View File
@@ -32,6 +32,7 @@ macro_rules! tl_ctx {
mod a11y_ui;
mod actions_ui;
mod all_tools_ui;
mod appearance;
mod calc_order_ui;
pub mod canvas;
@@ -708,6 +709,12 @@ 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,
/// The tab that showed when the expanded All tools view opened ([`Self::shown_tab`]). Another
/// tab coming to the front (Home, a document, Combine files) puts the view away.
all_tools_tab: (Option<DocId>, bool),
pub recent: Vec<RecentFile>,
/// Preferences: reopen the files that were open when PdfCraft last closed (#442).
pub reopen_last_session: bool,
@@ -1133,6 +1140,9 @@ impl PdfCraftApp {
palette_open: false,
palette_query: String::new(),
all_tools_expanded: false,
all_tools_full: false,
all_tools_query: String::new(),
all_tools_tab: (None, false),
recent: Vec::new(),
reopen_last_session: false,
restore_last_view: true,
@@ -1601,6 +1611,10 @@ impl PdfCraftApp {
{
view.open_organize();
}
// The document shows, not the expanded All tools view over it (whichever workspace opens).
if activate {
self.all_tools_full = false;
}
if activate && let Some(mode) = self.mode_override {
// Explicit mode options do not reset independent --tool / --left choices.
self.mode = mode;
@@ -2463,6 +2477,8 @@ impl PdfCraftApp {
/// Enter a tool from the catalogue, Home, palette or control channel. Organize is a
/// workspace, so opening its panel also opens the page grid (without toggling it off).
pub(crate) fn select_tool(&mut self, id: &'static str) {
// The tool's panel shows, not the expanded All tools view over it.
self.all_tools_full = false;
self.left = LeftPanel::Tool(id);
self.reveal_tools();
if let Some(view) = self.active.and_then(|i| self.views.get_mut(i)) {
@@ -2497,6 +2513,10 @@ impl PdfCraftApp {
/// Edit puts the editing tools down again and applies a paragraph still open in the editor.
pub(crate) fn enter_mode(&mut self, mode: Mode) {
self.mode = mode;
// Another workspace puts the expanded All tools view away.
if mode != Mode::AllTools {
self.all_tools_full = false;
}
if mode == Mode::Edit {
self.quick_tool = QuickTool::EditText;
} else if matches!(self.quick_tool, QuickTool::EditText | QuickTool::AddText) {
@@ -2509,6 +2529,21 @@ impl PdfCraftApp {
}
}
/// Fill the workspace with the expanded All tools view, entering All tools the way the mode
/// bar does: leaving Read restores its right panel and leaving Edit puts the editing tool down.
pub(crate) fn expand_all_tools(&mut self) {
if self.mode != Mode::AllTools {
self.switch_mode(Mode::AllTools, true);
}
self.all_tools_full = true;
self.all_tools_tab = self.shown_tab();
}
/// The tab at the front: the active document, or else whether Combine files shows over Home.
fn shown_tab(&self) -> (Option<DocId>, bool) {
(self.active.and_then(|i| self.views.get(i)).map(|v| v.id), self.combine_showing())
}
/// Back to the Select tool from an editing tool. A paragraph still open in the editor is
/// applied, as clicking away from it does; left open, its page would keep showing the
/// paragraph's text removed with no editor over it.
@@ -2893,6 +2928,10 @@ impl PdfCraftApp {
let mode = Mode::parse(value).unwrap_or(Mode::AllTools);
self.mode_override = Some(mode);
self.mode = mode;
// As switch_mode: another workspace puts the expanded All tools view away.
if mode != Mode::AllTools {
self.all_tools_full = false;
}
}
("default-mode", _) => {
self.default_mode = Mode::parse(value).ok_or("default-mode must be all, read, edit, convert or sign")?;
@@ -2904,7 +2943,12 @@ impl PdfCraftApp {
self.select_tool(g.id);
}
("left", _) => self.left_open = value != "closed",
("home", _) => self.active = None,
("home", _) => {
self.active = None;
// With no document open the expanded All tools view covers Home itself, so no
// other tab comes forward to put it away.
self.all_tools_full = false;
}
("dialog", _) => {
self.dialog = match value {
"properties" => Some(Dialog::Properties(PropsTab::Description)),
@@ -2949,7 +2993,11 @@ impl PdfCraftApp {
_ => Some(Dialog::About),
}
}
("tools", _) => self.all_tools_expanded = value != "collapsed",
("tools", _) if value == "full" => self.expand_all_tools(),
("tools", _) => {
self.all_tools_full = false;
self.all_tools_expanded = value != "collapsed";
}
("palette", _) => {
self.palette_open = true;
self.palette_query = value.to_string();
@@ -3662,6 +3710,10 @@ impl eframe::App for PdfCraftApp {
.show(ui, |ui| panels::right_panel(self, ui));
}
}
// Whichever way another tab came to the front, it shows instead of the expanded view.
if self.all_tools_full && self.all_tools_tab != self.shown_tab() {
self.all_tools_full = false;
}
let t = theme::Tokens::get(&ctx);
egui::CentralPanel::default().frame(egui::Frame::NONE.fill(t.pasteboard)).show(ui, |ui| {
if self.mode == Mode::Read {
@@ -3673,6 +3725,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);
}
+13 -7
View File
@@ -36,14 +36,14 @@ pub fn left_panel(app: &mut PdfCraftApp, ui: &mut egui::Ui) {
);
}
/// A panel's title row: Back (with `back` as its tooltip) when given, the title and Close.
/// Returns whether Back and Close were clicked.
pub(crate) fn panel_header(ui: &mut egui::Ui, t: &Tokens, title: &str, back: Option<&str>) -> (bool, bool) {
/// A panel's title row: a leading button (`lead`: its icon and tooltip) when given, the title
/// and Close. Returns whether the leading button and Close were clicked.
pub(crate) fn panel_header(ui: &mut egui::Ui, t: &Tokens, title: &str, lead: Option<(&str, &str)>) -> (bool, bool) {
let mut go_back = false;
let mut close = false;
ui.horizontal(|ui| {
if let Some(tip) = back
&& icons::button(ui, "chevron-left", 26.0, false, tip).clicked()
if let Some((icon, tip)) = lead
&& icons::button(ui, icon, 26.0, false, tip).clicked()
{
go_back = true;
}
@@ -62,8 +62,14 @@ pub(crate) fn panel_header(ui: &mut egui::Ui, t: &Tokens, title: &str, back: Opt
(go_back, close)
}
/// The All tools sidebar: Expand (the full view) and Close, then the tool list, the first few
/// tools until View more shows them all.
fn all_tools(app: &mut PdfCraftApp, ui: &mut egui::Ui, t: &Tokens) {
let (_, close) = panel_header(ui, t, "All tools", None);
// Labels distinct from the Combine grid's Expand and Collapse, which can show beside them.
let (expand, close) = panel_header(ui, t, "All tools", Some(("maximize-2", tl!("Expand all tools"))));
if expand {
app.expand_all_tools();
}
if close {
app.left_open = false;
}
@@ -154,7 +160,7 @@ fn tool_detail(app: &mut PdfCraftApp, ui: &mut egui::Ui, t: &Tokens, g: &'static
edit_panel(app, ui, t, g);
return;
}
let (back, close) = panel_header(ui, t, g.label, Some(tl!("Back to all tools")));
let (back, close) = panel_header(ui, t, g.label, Some(("chevron-left", tl!("Back to all tools"))));
if back {
app.left = LeftPanel::AllTools;
app.enter_mode(crate::Mode::AllTools);
+164
View File
@@ -125,6 +125,170 @@ 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 all tools").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 all tools").click();
h.run_steps(3);
assert!(!h.state().all_tools_full);
h.get_by_label("View more");
h.get_by_label("Expand all tools").click();
h.run_steps(3);
h.key_press(egui::Key::Escape);
h.run_steps(3);
assert!(!h.state().all_tools_full);
}
#[test]
fn escape_in_a_dialog_over_the_expanded_all_tools_leaves_the_view_open() {
let mut h = harness(|app| app.set_option("tools", "full").unwrap());
h.state_mut().dialog = Some(pdfcraft_ui_egui::Dialog::About);
h.run_steps(3);
h.key_press(egui::Key::Escape);
h.run_steps(3);
assert!(h.state().all_tools_full, "Escape belonged to the dialog");
}
#[test]
fn escape_in_the_command_palette_over_the_expanded_all_tools_leaves_the_view_open() {
let mut h = harness(|app| app.set_option("tools", "full").unwrap());
h.state_mut().palette_open = true;
h.run_steps(3);
h.key_press(egui::Key::Escape);
h.run_steps(3);
assert!(!h.state().palette_open, "Escape closed the palette");
assert!(h.state().all_tools_full, "and only the palette");
}
#[test]
fn another_workspace_from_the_mode_option_puts_the_expanded_all_tools_away() {
let mut app = PdfCraftApp::new();
app.set_option("tools", "full").unwrap();
app.set_option("mode", "all").unwrap();
assert!(app.all_tools_full, "All tools itself keeps the view");
app.set_option("mode", "read").unwrap();
app.set_option("mode", "all").unwrap();
assert!(!app.all_tools_full, "coming back shows the sidebar, as the mode bar does");
}
#[test]
fn opening_a_document_puts_the_expanded_all_tools_away() {
use pdfcraft_ui_egui::Mode;
// All tools as the default workspace: no workspace change, but the document must show.
let mut app = PdfCraftApp::new();
app.set_option("tools", "full").unwrap();
app.open_bytes("fixture.pdf", None, FIXTURE.to_vec()).expect("fixture opens");
assert!(!app.all_tools_full, "the document shows, not the catalogue over it");
// Read as the default workspace: coming back to All tools later shows the sidebar.
let mut app = PdfCraftApp::new();
app.set_option("default-mode", "read").unwrap();
app.set_option("tools", "full").unwrap();
app.open_bytes("fixture.pdf", None, FIXTURE.to_vec()).expect("fixture opens");
assert_eq!(app.mode, Mode::Read);
assert!(!app.all_tools_full);
}
#[test]
fn choosing_a_tool_puts_the_expanded_all_tools_away() {
use pdfcraft_ui_egui::LeftPanel;
let mut h = harness(|app| app.set_option("tools", "full").unwrap());
h.state_mut().set_option("tool", "organize").unwrap();
h.run_steps(3);
assert!(!h.state().all_tools_full, "the tool's panel shows, not the catalogue");
assert_eq!(h.state().left, LeftPanel::Tool("organize"));
}
#[test]
fn another_tab_coming_forward_puts_the_expanded_all_tools_away() {
// Home, through the control option.
let mut h = harness(|app| app.open_bytes("fixture.pdf", None, FIXTURE.to_vec()).expect("fixture opens"));
h.state_mut().set_option("tools", "full").unwrap();
h.run_steps(3);
assert!(h.state().all_tools_full, "the document's own tab keeps it");
h.state_mut().set_option("home", "").unwrap();
h.run_steps(3);
assert!(!h.state().all_tools_full);
h.get_by_label_contains("Welcome to PdfCraft");
// Another document's tab.
let mut h = harness(|app| {
app.open_bytes("one.pdf", None, FIXTURE.to_vec()).expect("fixture opens");
app.open_bytes("two.pdf", None, FIXTURE.to_vec()).expect("fixture opens");
});
h.state_mut().set_option("tools", "full").unwrap();
h.run_steps(3);
h.state_mut().active = Some(0);
h.run_steps(3);
assert!(!h.state().all_tools_full, "one.pdf shows, not the catalogue over it");
}
#[test]
fn going_home_with_no_document_puts_the_expanded_all_tools_away() {
// Home is already the tab in front, so only the option itself can put the view away.
let mut h = harness(|app| app.set_option("tools", "full").unwrap());
h.state_mut().set_option("home", "").unwrap();
h.run_steps(3);
assert!(!h.state().all_tools_full);
h.get_by_label_contains("Welcome to PdfCraft");
}
#[test]
fn expanding_all_tools_from_read_mode_restores_the_right_panel() {
use pdfcraft_ui_egui::{Mode, RightPanel};
let mut h = harness(|app| app.open_bytes("fixture.pdf", None, FIXTURE.to_vec()).expect("fixture opens"));
h.state_mut().right = Some(RightPanel::Bookmarks);
h.state_mut().execute("view.read_mode");
h.run_steps(3);
assert_eq!((h.state().mode, h.state().right), (Mode::Read, None), "Read mode hides the panel");
h.state_mut().set_option("tools", "full").unwrap();
h.run_steps(3);
assert!(h.state().all_tools_full);
assert_eq!((h.state().mode, h.state().right), (Mode::AllTools, Some(RightPanel::Bookmarks)));
}
#[test]
fn expanding_all_tools_from_edit_puts_edit_text_down() {
use pdfcraft_ui_egui::{LeftPanel, Mode, QuickTool};
let mut h = harness(|app| app.open_bytes("fixture.pdf", None, FIXTURE.to_vec()).expect("fixture opens"));
h.get_by_label("Edit").click();
h.run_steps(3);
assert_eq!(h.state().quick_tool, QuickTool::EditText);
h.state_mut().set_option("tools", "full").unwrap();
h.run_steps(3);
assert!(h.state().all_tools_full);
assert_eq!((h.state().mode, h.state().quick_tool), (Mode::AllTools, QuickTool::Select));
assert_eq!(h.state().left, LeftPanel::AllTools, "collapsing goes back to the All tools list");
}
#[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"));