mirror of
https://github.com/aphrody-labs/pdfcraft.git
synced 2026-10-13 19:26:52 +00:00
UI: checkboxes and radio buttons are outlined, and checkboxes are square (#1173)
* UI: checkboxes draw a rounded-square box, not a radio-button circle
The theme's 6 px widget radius made egui's 14 px checkbox box a circle,
so View > Page display's Continuous scrolling and Show cover page looked
like two more layout radio buttons. On the light theme an unticked box
had no outline and the panel's fill, so it couldn't be seen.
Every checkbox (81 call sites) now goes through widgets::checkbox /
checkbox_without_text, which draws the box with a 3 px radius and a 1 px
border outline, keeping an outline the caller already set (dialogs).
clippy.toml disallows egui::Ui::checkbox and egui::Checkbox::{new,
without_text} so new checkboxes use the helper too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* UI: radio buttons are outlined and filled, so they show on the light theme
On the light theme the field colour is the panels' white and widgets
have no outline, so an unselected radio button (and an unticked
checkbox) was invisible outside dialogs: View > Page display showed only
the selected dot.
Every radio button (79 call sites) now goes through widgets::radio /
radio_value, sharing the checkbox helper's styling: a 1 px border
outline, text_muted on hover, and on the light theme the plain fill
(hover) as dialogs already had, keeping what the caller set. The dark
theme keeps its fills. clippy.toml also disallows egui::Ui::radio,
egui::Ui::radio_value and egui::RadioButton::new.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
This commit is contained in:
26 files changed
+321
-161
No files matched your search
@@ -140,6 +140,7 @@ Ranked; detail and the full milestone table in [docs/roadmap.md](docs/roadmap.md
|
||||
|
||||
Newest first: the date, what moved, and the new overall estimate.
|
||||
|
||||
- **2026-10-11 (UI, checkboxes and radio buttons):** Checkboxes no longer look like radio buttons, and on the light theme neither is invisible. With the theme's 6 px widget radius egui drew a checkbox's 14 px box as a circle, so View ▸ Page display's Continuous scrolling and Show cover page read as two more layout choices; on the light theme an unticked box or an unselected radio button was white on white with no edge. Every checkbox (81) and radio button (79) now goes through `widgets::checkbox` / `widgets::radio` / `widgets::radio_value`: a 1 px outline (darker on hover), the plain fill on the light theme as dialogs already had, and for checkboxes a rounded-square box (3 px), keeping what the caller set (dialogs, the search panel). `clippy.toml` disallows `ui.checkbox`, `ui.radio`, `ui.radio_value`, `egui::Checkbox` and `egui::RadioButton` so new ones use them too. Estimates unchanged.
|
||||
- **2026-10-11 (M13, #971):** `text_find` now bounds each page's temporary match-range list to the remaining result limit plus one, which is enough to detect and return a continuation without allocating every match on a dense page. Added limited-search coverage; the existing end-to-end continuation test passes. The text cache byte budget and per-page extraction warnings remain open.
|
||||
- **2026-10-11 (M7, #594):** Ordinary paragraph body drags now use the existing source-preserving object translation path; edge drags still rewrap. An in-progress drag is discarded if the document generation changes, and added-content gestures keep their own edit handler. Added real-shell source regressions for overlapping and identical labels, history, save/reopen, partial overlap, kerning, rotation/crop/UserUnit and atomic refusal. Linux validation passed: focused UI/engine regressions, exact-base source-loss failures, native drag/undo/redo/save/reopen with independent Poppler renders, and all eight CI gates. Text replacement and resizing over unrelated overlapping content remain outside this repair. Estimates unchanged.
|
||||
|
||||
|
||||
+11
@@ -2,3 +2,14 @@ allow-unwrap-in-tests = true
|
||||
allow-expect-in-tests = true
|
||||
allow-panic-in-tests = true
|
||||
allow-indexing-slicing-in-tests = true
|
||||
|
||||
# With the theme's widget visuals egui draws a checkbox's box as a circle, like a radio button,
|
||||
# and on the light theme neither has an outline or a fill that shows on a white panel.
|
||||
disallowed-methods = [
|
||||
{ path = "egui::Ui::checkbox", reason = "use checkbox from crates/ui-egui/src/widgets.rs, whose box is a square" },
|
||||
{ path = "egui::Checkbox::new", reason = "use checkbox from crates/ui-egui/src/widgets.rs, whose box is a square" },
|
||||
{ path = "egui::Checkbox::without_text", reason = "use checkbox_without_text from crates/ui-egui/src/widgets.rs, whose box is a square" },
|
||||
{ path = "egui::Ui::radio", reason = "use radio from crates/ui-egui/src/widgets.rs, which is outlined and filled" },
|
||||
{ path = "egui::Ui::radio_value", reason = "use radio_value from crates/ui-egui/src/widgets.rs, which is outlined and filled" },
|
||||
{ path = "egui::RadioButton::new", reason = "use radio from crates/ui-egui/src/widgets.rs, which is outlined and filled" },
|
||||
]
|
||||
@@ -83,12 +83,12 @@ pub(crate) fn options_body(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tokens)
|
||||
ui.add_space(8.0);
|
||||
};
|
||||
group(ui, "Report Options", &mut |ui| {
|
||||
ui.checkbox(&mut o.create_report, tl!("Create accessibility report"));
|
||||
ui.add(widgets::checkbox(&mut o.create_report, tl!("Create accessibility report")));
|
||||
});
|
||||
group(ui, "Page Range", &mut |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.radio_value(&mut o.all_pages, true, tl!("All pages in document"));
|
||||
ui.radio_value(&mut o.all_pages, false, tl!("Pages from"));
|
||||
widgets::radio_value(ui, &mut o.all_pages, true, tl!("All pages in document"));
|
||||
widgets::radio_value(ui, &mut o.all_pages, false, tl!("Pages from"));
|
||||
ui.add_enabled(!o.all_pages, egui::DragValue::new(&mut o.from).range(1..=pages));
|
||||
ui.label(tl!("to"));
|
||||
ui.add_enabled(!o.all_pages, egui::DragValue::new(&mut o.to).range(1..=pages));
|
||||
@@ -107,7 +107,7 @@ pub(crate) fn options_body(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tokens)
|
||||
ui.add_space(4.0);
|
||||
for r in Rule::ALL.into_iter().filter(|r| r.category() == o.category) {
|
||||
let mut checked = o.rules.contains(&r);
|
||||
if ui.checkbox(&mut checked, tl!(r.description())).changed() {
|
||||
if ui.add(widgets::checkbox(&mut checked, tl!(r.description()))).changed() {
|
||||
if checked {
|
||||
o.rules.insert(r);
|
||||
} else {
|
||||
@@ -126,7 +126,7 @@ pub(crate) fn options_body(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tokens)
|
||||
}
|
||||
});
|
||||
});
|
||||
ui.checkbox(&mut o.show_dialog, tl!("Show this dialog when the Checker starts"));
|
||||
ui.add(widgets::checkbox(&mut o.show_dialog, tl!("Show this dialog when the Checker starts")));
|
||||
ui.add_space(10.0);
|
||||
let (mut start, mut cancel) = (false, false);
|
||||
ui.horizontal(|ui| {
|
||||
@@ -507,7 +507,7 @@ pub(crate) fn alt_body(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tokens) ->
|
||||
let l = ui.label(tl!("Alternate text"));
|
||||
ui.add(crate::widgets::block(&mut d.texts[i]).desired_rows(3).desired_width(380.0).hint_text(tl!("Describe the figure"))).labelled_by(l.id);
|
||||
});
|
||||
ui.checkbox(&mut d.decorative[i], tl!("Decorative figure"));
|
||||
ui.add(widgets::checkbox(&mut d.decorative[i], tl!("Decorative figure")));
|
||||
ui.add_space(10.0);
|
||||
let (mut save, mut cancel) = (false, false);
|
||||
ui.horizontal(|ui| {
|
||||
|
||||
@@ -5,6 +5,7 @@ use egui::{Rect, RichText, pos2, vec2};
|
||||
|
||||
use crate::PdfCraftApp;
|
||||
use crate::theme::{self, ThemeKind, ThemePreference, Tokens};
|
||||
use crate::widgets;
|
||||
|
||||
/// The themes each card offers. One of each today; a new theme is one more row here.
|
||||
pub const LIGHT_THEMES: &[(ThemeKind, &str)] = &[(ThemeKind::Light, "Light gray")];
|
||||
@@ -103,7 +104,7 @@ fn card(ui: &mut egui::Ui, t: &Tokens, title: &str, active: bool, themes: &[(The
|
||||
ui.add_space(4.0);
|
||||
for (kind, label) in themes {
|
||||
// One theme per card today, so it is always the chosen one.
|
||||
let _ = ui.radio(*kind == shown, tl!(label));
|
||||
let _ = ui.add(widgets::radio(*kind == shown, tl!(label)));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -203,7 +203,7 @@ impl Draft {
|
||||
}
|
||||
|
||||
fn row<T>(ui: &mut egui::Ui, label: &str, c: &mut Change<T>, edit: impl FnOnce(&mut egui::Ui, &mut T)) {
|
||||
ui.checkbox(&mut c.apply, tl!(label));
|
||||
ui.add(widgets::checkbox(&mut c.apply, tl!(label)));
|
||||
// Colour swatches contain their own grid; give every property's editor a separate
|
||||
// namespace so border and fill controls do not share widget identities.
|
||||
ui.push_id(label, |ui| ui.add_enabled_ui(c.apply, |ui| edit(ui, &mut c.value)));
|
||||
@@ -248,7 +248,7 @@ fn bool_row(ui: &mut egui::Ui, label: &str, c: &mut Change<bool>) {
|
||||
fn color(ui: &mut egui::Ui, value: &mut Option<[f64; 3]>) {
|
||||
ui.horizontal(|ui| {
|
||||
let mut none = value.is_none();
|
||||
if ui.checkbox(&mut none, tl!("No color")).changed() {
|
||||
if ui.add(widgets::checkbox(&mut none, tl!("No color"))).changed() {
|
||||
*value = if none { None } else { Some([0.0; 3]) };
|
||||
}
|
||||
if let Some(c) = crate::comments::swatch_grid(ui, *value) {
|
||||
|
||||
@@ -3423,8 +3423,8 @@ fn find_bar(view: &mut DocView, pages: usize, area: Rect, ui: &mut egui::Ui, t:
|
||||
}
|
||||
let opts = icons::button(ui, "settings-2", 26.0, find.case_sensitive || find.whole_words, tl!("Find options"));
|
||||
egui::Popup::menu(&opts).show(|ui| {
|
||||
let a = ui.checkbox(&mut find.whole_words, tl!("Whole words only")).changed();
|
||||
let b = ui.checkbox(&mut find.case_sensitive, tl!("Case-sensitive")).changed();
|
||||
let a = ui.add(widgets::checkbox(&mut find.whole_words, tl!("Whole words only"))).changed();
|
||||
let b = ui.add(widgets::checkbox(&mut find.case_sensitive, tl!("Case-sensitive"))).changed();
|
||||
if a || b {
|
||||
// Search again with the new options.
|
||||
find.case_query.clear();
|
||||
@@ -4050,7 +4050,7 @@ fn organize_grid(view: &mut DocView, info: &DocInfo, editable: bool, dirty: bool
|
||||
// outside the page image, including on very tall or wide pages.
|
||||
let mut checked = view.selected.contains(&i);
|
||||
let check_rect = Rect::from_min_size(c.min + vec2(8.0, 8.0), Vec2::splat(20.0));
|
||||
let check = ui.push_id(("org-check", i), |ui| ui.put(check_rect, egui::Checkbox::without_text(&mut checked))).inner;
|
||||
let check = ui.push_id(("org-check", i), |ui| ui.put(check_rect, widgets::checkbox_without_text(&mut checked))).inner;
|
||||
let label = crate::i18n::fmt(tl!("Select page {label}"), &[("label", &p.label)]);
|
||||
check.widget_info(|| egui::WidgetInfo::selected(egui::WidgetType::Checkbox, true, checked, label.clone()));
|
||||
if check.on_hover_text(label).changed() {
|
||||
|
||||
@@ -206,7 +206,7 @@ fn theme_menu(app: &mut PdfCraftApp, ui: &mut egui::Ui) {
|
||||
(ThemePreference::Light, "view.theme.light", "Light gray"),
|
||||
(ThemePreference::Dark, "view.theme.dark", "Dark gray"),
|
||||
] {
|
||||
if ui.radio(app.theme_preference == preference, tl!(label)).clicked() {
|
||||
if ui.add(widgets::radio(app.theme_preference == preference, tl!(label))).clicked() {
|
||||
app.execute(command);
|
||||
ui.close();
|
||||
}
|
||||
@@ -467,7 +467,7 @@ fn main_menu(app: &mut PdfCraftApp, ui: &mut egui::Ui) {
|
||||
(RightPanel::Search, tl!("Search")),
|
||||
(RightPanel::Compare, tl!("Compare")),
|
||||
] {
|
||||
if ui.radio(app.right == Some(p), tl!(label)).clicked() {
|
||||
if ui.add(widgets::radio(app.right == Some(p), tl!(label))).clicked() {
|
||||
app.choose_right_panel(Some(p));
|
||||
}
|
||||
}
|
||||
@@ -595,18 +595,18 @@ fn page_display_menu(ui: &mut egui::Ui, view: &DocView) -> Option<&'static str>
|
||||
ui.label(egui::RichText::new(tl!("Page display")).color(t.text_faint).small());
|
||||
let mut picked = None;
|
||||
for l in PageLayout::ORDER {
|
||||
if ui.radio(view.layout == l, tl!(l.label())).clicked() {
|
||||
if ui.add(widgets::radio(view.layout == l, tl!(l.label()))).clicked() {
|
||||
picked = Some(l.command());
|
||||
}
|
||||
}
|
||||
ui.separator();
|
||||
// Continuous scrolling is an independent checkbox over the base layout, as in Acrobat (#752).
|
||||
let mut continuous = view.continuous();
|
||||
if ui.add(egui::Checkbox::new(&mut continuous, tl!("Continuous scrolling"))).changed() {
|
||||
if ui.add(widgets::checkbox(&mut continuous, tl!("Continuous scrolling"))).changed() {
|
||||
picked = Some("view.continuous");
|
||||
}
|
||||
let mut cover = view.cover;
|
||||
if ui.add_enabled(view.cover_applies(), egui::Checkbox::new(&mut cover, tl!("Show cover page in two-page view"))).changed() {
|
||||
if ui.add_enabled(view.cover_applies(), widgets::checkbox(&mut cover, tl!("Show cover page in two-page view"))).changed() {
|
||||
picked = Some("view.layout.cover");
|
||||
}
|
||||
if picked.is_some() {
|
||||
|
||||
@@ -260,7 +260,7 @@ pub(crate) fn body(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tokens) -> (boo
|
||||
}
|
||||
}
|
||||
ui.add_space(12.0);
|
||||
ui.checkbox(&mut d.edited.locked, tl!("Locked"));
|
||||
ui.add(widgets::checkbox(&mut d.edited.locked, tl!("Locked")));
|
||||
let (mut apply, mut cancel) = (false, false);
|
||||
ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
|
||||
if widgets::pill_button(ui, tl!("OK"), true).clicked() {
|
||||
|
||||
@@ -13,6 +13,7 @@ use crate::comments::{CommentPrefs, CommentTool, SortBy, color32, status_badge};
|
||||
use crate::icons;
|
||||
use crate::panels::Nav;
|
||||
use crate::theme::{self, Tokens};
|
||||
use crate::widgets;
|
||||
|
||||
/// Accent bar of the selected card (11-visual-spec §3: `#0165DD`).
|
||||
const CARD_ACCENT: Color32 = Color32::from_rgb(0x01, 0x65, 0xDD);
|
||||
@@ -510,7 +511,7 @@ pub(crate) fn header_controls(ui: &mut egui::Ui, info: &DocInfo, view: &mut DocV
|
||||
(SortBy::Type, tl!("Type")),
|
||||
(SortBy::Color, tl!("Colour")),
|
||||
] {
|
||||
if ui.radio(cv.sort == s, label).clicked() {
|
||||
if ui.add(widgets::radio(cv.sort == s, label)).clicked() {
|
||||
cv.sort = s;
|
||||
ui.close();
|
||||
}
|
||||
@@ -533,7 +534,7 @@ pub(crate) fn header_controls(ui: &mut egui::Ui, info: &DocInfo, view: &mut DocV
|
||||
ui.set_min_width(200.0);
|
||||
let toggle = |ui: &mut egui::Ui, list: &mut Vec<String>, value: &str, label: &str| {
|
||||
let mut shown = !list.iter().any(|x| x == value);
|
||||
if ui.checkbox(&mut shown, label).changed() {
|
||||
if ui.add(widgets::checkbox(&mut shown, label)).changed() {
|
||||
if shown {
|
||||
list.retain(|x| x != value);
|
||||
} else {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::PdfCraftApp;
|
||||
use crate::widgets;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
pub enum ResolutionChoice {
|
||||
@@ -24,10 +25,10 @@ pub(crate) fn image_import_body(ui: &mut egui::Ui, app: &mut PdfCraftApp) -> (bo
|
||||
let Some(draft) = app.image_import.as_mut() else { return (false, true) };
|
||||
ui.label(crate::i18n::fmt(tl!("Selected image files: {count}"), &[("count", &draft.images.len().to_string())]));
|
||||
ui.add_space(8.0);
|
||||
ui.radio_value(&mut draft.choice, ResolutionChoice::Image, tl!("Use image resolution"));
|
||||
widgets::radio_value(ui, &mut draft.choice, ResolutionChoice::Image, tl!("Use image resolution"));
|
||||
ui.label(tl!("Use each image's embedded DPI; use 72 DPI when it is absent."));
|
||||
ui.radio_value(&mut draft.choice, ResolutionChoice::Default72, tl!("Use 72 DPI (one point per pixel)"));
|
||||
ui.radio_value(&mut draft.choice, ResolutionChoice::Custom, tl!("Use custom DPI"));
|
||||
widgets::radio_value(ui, &mut draft.choice, ResolutionChoice::Default72, tl!("Use 72 DPI (one point per pixel)"));
|
||||
widgets::radio_value(ui, &mut draft.choice, ResolutionChoice::Custom, tl!("Use custom DPI"));
|
||||
ui.add_enabled(draft.choice == ResolutionChoice::Custom, egui::DragValue::new(&mut draft.dpi).range(1.0..=1200.0).suffix(" DPI"));
|
||||
ui.label(tl!("DPI sets the printed page size without resampling the image."));
|
||||
ui.add_space(12.0);
|
||||
|
||||
@@ -223,13 +223,13 @@ fn dialogs(app: &mut PdfCraftApp, ctx: &egui::Context) {
|
||||
ui.label("");
|
||||
ui.add_enabled_ui(editable, |ui| {
|
||||
ui.vertical(|ui| {
|
||||
ui.checkbox(&mut v.fit_window, tl!("Resize window to initial page"));
|
||||
ui.checkbox(&mut v.center_window, tl!("Center window on screen"));
|
||||
ui.checkbox(&mut v.full_screen, tl!("Open in Full Screen mode"));
|
||||
ui.add(widgets::checkbox(&mut v.fit_window, tl!("Resize window to initial page")));
|
||||
ui.add(widgets::checkbox(&mut v.center_window, tl!("Center window on screen")));
|
||||
ui.add(widgets::checkbox(&mut v.full_screen, tl!("Open in Full Screen mode")));
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(tl!("Show:"));
|
||||
ui.radio_value(&mut v.display_title, false, tl!("File Name"));
|
||||
ui.radio_value(&mut v.display_title, true, tl!("Document Title"));
|
||||
widgets::radio_value(ui, &mut v.display_title, false, tl!("File Name"));
|
||||
widgets::radio_value(ui, &mut v.display_title, true, tl!("Document Title"));
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -239,9 +239,9 @@ fn dialogs(app: &mut PdfCraftApp, ctx: &egui::Context) {
|
||||
ui.label("");
|
||||
ui.add_enabled_ui(editable, |ui| {
|
||||
ui.vertical(|ui| {
|
||||
ui.checkbox(&mut v.hide_menubar, tl!("Hide menu bar"));
|
||||
ui.checkbox(&mut v.hide_toolbar, tl!("Hide toolbars"));
|
||||
ui.checkbox(&mut v.hide_window_ui, tl!("Hide window controls"));
|
||||
ui.add(widgets::checkbox(&mut v.hide_menubar, tl!("Hide menu bar")));
|
||||
ui.add(widgets::checkbox(&mut v.hide_toolbar, tl!("Hide toolbars")));
|
||||
ui.add(widgets::checkbox(&mut v.hide_window_ui, tl!("Hide window controls")));
|
||||
});
|
||||
});
|
||||
ui.end_row();
|
||||
@@ -359,8 +359,8 @@ fn dialogs(app: &mut PdfCraftApp, ctx: &egui::Context) {
|
||||
ui.label(egui::RichText::new(tl!("Binding")).color(t.text_muted));
|
||||
ui.add_enabled_ui(editable, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.radio_value(&mut v.right_to_left, false, tl!("Left Edge"));
|
||||
ui.radio_value(&mut v.right_to_left, true, tl!("Right Edge"));
|
||||
widgets::radio_value(ui, &mut v.right_to_left, false, tl!("Left Edge"));
|
||||
widgets::radio_value(ui, &mut v.right_to_left, true, tl!("Right Edge"));
|
||||
});
|
||||
});
|
||||
ui.end_row();
|
||||
@@ -403,7 +403,7 @@ fn dialogs(app: &mut PdfCraftApp, ctx: &egui::Context) {
|
||||
if selected.is_empty() && draft.mode == M::Selection {
|
||||
draft.mode = M::Pages;
|
||||
}
|
||||
ui.radio_value(&mut draft.mode, M::Pages, tl!("Number of pages"));
|
||||
widgets::radio_value(ui, &mut draft.mode, M::Pages, tl!("Number of pages"));
|
||||
ui.add_enabled_ui(draft.mode == M::Pages, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.add_space(24.0);
|
||||
@@ -411,7 +411,7 @@ fn dialogs(app: &mut PdfCraftApp, ctx: &egui::Context) {
|
||||
ui.add(egui::DragValue::new(&mut draft.every).range(1..=n.max(1)));
|
||||
});
|
||||
});
|
||||
ui.radio_value(&mut draft.mode, M::Size, tl!("File size"));
|
||||
widgets::radio_value(ui, &mut draft.mode, M::Size, tl!("File size"));
|
||||
ui.add_enabled_ui(draft.mode == M::Size, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.add_space(24.0);
|
||||
@@ -420,14 +420,15 @@ fn dialogs(app: &mut PdfCraftApp, ctx: &egui::Context) {
|
||||
});
|
||||
});
|
||||
ui.add_enabled_ui(!marks.is_empty(), |ui| {
|
||||
ui.radio_value(
|
||||
widgets::radio_value(
|
||||
ui,
|
||||
&mut draft.mode,
|
||||
M::Bookmarks,
|
||||
crate::i18n::fmt(tl!("Top-level bookmarks ({n})"), &[("n", &marks.len().to_string())]),
|
||||
)
|
||||
});
|
||||
ui.add_enabled_ui(!selected.is_empty(), |ui| {
|
||||
ui.radio_value(&mut draft.mode, M::Selection, tl!("Before each selected page (select pages in Organize)"))
|
||||
widgets::radio_value(ui, &mut draft.mode, M::Selection, tl!("Before each selected page (select pages in Organize)"))
|
||||
});
|
||||
let plan = match draft.mode {
|
||||
M::Pages => crate::SplitPlan::By(pdfcraft_engine::SplitBy::PageCount(draft.every)),
|
||||
@@ -570,8 +571,8 @@ fn dialogs(app: &mut PdfCraftApp, ctx: &egui::Context) {
|
||||
let l = ui.label(tl!("File name"));
|
||||
ui.add(egui::TextEdit::singleline(&mut app.extract_draft.name).desired_width(240.0)).labelled_by(l.id);
|
||||
});
|
||||
ui.checkbox(&mut app.extract_draft.delete, tl!("Delete pages after extracting"));
|
||||
ui.checkbox(&mut app.extract_draft.separate, tl!("Extract pages as separate files"));
|
||||
ui.add(widgets::checkbox(&mut app.extract_draft.delete, tl!("Delete pages after extracting")));
|
||||
ui.add(widgets::checkbox(&mut app.extract_draft.separate, tl!("Extract pages as separate files")));
|
||||
ui.add_space(12.0);
|
||||
ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
|
||||
if widgets::pill_button(ui, tl!("Extract"), true).clicked() {
|
||||
@@ -606,10 +607,10 @@ fn dialogs(app: &mut PdfCraftApp, ctx: &egui::Context) {
|
||||
ui.end_row();
|
||||
ui.label(tl!("Pages:"));
|
||||
ui.vertical(|ui| {
|
||||
ui.radio_value(&mut d.which, 0, tl!("All"));
|
||||
ui.radio_value(&mut d.which, 1, tl!("Selection"));
|
||||
widgets::radio_value(ui, &mut d.which, 0, tl!("All"));
|
||||
widgets::radio_value(ui, &mut d.which, 1, tl!("Selection"));
|
||||
ui.horizontal(|ui| {
|
||||
ui.radio_value(&mut d.which, 2, tl!("From"));
|
||||
widgets::radio_value(ui, &mut d.which, 2, tl!("From"));
|
||||
ui.add_enabled(d.which == 2, egui::DragValue::new(&mut d.from).range(1..=n));
|
||||
ui.label(tl!("to"));
|
||||
ui.add_enabled(d.which == 2, egui::DragValue::new(&mut d.to).range(1..=n));
|
||||
@@ -666,9 +667,9 @@ fn dialogs(app: &mut PdfCraftApp, ctx: &egui::Context) {
|
||||
ui.label(egui::RichText::new(tl!("Duplicate Field")).font(theme::semibold(18.0)));
|
||||
ui.add_space(8.0);
|
||||
ui.label(crate::i18n::fmt(tl!("Duplicate \"{name}\" onto:"), &[("name", &d.name)]));
|
||||
ui.radio_value(&mut d.all, true, tl!("All pages"));
|
||||
widgets::radio_value(ui, &mut d.all, true, tl!("All pages"));
|
||||
ui.horizontal(|ui| {
|
||||
ui.radio_value(&mut d.all, false, tl!("From"));
|
||||
widgets::radio_value(ui, &mut d.all, false, tl!("From"));
|
||||
ui.add_enabled(!d.all, egui::DragValue::new(&mut d.from).range(1..=n));
|
||||
ui.label(tl!("to"));
|
||||
ui.add_enabled(!d.all, egui::DragValue::new(&mut d.to).range(1..=n));
|
||||
@@ -734,7 +735,7 @@ fn dialogs(app: &mut PdfCraftApp, ctx: &egui::Context) {
|
||||
ui.label(egui::RichText::new(tl!("Summarize Options")).font(theme::semibold(18.0)));
|
||||
ui.add_space(8.0);
|
||||
ui.label(tl!("Choose a layout:"));
|
||||
let _ = ui.radio(true, tl!("Comments only"));
|
||||
let _ = ui.add(widgets::radio(true, tl!("Comments only")));
|
||||
ui.add_space(6.0);
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(tl!("Sort comments by:"));
|
||||
|
||||
@@ -12,6 +12,7 @@ use pdfcraft_render::DocInfo;
|
||||
|
||||
use crate::canvas::{DocView, PageXform};
|
||||
use crate::theme::Tokens;
|
||||
use crate::widgets;
|
||||
|
||||
const FOCUS_BLUE: Color32 = Color32::from_rgb(0x14, 0x73, 0xE6);
|
||||
/// Background of the in-place text field editor, the same in every theme.
|
||||
@@ -425,7 +426,7 @@ pub(crate) fn overlay(ctx: &egui::Context, view: &mut DocView, info: &DocInfo, f
|
||||
let on = if multi { fx.picked.contains(export) } else { f.value.first() == Some(export) };
|
||||
if multi {
|
||||
let mut c = on;
|
||||
if ui.checkbox(&mut c, display).changed() {
|
||||
if ui.add(widgets::checkbox(&mut c, display)).changed() {
|
||||
if c {
|
||||
fx.picked.push(export.clone());
|
||||
} else {
|
||||
|
||||
+12
-12
@@ -291,7 +291,7 @@ pub(crate) fn preferences_body(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tok
|
||||
(crate::Mode::Convert, "Convert"),
|
||||
(crate::Mode::Sign, "E-Sign"),
|
||||
] {
|
||||
ui.radio_value(&mut app.default_mode, mode, tl!(label));
|
||||
widgets::radio_value(ui, &mut app.default_mode, mode, tl!(label));
|
||||
}
|
||||
});
|
||||
ui.label(
|
||||
@@ -303,16 +303,16 @@ pub(crate) fn preferences_body(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tok
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(tl!("Default page display"));
|
||||
for l in crate::canvas::PageLayout::ORDER {
|
||||
ui.radio_value(&mut defaults.layout, l, tl!(l.label()));
|
||||
widgets::radio_value(ui, &mut defaults.layout, l, tl!(l.label()));
|
||||
}
|
||||
ui.checkbox(&mut defaults.continuous, tl!("Continuous scrolling"));
|
||||
ui.add(widgets::checkbox(&mut defaults.continuous, tl!("Continuous scrolling")));
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
use crate::canvas::Fit;
|
||||
ui.label(tl!("Default zoom"));
|
||||
ui.radio_value(&mut defaults.fit, Fit::Width, tl!("Fit to width"));
|
||||
ui.radio_value(&mut defaults.fit, Fit::Page, tl!("Zoom to page level"));
|
||||
ui.radio_value(&mut defaults.fit, Fit::None, tl!("Custom"));
|
||||
widgets::radio_value(ui, &mut defaults.fit, Fit::Width, tl!("Fit to width"));
|
||||
widgets::radio_value(ui, &mut defaults.fit, Fit::Page, tl!("Zoom to page level"));
|
||||
widgets::radio_value(ui, &mut defaults.fit, Fit::None, tl!("Custom"));
|
||||
// Editing the percentage selects Custom.
|
||||
let mut percent = defaults.zoom * 100.0;
|
||||
if ui.add(egui::DragValue::new(&mut percent).range(8.0..=6400.0).max_decimals(0).suffix("%")).changed() {
|
||||
@@ -327,20 +327,20 @@ pub(crate) fn preferences_body(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tok
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(tl!("Units"));
|
||||
for u in crate::units::Unit::ALL {
|
||||
ui.radio_value(&mut app.units, u, u.label());
|
||||
widgets::radio_value(ui, &mut app.units, u, u.label());
|
||||
}
|
||||
});
|
||||
ui.label(egui::RichText::new(tl!("For page sizes and margins.")).small().color(t.text_muted));
|
||||
// The web build has no file paths to reopen.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
ui.checkbox(&mut app.reopen_last_session, tl!("Reopen the files that were open when PdfCraft last closed"));
|
||||
ui.add(widgets::checkbox(&mut app.reopen_last_session, tl!("Reopen the files that were open when PdfCraft last closed")));
|
||||
// The web build has no files on disk to watch.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
ui.checkbox(&mut app.reload_changed_files, tl!("Reload documents when their files change on disk"));
|
||||
ui.add(widgets::checkbox(&mut app.reload_changed_files, tl!("Reload documents when their files change on disk")));
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(tl!("Mouse wheel scroll speed"));
|
||||
for speed in crate::scroll_speed::ScrollSpeed::ALL {
|
||||
ui.radio_value(&mut app.scroll_speed, speed, tl_ctx!("scroll speed", speed.label()));
|
||||
widgets::radio_value(ui, &mut app.scroll_speed, speed, tl_ctx!("scroll speed", speed.label()));
|
||||
}
|
||||
});
|
||||
ui.add_space(8.0);
|
||||
@@ -352,7 +352,7 @@ pub(crate) fn preferences_body(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tok
|
||||
});
|
||||
ui.add_space(8.0);
|
||||
ui.label(egui::RichText::new(tl!("Fill & Sign")).font(theme::semibold(13.0)));
|
||||
ui.checkbox(&mut app.flatten_fill_sign_on_save, tl!("Flatten Fill & Sign when saving"));
|
||||
ui.add(widgets::checkbox(&mut app.flatten_fill_sign_on_save, tl!("Flatten Fill & Sign when saving")));
|
||||
ui.label(
|
||||
egui::RichText::new(tl!("Text, marks and signatures become part of the page. Other comments stay editable.")).small().color(t.text_muted),
|
||||
);
|
||||
@@ -363,7 +363,7 @@ pub(crate) fn preferences_body(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tok
|
||||
egui::Frame::new().fill(t.hover).corner_radius(egui::CornerRadius::same(6)).inner_margin(egui::Margin::same(10)).show(ui, |ui| {
|
||||
ui.set_width(ui.available_width());
|
||||
let mut on = app.session.javascript();
|
||||
if ui.checkbox(&mut on, tl!("Enable Acrobat JavaScript")).changed() {
|
||||
if ui.add(widgets::checkbox(&mut on, tl!("Enable Acrobat JavaScript"))).changed() {
|
||||
app.session.set_javascript(on);
|
||||
}
|
||||
ui.label(
|
||||
|
||||
@@ -249,14 +249,14 @@ pub(crate) fn body(ui: &mut egui::Ui, d: &mut LinkDraft, pages: usize, t: &Token
|
||||
});
|
||||
ui.add_space(8.0);
|
||||
widgets::section_title(ui, tl!("Link Action"));
|
||||
ui.radio_value(&mut d.web, true, tl!("Open a web page"));
|
||||
widgets::radio_value(ui, &mut d.web, true, tl!("Open a web page"));
|
||||
ui.add_enabled_ui(d.web, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.add_space(24.0);
|
||||
ui.add(crate::widgets::line(&mut d.url).desired_width(360.0).hint_text("https://example.org"));
|
||||
});
|
||||
});
|
||||
ui.radio_value(&mut d.web, false, tl!("Go to a page view"));
|
||||
widgets::radio_value(ui, &mut d.web, false, tl!("Go to a page view"));
|
||||
ui.add_enabled_ui(!d.web, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.add_space(24.0);
|
||||
|
||||
@@ -49,8 +49,8 @@ impl PageRange {
|
||||
|
||||
pub(crate) fn ui(&mut self, ui: &mut egui::Ui, count: usize) {
|
||||
ui.horizontal(|ui| {
|
||||
ui.radio_value(&mut self.all, true, tl!("All pages"));
|
||||
ui.radio_value(&mut self.all, false, tl!("Pages from"));
|
||||
widgets::radio_value(ui, &mut self.all, true, tl!("All pages"));
|
||||
widgets::radio_value(ui, &mut self.all, false, tl!("Pages from"));
|
||||
ui.add_enabled(!self.all, egui::DragValue::new(&mut self.from).range(1..=count.max(1)));
|
||||
ui.label(tl!("to"));
|
||||
if self.to == 1 && self.from == 1 {
|
||||
@@ -164,7 +164,7 @@ pub(crate) fn body(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tokens, kind: M
|
||||
ui.label(tl!("Font: Helvetica"));
|
||||
ui.label(tl!("Size"));
|
||||
ui.add(egui::DragValue::new(&mut d.hf.font_size).range(4.0..=72.0).speed(0.5));
|
||||
ui.checkbox(&mut d.hf.underline, tl!("Underline"));
|
||||
ui.add(widgets::checkbox(&mut d.hf.underline, tl!("Underline")));
|
||||
color_button(ui, &mut d.hf.color);
|
||||
ui.add_space(16.0);
|
||||
ui.label(format!("{} ({})", tl!("Margins"), units.label()));
|
||||
@@ -228,8 +228,8 @@ pub(crate) fn body(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tokens, kind: M
|
||||
MarkKind::Watermark => {
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(tl!("Source"));
|
||||
ui.radio_value(&mut d.use_file, false, tl!("Text"));
|
||||
ui.radio_value(&mut d.use_file, true, tl!("File"));
|
||||
widgets::radio_value(ui, &mut d.use_file, false, tl!("Text"));
|
||||
widgets::radio_value(ui, &mut d.use_file, true, tl!("File"));
|
||||
});
|
||||
if d.use_file {
|
||||
browse |= file_source(ui, d, MarkKind::Watermark);
|
||||
@@ -243,7 +243,7 @@ pub(crate) fn body(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tokens, kind: M
|
||||
);
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
ui.add_enabled_ui(!d.use_file, |ui| ui.checkbox(&mut d.fit, tl!("Fit to page")));
|
||||
ui.add_enabled_ui(!d.use_file, |ui| ui.add(widgets::checkbox(&mut d.fit, tl!("Fit to page"))));
|
||||
ui.add_enabled_ui(!d.fit, |ui| {
|
||||
ui.label(tl!("Size"));
|
||||
if d.wm.font_size == 0.0 {
|
||||
@@ -256,7 +256,7 @@ pub(crate) fn body(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tokens, kind: M
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(tl!("Rotation"));
|
||||
for r in [-45.0, 0.0, 45.0] {
|
||||
ui.radio_value(&mut d.wm.rotation, r, if r == 0.0 { tl!("None").to_string() } else { format!("{r}°") });
|
||||
widgets::radio_value(ui, &mut d.wm.rotation, r, if r == 0.0 { tl!("None").to_string() } else { format!("{r}°") });
|
||||
}
|
||||
ui.add(egui::DragValue::new(&mut d.wm.rotation).range(-180.0..=180.0).suffix("°"));
|
||||
});
|
||||
@@ -269,8 +269,8 @@ pub(crate) fn body(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tokens, kind: M
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(tl!("Location"));
|
||||
ui.radio_value(&mut d.wm.behind, true, tl!("Appear behind page"));
|
||||
ui.radio_value(&mut d.wm.behind, false, tl!("Appear on top of page"));
|
||||
widgets::radio_value(ui, &mut d.wm.behind, true, tl!("Appear behind page"));
|
||||
widgets::radio_value(ui, &mut d.wm.behind, false, tl!("Appear on top of page"));
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(tl!("Offset from centre"));
|
||||
@@ -284,10 +284,10 @@ pub(crate) fn body(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tokens, kind: M
|
||||
}
|
||||
MarkKind::Background => {
|
||||
ui.horizontal(|ui| {
|
||||
ui.radio_value(&mut d.use_file, false, tl!("From colour"));
|
||||
widgets::radio_value(ui, &mut d.use_file, false, tl!("From colour"));
|
||||
ui.add_enabled_ui(!d.use_file, |ui| color_button(ui, &mut d.bg.color));
|
||||
ui.add_space(12.0);
|
||||
ui.radio_value(&mut d.use_file, true, tl!("File"));
|
||||
widgets::radio_value(ui, &mut d.use_file, true, tl!("File"));
|
||||
});
|
||||
if d.use_file {
|
||||
browse |= file_source(ui, d, MarkKind::Background);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//! Measure objects: page-space clicks, live readings and persistent drawing scales.
|
||||
use crate::{LeftPanel, PdfCraftApp, QuickTool, canvas::DocView, theme::Tokens};
|
||||
use crate::{LeftPanel, PdfCraftApp, QuickTool, canvas::DocView, theme::Tokens, widgets};
|
||||
use egui::{Color32, Pos2, Stroke};
|
||||
use pdfcraft_engine::{
|
||||
Document, Edit, Style,
|
||||
@@ -338,15 +338,15 @@ fn panel_body(app: &mut PdfCraftApp, ui: &mut egui::Ui, _t: &Tokens) {
|
||||
ui.add(crate::widgets::line(&mut state.label).desired_width(130.0).char_limit(128));
|
||||
});
|
||||
ui.separator();
|
||||
ui.checkbox(&mut state.snap_enabled, tl!("Snap to drawing"));
|
||||
ui.add(widgets::checkbox(&mut state.snap_enabled, tl!("Snap to drawing")));
|
||||
if state.snap_enabled {
|
||||
ui.horizontal(|ui| {
|
||||
ui.checkbox(&mut state.snaps.endpoints, tl!("Endpoints"));
|
||||
ui.checkbox(&mut state.snaps.midpoints, tl!("Midpoints"));
|
||||
ui.add(widgets::checkbox(&mut state.snaps.endpoints, tl!("Endpoints")));
|
||||
ui.add(widgets::checkbox(&mut state.snaps.midpoints, tl!("Midpoints")));
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
ui.checkbox(&mut state.snaps.intersections, tl!("Intersections"));
|
||||
ui.checkbox(&mut state.snaps.paths, tl!("Paths"));
|
||||
ui.add(widgets::checkbox(&mut state.snaps.intersections, tl!("Intersections")));
|
||||
ui.add(widgets::checkbox(&mut state.snaps.paths, tl!("Paths")));
|
||||
});
|
||||
ui.add(egui::Slider::new(&mut state.sensitivity, 1.0..=20.0).text(tl!("Sensitivity")));
|
||||
}
|
||||
@@ -365,7 +365,7 @@ fn panel_body(app: &mut PdfCraftApp, ui: &mut egui::Ui, _t: &Tokens) {
|
||||
if ui.button(tl!("Calibrate from two points")).clicked() {
|
||||
tool = Some(Tool::Calibrate);
|
||||
}
|
||||
ui.checkbox(&mut state.whole_page, tl!("Entire page"));
|
||||
ui.add(widgets::checkbox(&mut state.whole_page, tl!("Entire page")));
|
||||
if !state.whole_page {
|
||||
ui.label(tl!("Viewport rectangle in page points (x, y, width, height)"));
|
||||
for (i, name) in ["X", "Y", "Width", "Height"].iter().enumerate() {
|
||||
|
||||
@@ -75,10 +75,10 @@ pub(crate) fn body(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tokens) -> (boo
|
||||
ui.add_space(8.0);
|
||||
};
|
||||
group(ui, "Pages", &mut |ui| {
|
||||
ui.radio_value(&mut d.pages, OcrPages::All, tl!("All pages"));
|
||||
ui.radio_value(&mut d.pages, OcrPages::Current, tl!("Current page"));
|
||||
widgets::radio_value(ui, &mut d.pages, OcrPages::All, tl!("All pages"));
|
||||
widgets::radio_value(ui, &mut d.pages, OcrPages::Current, tl!("Current page"));
|
||||
ui.horizontal(|ui| {
|
||||
ui.radio_value(&mut d.pages, OcrPages::Range, tl!("From"));
|
||||
widgets::radio_value(ui, &mut d.pages, OcrPages::Range, tl!("From"));
|
||||
let on = d.pages == OcrPages::Range;
|
||||
ui.add_enabled(on, egui::DragValue::new(&mut d.from).range(1..=pages));
|
||||
ui.label(tl!("to"));
|
||||
|
||||
@@ -107,7 +107,7 @@ fn summary(r: &OptimizeReport) -> String {
|
||||
fn image_row(ui: &mut egui::Ui, id: &str, title: &str, s: &mut ImageSettings) {
|
||||
ui.label(egui::RichText::new(tl!(title)).font(theme::semibold(13.0)));
|
||||
ui.horizontal(|ui| {
|
||||
ui.checkbox(&mut s.downsample, tl!("Bicubic downsampling to"));
|
||||
ui.add(widgets::checkbox(&mut s.downsample, tl!("Bicubic downsampling to")));
|
||||
ui.add_enabled(s.downsample, egui::DragValue::new(&mut s.target_ppi).range(9.0..=2400.0).suffix(" ppi"));
|
||||
ui.label(tl!("for images above"));
|
||||
ui.add_enabled(s.downsample, egui::DragValue::new(&mut s.above_ppi).range(9.0..=2400.0).suffix(" ppi"));
|
||||
@@ -144,7 +144,7 @@ fn image_row(ui: &mut egui::Ui, id: &str, title: &str, s: &mut ImageSettings) {
|
||||
|
||||
fn discard_box(ui: &mut egui::Ui, list: &mut Vec<Hidden>, h: Hidden, label: &str) {
|
||||
let mut on = list.contains(&h);
|
||||
if ui.checkbox(&mut on, tl!(label)).changed() {
|
||||
if ui.add(widgets::checkbox(&mut on, tl!(label))).changed() {
|
||||
if on {
|
||||
list.push(h);
|
||||
} else {
|
||||
@@ -184,10 +184,10 @@ pub(crate) fn body(ui: &mut egui::Ui, d: &mut OptimizeDraft, t: &Tokens) -> (boo
|
||||
}
|
||||
OptimizeTab::DiscardObjects => {
|
||||
discard_box(ui, &mut d.discard, Hidden::LinksActionsScripts, "Discard all links, actions and JavaScript");
|
||||
ui.checkbox(&mut s.discard_alternate_images, tl!("Discard alternate images"));
|
||||
ui.checkbox(&mut s.discard_thumbnails, tl!("Discard embedded page thumbnails"));
|
||||
ui.checkbox(&mut s.discard_tags, tl!("Discard document tags"));
|
||||
ui.checkbox(&mut s.discard_print_settings, tl!("Discard embedded print settings"));
|
||||
ui.add(widgets::checkbox(&mut s.discard_alternate_images, tl!("Discard alternate images")));
|
||||
ui.add(widgets::checkbox(&mut s.discard_thumbnails, tl!("Discard embedded page thumbnails")));
|
||||
ui.add(widgets::checkbox(&mut s.discard_tags, tl!("Discard document tags")));
|
||||
ui.add(widgets::checkbox(&mut s.discard_print_settings, tl!("Discard embedded print settings")));
|
||||
discard_box(ui, &mut d.discard, Hidden::Bookmarks, "Discard bookmarks");
|
||||
discard_box(ui, &mut d.discard, Hidden::FormFields, "Flatten form fields");
|
||||
discard_box(ui, &mut d.discard, Hidden::HiddenLayers, "Discard hidden layer content");
|
||||
@@ -200,11 +200,11 @@ pub(crate) fn body(ui: &mut egui::Ui, d: &mut OptimizeDraft, t: &Tokens) -> (boo
|
||||
discard_box(ui, &mut d.discard, Hidden::HiddenText, "Discard hidden text");
|
||||
}
|
||||
OptimizeTab::CleanUp => {
|
||||
ui.checkbox(&mut s.flate_unencoded, tl!("Use Flate to encode streams that are not encoded"));
|
||||
ui.checkbox(&mut s.remove_invalid_links, tl!("Remove invalid links and bookmarks"));
|
||||
ui.checkbox(&mut s.remove_unreferenced_dests, tl!("Remove unreferenced named destinations"));
|
||||
ui.add_enabled(false, egui::Checkbox::new(&mut true, tl!("Compress document structure (object streams)")));
|
||||
ui.add_enabled(false, egui::Checkbox::new(&mut true, tl!("Remove unused objects and merge identical ones")));
|
||||
ui.add(widgets::checkbox(&mut s.flate_unencoded, tl!("Use Flate to encode streams that are not encoded")));
|
||||
ui.add(widgets::checkbox(&mut s.remove_invalid_links, tl!("Remove invalid links and bookmarks")));
|
||||
ui.add(widgets::checkbox(&mut s.remove_unreferenced_dests, tl!("Remove unreferenced named destinations")));
|
||||
ui.add_enabled(false, widgets::checkbox(&mut true, tl!("Compress document structure (object streams)")));
|
||||
ui.add_enabled(false, widgets::checkbox(&mut true, tl!("Remove unused objects and merge identical ones")));
|
||||
}
|
||||
}
|
||||
ui.add_space(12.0);
|
||||
|
||||
@@ -135,15 +135,20 @@ pub(crate) fn body(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tokens) -> (boo
|
||||
ui.end_row();
|
||||
ui.label(tl!("Pages"));
|
||||
ui.vertical(|ui| {
|
||||
ui.radio_value(&mut d.range, Range::All, tl!("All"));
|
||||
ui.radio_value(&mut d.range, Range::Current, crate::i18n::fmt(tl!("Current page ({n})"), &[("n", &(current + 1).to_string())]));
|
||||
widgets::radio_value(ui, &mut d.range, Range::All, tl!("All"));
|
||||
widgets::radio_value(
|
||||
ui,
|
||||
&mut d.range,
|
||||
Range::Current,
|
||||
crate::i18n::fmt(tl!("Current page ({n})"), &[("n", &(current + 1).to_string())]),
|
||||
);
|
||||
let (mut a, mut b) = match d.range {
|
||||
Range::Pages(a, b) => (a, b),
|
||||
_ => (1, count),
|
||||
};
|
||||
ui.horizontal(|ui| {
|
||||
let on = matches!(d.range, Range::Pages(..));
|
||||
if ui.radio(on, tl!("From")).clicked() {
|
||||
if ui.add(widgets::radio(on, tl!("From"))).clicked() {
|
||||
d.range = Range::Pages(a, b);
|
||||
}
|
||||
let ra = ui.add(egui::DragValue::new(&mut a).range(1..=count));
|
||||
|
||||
@@ -12,6 +12,7 @@ use pdfcraft_render::DocInfo;
|
||||
|
||||
use crate::canvas::{DocView, PageXform};
|
||||
use crate::theme;
|
||||
use crate::widgets;
|
||||
|
||||
const SELECT_BLUE: Color32 = Color32::from_rgb(0x14, 0x73, 0xE6);
|
||||
/// Acrobat's field fill in Prepare Form (light blue).
|
||||
@@ -918,7 +919,7 @@ const OPTION_FLAGS: [u32; 10] = {
|
||||
/// A check box for a `/Ff` bit (`inverted`: checked when the bit is clear).
|
||||
fn flag_box(ui: &mut egui::Ui, flags: &mut u32, bit: u32, inverted: bool, label: &str) {
|
||||
let mut on = (*flags & bit != 0) != inverted;
|
||||
if ui.checkbox(&mut on, label).changed() {
|
||||
if ui.add(widgets::checkbox(&mut on, label)).changed() {
|
||||
if on != inverted {
|
||||
*flags |= bit;
|
||||
} else {
|
||||
@@ -965,15 +966,15 @@ pub(crate) fn body(ui: &mut egui::Ui, d: &mut FieldDraft, t: &crate::theme::Toke
|
||||
});
|
||||
ui.add_space(12.0);
|
||||
widgets::section_title(ui, tl!("Common Properties"));
|
||||
ui.checkbox(&mut d.read_only, tl!("Read Only"));
|
||||
ui.checkbox(&mut d.required, tl!("Required"));
|
||||
ui.add(widgets::checkbox(&mut d.read_only, tl!("Read Only")));
|
||||
ui.add(widgets::checkbox(&mut d.required, tl!("Required")));
|
||||
}
|
||||
FieldTab::Appearance => {
|
||||
egui::Grid::new("field-appearance").num_columns(2).spacing([12.0, 10.0]).show(ui, |ui| {
|
||||
ui.label(tl!("Font Size:"));
|
||||
let mut auto = d.font_size == 0.0;
|
||||
ui.horizontal(|ui| {
|
||||
if ui.checkbox(&mut auto, tl!("Auto")).changed() {
|
||||
if ui.add(widgets::checkbox(&mut auto, tl!("Auto"))).changed() {
|
||||
d.font_size = if auto { 0.0 } else { 12.0 };
|
||||
}
|
||||
ui.add_enabled(!auto, egui::DragValue::new(&mut d.font_size).range(2.0..=100.0).speed(0.25).suffix(" pt"));
|
||||
@@ -1004,7 +1005,7 @@ pub(crate) fn body(ui: &mut egui::Ui, d: &mut FieldDraft, t: &crate::theme::Toke
|
||||
ui.push_id(("field-color", index), |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
let mut none = slot.is_none();
|
||||
if ui.checkbox(&mut none, tl!("No color")).changed() {
|
||||
if ui.add(widgets::checkbox(&mut none, tl!("No color"))).changed() {
|
||||
*slot = if none { None } else { Some([0.0; 3]) };
|
||||
}
|
||||
if let Some(c) = crate::comments::swatch_grid(ui, *slot) {
|
||||
@@ -1094,11 +1095,11 @@ pub(crate) fn body(ui: &mut egui::Ui, d: &mut FieldDraft, t: &crate::theme::Toke
|
||||
ui.end_row();
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
ui.checkbox(&mut d.multiline, tl!("Multi-line"));
|
||||
ui.add(widgets::checkbox(&mut d.multiline, tl!("Multi-line")));
|
||||
flag_box(ui, &mut d.flags, ff::DO_NOT_SCROLL, true, tl!("Scroll long text"));
|
||||
flag_box(ui, &mut d.flags, ff::RICH_TEXT, false, tl!("Allow Rich Text Formatting"));
|
||||
ui.horizontal(|ui| {
|
||||
ui.checkbox(&mut d.limit, tl!("Limit of"));
|
||||
ui.add(widgets::checkbox(&mut d.limit, tl!("Limit of")));
|
||||
ui.add_enabled(d.limit, egui::DragValue::new(&mut d.max_len).range(0..=10_000));
|
||||
ui.label(tl!("characters"));
|
||||
});
|
||||
@@ -1126,7 +1127,7 @@ pub(crate) fn body(ui: &mut egui::Ui, d: &mut FieldDraft, t: &crate::theme::Toke
|
||||
let mut checked = !d.default.is_empty() && d.default == on;
|
||||
let label =
|
||||
if d.kind == FormFieldKind::CheckBox { tl!("Check box is checked by default") } else { tl!("Button is checked by default") };
|
||||
if ui.checkbox(&mut checked, label).changed() {
|
||||
if ui.add(widgets::checkbox(&mut checked, label)).changed() {
|
||||
d.default = if checked { on } else { String::new() };
|
||||
}
|
||||
if let Some(style) = d.check_style.as_mut() {
|
||||
@@ -1210,7 +1211,7 @@ pub(crate) fn body(ui: &mut egui::Ui, d: &mut FieldDraft, t: &crate::theme::Toke
|
||||
}
|
||||
});
|
||||
ui.add_space(8.0);
|
||||
ui.checkbox(&mut d.locked, tl!("Locked"));
|
||||
ui.add(widgets::checkbox(&mut d.locked, tl!("Locked")));
|
||||
ui.add_space(6.0);
|
||||
let (mut apply, mut cancel) = (enter, false);
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||
@@ -1323,7 +1324,7 @@ fn format_tab(ui: &mut egui::Ui, d: &mut FieldDraft, t: &crate::theme::Tokens) {
|
||||
ui.selectable_value(currency, c.to_string(), if c.is_empty() { tl!("None") } else { c });
|
||||
}
|
||||
});
|
||||
ui.checkbox(prepend, tl!("Before the number"));
|
||||
ui.add(widgets::checkbox(prepend, tl!("Before the number")));
|
||||
});
|
||||
ui.end_row();
|
||||
ui.label(tl!("Negative number style:"));
|
||||
@@ -1406,8 +1407,8 @@ fn format_tab(ui: &mut egui::Ui, d: &mut FieldDraft, t: &crate::theme::Tokens) {
|
||||
|
||||
fn validate_tab(ui: &mut egui::Ui, d: &mut FieldDraft) {
|
||||
let mut ranged = matches!(d.validate, Validate::Range { .. });
|
||||
ui.radio_value(&mut ranged, false, tl!("Field value is not validated"));
|
||||
ui.radio_value(&mut ranged, true, tl!("Field value is in range:"));
|
||||
widgets::radio_value(ui, &mut ranged, false, tl!("Field value is not validated"));
|
||||
widgets::radio_value(ui, &mut ranged, true, tl!("Field value is in range:"));
|
||||
if !ranged {
|
||||
d.validate = Validate::None;
|
||||
return;
|
||||
@@ -1418,12 +1419,12 @@ fn validate_tab(ui: &mut egui::Ui, d: &mut FieldDraft) {
|
||||
if let Validate::Range { min, max } = &mut d.validate {
|
||||
ui.horizontal(|ui| {
|
||||
let mut has = min.is_some();
|
||||
ui.checkbox(&mut has, tl!("From:"));
|
||||
ui.add(widgets::checkbox(&mut has, tl!("From:")));
|
||||
let mut v = min.unwrap_or(0.0);
|
||||
ui.add_enabled(has, egui::DragValue::new(&mut v).speed(1.0));
|
||||
*min = has.then_some(v);
|
||||
let mut has = max.is_some();
|
||||
ui.checkbox(&mut has, tl!("To:"));
|
||||
ui.add(widgets::checkbox(&mut has, tl!("To:")));
|
||||
let mut v = max.unwrap_or(100.0);
|
||||
ui.add_enabled(has, egui::DragValue::new(&mut v).speed(1.0));
|
||||
*max = has.then_some(v);
|
||||
@@ -1521,8 +1522,8 @@ fn calculate_tab(ui: &mut egui::Ui, d: &mut FieldDraft, t: &crate::theme::Tokens
|
||||
Calculate::Notation(_) => Mode::Notation,
|
||||
};
|
||||
let mut m = mode;
|
||||
ui.radio_value(&mut m, Mode::None, tl!("Value is not calculated"));
|
||||
ui.radio_value(&mut m, Mode::Simple, tl!("Value is the"));
|
||||
widgets::radio_value(ui, &mut m, Mode::None, tl!("Value is not calculated"));
|
||||
widgets::radio_value(ui, &mut m, Mode::Simple, tl!("Value is the"));
|
||||
if m != mode {
|
||||
d.calculate = match m {
|
||||
Mode::None => Calculate::None,
|
||||
@@ -1543,7 +1544,7 @@ fn calculate_tab(ui: &mut egui::Ui, d: &mut FieldDraft, t: &crate::theme::Tokens
|
||||
egui::ScrollArea::vertical().max_height(110.0).show(ui, |ui| {
|
||||
for name in &d.others {
|
||||
let mut on = fields.contains(name);
|
||||
if ui.checkbox(&mut on, name).changed() {
|
||||
if ui.add(widgets::checkbox(&mut on, name)).changed() {
|
||||
if on {
|
||||
fields.push(name.clone());
|
||||
} else {
|
||||
@@ -1557,7 +1558,9 @@ fn calculate_tab(ui: &mut egui::Ui, d: &mut FieldDraft, t: &crate::theme::Tokens
|
||||
Calculate::Notation(_) => Mode::Notation,
|
||||
_ => m,
|
||||
};
|
||||
if ui.radio_value(&mut m2, Mode::Notation, tl!("Simplified field notation:")).clicked() && !matches!(d.calculate, Calculate::Notation(_)) {
|
||||
if widgets::radio_value(ui, &mut m2, Mode::Notation, tl!("Simplified field notation:")).clicked()
|
||||
&& !matches!(d.calculate, Calculate::Notation(_))
|
||||
{
|
||||
d.calculate = Calculate::Notation(String::new());
|
||||
}
|
||||
if let Calculate::Notation(expr) = &mut d.calculate {
|
||||
|
||||
@@ -591,8 +591,8 @@ pub(crate) fn body(
|
||||
ui.label(tl!("Copies:"));
|
||||
ui.horizontal(|ui| {
|
||||
ui.add(egui::DragValue::new(&mut d.copies).range(1..=999));
|
||||
ui.checkbox(&mut d.collate, tl!("Collate"));
|
||||
ui.checkbox(&mut d.grayscale, tl!("Print in grayscale"));
|
||||
ui.add(widgets::checkbox(&mut d.collate, tl!("Collate")));
|
||||
ui.add(widgets::checkbox(&mut d.grayscale, tl!("Print in grayscale")));
|
||||
});
|
||||
ui.end_row();
|
||||
ui.label(tl!("Two-sided:"));
|
||||
@@ -641,9 +641,9 @@ pub(crate) fn body(
|
||||
ui.add_space(6.0);
|
||||
widgets::section_title(ui, tl!("Pages to Print"));
|
||||
ui.horizontal(|ui| {
|
||||
ui.radio_value(&mut d.which, Which::All, tl!("All"));
|
||||
ui.radio_value(&mut d.which, Which::Current, tl!("Current page"));
|
||||
ui.radio_value(&mut d.which, Which::Range, tl!("Pages"));
|
||||
widgets::radio_value(ui, &mut d.which, Which::All, tl!("All"));
|
||||
widgets::radio_value(ui, &mut d.which, Which::Current, tl!("Current page"));
|
||||
widgets::radio_value(ui, &mut d.which, Which::Range, tl!("Pages"));
|
||||
let r = ui.add_enabled(
|
||||
d.which == Which::Range,
|
||||
crate::widgets::line(&mut d.range).hint_text(format!("1-{}", sizes.len())).desired_width(110.0),
|
||||
@@ -655,7 +655,7 @@ pub(crate) fn body(
|
||||
// Offered only when pages were picked before the dialog opened.
|
||||
if !d.selected.is_empty() {
|
||||
let label = format!("{} ({})", tl!("Selected pages"), d.selected.len());
|
||||
ui.radio_value(&mut d.which, Which::Selected, label);
|
||||
widgets::radio_value(ui, &mut d.which, Which::Selected, label);
|
||||
}
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(tl!("More options:"));
|
||||
@@ -666,7 +666,7 @@ pub(crate) fn body(
|
||||
&[(Subset::All, tl!("All pages in range")), (Subset::Odd, tl!("Odd pages only")), (Subset::Even, tl!("Even pages only"))],
|
||||
150.0,
|
||||
);
|
||||
ui.checkbox(&mut d.reverse, tl!("Reverse pages"));
|
||||
ui.add(widgets::checkbox(&mut d.reverse, tl!("Reverse pages")));
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
widgets::section_title(ui, tl!("Page Sizing & Handling"));
|
||||
@@ -687,13 +687,13 @@ pub(crate) fn body(
|
||||
match d.handling {
|
||||
Handling::Size => {
|
||||
ui.horizontal(|ui| {
|
||||
ui.radio_value(&mut d.size, SizeMode::Fit, tl!("Fit"));
|
||||
ui.radio_value(&mut d.size, SizeMode::Actual, tl!("Actual size"));
|
||||
ui.radio_value(&mut d.size, SizeMode::Shrink, tl!("Shrink oversized pages"));
|
||||
widgets::radio_value(ui, &mut d.size, SizeMode::Fit, tl!("Fit"));
|
||||
widgets::radio_value(ui, &mut d.size, SizeMode::Actual, tl!("Actual size"));
|
||||
widgets::radio_value(ui, &mut d.size, SizeMode::Shrink, tl!("Shrink oversized pages"));
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
let custom = matches!(d.size, SizeMode::Custom(_));
|
||||
if ui.radio(custom, tl!("Custom scale:")).clicked() {
|
||||
if ui.add(widgets::radio(custom, tl!("Custom scale:"))).clicked() {
|
||||
d.size = SizeMode::Custom(d.custom_scale);
|
||||
}
|
||||
ui.add_enabled(custom, egui::DragValue::new(&mut d.custom_scale).range(1.0..=1000.0).suffix(" %"));
|
||||
@@ -705,7 +705,7 @@ pub(crate) fn body(
|
||||
ui.add(egui::DragValue::new(&mut d.poster_scale).range(10.0..=1000.0).suffix(" %"));
|
||||
ui.label(tl!("Overlap:"));
|
||||
ui.add(egui::DragValue::new(&mut d.overlap).range(0.0..=144.0).suffix(" pt"));
|
||||
ui.checkbox(&mut d.cut_marks, tl!("Cut marks"));
|
||||
ui.add(widgets::checkbox(&mut d.cut_marks, tl!("Cut marks")));
|
||||
});
|
||||
}
|
||||
Handling::Multiple => {
|
||||
@@ -728,8 +728,8 @@ pub(crate) fn body(
|
||||
);
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
ui.checkbox(&mut d.border, tl!("Print page border"));
|
||||
ui.checkbox(&mut d.auto_rotate, tl!("Auto-rotate pages"));
|
||||
ui.add(widgets::checkbox(&mut d.border, tl!("Print page border")));
|
||||
ui.add(widgets::checkbox(&mut d.auto_rotate, tl!("Auto-rotate pages")));
|
||||
});
|
||||
if d.order == PageOrder::CutStack {
|
||||
ui.label(tl!(
|
||||
@@ -759,9 +759,9 @@ pub(crate) fn body(
|
||||
ui.add_space(6.0);
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(tl!("Orientation:"));
|
||||
ui.radio_value(&mut d.orientation, Orientation::Auto, tl!("Auto portrait/landscape"));
|
||||
ui.radio_value(&mut d.orientation, Orientation::Portrait, tl!("Portrait"));
|
||||
ui.radio_value(&mut d.orientation, Orientation::Landscape, tl!("Landscape"));
|
||||
widgets::radio_value(ui, &mut d.orientation, Orientation::Auto, tl!("Auto portrait/landscape"));
|
||||
widgets::radio_value(ui, &mut d.orientation, Orientation::Portrait, tl!("Portrait"));
|
||||
widgets::radio_value(ui, &mut d.orientation, Orientation::Landscape, tl!("Landscape"));
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
widgets::section_title(ui, tl!("Comments & Forms"));
|
||||
|
||||
@@ -152,10 +152,10 @@ pub(crate) fn body(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tokens) -> (boo
|
||||
ui.end_row();
|
||||
ui.label(tl!("Encrypt"));
|
||||
ui.vertical(|ui| {
|
||||
ui.radio_value(&mut p.encrypt_metadata, true, tl!("All document contents"));
|
||||
widgets::radio_value(ui, &mut p.encrypt_metadata, true, tl!("All document contents"));
|
||||
// Unencrypted metadata needs crypt filters (Acrobat 6.0 and later).
|
||||
ui.add_enabled_ui(p.algorithm != A::Rc4_40 && p.algorithm != A::Rc4_128, |ui| {
|
||||
ui.radio_value(&mut p.encrypt_metadata, false, tl!("All document contents except metadata"));
|
||||
widgets::radio_value(ui, &mut p.encrypt_metadata, false, tl!("All document contents except metadata"));
|
||||
});
|
||||
});
|
||||
ui.end_row();
|
||||
@@ -187,8 +187,8 @@ pub(crate) fn body(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tokens) -> (boo
|
||||
ui.end_row();
|
||||
ui.label("");
|
||||
ui.vertical(|ui| {
|
||||
ui.checkbox(&mut p.copy, tl!("Enable copying of text, images, and other content"));
|
||||
ui.checkbox(&mut p.accessibility, tl!("Enable text access for screen reader devices for the visually impaired"));
|
||||
ui.add(widgets::checkbox(&mut p.copy, tl!("Enable copying of text, images, and other content")));
|
||||
ui.add(widgets::checkbox(&mut p.accessibility, tl!("Enable text access for screen reader devices for the visually impaired")));
|
||||
});
|
||||
ui.end_row();
|
||||
}
|
||||
|
||||
@@ -248,9 +248,9 @@ impl PdfCraftApp {
|
||||
pub(crate) fn pages_body(ui: &mut egui::Ui, d: &mut PagesDraft, pages: usize, _t: &Tokens) -> (bool, bool) {
|
||||
ui.label(egui::RichText::new(tl!("Mark Page Range")).font(crate::theme::semibold(18.0)));
|
||||
ui.add_space(8.0);
|
||||
ui.radio_value(&mut d.current, true, tl!("Current page"));
|
||||
widgets::radio_value(ui, &mut d.current, true, tl!("Current page"));
|
||||
ui.horizontal(|ui| {
|
||||
ui.radio_value(&mut d.current, false, tl!("Pages from"));
|
||||
widgets::radio_value(ui, &mut d.current, false, tl!("Pages from"));
|
||||
ui.add_enabled(!d.current, egui::DragValue::new(&mut d.from).range(1..=pages));
|
||||
ui.label(tl!("to"));
|
||||
ui.add_enabled(!d.current, egui::DragValue::new(&mut d.to).range(1..=pages));
|
||||
@@ -266,14 +266,14 @@ pub(crate) fn search_body(ui: &mut egui::Ui, d: &mut SearchDraft, t: &Tokens) ->
|
||||
ui.set_width(420.0);
|
||||
ui.label(egui::RichText::new(tl!("Find text and redact")).font(crate::theme::semibold(18.0)));
|
||||
ui.add_space(8.0);
|
||||
ui.radio_value(&mut d.mode, SearchMode::Phrase, tl!("Single word or phrase"));
|
||||
widgets::radio_value(ui, &mut d.mode, SearchMode::Phrase, tl!("Single word or phrase"));
|
||||
let mut enter = false;
|
||||
ui.add_enabled_ui(d.mode == SearchMode::Phrase, |ui| {
|
||||
let r = ui.add(crate::widgets::line(&mut d.text).hint_text(tl!("Text to find")).desired_width(380.0));
|
||||
enter = r.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter));
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
ui.radio_value(&mut d.mode, SearchMode::Words, tl!("Multiple words or phrases"));
|
||||
widgets::radio_value(ui, &mut d.mode, SearchMode::Words, tl!("Multiple words or phrases"));
|
||||
let mut import = false;
|
||||
ui.add_enabled_ui(d.mode == SearchMode::Words, |ui| {
|
||||
egui::ScrollArea::vertical().id_salt("redact-words").max_height(120.0).show(ui, |ui| {
|
||||
@@ -286,7 +286,7 @@ pub(crate) fn search_body(ui: &mut egui::Ui, d: &mut SearchDraft, t: &Tokens) ->
|
||||
});
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
ui.radio_value(&mut d.mode, SearchMode::Patterns, tl!("Patterns"));
|
||||
widgets::radio_value(ui, &mut d.mode, SearchMode::Patterns, tl!("Patterns"));
|
||||
ui.add_enabled_ui(d.mode == SearchMode::Patterns, |ui| {
|
||||
egui::ComboBox::from_id_salt("redact-pattern").selected_text(tl!(d.pattern.label())).width(240.0).show_ui(ui, |ui| {
|
||||
for p in REDACT_PATTERNS {
|
||||
@@ -320,7 +320,7 @@ pub(crate) fn props_body(ui: &mut egui::Ui, d: &mut RedactPrefs, _t: &Tokens) ->
|
||||
ui.label(tl!("Redacted area fill colour:"));
|
||||
ui.horizontal(|ui| {
|
||||
let mut none = d.fill.is_none();
|
||||
if ui.checkbox(&mut none, tl!("No colour")).changed() {
|
||||
if ui.add(widgets::checkbox(&mut none, tl!("No colour"))).changed() {
|
||||
d.fill = if none { None } else { Some([0.0, 0.0, 0.0]) };
|
||||
}
|
||||
});
|
||||
@@ -331,16 +331,16 @@ pub(crate) fn props_body(ui: &mut egui::Ui, d: &mut RedactPrefs, _t: &Tokens) ->
|
||||
}
|
||||
ui.end_row();
|
||||
ui.label("");
|
||||
ui.checkbox(&mut d.use_overlay, tl!("Use overlay text"));
|
||||
ui.add(widgets::checkbox(&mut d.use_overlay, tl!("Use overlay text")));
|
||||
ui.end_row();
|
||||
let on = d.use_overlay;
|
||||
let l = ui.add_enabled(on, egui::RadioButton::new(!d.use_code, tl!("Custom text:")));
|
||||
let l = ui.add_enabled(on, widgets::radio(!d.use_code, tl!("Custom text:")));
|
||||
if l.clicked() {
|
||||
d.use_code = false;
|
||||
}
|
||||
ui.add_enabled(on && !d.use_code, crate::widgets::line(&mut d.overlay).desired_width(220.0)).labelled_by(l.id);
|
||||
ui.end_row();
|
||||
if ui.add_enabled(on, egui::RadioButton::new(d.use_code, tl!("Redaction code:"))).clicked() {
|
||||
if ui.add_enabled(on, widgets::radio(d.use_code, tl!("Redaction code:"))).clicked() {
|
||||
d.use_code = true;
|
||||
}
|
||||
let codes_on = on && d.use_code;
|
||||
@@ -361,7 +361,7 @@ pub(crate) fn props_body(ui: &mut egui::Ui, d: &mut RedactPrefs, _t: &Tokens) ->
|
||||
ui.horizontal(|ui| {
|
||||
for &code in row {
|
||||
let mut picked = d.codes.contains(&code);
|
||||
if ui.add_sized([62.0, 18.0], egui::Checkbox::new(&mut picked, code)).changed() {
|
||||
if ui.add_sized([62.0, 18.0], widgets::checkbox(&mut picked, code)).changed() {
|
||||
d.codes.retain(|c| *c != code);
|
||||
if picked {
|
||||
d.codes.push(code);
|
||||
@@ -386,7 +386,7 @@ pub(crate) fn props_body(ui: &mut egui::Ui, d: &mut RedactPrefs, _t: &Tokens) ->
|
||||
ui.add_enabled_ui(on, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
let mut auto = look.size <= 0.0;
|
||||
if ui.checkbox(&mut auto, tl!("Auto-size text to fit redaction region")).changed() {
|
||||
if ui.add(widgets::checkbox(&mut auto, tl!("Auto-size text to fit redaction region"))).changed() {
|
||||
look.size = if auto { 0.0 } else { 10.0 };
|
||||
}
|
||||
if !auto {
|
||||
@@ -403,13 +403,13 @@ pub(crate) fn props_body(ui: &mut egui::Ui, d: &mut RedactPrefs, _t: &Tokens) ->
|
||||
});
|
||||
ui.end_row();
|
||||
ui.label("");
|
||||
ui.add_enabled(on, egui::Checkbox::new(&mut look.repeat, tl!("Repeat overlay text")));
|
||||
ui.add_enabled(on, widgets::checkbox(&mut look.repeat, tl!("Repeat overlay text")));
|
||||
ui.end_row();
|
||||
ui.label(tl!("Text alignment:"));
|
||||
ui.add_enabled_ui(on, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
for (a, label) in [(0u8, tl!("Left")), (1, tl!("Center")), (2, tl!("Right"))] {
|
||||
ui.radio_value(&mut look.align, a, label);
|
||||
widgets::radio_value(ui, &mut look.align, a, label);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -480,7 +480,7 @@ pub(crate) fn hidden_body(ui: &mut egui::Ui, d: &mut HiddenDraft, t: &Tokens) ->
|
||||
for (h, n, on) in d.found.iter_mut() {
|
||||
ui.add_enabled_ui(*n > 0, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.checkbox(on, tl!(h.label()));
|
||||
ui.add(widgets::checkbox(on, tl!(h.label())));
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||
ui.label(egui::RichText::new(if *n == 0 { tl!("None found").to_string() } else { n.to_string() }).color(t.text_muted));
|
||||
});
|
||||
|
||||
@@ -6,6 +6,7 @@ use egui::{Color32, FontId, TextFormat};
|
||||
|
||||
use crate::canvas::DocView;
|
||||
use crate::theme::{self, Tokens};
|
||||
use crate::widgets;
|
||||
|
||||
/// Glyphs of context shown on each side of a match.
|
||||
const CONTEXT: usize = 36;
|
||||
@@ -28,8 +29,8 @@ pub(crate) fn panel(ui: &mut egui::Ui, t: &Tokens, view: &mut DocView, pages: us
|
||||
let w = &mut ui.visuals_mut().widgets;
|
||||
w.inactive.bg_stroke = egui::Stroke::new(1.0, t.border);
|
||||
w.inactive.bg_fill = t.hover;
|
||||
let a = ui.checkbox(&mut find.whole_words, tl!("Whole words only")).changed();
|
||||
let b = ui.checkbox(&mut find.case_sensitive, tl!("Case-sensitive")).changed();
|
||||
let a = ui.add(widgets::checkbox(&mut find.whole_words, tl!("Whole words only"))).changed();
|
||||
let b = ui.add(widgets::checkbox(&mut find.case_sensitive, tl!("Case-sensitive"))).changed();
|
||||
if a || b {
|
||||
find.case_query.clear();
|
||||
}
|
||||
|
||||
@@ -652,8 +652,8 @@ fn configure(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tokens) -> bool {
|
||||
// Browse… is desktop-only.
|
||||
#[cfg_attr(target_arch = "wasm32", allow(unused_mut))]
|
||||
let mut browse = false;
|
||||
ui.radio_value(&mut d.new_id.create, false, tl!("Use a Digital ID from a file"));
|
||||
ui.radio_value(&mut d.new_id.create, true, tl!("Create a new Digital ID (self-signed, saved to a password-protected file)"));
|
||||
widgets::radio_value(ui, &mut d.new_id.create, false, tl!("Use a Digital ID from a file"));
|
||||
widgets::radio_value(ui, &mut d.new_id.create, true, tl!("Create a new Digital ID (self-signed, saved to a password-protected file)"));
|
||||
ui.add_space(8.0);
|
||||
let n = &mut d.new_id;
|
||||
if n.create {
|
||||
@@ -831,24 +831,24 @@ fn sign_as(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tokens) -> bool {
|
||||
preview(ui, t, &entry.name, d, graphic.as_ref(), &mut app.sign_graphic_preview);
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
let a = &mut d.appearance;
|
||||
ui.checkbox(&mut a.name, tl!("Name"));
|
||||
ui.checkbox(&mut a.date, tl!("Date"));
|
||||
ui.checkbox(&mut a.reason, tl!("Reason"));
|
||||
ui.checkbox(&mut a.location, tl!("Location"));
|
||||
ui.checkbox(&mut a.distinguished_name, tl!("Distinguished name"));
|
||||
ui.checkbox(&mut a.labels, tl!("Labels"));
|
||||
ui.add(widgets::checkbox(&mut a.name, tl!("Name")));
|
||||
ui.add(widgets::checkbox(&mut a.date, tl!("Date")));
|
||||
ui.add(widgets::checkbox(&mut a.reason, tl!("Reason")));
|
||||
ui.add(widgets::checkbox(&mut a.location, tl!("Location")));
|
||||
ui.add(widgets::checkbox(&mut a.distinguished_name, tl!("Distinguished name")));
|
||||
ui.add(widgets::checkbox(&mut a.labels, tl!("Labels")));
|
||||
});
|
||||
// Acrobat: Configure Signature Appearance, Text, Draw or Image.
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
ui.label(tl!("Graphic"));
|
||||
ui.radio_value(&mut d.graphic, SignGraphic::Name, tl!("Signer's name"));
|
||||
widgets::radio_value(ui, &mut d.graphic, SignGraphic::Name, tl!("Signer's name"));
|
||||
ui.add_enabled_ui(app.signature.is_some(), |ui| {
|
||||
if ui.radio(d.graphic == SignGraphic::FillSign, tl!("Fill & Sign signature")).clicked() {
|
||||
if ui.add(widgets::radio(d.graphic == SignGraphic::FillSign, tl!("Fill & Sign signature"))).clicked() {
|
||||
d.graphic = SignGraphic::FillSign;
|
||||
}
|
||||
});
|
||||
ui.add_enabled_ui(d.image.is_some(), |ui| {
|
||||
if ui.radio(matches!(d.graphic, SignGraphic::Image(_)), tl!("Image")).clicked()
|
||||
if ui.add(widgets::radio(matches!(d.graphic, SignGraphic::Image(_)), tl!("Image"))).clicked()
|
||||
&& let Some(image) = &d.image
|
||||
{
|
||||
d.graphic = SignGraphic::Image(image.clone());
|
||||
|
||||
@@ -188,6 +188,76 @@ pub fn menu_item(ui: &mut egui::Ui, label: &str, shortcut: &str) -> Response {
|
||||
ui.add(egui::Button::new(label).shortcut_text(shortcut))
|
||||
}
|
||||
|
||||
/// Corner radius of a checkbox's box. With the widgets' radius (`Tokens::radius`, 6) egui's 14 px
|
||||
/// box is a circle, the shape of a radio button, and View ▸ Page display has both.
|
||||
const CHECKBOX_RADIUS: u8 = 3;
|
||||
|
||||
/// A checkbox whose box is an outlined rounded square, never mistaken for a radio button. Every
|
||||
/// checkbox goes through here: clippy.toml disallows `ui.checkbox` and `egui::Checkbox`.
|
||||
pub fn checkbox<'a>(checked: &'a mut bool, atoms: impl egui::IntoAtoms<'a>) -> impl egui::Widget + 'a {
|
||||
#[expect(clippy::disallowed_methods, reason = "the one place that styles the box")]
|
||||
let widget = egui::Checkbox::new(checked, atoms);
|
||||
toggle(widget, Some(CHECKBOX_RADIUS))
|
||||
}
|
||||
|
||||
/// [`checkbox`] without a label: the selection box on Organize Pages thumbnails.
|
||||
pub fn checkbox_without_text(checked: &mut bool) -> impl egui::Widget + '_ {
|
||||
#[expect(clippy::disallowed_methods, reason = "the one place that styles the box")]
|
||||
let widget = egui::Checkbox::without_text(checked);
|
||||
toggle(widget, Some(CHECKBOX_RADIUS))
|
||||
}
|
||||
|
||||
/// A radio button with an outline and a fill, like [`checkbox`]. Every radio button goes through
|
||||
/// here or [`radio_value`]: clippy.toml disallows `ui.radio`, `ui.radio_value` and
|
||||
/// `egui::RadioButton`.
|
||||
pub fn radio<'a>(selected: bool, atoms: impl egui::IntoAtoms<'a>) -> impl egui::Widget + 'a {
|
||||
#[expect(clippy::disallowed_methods, reason = "the one place that styles the circle")]
|
||||
let widget = egui::RadioButton::new(selected, atoms);
|
||||
toggle(widget, None)
|
||||
}
|
||||
|
||||
/// [`radio`] selected while `*current == alternative`; a click selects it (`Ui::radio_value`).
|
||||
pub fn radio_value<'a, V: PartialEq>(ui: &mut egui::Ui, current: &mut V, alternative: V, atoms: impl egui::IntoAtoms<'a>) -> Response {
|
||||
let mut response = ui.add(radio(*current == alternative, atoms));
|
||||
if response.clicked() && *current != alternative {
|
||||
*current = alternative;
|
||||
response.mark_changed();
|
||||
}
|
||||
response
|
||||
}
|
||||
|
||||
/// The box of a checkbox or the circle of a radio button. egui draws it with the widgets' visuals
|
||||
/// and has no style of its own for it, so it is set in a scope around the widget, keeping what
|
||||
/// the caller set (dialogs and the search panel already outline theirs):
|
||||
/// - an outline, darker on hover;
|
||||
/// - on the light theme, the plain fill (`Tokens::hover`), as dialogs have: the field colour is
|
||||
/// the panels' white, so the box would vanish;
|
||||
/// - for a checkbox, a corner radius small enough to keep the box square.
|
||||
fn toggle<'a>(widget: impl egui::Widget + 'a, radius: Option<u8>) -> impl egui::Widget + 'a {
|
||||
move |ui: &mut egui::Ui| {
|
||||
ui.scope(|ui| {
|
||||
let t = Tokens::get(ui.ctx());
|
||||
let w = &mut ui.visuals_mut().widgets;
|
||||
if let Some(radius) = radius {
|
||||
for v in [&mut w.noninteractive, &mut w.inactive, &mut w.hovered, &mut w.active, &mut w.open] {
|
||||
v.corner_radius = CornerRadius::same(radius);
|
||||
}
|
||||
}
|
||||
if !t.dark() && w.inactive.bg_fill == t.field {
|
||||
w.inactive.bg_fill = t.hover;
|
||||
}
|
||||
if w.inactive.bg_stroke.is_empty() {
|
||||
w.inactive.bg_stroke = Stroke::new(1.0, t.border);
|
||||
}
|
||||
if w.hovered.bg_stroke.is_empty() {
|
||||
w.hovered.bg_stroke = Stroke::new(1.0, t.text_muted);
|
||||
}
|
||||
ui.add(widget)
|
||||
})
|
||||
.inner
|
||||
}
|
||||
}
|
||||
|
||||
pub fn section_title(ui: &mut egui::Ui, text: &str) {
|
||||
let t = Tokens::get(ui.ctx());
|
||||
ui.add_space(10.0);
|
||||
@@ -443,6 +513,70 @@ mod tests {
|
||||
crate::i18n::set_current(previous);
|
||||
}
|
||||
|
||||
/// View ▸ Page display mixes radio buttons and checkboxes. With the theme's 6 px widget radius
|
||||
/// egui's 14 px checkbox box drew as a circle, and on the light theme an unticked box or an
|
||||
/// unselected radio button was white on white with no outline, so it couldn't be seen.
|
||||
#[test]
|
||||
fn checkboxes_and_radio_buttons_are_outlined_filled_and_distinct() {
|
||||
for kind in [theme::ThemeKind::Light, theme::ThemeKind::Dark] {
|
||||
let ctx = egui::Context::default();
|
||||
theme::apply(&ctx, kind);
|
||||
let t = Tokens::for_kind(kind);
|
||||
let side = ctx.global_style().spacing.icon_width;
|
||||
let output = ctx.run_ui(egui::RawInput::default(), |ui| {
|
||||
ui.add(checkbox(&mut false, "Show cover page in two-page view"));
|
||||
ui.add(radio(false, "Single page"));
|
||||
});
|
||||
let boxes: Vec<_> = output
|
||||
.shapes
|
||||
.iter()
|
||||
.filter_map(|s| match &s.shape {
|
||||
egui::Shape::Rect(r) if (r.rect.width() - side).abs() < 0.5 && (r.rect.height() - side).abs() < 0.5 => Some(r),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
let circles: Vec<_> = output
|
||||
.shapes
|
||||
.iter()
|
||||
.filter_map(|s| match &s.shape {
|
||||
egui::Shape::Circle(c) if (c.radius * 2.0 - side).abs() < 1.0 => Some(c),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
assert_eq!((boxes.len(), circles.len()), (1, 1), "{kind:?}: one checkbox box and one radio circle");
|
||||
let r = boxes[0].corner_radius;
|
||||
let radius = r.nw.max(r.ne).max(r.sw).max(r.se);
|
||||
assert!(f32::from(radius) <= side / 4.0, "{kind:?}: a {side} px box with corner radius {radius} looks like a radio button");
|
||||
for (what, fill, edge) in [("checkbox", boxes[0].fill, boxes[0].stroke), ("radio button", circles[0].fill, circles[0].stroke)] {
|
||||
assert!(fill != t.panel && fill != t.card, "{kind:?}: an unselected {what} is filled like the panel behind it");
|
||||
assert!(
|
||||
edge.width >= 1.0 && edge.color != t.panel && edge.color != t.card,
|
||||
"{kind:?}: an unselected {what} needs an outline, got {edge:?}"
|
||||
);
|
||||
}
|
||||
output.drop_without_applying_deltas();
|
||||
}
|
||||
// An outline the caller set (dialogs) is kept.
|
||||
let ctx = egui::Context::default();
|
||||
let own = Stroke::new(1.5, Color32::RED);
|
||||
let output = ctx.run_ui(egui::RawInput::default(), |ui| {
|
||||
ui.visuals_mut().widgets.inactive.bg_stroke = own;
|
||||
ui.add(checkbox(&mut true, "Collate"));
|
||||
ui.add(radio(false, "All"));
|
||||
});
|
||||
let kept = output
|
||||
.shapes
|
||||
.iter()
|
||||
.filter(|s| match &s.shape {
|
||||
egui::Shape::Rect(r) => r.stroke == own,
|
||||
egui::Shape::Circle(c) => c.stroke == own,
|
||||
_ => false,
|
||||
})
|
||||
.count();
|
||||
assert_eq!(kept, 2, "the checkbox and the radio button keep the caller's outline");
|
||||
output.drop_without_applying_deltas();
|
||||
}
|
||||
|
||||
fn toast_rect(app: &mut PdfCraftApp, ctx: &egui::Context, width: f32, now: &mut f64) -> Rect {
|
||||
// Let the Area settle after a notice/viewport change, without advancing to
|
||||
// expiry. No native rendering or renderer worker is needed for this layout.
|
||||
|
||||
Reference in new issue
Block a user