diff --git a/crates/engine/src/catalog.rs b/crates/engine/src/catalog.rs index 0134d39af8..e479232bbb 100644 --- a/crates/engine/src/catalog.rs +++ b/crates/engine/src/catalog.rs @@ -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 { diff --git a/crates/ui-egui/src/all_tools_ui.rs b/crates/ui-egui/src/all_tools_ui.rs new file mode 100644 index 0000000000..c8c510a11c --- /dev/null +++ b/crates/ui-egui/src/all_tools_ui.rs @@ -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> { + 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::>().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"); + } +} diff --git a/crates/ui-egui/src/calc_order_ui.rs b/crates/ui-egui/src/calc_order_ui.rs index b9a9fe9e83..70a6dec2c0 100644 --- a/crates/ui-egui/src/calc_order_ui.rs +++ b/crates/ui-egui/src/calc_order_ui.rs @@ -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"); } diff --git a/crates/ui-egui/src/home.rs b/crates/ui-egui/src/home.rs index ddd60f0bde..59b0a52bb3 100644 --- a/crates/ui-egui/src/home.rs +++ b/crates/ui-egui/src/home.rs @@ -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) }; diff --git a/crates/ui-egui/src/i18n/ar.tsv b/crates/ui-egui/src/i18n/ar.tsv index 12291701e2..25ddac1d5f 100644 --- a/crates/ui-egui/src/i18n/ar.tsv +++ b/crates/ui-egui/src/i18n/ar.tsv @@ -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 إرفاق الملفات غير متاح على الويب بعد diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index 95daeb79ec..a8af8ed655 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -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 Прикачването на файлове още не е налично в уеб версията diff --git a/crates/ui-egui/src/i18n/ca.tsv b/crates/ui-egui/src/i18n/ca.tsv index bd1eb542e2..42b036d161 100644 --- a/crates/ui-egui/src/i18n/ca.tsv +++ b/crates/ui-egui/src/i18n/ca.tsv @@ -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 diff --git a/crates/ui-egui/src/i18n/ckb.tsv b/crates/ui-egui/src/i18n/ckb.tsv index 252e7ae6ed..845f437845 100644 --- a/crates/ui-egui/src/i18n/ckb.tsv +++ b/crates/ui-egui/src/i18n/ckb.tsv @@ -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 ئەمڕۆ diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index b9f986df82..3cadb98e3b 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -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 diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index eea5315649..7b9dcc06f0 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -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 diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index 07973764a3..dbe7969173 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -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 Η επισύναψη αρχείων δεν είναι ακόμη διαθέσιμη στον ιστό diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index d26e87ef70..951a3dd8c1 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -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 diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv index ab3fb4bde8..cd5a8040de 100644 --- a/crates/ui-egui/src/i18n/fi.tsv +++ b/crates/ui-egui/src/i18n/fi.tsv @@ -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. diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index 1caab024af..ea6008039f 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -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 diff --git a/crates/ui-egui/src/i18n/hi.tsv b/crates/ui-egui/src/i18n/hi.tsv index ee417e9134..7f403caba1 100644 --- a/crates/ui-egui/src/i18n/hi.tsv +++ b/crates/ui-egui/src/i18n/hi.tsv @@ -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 दबाएँ diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index cd2e4434c6..33ced9925d 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -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 diff --git a/crates/ui-egui/src/i18n/id.tsv b/crates/ui-egui/src/i18n/id.tsv index 1df73c5446..422fac2793 100644 --- a/crates/ui-egui/src/i18n/id.tsv +++ b/crates/ui-egui/src/i18n/id.tsv @@ -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 diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index feafb8f03a..60ecf2de3c 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -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 diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index f7becb9e79..db338ebb11 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -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 版ではまだファイルを添付できません diff --git a/crates/ui-egui/src/i18n/kk.tsv b/crates/ui-egui/src/i18n/kk.tsv index 1eb5674ed8..ea0d0d0fb4 100644 --- a/crates/ui-egui/src/i18n/kk.tsv +++ b/crates/ui-egui/src/i18n/kk.tsv @@ -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 Вебте файл тіркеу әзірге қолжетімсіз diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index a0fe5d4253..a9ee5b4878 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -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를 누르세요 diff --git a/crates/ui-egui/src/i18n/lo.tsv b/crates/ui-egui/src/i18n/lo.tsv index f14df82b20..3b702756bc 100644 --- a/crates/ui-egui/src/i18n/lo.tsv +++ b/crates/ui-egui/src/i18n/lo.tsv @@ -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 ມື້ນີ້ diff --git a/crates/ui-egui/src/i18n/lt.tsv b/crates/ui-egui/src/i18n/lt.tsv index a93cf9a91d..058bbe9052 100644 --- a/crates/ui-egui/src/i18n/lt.tsv +++ b/crates/ui-egui/src/i18n/lt.tsv @@ -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. diff --git a/crates/ui-egui/src/i18n/mod.rs b/crates/ui-egui/src/i18n/mod.rs index 8f17a8acaf..ab5e450428 100644 --- a/crates/ui-egui/src/i18n/mod.rs +++ b/crates/ui-egui/src/i18n/mod.rs @@ -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, diff --git a/crates/ui-egui/src/i18n/nb.tsv b/crates/ui-egui/src/i18n/nb.tsv index d7a43a6b33..bc5fc98f58 100644 --- a/crates/ui-egui/src/i18n/nb.tsv +++ b/crates/ui-egui/src/i18n/nb.tsv @@ -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… diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index b2300e3d21..056ca7d403 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -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 diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index 5f93cc5ce6..1434c7c6e2 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -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 diff --git a/crates/ui-egui/src/i18n/ro.tsv b/crates/ui-egui/src/i18n/ro.tsv index f4157022a4..7c739932e2 100644 --- a/crates/ui-egui/src/i18n/ro.tsv +++ b/crates/ui-egui/src/i18n/ro.tsv @@ -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 diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index 1687750cf2..bea98cd797 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -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 Прикрепление файлов в веб-версии пока недоступно diff --git a/crates/ui-egui/src/i18n/sl.tsv b/crates/ui-egui/src/i18n/sl.tsv index bd51400925..ccbb42d57b 100644 --- a/crates/ui-egui/src/i18n/sl.tsv +++ b/crates/ui-egui/src/i18n/sl.tsv @@ -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 diff --git a/crates/ui-egui/src/i18n/sq.tsv b/crates/ui-egui/src/i18n/sq.tsv index b2e730081e..a720a5333f 100644 --- a/crates/ui-egui/src/i18n/sq.tsv +++ b/crates/ui-egui/src/i18n/sq.tsv @@ -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 diff --git a/crates/ui-egui/src/i18n/te.tsv b/crates/ui-egui/src/i18n/te.tsv index ea4a2a3df9..317d47f9b9 100644 --- a/crates/ui-egui/src/i18n/te.tsv +++ b/crates/ui-egui/src/i18n/te.tsv @@ -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 వెబ్‌లో ఫైల్‌లను జోడించడం ఇంకా అందుబాటులో లేదు diff --git a/crates/ui-egui/src/i18n/th.tsv b/crates/ui-egui/src/i18n/th.tsv index bab6893665..92bb15f1ad 100644 --- a/crates/ui-egui/src/i18n/th.tsv +++ b/crates/ui-egui/src/i18n/th.tsv @@ -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 ลักษณะที่ปรากฏ diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index 216bd3f59b..bcdfe9a90e 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -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 diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index 115dc29004..599b4b63af 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -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 Прикріплення файлів у веб-версії поки недоступне diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index f6a7c0430b..048f78f624 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -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. diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index d80fe181dc..7ea3ca941c 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -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 网页版尚不支持附加文件 diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index a6e1f182af..b34cb787ca 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -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 網頁版尚不支援附加檔案 diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index c46a945e70..0300afd461 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -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, bool), pub recent: Vec, /// 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, 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); } diff --git a/crates/ui-egui/src/panels.rs b/crates/ui-egui/src/panels.rs index 19c2ba65b4..09e99ab7ac 100644 --- a/crates/ui-egui/src/panels.rs +++ b/crates/ui-egui/src/panels.rs @@ -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); diff --git a/crates/ui-egui/tests/it/ui.rs b/crates/ui-egui/tests/it/ui.rs index c15a5c869b..7582b6c4dc 100644 --- a/crates/ui-egui/tests/it/ui.rs +++ b/crates/ui-egui/tests/it/ui.rs @@ -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"));