Give dialogs roomier text fields and padding (#487)
* Give dialogs roomier text fields and padding Single-line fields in dialogs and side panels were only as tall as one line of text, and modals used the popup frame's tight margin. Fields are now 30 points with more inset, and every modal shares one padded card so titles and buttons sit clear of the edge. * Keep long labels inside their rows and dialogs Painted names ran past the edge of fixed rows, and a page hover tip was drawn above open dialogs. Labels now end in an ellipsis, tab rows wrap, and those tips stay hidden while a dialog is open. --------- Co-authored-by: Brandon Thomas <echelon@gmail.com>
This commit is contained in:
parent
315a0cc458
commit
116fa51262
|
|
@ -493,8 +493,7 @@ pub(crate) fn alt_body(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tokens) ->
|
|||
ui.add_space(8.0);
|
||||
ui.add_enabled_ui(!d.decorative[i], |ui| {
|
||||
let l = ui.label(tl!("Alternate text"));
|
||||
ui.add(egui::TextEdit::multiline(&mut d.texts[i]).desired_rows(3).desired_width(380.0).hint_text(tl!("Describe the figure")))
|
||||
.labelled_by(l.id);
|
||||
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_space(10.0);
|
||||
|
|
|
|||
|
|
@ -307,10 +307,10 @@ fn edit_body(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tokens) -> bool {
|
|||
ui.add_space(8.0);
|
||||
egui::Grid::new("action-edit").num_columns(2).spacing([12.0, 8.0]).show(ui, |ui| {
|
||||
ui.label(tl!("Action name:"));
|
||||
ui.add(egui::TextEdit::singleline(&mut a.name).desired_width(320.0).id_salt("action-name"));
|
||||
ui.add(crate::widgets::line(&mut a.name).desired_width(320.0).id_salt("action-name"));
|
||||
ui.end_row();
|
||||
ui.label(tl!("Description:"));
|
||||
ui.add(egui::TextEdit::singleline(&mut a.description).desired_width(320.0).id_salt("action-description"));
|
||||
ui.add(crate::widgets::line(&mut a.description).desired_width(320.0).id_salt("action-description"));
|
||||
ui.end_row();
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
|
|
@ -326,7 +326,7 @@ fn edit_body(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tokens) -> bool {
|
|||
ui.horizontal(|ui| {
|
||||
ui.label(format!("{}. {}", i + 1, tl!(s.label())));
|
||||
if let Some(arg) = s.arg_mut() {
|
||||
ui.add(egui::TextEdit::singleline(arg).desired_width(220.0));
|
||||
ui.add(crate::widgets::line(arg).desired_width(220.0));
|
||||
}
|
||||
ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
|
||||
if ui.button(tl!("Remove")).clicked() {
|
||||
|
|
|
|||
|
|
@ -2190,6 +2190,14 @@ pub fn document_area(app: &mut PdfCraftApp, index: usize, ui: &mut egui::Ui) {
|
|||
};
|
||||
let mut open_signature = false;
|
||||
let mut hover_text: Option<(Pos2, String)> = None;
|
||||
// A dialog, save prompt or palette covers the page. Hover tips are drawn above those
|
||||
// windows, so a comment under the pointer would otherwise float over the dialog.
|
||||
let page_covered = app.dialog.is_some()
|
||||
|| app.close_request.is_some()
|
||||
|| app.palette_open
|
||||
|| app.password_prompt.is_some()
|
||||
|| app.pending_link.is_some()
|
||||
|| app.updates.open;
|
||||
let mut clicked_link: Option<LinkTarget> = None;
|
||||
let mut canvas_action: Option<comments::CanvasAction> = None;
|
||||
let mut field_menu: Option<FieldMenu> = None;
|
||||
|
|
@ -2734,7 +2742,8 @@ pub fn document_area(app: &mut PdfCraftApp, index: usize, ui: &mut egui::Ui) {
|
|||
}
|
||||
}
|
||||
// Fill & Sign keeps its placement cursor clear of link/comment hover feedback.
|
||||
if !matches!(tool, QuickTool::Fill(_))
|
||||
if !page_covered
|
||||
&& !matches!(tool, QuickTool::Fill(_))
|
||||
&& let Some(p) = pointer
|
||||
{
|
||||
for l in info.links.iter().filter(|l| l.page == i) {
|
||||
|
|
@ -3479,12 +3488,14 @@ fn quick_bar(app: &mut PdfCraftApp, area: Rect, ui: &mut egui::Ui) {
|
|||
ui.painter().rect_filled(row, CornerRadius::same(4), t.hover);
|
||||
}
|
||||
icons::paint(ui, Rect::from_min_size(row.min + vec2(8.0, 6.0), vec2(16.0, 16.0)), tool.icon(), 16.0, t.text);
|
||||
ui.painter().text(
|
||||
let reserve = if on { 28.0 } else { 8.0 };
|
||||
crate::widgets::paint_left(
|
||||
ui,
|
||||
row.left_center() + vec2(34.0, 0.0),
|
||||
Align2::LEFT_CENTER,
|
||||
tl!(tool.label()),
|
||||
theme::regular(13.0),
|
||||
t.text,
|
||||
(row.width() - 34.0 - reserve).max(0.0),
|
||||
);
|
||||
if on {
|
||||
icons::paint(
|
||||
|
|
@ -3544,12 +3555,14 @@ fn quick_bar(app: &mut PdfCraftApp, area: Rect, ui: &mut egui::Ui) {
|
|||
ui.painter().rect_filled(row, CornerRadius::same(4), t.hover);
|
||||
}
|
||||
icons::paint(ui, Rect::from_min_size(row.min + vec2(8.0, 6.0), vec2(16.0, 16.0)), tool.icon(), 16.0, t.text);
|
||||
ui.painter().text(
|
||||
let reserve = if on { 28.0 } else { 8.0 };
|
||||
crate::widgets::paint_left(
|
||||
ui,
|
||||
row.left_center() + vec2(34.0, 0.0),
|
||||
Align2::LEFT_CENTER,
|
||||
tl!(tool.label()),
|
||||
theme::regular(13.0),
|
||||
t.text,
|
||||
(row.width() - 34.0 - reserve).max(0.0),
|
||||
);
|
||||
if on {
|
||||
icons::paint(
|
||||
|
|
@ -3834,7 +3847,17 @@ fn organize_grid(view: &mut DocView, info: &DocInfo, editable: bool, dirty: bool
|
|||
ui.painter().image(tex.id(), pr, Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0)), Color32::WHITE);
|
||||
}
|
||||
ui.painter().rect_stroke(pr, CornerRadius::ZERO, Stroke::new(1.0, t.border), egui::StrokeKind::Outside);
|
||||
ui.painter().text(pos2(c.center().x, c.bottom() - 48.0), Align2::CENTER_CENTER, &p.label, theme::medium(12.0), t.text_muted);
|
||||
let label_font = theme::medium(12.0);
|
||||
let label_w = (c.width() - 12.0).max(0.0);
|
||||
let page_label = crate::widgets::fit_line(&p.label, label_w, |s| crate::widgets::text_width(ui, s, &label_font));
|
||||
let label_pos = pos2(c.center().x, c.bottom() - 48.0);
|
||||
ui.painter().with_clip_rect(Rect::from_center_size(label_pos, vec2(label_w, 18.0))).text(
|
||||
label_pos,
|
||||
Align2::CENTER_CENTER,
|
||||
page_label,
|
||||
label_font,
|
||||
t.text_muted,
|
||||
);
|
||||
// The checkbox toggles just this page without requiring a modifier. Keep it
|
||||
// outside the page image, including on very tall or wide pages.
|
||||
let mut checked = view.selected.contains(&i);
|
||||
|
|
|
|||
|
|
@ -898,8 +898,8 @@ fn unlock_prompt(app: &mut PdfCraftApp, ctx: &egui::Context) {
|
|||
}
|
||||
ui.add_space(8.0);
|
||||
ui.horizontal(|ui| {
|
||||
let field =
|
||||
ui.add(egui::TextEdit::singleline(&mut prompt.input.0).password(!prompt.show).hint_text(tl!("Password")).desired_width(290.0));
|
||||
crate::widgets::begin_dialog(ui);
|
||||
let field = ui.add(crate::widgets::line(&mut prompt.input.0).password(!prompt.show).hint_text(tl!("Password")).desired_width(290.0));
|
||||
if !prompt.focused {
|
||||
field.request_focus();
|
||||
prompt.focused = true;
|
||||
|
|
@ -1374,9 +1374,10 @@ fn table_body(
|
|||
}
|
||||
// The range field and how many pages it takes.
|
||||
SortKey::Pages => {
|
||||
crate::widgets::begin_dialog(ui);
|
||||
let edit = ui.add_enabled(
|
||||
f.problem.is_none(),
|
||||
egui::TextEdit::singleline(&mut f.range).hint_text(tl!("All pages")).desired_width(88.0),
|
||||
crate::widgets::line(&mut f.range).frame(egui::Frame::NONE).hint_text(tl!("All pages")).desired_width(88.0),
|
||||
);
|
||||
// A visible field on plain and striped rows alike; red when the range is wrong.
|
||||
let border = if check.is_err() { Stroke::new(1.5, ERROR) } else { Stroke::new(1.0, t.border) };
|
||||
|
|
|
|||
|
|
@ -167,7 +167,7 @@ pub(crate) fn body(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tokens) -> (boo
|
|||
let kind = tl!(crate::comments_panel::subtype_label(&d.edited.subtype)).to_string();
|
||||
ui.label(egui::RichText::new(crate::i18n::fmt(tl!("{kind} Properties"), &[("kind", &kind)])).font(theme::semibold(18.0)));
|
||||
ui.add_space(6.0);
|
||||
ui.horizontal(|ui| {
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
for (tab, label) in
|
||||
[(PropsTab::Appearance, tl!("Appearance")), (PropsTab::General, tl!("General")), (PropsTab::ReviewHistory, tl!("Review History"))]
|
||||
{
|
||||
|
|
@ -240,10 +240,10 @@ pub(crate) fn body(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tokens) -> (boo
|
|||
PropsTab::General => {
|
||||
egui::Grid::new("comment-general").num_columns(2).spacing([12.0, 10.0]).show(ui, |ui| {
|
||||
let l = ui.label(tl!("Author"));
|
||||
ui.add(egui::TextEdit::singleline(&mut e.author).desired_width(280.0)).labelled_by(l.id);
|
||||
ui.add(crate::widgets::line(&mut e.author).desired_width(280.0)).labelled_by(l.id);
|
||||
ui.end_row();
|
||||
let l = ui.label(tl!("Subject"));
|
||||
ui.add(egui::TextEdit::singleline(&mut e.subject).desired_width(280.0)).labelled_by(l.id);
|
||||
ui.add(crate::widgets::line(&mut e.subject).desired_width(280.0)).labelled_by(l.id);
|
||||
ui.end_row();
|
||||
ui.label(tl!("Modified"));
|
||||
ui.label(e.modified.as_deref().map(pdfcraft_render::pretty_date).unwrap_or_else(|| "—".into()));
|
||||
|
|
|
|||
|
|
@ -109,10 +109,11 @@ pub(crate) fn show(
|
|||
allowed: bool,
|
||||
nav: &mut Option<Nav>,
|
||||
) -> Option<Edit> {
|
||||
crate::widgets::begin_dialog(ui);
|
||||
let mut edit = None;
|
||||
// Search (the header's magnifier toggles it).
|
||||
if let Some(q) = view.comments.search.as_mut() {
|
||||
let r = ui.add(egui::TextEdit::singleline(q).hint_text(tl!("Search comments")).desired_width(f32::INFINITY).id_salt("comment-search"));
|
||||
let r = ui.add(crate::widgets::line(q).hint_text(tl!("Search comments")).desired_width(f32::INFINITY).id_salt("comment-search"));
|
||||
if view.comments.search_focus {
|
||||
r.request_focus();
|
||||
view.comments.search_focus = false;
|
||||
|
|
@ -122,11 +123,7 @@ pub(crate) fn show(
|
|||
// "Add a comment": a sticky note on the current page, near its top-right corner.
|
||||
if allowed {
|
||||
let r = ui.add(
|
||||
egui::TextEdit::singleline(&mut view.comments.add_box)
|
||||
.hint_text(tl!("Add a comment"))
|
||||
.desired_width(f32::INFINITY)
|
||||
.margin(egui::Margin::symmetric(8, 6))
|
||||
.id_salt("comment-add-box"),
|
||||
crate::widgets::line(&mut view.comments.add_box).hint_text(tl!("Add a comment")).desired_width(f32::INFINITY).id_salt("comment-add-box"),
|
||||
);
|
||||
if r.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter)) && !view.comments.add_box.trim().is_empty() {
|
||||
let page = view.current.min(info.pages.len().saturating_sub(1));
|
||||
|
|
@ -296,7 +293,7 @@ fn card(
|
|||
egui::Frame::NONE.inner_margin(egui::Margin { left: 34, right: 0, top: 4, bottom: 0 }).show(ui, |ui| {
|
||||
ui.set_width(ui.available_width());
|
||||
if editing && let Some((_, _, text)) = view.comments.editing.as_mut() {
|
||||
let r = ui.add(egui::TextEdit::multiline(text).desired_rows(2).desired_width(f32::INFINITY).id_salt(("comment-edit", key)));
|
||||
let r = ui.add(crate::widgets::block(text).desired_rows(2).desired_width(f32::INFINITY).id_salt(("comment-edit", key)));
|
||||
if !r.has_focus() && !ui.memory(|m| m.focused().is_some()) {
|
||||
r.request_focus();
|
||||
}
|
||||
|
|
@ -362,7 +359,7 @@ fn card(
|
|||
// around the button can't fit every language's "Post" exactly, and any overflow
|
||||
// would widen the resizable panel again on every repaint.
|
||||
let r = ui.add(
|
||||
egui::TextEdit::singleline(&mut view.comments.reply)
|
||||
crate::widgets::line(&mut view.comments.reply)
|
||||
.hint_text(tl!("Add a reply"))
|
||||
.desired_width(f32::INFINITY)
|
||||
.id_salt(("comment-reply", key)),
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ fn dialogs(app: &mut PdfCraftApp, ctx: &egui::Context) {
|
|||
let t = Tokens::get(ctx);
|
||||
let mut close = false;
|
||||
let mut next = dialog;
|
||||
let modal = egui::Modal::new(egui::Id::new("dialog")).show(ctx, |ui| {
|
||||
let modal = widgets::show_modal(ctx, "dialog", |ui| {
|
||||
ui.set_width(match dialog {
|
||||
Dialog::Properties(_) => 640.0,
|
||||
Dialog::Print => 820.0,
|
||||
|
|
@ -76,14 +76,6 @@ fn dialogs(app: &mut PdfCraftApp, ctx: &egui::Context) {
|
|||
Dialog::About => 780.0,
|
||||
_ => 520.0,
|
||||
});
|
||||
// Dialog controls are outlined (radio buttons, check boxes, combo boxes and number fields
|
||||
// would otherwise blend into the dialog, whose fill matches the theme's field colour).
|
||||
let w = &mut ui.visuals_mut().widgets;
|
||||
w.inactive.bg_stroke = egui::Stroke::new(1.0, t.border);
|
||||
w.inactive.weak_bg_fill = t.field;
|
||||
// Slider rails and check-box interiors use the plain fill.
|
||||
w.inactive.bg_fill = t.hover;
|
||||
w.hovered.bg_stroke = egui::Stroke::new(1.0, t.text_muted);
|
||||
match dialog {
|
||||
Dialog::CreateImages => {
|
||||
let (go, cancel) = crate::create_ui::image_import_body(ui, app);
|
||||
|
|
@ -94,7 +86,7 @@ fn dialogs(app: &mut PdfCraftApp, ctx: &egui::Context) {
|
|||
Dialog::Properties(tab) => {
|
||||
ui.label(egui::RichText::new(tl!("Document Properties")).font(theme::semibold(18.0)));
|
||||
ui.add_space(8.0);
|
||||
ui.horizontal(|ui| {
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
for (tb, label) in [
|
||||
(PropsTab::Description, tl!("Description")),
|
||||
(PropsTab::InitialView, tl!("Initial View")),
|
||||
|
|
@ -126,14 +118,8 @@ fn dialogs(app: &mut PdfCraftApp, ctx: &egui::Context) {
|
|||
Some((_, draft)) if doc.allows_modification() => {
|
||||
for (k, v) in INFO_KEYS.iter().zip(draft.iter_mut()) {
|
||||
let l = ui.label(egui::RichText::new(tl!(k)).color(t.text_muted));
|
||||
ui.add(
|
||||
egui::TextEdit::singleline(v)
|
||||
.desired_width(420.0)
|
||||
.background_color(t.field)
|
||||
.margin(egui::Margin::symmetric(6, 4))
|
||||
.id_salt(("info", *k)),
|
||||
)
|
||||
.labelled_by(l.id);
|
||||
ui.add(widgets::line(v).desired_width(420.0).background_color(t.field).id_salt(("info", *k)))
|
||||
.labelled_by(l.id);
|
||||
ui.end_row();
|
||||
}
|
||||
}
|
||||
|
|
@ -377,7 +363,7 @@ fn dialogs(app: &mut PdfCraftApp, ctx: &egui::Context) {
|
|||
let l = ui.label(egui::RichText::new(tl!("Language")).color(t.text_muted));
|
||||
let mut lang = v.language.clone().unwrap_or_default();
|
||||
if ui
|
||||
.add_enabled(editable, egui::TextEdit::singleline(&mut lang).hint_text(tl!("e.g. en-US")).desired_width(160.0))
|
||||
.add_enabled(editable, widgets::line(&mut lang).hint_text(tl!("e.g. en-US")).desired_width(160.0))
|
||||
.labelled_by(l.id)
|
||||
.changed()
|
||||
{
|
||||
|
|
@ -960,15 +946,6 @@ fn dialogs(app: &mut PdfCraftApp, ctx: &egui::Context) {
|
|||
let d = &mut app.number_draft;
|
||||
d.to = d.to.clamp(1, n);
|
||||
d.from = d.from.clamp(1, d.to);
|
||||
// Editable values get a visible border (the dialog and field fills are alike).
|
||||
let boxed = |ui: &mut egui::Ui, add: &mut dyn FnMut(&mut egui::Ui) -> egui::Response| {
|
||||
egui::Frame::new()
|
||||
.stroke(egui::Stroke::new(1.0, t.border))
|
||||
.corner_radius(egui::CornerRadius::same(5))
|
||||
.inner_margin(egui::Margin::symmetric(4, 1))
|
||||
.show(ui, |ui| add(ui))
|
||||
.inner
|
||||
};
|
||||
egui::Grid::new("number_pages").num_columns(2).spacing([12.0, 8.0]).show(ui, |ui| {
|
||||
ui.label(tl!("Pages"));
|
||||
ui.horizontal(|ui| {
|
||||
|
|
@ -995,8 +972,7 @@ fn dialogs(app: &mut PdfCraftApp, ctx: &egui::Context) {
|
|||
});
|
||||
ui.end_row();
|
||||
let l = ui.label(tl!("Prefix"));
|
||||
boxed(ui, &mut |ui| ui.add(egui::TextEdit::singleline(&mut d.prefix).desired_width(160.0).frame(egui::Frame::NONE)))
|
||||
.labelled_by(l.id);
|
||||
ui.add(widgets::line(&mut d.prefix).desired_width(160.0)).labelled_by(l.id);
|
||||
ui.end_row();
|
||||
ui.label(tl!("Start"));
|
||||
ui.add(egui::DragValue::new(&mut d.start).range(1..=99_999));
|
||||
|
|
@ -1096,7 +1072,7 @@ fn dialogs(app: &mut PdfCraftApp, ctx: &egui::Context) {
|
|||
// Tabs About · Contributors · Models (craftrules standards/contributors.md).
|
||||
let tab_id = egui::Id::new("about_tab");
|
||||
let mut tab = ui.data_mut(|d| d.get_temp::<u8>(tab_id)).unwrap_or(0);
|
||||
ui.horizontal(|ui| {
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
for (i, label) in ["About", "Contributors", "Models"].into_iter().enumerate() {
|
||||
let i = i as u8;
|
||||
if widgets::mode_tab(ui, tl!(label), tab == i).clicked() {
|
||||
|
|
@ -1139,7 +1115,7 @@ fn dialogs(app: &mut PdfCraftApp, ctx: &egui::Context) {
|
|||
}
|
||||
}
|
||||
}
|
||||
ui.add_space(12.0);
|
||||
ui.add_space(16.0);
|
||||
let changed = draft_changes(app).is_some_and(|c| !c.is_empty());
|
||||
ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
|
||||
if dialog == Dialog::Recovery {
|
||||
|
|
@ -1408,7 +1384,7 @@ fn save_prompt(app: &mut PdfCraftApp, ctx: &egui::Context) {
|
|||
// the prompt taller than the window (#236); 80 characters wrap to a few lines.
|
||||
let shown = shorten_middle(&name, 80);
|
||||
let mut choice: Option<Option<bool>> = None;
|
||||
let modal = egui::Modal::new(egui::Id::new("save_prompt")).show(ctx, |ui| {
|
||||
let modal = widgets::show_modal(ctx, "save_prompt", |ui| {
|
||||
ui.set_width(420.0);
|
||||
ui.horizontal(|ui| {
|
||||
ui.add(crate::icons::image("save", 22.0, t.accent));
|
||||
|
|
@ -1469,7 +1445,7 @@ fn link_prompt(app: &mut PdfCraftApp, ctx: &egui::Context) {
|
|||
let email = pending.url.get(..7).is_some_and(|s| s.eq_ignore_ascii_case("mailto:"));
|
||||
let (title, open) = if email { (tl!("Write this email?"), tl!("Open email app")) } else { (tl!("Open this web page?"), tl!("Open link")) };
|
||||
let mut choice: Option<bool> = None;
|
||||
let modal = egui::Modal::new(egui::Id::new("link_prompt")).show(ctx, |ui| {
|
||||
let modal = widgets::show_modal(ctx, "link_prompt", |ui| {
|
||||
ui.set_width(460.0);
|
||||
ui.horizontal(|ui| {
|
||||
ui.add(crate::icons::image("external-link", 22.0, t.accent));
|
||||
|
|
@ -1547,7 +1523,7 @@ fn password_modal(
|
|||
) -> Option<PasswordAnswer> {
|
||||
let t = Tokens::get(ctx);
|
||||
let mut answer = None;
|
||||
let modal = egui::Modal::new(egui::Id::new(id)).show(ctx, |ui| {
|
||||
let modal = widgets::show_modal(ctx, id, |ui| {
|
||||
ui.set_width(400.0);
|
||||
ui.horizontal(|ui| {
|
||||
ui.add(crate::icons::image("lock", 22.0, t.accent));
|
||||
|
|
@ -1556,7 +1532,7 @@ fn password_modal(
|
|||
ui.add_space(6.0);
|
||||
ui.add(egui::Label::new(message).wrap());
|
||||
ui.add_space(8.0);
|
||||
let r = ui.add(egui::TextEdit::singleline(input).password(true).hint_text(tl!("Password")).desired_width(f32::INFINITY));
|
||||
let r = ui.add(widgets::line(input).password(true).hint_text(tl!("Password")).desired_width(f32::INFINITY));
|
||||
// Enter submits. The field keeps focus (we request it every frame), so check the key
|
||||
// while it is focused as well as on the frame focus is lost.
|
||||
if (r.has_focus() || r.lost_focus()) && ui.input(|i| i.key_pressed(egui::Key::Enter)) {
|
||||
|
|
|
|||
|
|
@ -163,7 +163,7 @@ pub(crate) fn console_body(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tokens)
|
|||
});
|
||||
ui.add_space(6.0);
|
||||
let input = ui.add(
|
||||
egui::TextEdit::multiline(&mut app.js_console.input)
|
||||
crate::widgets::block(&mut app.js_console.input)
|
||||
.code_editor()
|
||||
.desired_rows(4)
|
||||
.desired_width(f32::INFINITY)
|
||||
|
|
@ -190,7 +190,7 @@ pub(crate) fn document_js_body(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tok
|
|||
let mut edit: Option<Edit> = None;
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(tl!("Script Name:"));
|
||||
ui.add(egui::TextEdit::singleline(&mut app.doc_js.name).desired_width(240.0).id_salt("doc-js-name"));
|
||||
ui.add(crate::widgets::line(&mut app.doc_js.name).desired_width(240.0).id_salt("doc-js-name"));
|
||||
});
|
||||
ui.add_space(4.0);
|
||||
egui::Frame::new().fill(t.hover).corner_radius(egui::CornerRadius::same(6)).inner_margin(egui::Margin::same(8)).show(ui, |ui| {
|
||||
|
|
@ -208,7 +208,7 @@ pub(crate) fn document_js_body(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tok
|
|||
});
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
ui.add(egui::TextEdit::multiline(&mut app.doc_js.script).code_editor().desired_rows(8).desired_width(f32::INFINITY).id_salt("doc-js-script"));
|
||||
ui.add(crate::widgets::block(&mut app.doc_js.script).code_editor().desired_rows(8).desired_width(f32::INFINITY).id_salt("doc-js-script"));
|
||||
ui.add_space(8.0);
|
||||
let name = app.doc_js.name.trim().to_string();
|
||||
let close = match buttons(ui, "Save", &["Close", "Delete"]).as_deref() {
|
||||
|
|
@ -348,8 +348,7 @@ pub(crate) fn preferences_body(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tok
|
|||
ui.label(egui::RichText::new(tl!("Identity")).font(theme::semibold(13.0)));
|
||||
ui.horizontal(|ui| {
|
||||
let label = ui.label(tl!("Name on new comments"));
|
||||
ui.add(egui::TextEdit::singleline(&mut app.comment_prefs.author).desired_width(220.0).char_limit(crate::MAX_AUTHOR_CHARS))
|
||||
.labelled_by(label.id);
|
||||
ui.add(crate::widgets::line(&mut app.comment_prefs.author).desired_width(220.0).char_limit(crate::MAX_AUTHOR_CHARS)).labelled_by(label.id);
|
||||
});
|
||||
ui.add_space(8.0);
|
||||
ui.label(egui::RichText::new(tl!("Fill & Sign")).font(theme::semibold(13.0)));
|
||||
|
|
|
|||
|
|
@ -253,7 +253,7 @@ pub(crate) fn body(ui: &mut egui::Ui, d: &mut LinkDraft, pages: usize, t: &Token
|
|||
ui.add_enabled_ui(d.web, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.add_space(24.0);
|
||||
ui.add(egui::TextEdit::singleline(&mut d.url).desired_width(360.0).hint_text("https://example.org"));
|
||||
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"));
|
||||
|
|
|
|||
|
|
@ -188,7 +188,7 @@ pub(crate) fn body(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tokens, kind: M
|
|||
ui.end_row();
|
||||
for col in 0..3 {
|
||||
let k = row * 3 + col;
|
||||
let r = ui.add_sized([220.0, 40.0], egui::TextEdit::multiline(&mut d.hf.text[k]).desired_rows(2).id_salt(("hf-box", k)));
|
||||
let r = ui.add_sized([220.0, 40.0], crate::widgets::block(&mut d.hf.text[k]).desired_rows(2).id_salt(("hf-box", k)));
|
||||
if r.gained_focus() || r.has_focus() {
|
||||
d.focused_box = k;
|
||||
}
|
||||
|
|
@ -239,11 +239,7 @@ pub(crate) fn body(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tokens, kind: M
|
|||
ui.add_enabled_ui(!d.use_file, |ui| {
|
||||
ui.label(tl!("Text"));
|
||||
ui.add(
|
||||
egui::TextEdit::multiline(&mut d.wm.text)
|
||||
.desired_rows(2)
|
||||
.desired_width(320.0)
|
||||
.hint_text("CONFIDENTIAL")
|
||||
.id_salt("wm-text"),
|
||||
crate::widgets::block(&mut d.wm.text).desired_rows(2).desired_width(320.0).hint_text("CONFIDENTIAL").id_salt("wm-text"),
|
||||
);
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
|
|
|
|||
|
|
@ -286,6 +286,7 @@ pub(crate) fn panel(app: &mut PdfCraftApp, ui: &mut egui::Ui, t: &Tokens) {
|
|||
egui::ScrollArea::vertical().id_salt("measure-panel").show(ui, |ui| panel_body(app, ui, t));
|
||||
}
|
||||
fn panel_body(app: &mut PdfCraftApp, ui: &mut egui::Ui, _t: &Tokens) {
|
||||
crate::widgets::begin_dialog(ui);
|
||||
let Some((index, id)) = app.active_ids() else {
|
||||
ui.label(tl!("Open a PDF to measure it."));
|
||||
return;
|
||||
|
|
@ -334,7 +335,7 @@ fn panel_body(app: &mut PdfCraftApp, ui: &mut egui::Ui, _t: &Tokens) {
|
|||
}
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(tl!("Label"));
|
||||
ui.add(egui::TextEdit::singleline(&mut state.label).desired_width(130.0).char_limit(128));
|
||||
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"));
|
||||
|
|
@ -358,7 +359,7 @@ fn panel_body(app: &mut PdfCraftApp, ui: &mut egui::Ui, _t: &Tokens) {
|
|||
});
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(tl!("Unit"));
|
||||
ui.add(egui::TextEdit::singleline(&mut state.unit).desired_width(64.0).char_limit(12));
|
||||
ui.add(crate::widgets::line(&mut state.unit).desired_width(64.0).char_limit(12));
|
||||
ui.add(egui::DragValue::new(&mut state.precision).range(0..=6).prefix(format!("{} ", tl!("Decimals"))));
|
||||
});
|
||||
if ui.button(tl!("Calibrate from two points")).clicked() {
|
||||
|
|
@ -375,7 +376,7 @@ fn panel_body(app: &mut PdfCraftApp, ui: &mut egui::Ui, _t: &Tokens) {
|
|||
}
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(tl!("Viewport"));
|
||||
ui.add(egui::TextEdit::singleline(&mut state.name).desired_width(130.0).char_limit(128));
|
||||
ui.add(crate::widgets::line(&mut state.name).desired_width(130.0).char_limit(128));
|
||||
});
|
||||
let apply = ui.add_enabled(doc.allows_annotation(), egui::Button::new(tl!("Apply scale"))).clicked();
|
||||
if let Some(error) = &state.error {
|
||||
|
|
|
|||
|
|
@ -159,7 +159,7 @@ fn discard_box(ui: &mut egui::Ui, list: &mut Vec<Hidden>, h: Hidden, label: &str
|
|||
pub(crate) fn body(ui: &mut egui::Ui, d: &mut OptimizeDraft, t: &Tokens) -> (bool, bool) {
|
||||
ui.label(egui::RichText::new(tl!("PDF Optimizer")).font(theme::semibold(18.0)));
|
||||
ui.add_space(6.0);
|
||||
ui.horizontal(|ui| {
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
for (tab, label) in [
|
||||
(OptimizeTab::Images, tl!("Images")),
|
||||
(OptimizeTab::DiscardObjects, tl!("Discard Objects")),
|
||||
|
|
|
|||
|
|
@ -163,8 +163,19 @@ pub fn show(app: &mut PdfCraftApp, ctx: &egui::Context) {
|
|||
let shown_detail = tl!(&h.detail).to_string();
|
||||
resp.widget_info(|| egui::WidgetInfo::labeled(egui::WidgetType::Button, h.ready, &shown));
|
||||
let fg = if h.ready { t.text } else { t.text_faint };
|
||||
ui.painter().text(rect.left_center() + vec2(36.0, 0.0), Align2::LEFT_CENTER, shown, theme::regular(13.5), fg);
|
||||
ui.painter().text(rect.right_center() - vec2(10.0, 0.0), Align2::RIGHT_CENTER, shown_detail, theme::regular(12.0), t.text_faint);
|
||||
let detail_font = theme::regular(12.0);
|
||||
let detail_max = (rect.width() * 0.42).clamp(0.0, 220.0);
|
||||
let detail = crate::widgets::fit_line(&shown_detail, detail_max, |s| crate::widgets::text_width(ui, s, &detail_font));
|
||||
let detail_w = crate::widgets::text_width(ui, &detail, &detail_font);
|
||||
ui.painter().text(rect.right_center() - vec2(10.0, 0.0), Align2::RIGHT_CENTER, detail, detail_font, t.text_faint);
|
||||
crate::widgets::paint_left(
|
||||
ui,
|
||||
rect.left_center() + vec2(36.0, 0.0),
|
||||
&shown,
|
||||
theme::regular(13.5),
|
||||
fg,
|
||||
(rect.width() - 36.0 - detail_w - 24.0).max(0.0),
|
||||
);
|
||||
if resp.clicked() {
|
||||
chosen = Some((h.command, h.group));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -221,11 +221,12 @@ fn tool_detail(app: &mut PdfCraftApp, ui: &mut egui::Ui, t: &Tokens, g: &'static
|
|||
Availability::Provider => ("AI", t.pressed, t.text_muted),
|
||||
};
|
||||
let font = theme::semibold(9.5);
|
||||
let w = ui.fonts_mut(|f| f.layout_no_wrap(tl!(chip).to_string(), font.clone(), cfg).size().x);
|
||||
let chip_label = tl!(chip).to_string();
|
||||
let w = crate::widgets::text_width(ui, &chip_label, &font);
|
||||
let r = Rect::from_center_size(rect.right_center() - vec2(w / 2.0 + 10.0, 0.0), vec2(w + 10.0, 16.0));
|
||||
row_label(ui, tl!(item.label), theme::regular(13.0), fg, rect.left_center() + vec2(34.0, 0.0), r.left() - rect.left() - 42.0);
|
||||
ui.painter().rect_filled(r, CornerRadius::same(4), fill);
|
||||
ui.painter().text(r.center(), Align2::CENTER_CENTER, tl!(chip), font, cfg);
|
||||
ui.painter().text(r.center(), Align2::CENTER_CENTER, chip_label, font, cfg);
|
||||
if resp.on_hover_text(format!("{} — {}", tl!(item.label), item.command)).clicked() {
|
||||
run = Some(item.command);
|
||||
}
|
||||
|
|
@ -300,23 +301,31 @@ fn stamp_palette(app: &mut PdfCraftApp, ui: &mut egui::Ui, t: &Tokens) {
|
|||
chip.left_bottom() + vec2(tip, 0.0),
|
||||
];
|
||||
ui.painter().add(egui::Shape::convex_polygon(pts, col, Stroke::NONE));
|
||||
ui.painter().text(
|
||||
let label = kind.label();
|
||||
let font = theme::semibold(11.0);
|
||||
let shown = crate::widgets::fit_line(label, (chip.width() - 10.0).max(0.0), |s| crate::widgets::text_width(ui, s, &font));
|
||||
ui.painter().with_clip_rect(chip.shrink(3.0)).text(
|
||||
chip.center() + vec2(tip / 2.0, 0.0),
|
||||
Align2::CENTER_CENTER,
|
||||
kind.label(),
|
||||
theme::semibold(11.0),
|
||||
shown,
|
||||
font,
|
||||
Color32::WHITE,
|
||||
);
|
||||
} else {
|
||||
ui.painter().rect(chip, CornerRadius::same(5), col.gamma_multiply(0.1), Stroke::new(1.5, col), egui::StrokeKind::Inside);
|
||||
let y = if group == StampGroup::Dynamic { chip.center().y - 4.0 } else { chip.center().y };
|
||||
ui.painter().text(egui::pos2(chip.center().x, y), Align2::CENTER_CENTER, kind.label(), theme::semibold(11.0), col);
|
||||
let font = theme::semibold(11.0);
|
||||
let shown = crate::widgets::fit_line(kind.label(), (chip.width() - 10.0).max(0.0), |s| crate::widgets::text_width(ui, s, &font));
|
||||
ui.painter().with_clip_rect(chip.shrink(3.0)).text(egui::pos2(chip.center().x, y), Align2::CENTER_CENTER, shown, font, col);
|
||||
if group == StampGroup::Dynamic {
|
||||
ui.painter().text(
|
||||
let by = "By name at time, date";
|
||||
let by_font = theme::regular(7.5);
|
||||
let by_shown = crate::widgets::fit_line(by, (chip.width() - 8.0).max(0.0), |s| crate::widgets::text_width(ui, s, &by_font));
|
||||
ui.painter().with_clip_rect(chip.shrink(2.0)).text(
|
||||
egui::pos2(chip.center().x, chip.bottom() - 6.0),
|
||||
Align2::CENTER_CENTER,
|
||||
"By name at time, date",
|
||||
theme::regular(7.5),
|
||||
by_shown,
|
||||
by_font,
|
||||
col,
|
||||
);
|
||||
}
|
||||
|
|
@ -605,7 +614,14 @@ pub fn right_panel(app: &mut PdfCraftApp, ui: &mut egui::Ui) {
|
|||
t.icon,
|
||||
);
|
||||
let fg = if l.visible { t.text } else { t.text_faint };
|
||||
ui.painter().text(rect.left_center() + vec2(32.0, 0.0), Align2::LEFT_CENTER, &l.name, theme::regular(13.0), fg);
|
||||
crate::widgets::paint_left(
|
||||
ui,
|
||||
rect.left_center() + vec2(32.0, 0.0),
|
||||
&l.name,
|
||||
theme::regular(13.0),
|
||||
fg,
|
||||
(rect.width() - 40.0).max(0.0),
|
||||
);
|
||||
if resp.on_hover_text(if l.visible { tl!("Hide layer") } else { tl!("Show layer") }).clicked() {
|
||||
toggle_layer = Some((li, !l.visible));
|
||||
}
|
||||
|
|
@ -841,7 +857,8 @@ fn outline_item(ui: &mut egui::Ui, t: &Tokens, info: &DocInfo, item: &OutlineIte
|
|||
let mut cancel = false;
|
||||
ui.horizontal(|ui| {
|
||||
ui.add_space(x_text);
|
||||
let edit = egui::TextEdit::singleline(text).desired_width(ui.available_width() - 8.0).id(id.with("rename"));
|
||||
crate::widgets::begin_dialog(ui);
|
||||
let edit = crate::widgets::line(text).desired_width(ui.available_width() - 8.0).id(id.with("rename"));
|
||||
let resp = ui.add(edit);
|
||||
if !resp.has_focus() && !resp.lost_focus() {
|
||||
resp.request_focus();
|
||||
|
|
@ -1202,10 +1219,16 @@ fn fields(
|
|||
ui.painter().rect_filled(rect, CornerRadius::same(6), if selected { t.pressed } else { t.hover });
|
||||
}
|
||||
icons::paint(ui, Rect::from_min_size(rect.min + vec2(8.0, 7.0), vec2(16.0, 16.0)), icon, 15.0, Color32::from_rgb(0x8E, 0x4E, 0xE6));
|
||||
ui.painter().text(rect.left_center() + vec2(32.0, 0.0), Align2::LEFT_CENTER, &f.name, theme::regular(13.0), t.text);
|
||||
if let Some(v) = &f.value {
|
||||
let v: String = if v.chars().count() > 18 { format!("{}…", v.chars().take(17).collect::<String>()) } else { v.clone() };
|
||||
ui.painter().text(rect.right_center() - vec2(8.0, 0.0), Align2::RIGHT_CENTER, v, theme::regular(11.5), t.text_faint);
|
||||
let value =
|
||||
f.value.as_ref().map(|v| if v.chars().count() > 18 { format!("{}…", v.chars().take(17).collect::<String>()) } else { v.clone() });
|
||||
let value_font = theme::regular(11.5);
|
||||
let value_w = value.as_ref().map(|v| crate::widgets::text_width(ui, v, &value_font)).unwrap_or(0.0);
|
||||
let arrows = if preparing && f.page.is_some() { 56.0 } else { 0.0 };
|
||||
let max_w = (rect.width() - 32.0 - value_w - arrows - 16.0).max(0.0);
|
||||
crate::widgets::paint_left(ui, rect.left_center() + vec2(32.0, 0.0), &f.name, theme::regular(13.0), t.text, max_w);
|
||||
if let Some(v) = value {
|
||||
let x = if arrows > 0.0 { 64.0 } else { 8.0 };
|
||||
ui.painter().text(rect.right_center() - vec2(x, 0.0), Align2::RIGHT_CENTER, v, value_font, t.text_faint);
|
||||
}
|
||||
let kind = format!("{:?}", f.kind);
|
||||
let mut tip = crate::i18n::fmt(tl!("{kind} field"), &[("kind", tl!(&kind))]);
|
||||
|
|
|
|||
|
|
@ -933,7 +933,7 @@ pub(crate) fn body(ui: &mut egui::Ui, d: &mut FieldDraft, t: &crate::theme::Toke
|
|||
ui.set_width(600.0);
|
||||
ui.label(egui::RichText::new(tl!(d.title())).font(theme::semibold(18.0)));
|
||||
ui.add_space(6.0);
|
||||
ui.horizontal(|ui| {
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
for (tab, label) in d.tabs() {
|
||||
if widgets::mode_tab(ui, tl!(label), d.tab == tab).clicked() {
|
||||
d.tab = tab;
|
||||
|
|
@ -956,11 +956,11 @@ pub(crate) fn body(ui: &mut egui::Ui, d: &mut FieldDraft, t: &crate::theme::Toke
|
|||
FieldTab::General => {
|
||||
egui::Grid::new("field-general").num_columns(2).spacing([12.0, 10.0]).show(ui, |ui| {
|
||||
let l = ui.label(tl!("Name:"));
|
||||
let r = ui.add(egui::TextEdit::singleline(&mut d.name).desired_width(340.0)).labelled_by(l.id);
|
||||
let r = ui.add(crate::widgets::line(&mut d.name).desired_width(340.0)).labelled_by(l.id);
|
||||
enter |= r.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter));
|
||||
ui.end_row();
|
||||
let l = ui.label(tl!("Tooltip:"));
|
||||
ui.add(egui::TextEdit::singleline(&mut d.tooltip).desired_width(340.0)).labelled_by(l.id);
|
||||
ui.add(crate::widgets::line(&mut d.tooltip).desired_width(340.0)).labelled_by(l.id);
|
||||
ui.end_row();
|
||||
});
|
||||
ui.add_space(12.0);
|
||||
|
|
@ -1090,7 +1090,7 @@ pub(crate) fn body(ui: &mut egui::Ui, d: &mut FieldDraft, t: &crate::theme::Toke
|
|||
});
|
||||
ui.end_row();
|
||||
let l = ui.label(tl!("Default Value:"));
|
||||
ui.add(egui::TextEdit::singleline(&mut d.default).desired_width(260.0)).labelled_by(l.id);
|
||||
ui.add(crate::widgets::line(&mut d.default).desired_width(260.0)).labelled_by(l.id);
|
||||
ui.end_row();
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
|
|
@ -1150,7 +1150,7 @@ pub(crate) fn body(ui: &mut egui::Ui, d: &mut FieldDraft, t: &crate::theme::Toke
|
|||
_ => {
|
||||
ui.horizontal(|ui| {
|
||||
let l = ui.label(tl!("Item:"));
|
||||
let r = ui.add(egui::TextEdit::singleline(&mut d.new_option).desired_width(240.0)).labelled_by(l.id);
|
||||
let r = ui.add(crate::widgets::line(&mut d.new_option).desired_width(240.0)).labelled_by(l.id);
|
||||
let typed = r.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter));
|
||||
if (widgets::pill_button(ui, tl!("Add"), false).clicked() || typed) && !d.new_option.trim().is_empty() {
|
||||
d.options.push(d.new_option.trim().to_string());
|
||||
|
|
@ -1363,7 +1363,7 @@ fn format_tab(ui: &mut egui::Ui, d: &mut FieldDraft, t: &crate::theme::Tokens) {
|
|||
}
|
||||
});
|
||||
ui.label(tl!("Custom:"));
|
||||
ui.add(egui::TextEdit::singleline(p).desired_width(120.0));
|
||||
ui.add(crate::widgets::line(p).desired_width(120.0));
|
||||
});
|
||||
}
|
||||
Format::Special(n) => {
|
||||
|
|
@ -1388,7 +1388,7 @@ fn format_tab(ui: &mut egui::Ui, d: &mut FieldDraft, t: &crate::theme::Tokens) {
|
|||
});
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(tl!("Mask:"));
|
||||
ui.add(egui::TextEdit::singleline(m).desired_width(160.0));
|
||||
ui.add(crate::widgets::line(m).desired_width(160.0));
|
||||
});
|
||||
ui.label(egui::RichText::new(tl!("9 digit, A letter, O letter or digit, X any character")).small().color(t.text_faint));
|
||||
if idx < 4 {
|
||||
|
|
@ -1461,9 +1461,9 @@ fn actions_tab(ui: &mut egui::Ui, d: &mut FieldDraft, t: &crate::theme::Tokens)
|
|||
};
|
||||
ui.label(if a.kind == 0 { tl!("Script:") } else { tl!("Value:") });
|
||||
if a.kind == 0 {
|
||||
ui.add(egui::TextEdit::multiline(&mut a.text).code_editor().desired_rows(3).desired_width(340.0).hint_text(hint).id_salt("action-text"));
|
||||
ui.add(crate::widgets::block(&mut a.text).code_editor().desired_rows(3).desired_width(340.0).hint_text(hint).id_salt("action-text"));
|
||||
} else {
|
||||
ui.add(egui::TextEdit::singleline(&mut a.text).desired_width(340.0).hint_text(hint).id_salt("action-text"));
|
||||
ui.add(crate::widgets::line(&mut a.text).desired_width(340.0).hint_text(hint).id_salt("action-text"));
|
||||
}
|
||||
ui.end_row();
|
||||
});
|
||||
|
|
@ -1561,7 +1561,7 @@ fn calculate_tab(ui: &mut egui::Ui, d: &mut FieldDraft, t: &crate::theme::Tokens
|
|||
d.calculate = Calculate::Notation(String::new());
|
||||
}
|
||||
if let Calculate::Notation(expr) = &mut d.calculate {
|
||||
ui.add(egui::TextEdit::multiline(expr).hint_text("Price * Quantity").desired_rows(2).desired_width(420.0));
|
||||
ui.add(crate::widgets::block(expr).hint_text("Price * Quantity").desired_rows(2).desired_width(420.0));
|
||||
ui.label(egui::RichText::new(tl!("Field names with + - * / and parentheses; put \\ before spaces in names.")).small().color(t.text_faint));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -644,7 +644,7 @@ pub(crate) fn body(
|
|||
ui.radio_value(&mut d.which, Which::Range, tl!("Pages"));
|
||||
let r = ui.add_enabled(
|
||||
d.which == Which::Range,
|
||||
egui::TextEdit::singleline(&mut d.range).hint_text(format!("1-{}", sizes.len())).desired_width(110.0),
|
||||
crate::widgets::line(&mut d.range).hint_text(format!("1-{}", sizes.len())).desired_width(110.0),
|
||||
);
|
||||
if r.gained_focus() {
|
||||
d.which = Which::Range;
|
||||
|
|
@ -668,7 +668,7 @@ pub(crate) fn body(
|
|||
});
|
||||
ui.add_space(6.0);
|
||||
widgets::section_title(ui, tl!("Page Sizing & Handling"));
|
||||
ui.horizontal(|ui| {
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
for (h, label) in [
|
||||
(Handling::Size, tl!("Size")),
|
||||
(Handling::Poster, tl!("Poster")),
|
||||
|
|
|
|||
|
|
@ -107,17 +107,9 @@ pub(crate) fn body(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tokens) -> (boo
|
|||
d.viewing = false;
|
||||
}
|
||||
ui.add_space(10.0);
|
||||
// Bordered fields (Acrobat: 1 pt #B1B1B1, radius 4).
|
||||
let field = |ui: &mut egui::Ui, label: &str, text: &mut String, id: &str| {
|
||||
let l = ui.label(tl!(label));
|
||||
egui::Frame::new()
|
||||
.stroke(egui::Stroke::new(1.0, t.border))
|
||||
.corner_radius(egui::CornerRadius::same(4))
|
||||
.inner_margin(egui::Margin::symmetric(8, 6))
|
||||
.show(ui, |ui| {
|
||||
ui.add(egui::TextEdit::singleline(text).password(true).desired_width(290.0).frame(egui::Frame::NONE).id_salt(id)).labelled_by(l.id)
|
||||
})
|
||||
.inner
|
||||
ui.add(crate::widgets::line(text).password(true).desired_width(290.0).id_salt(id)).labelled_by(l.id)
|
||||
};
|
||||
let r = field(ui, "Type Password", &mut d.password, "protect-pw");
|
||||
if r.changed() || !d.password.is_empty() {
|
||||
|
|
|
|||
|
|
@ -269,7 +269,7 @@ pub(crate) fn search_body(ui: &mut egui::Ui, d: &mut SearchDraft, t: &Tokens) ->
|
|||
ui.radio_value(&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(egui::TextEdit::singleline(&mut d.text).hint_text(tl!("Text to find")).desired_width(380.0));
|
||||
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);
|
||||
|
|
@ -338,7 +338,7 @@ pub(crate) fn props_body(ui: &mut egui::Ui, d: &mut RedactPrefs, _t: &Tokens) ->
|
|||
if l.clicked() {
|
||||
d.use_code = false;
|
||||
}
|
||||
ui.add_enabled(on && !d.use_code, egui::TextEdit::singleline(&mut d.overlay).desired_width(220.0)).labelled_by(l.id);
|
||||
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() {
|
||||
d.use_code = true;
|
||||
|
|
|
|||
|
|
@ -11,14 +11,13 @@ use crate::theme::{self, Tokens};
|
|||
const CONTEXT: usize = 36;
|
||||
|
||||
pub(crate) fn panel(ui: &mut egui::Ui, t: &Tokens, view: &mut DocView, pages: usize) {
|
||||
crate::widgets::begin_dialog(ui);
|
||||
let searched = view.texts.len() + view.text_failed.len();
|
||||
let Some(find) = view.find.as_mut() else { return };
|
||||
find.in_panel = true;
|
||||
let l = ui.label(egui::RichText::new(tl!("What word or phrase would you like to search for?")).color(t.text_muted));
|
||||
let r = ui
|
||||
.add(
|
||||
egui::TextEdit::singleline(&mut find.query).id(egui::Id::new("search-panel-input")).hint_text(tl!("Search")).desired_width(f32::INFINITY),
|
||||
)
|
||||
.add(crate::widgets::line(&mut find.query).id(egui::Id::new("search-panel-input")).hint_text(tl!("Search")).desired_width(f32::INFINITY))
|
||||
.labelled_by(l.id);
|
||||
if find.focus {
|
||||
r.request_focus();
|
||||
|
|
|
|||
|
|
@ -665,11 +665,11 @@ fn configure(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tokens) -> bool {
|
|||
(tl!("Email Address"), &mut n.email),
|
||||
] {
|
||||
let l = ui.label(label);
|
||||
ui.add(egui::TextEdit::singleline(value).desired_width(260.0)).labelled_by(l.id);
|
||||
ui.add(crate::widgets::line(value).desired_width(260.0)).labelled_by(l.id);
|
||||
ui.end_row();
|
||||
}
|
||||
let l = ui.label(tl!("Country/Region"));
|
||||
ui.add(egui::TextEdit::singleline(&mut n.country).hint_text("US").char_limit(2).desired_width(60.0)).labelled_by(l.id);
|
||||
ui.add(crate::widgets::line(&mut n.country).hint_text("US").char_limit(2).desired_width(60.0)).labelled_by(l.id);
|
||||
ui.end_row();
|
||||
ui.label(tl!("Key Algorithm"));
|
||||
egui::ComboBox::from_id_salt("key-alg").selected_text(KEY_ALGORITHMS[n.key].0).show_ui(ui, |ui| {
|
||||
|
|
@ -679,10 +679,10 @@ fn configure(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tokens) -> bool {
|
|||
});
|
||||
ui.end_row();
|
||||
let l = ui.label(tl!("Password"));
|
||||
ui.add(egui::TextEdit::singleline(&mut n.password).password(true).desired_width(200.0)).labelled_by(l.id);
|
||||
ui.add(crate::widgets::line(&mut n.password).password(true).desired_width(200.0)).labelled_by(l.id);
|
||||
ui.end_row();
|
||||
let l = ui.label(tl!("Confirm Password"));
|
||||
ui.add(egui::TextEdit::singleline(&mut n.confirm).password(true).desired_width(200.0)).labelled_by(l.id);
|
||||
ui.add(crate::widgets::line(&mut n.confirm).password(true).desired_width(200.0)).labelled_by(l.id);
|
||||
ui.end_row();
|
||||
});
|
||||
ui.label(
|
||||
|
|
@ -694,7 +694,7 @@ fn configure(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tokens) -> bool {
|
|||
egui::Grid::new("id-file").num_columns(2).spacing([12.0, 8.0]).show(ui, |ui| {
|
||||
let l = ui.label(tl!("File (.p12, .pfx)"));
|
||||
ui.horizontal(|ui| {
|
||||
ui.add(egui::TextEdit::singleline(&mut n.file).desired_width(220.0)).labelled_by(l.id);
|
||||
ui.add(crate::widgets::line(&mut n.file).desired_width(220.0)).labelled_by(l.id);
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
if ui.button(tl!("Browse…")).clicked() {
|
||||
browse = true;
|
||||
|
|
@ -702,7 +702,7 @@ fn configure(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tokens) -> bool {
|
|||
});
|
||||
ui.end_row();
|
||||
let l = ui.label(tl!("Password"));
|
||||
ui.add(egui::TextEdit::singleline(&mut n.file_password).password(true).desired_width(200.0)).labelled_by(l.id);
|
||||
ui.add(crate::widgets::line(&mut n.file_password).password(true).desired_width(200.0)).labelled_by(l.id);
|
||||
ui.end_row();
|
||||
});
|
||||
}
|
||||
|
|
@ -881,10 +881,10 @@ fn sign_as(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tokens) -> bool {
|
|||
ui.end_row();
|
||||
}
|
||||
let l = ui.label(tl!("Reason"));
|
||||
ui.add(egui::TextEdit::singleline(&mut d.reason).hint_text(tl!("Optional")).desired_width(260.0)).labelled_by(l.id);
|
||||
ui.add(crate::widgets::line(&mut d.reason).hint_text(tl!("Optional")).desired_width(260.0)).labelled_by(l.id);
|
||||
ui.end_row();
|
||||
let l = ui.label(tl!("Location"));
|
||||
ui.add(egui::TextEdit::singleline(&mut d.location).hint_text(tl!("Optional")).desired_width(260.0)).labelled_by(l.id);
|
||||
ui.add(crate::widgets::line(&mut d.location).hint_text(tl!("Optional")).desired_width(260.0)).labelled_by(l.id);
|
||||
ui.end_row();
|
||||
if in_os_key_store {
|
||||
ui.label("");
|
||||
|
|
@ -899,7 +899,7 @@ fn sign_as(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tokens) -> bool {
|
|||
);
|
||||
} else {
|
||||
let l = ui.label(tl!("Digital ID password"));
|
||||
let r = ui.add(egui::TextEdit::singleline(&mut d.password).password(true).desired_width(200.0)).labelled_by(l.id);
|
||||
let r = ui.add(crate::widgets::line(&mut d.password).password(true).desired_width(200.0)).labelled_by(l.id);
|
||||
if r.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter)) {
|
||||
go = true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ pub(crate) fn create_body(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tokens)
|
|||
egui::Grid::new("stamp-create").num_columns(2).spacing([10.0, 8.0]).show(ui, |ui| {
|
||||
let l = ui.label(tl!("Category:"));
|
||||
ui.horizontal(|ui| {
|
||||
ui.add(egui::TextEdit::singleline(&mut d.category).desired_width(200.0).hint_text(tl!("e.g. My stamps"))).labelled_by(l.id);
|
||||
ui.add(crate::widgets::line(&mut d.category).desired_width(200.0).hint_text(tl!("e.g. My stamps"))).labelled_by(l.id);
|
||||
if !categories.is_empty() {
|
||||
egui::ComboBox::from_id_salt("stamp-categories").selected_text("").width(24.0).show_ui(ui, |ui| {
|
||||
for c in &categories {
|
||||
|
|
@ -78,7 +78,7 @@ pub(crate) fn create_body(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tokens)
|
|||
});
|
||||
ui.end_row();
|
||||
let l = ui.label(tl!("Name:"));
|
||||
ui.add(egui::TextEdit::singleline(&mut d.name).desired_width(200.0)).labelled_by(l.id);
|
||||
ui.add(crate::widgets::line(&mut d.name).desired_width(200.0)).labelled_by(l.id);
|
||||
ui.end_row();
|
||||
});
|
||||
ui.add_space(12.0);
|
||||
|
|
|
|||
|
|
@ -116,7 +116,7 @@ pub(crate) fn dialog(app: &mut PdfCraftApp, ctx: &egui::Context) {
|
|||
let current = env!("CARGO_PKG_VERSION");
|
||||
let mut close = false;
|
||||
let mut download: Option<String> = None;
|
||||
let modal = egui::Modal::new(egui::Id::new("updates")).show(ctx, |ui| {
|
||||
let modal = crate::widgets::show_modal(ctx, "updates", |ui| {
|
||||
ui.set_width(420.0);
|
||||
ui.horizontal(|ui| {
|
||||
ui.add(crate::icons::image("cloud", 22.0, t.accent));
|
||||
|
|
|
|||
|
|
@ -5,6 +5,104 @@ use egui::{Align2, Color32, CornerRadius, Rect, Response, Sense, Stroke, vec2};
|
|||
use crate::theme::{self, Tokens};
|
||||
use crate::{PdfCraftApp, icons};
|
||||
|
||||
/// Horizontal and vertical inset of a single-line field.
|
||||
const FIELD_MARGIN: egui::Margin = egui::Margin::symmetric(10, 6);
|
||||
/// Outer height of a single-line field, including its inset.
|
||||
const FIELD_MIN_HEIGHT: f32 = 30.0;
|
||||
/// Padding inside every modal.
|
||||
const DIALOG_PAD_X: i8 = 20;
|
||||
const DIALOG_PAD_Y: i8 = 16;
|
||||
|
||||
fn dialog_frame(ctx: &egui::Context) -> egui::Frame {
|
||||
let t = Tokens::get(ctx);
|
||||
egui::Frame::new()
|
||||
.fill(t.card)
|
||||
.stroke(Stroke::new(1.0, t.border))
|
||||
.corner_radius(CornerRadius::same(10))
|
||||
.inner_margin(egui::Margin::symmetric(DIALOG_PAD_X, DIALOG_PAD_Y))
|
||||
.shadow(ctx.global_style().visuals.window_shadow)
|
||||
}
|
||||
|
||||
/// A modal with the card fill, a 10-point corner and room around its contents.
|
||||
pub fn modal(ctx: &egui::Context, id: impl std::hash::Hash + std::fmt::Debug) -> egui::Modal {
|
||||
egui::Modal::new(egui::Id::new(id)).frame(dialog_frame(ctx))
|
||||
}
|
||||
|
||||
/// Show [`modal`], outline its controls, and remember the card rect for layout checks.
|
||||
pub fn show_modal<R>(ctx: &egui::Context, id: &str, content: impl FnOnce(&mut egui::Ui) -> R) -> egui::ModalResponse<R> {
|
||||
let stored = egui::Id::new(id);
|
||||
modal(ctx, id).show(ctx, |ui| {
|
||||
begin_dialog(ui);
|
||||
let inner = content(ui);
|
||||
// The frame's stroke sits outside this content rect; the stored card is the fill.
|
||||
let card = ui.min_rect().expand2(egui::vec2(f32::from(DIALOG_PAD_X), f32::from(DIALOG_PAD_Y)));
|
||||
ui.ctx().data_mut(|d| d.insert_temp(stored.with("card"), card));
|
||||
inner
|
||||
})
|
||||
}
|
||||
|
||||
/// Outlined controls at a shared 30-point height, with a focus stroke and 4-point corners.
|
||||
///
|
||||
/// Dialog fills match the theme's field colour, so radio buttons, check boxes, combo boxes
|
||||
/// and number fields need the border or they disappear into the card.
|
||||
pub fn begin_dialog(ui: &mut egui::Ui) {
|
||||
let t = Tokens::get(ui.ctx());
|
||||
let w = &mut ui.visuals_mut().widgets;
|
||||
w.inactive.bg_stroke = Stroke::new(1.0, t.border);
|
||||
w.inactive.weak_bg_fill = t.field;
|
||||
// Slider rails and check-box interiors use the plain fill.
|
||||
w.inactive.bg_fill = t.hover;
|
||||
w.hovered.bg_stroke = Stroke::new(1.0, t.text_muted);
|
||||
for state in [&mut w.inactive, &mut w.hovered, &mut w.active, &mut w.open] {
|
||||
state.corner_radius = CornerRadius::same(4);
|
||||
}
|
||||
ui.visuals_mut().selection.stroke = Stroke::new(1.5, t.accent);
|
||||
ui.spacing_mut().interact_size.y = FIELD_MIN_HEIGHT;
|
||||
}
|
||||
|
||||
/// A single-line field tall enough to read and to click.
|
||||
pub fn line(text: &mut dyn egui::TextBuffer) -> egui::TextEdit<'_> {
|
||||
egui::TextEdit::singleline(text).margin(FIELD_MARGIN).min_size(vec2(0.0, FIELD_MIN_HEIGHT))
|
||||
}
|
||||
|
||||
/// A multiline field with the same horizontal inset as [`line`].
|
||||
pub fn block(text: &mut dyn egui::TextBuffer) -> egui::TextEdit<'_> {
|
||||
egui::TextEdit::multiline(text).margin(egui::Margin::symmetric(10, 6))
|
||||
}
|
||||
|
||||
/// One line that stays within `max_width`, cut on a character boundary with an ellipsis.
|
||||
/// Only the first 160 characters are measured, so a huge name cannot lay out thousands of candidates.
|
||||
pub(crate) fn fit_line(text: &str, max_width: f32, mut width_of: impl FnMut(&str) -> f32) -> String {
|
||||
if !max_width.is_finite() || max_width <= 0.0 {
|
||||
return String::new();
|
||||
}
|
||||
let mut s: String = text.chars().take(160).collect();
|
||||
if s.len() == text.len() && width_of(text) <= max_width {
|
||||
return text.to_owned();
|
||||
}
|
||||
loop {
|
||||
s.pop();
|
||||
if s.is_empty() {
|
||||
return if width_of("\u{2026}") <= max_width { "\u{2026}".into() } else { String::new() };
|
||||
}
|
||||
let candidate = format!("{s}\u{2026}");
|
||||
if width_of(&candidate) <= max_width {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn text_width(ui: &egui::Ui, text: &str, font: &egui::FontId) -> f32 {
|
||||
ui.fonts_mut(|f| f.layout_no_wrap(text.to_owned(), font.clone(), Color32::WHITE).size().x)
|
||||
}
|
||||
|
||||
/// Draw `text` from `pos` (left, vertically centered) so it never runs past `max_width`.
|
||||
pub(crate) fn paint_left(ui: &egui::Ui, pos: egui::Pos2, text: &str, font: egui::FontId, color: Color32, max_width: f32) {
|
||||
let shown = fit_line(text, max_width, |s| text_width(ui, s, &font));
|
||||
let clip = Rect::from_min_size(egui::pos2(pos.x, pos.y - 14.0), vec2(max_width.max(0.0), 28.0));
|
||||
ui.painter().with_clip_rect(clip).text(pos, Align2::LEFT_CENTER, shown, font, color);
|
||||
}
|
||||
|
||||
/// A mode-bar tab: text with an underline when active.
|
||||
pub fn mode_tab(ui: &mut egui::Ui, label: &str, active: bool) -> Response {
|
||||
let t = Tokens::get(ui.ctx());
|
||||
|
|
@ -380,4 +478,30 @@ mod tests {
|
|||
let again = toast_rect(&mut app, &ctx, 1280.0, &mut now);
|
||||
assert!((again.width() - short.width()).abs() < 1.0, "long notices must not impose a permanent minimum width");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn line_field_is_at_least_thirty_points_tall() {
|
||||
let ctx = egui::Context::default();
|
||||
let mut text = String::new();
|
||||
let mut height = 0.0;
|
||||
ctx.run_ui(egui::RawInput { screen_rect: Some(Rect::from_min_size(Pos2::ZERO, vec2(400.0, 200.0))), ..Default::default() }, |ui| {
|
||||
begin_dialog(ui);
|
||||
height = ui.add(line(&mut text)).rect.height();
|
||||
})
|
||||
.drop_without_applying_deltas();
|
||||
assert!(height >= FIELD_MIN_HEIGHT, "field height {height}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fit_line_keeps_short_text_and_ellipsizes_the_rest() {
|
||||
let width = |s: &str| s.chars().count() as f32;
|
||||
assert_eq!(fit_line("localhost", 20.0, width), "localhost");
|
||||
let long = "Apple Worldwide Developer Relations Certification Authority";
|
||||
let fitted = fit_line(long, 24.0, width);
|
||||
assert!(fitted.ends_with('\u{2026}'), "{fitted}");
|
||||
assert!(fitted.chars().count() <= 24, "{fitted}");
|
||||
assert!(long.starts_with(fitted.trim_end_matches('\u{2026}')));
|
||||
assert_eq!(fit_line(long, 0.0, width), "");
|
||||
assert_eq!(fit_line("ééé", 2.0, width), "é\u{2026}");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -785,6 +785,11 @@ fn hovering_a_comment_shows_its_author_and_text() {
|
|||
h.run_steps(3);
|
||||
h.get_by_label_contains("Check this figure");
|
||||
h.get_by_label_contains("Ada");
|
||||
// The tip is drawn above every window. A dialog must hide it, or the author floats over the dialog.
|
||||
h.state_mut().set_option("dialog", "shortcuts").unwrap();
|
||||
h.hover_at(p);
|
||||
h.run_steps(3);
|
||||
assert!(h.query_by_label_contains("Check this figure").is_none(), "a dialog covers the page hover");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,83 @@
|
|||
//! Dialog cards keep their padding, and text fields are tall enough to use.
|
||||
|
||||
use egui::accesskit::Role;
|
||||
use egui_kittest::Harness;
|
||||
use egui_kittest::kittest::Queryable;
|
||||
use pdfcraft_ui_egui::PdfCraftApp;
|
||||
|
||||
fn fixture() -> Vec<u8> {
|
||||
let objs = [
|
||||
"<< /Type /Catalog /Pages 2 0 R >>",
|
||||
"<< /Type /Pages /Kids [3 0 R] /Count 1 /MediaBox [0 0 200 300] >>",
|
||||
"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 200 300] >>",
|
||||
];
|
||||
let mut out = b"%PDF-1.7\n".to_vec();
|
||||
let mut offsets = Vec::new();
|
||||
for (i, o) in objs.iter().enumerate() {
|
||||
offsets.push(out.len());
|
||||
out.extend_from_slice(format!("{} 0 obj\n{o}\nendobj\n", i + 1).as_bytes());
|
||||
}
|
||||
let xref = out.len();
|
||||
out.extend_from_slice(format!("xref\n0 {}\n0000000000 65535 f \n", objs.len() + 1).as_bytes());
|
||||
for o in offsets {
|
||||
out.extend_from_slice(format!("{o:010} 00000 n \n").as_bytes());
|
||||
}
|
||||
out.extend_from_slice(format!("trailer\n<< /Size {} /Root 1 0 R >>\nstartxref\n{xref}\n%%EOF\n", objs.len() + 1).as_bytes());
|
||||
out
|
||||
}
|
||||
|
||||
fn card(h: &Harness<'_, PdfCraftApp>, id: &str) -> egui::Rect {
|
||||
h.ctx.data(|d| d.get_temp(egui::Id::new(id).with("card"))).unwrap_or_else(|| panic!("missing dialog card {id}"))
|
||||
}
|
||||
|
||||
fn protected() -> Vec<u8> {
|
||||
let mut doc = pdfcraft_cos::Document::open(std::sync::Arc::new(fixture())).unwrap();
|
||||
doc.set_encryption(&pdfcraft_cos::NewEncryption {
|
||||
algorithm: pdfcraft_cos::Algorithm::Aes256,
|
||||
user_password: "pw",
|
||||
owner_password: "owner",
|
||||
permissions: -1,
|
||||
encrypt_metadata: true,
|
||||
seed: [4; 32],
|
||||
})
|
||||
.unwrap();
|
||||
pdfcraft_cos::write_full(&doc, &Default::default()).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn document_properties_fields_are_tall_and_inset() {
|
||||
let mut h = Harness::builder().with_size(egui::vec2(1400.0, 900.0)).build_eframe(|_cc| {
|
||||
let mut app = PdfCraftApp::new();
|
||||
app.set_option("language", "en").unwrap();
|
||||
app.open_bytes("doc.pdf", None, fixture()).unwrap();
|
||||
app.set_option("dialog", "properties").unwrap();
|
||||
app
|
||||
});
|
||||
h.run_steps(4);
|
||||
let card = card(&h, "dialog");
|
||||
let heading = h.get_by_label("Document Properties").rect();
|
||||
assert!(heading.left() - card.left() >= 16.0, "heading inset {heading:?} in {card:?}");
|
||||
assert!(heading.top() - card.top() >= 16.0, "heading inset {heading:?} in {card:?}");
|
||||
let field = h.get_by_role_and_label(Role::TextInput, "Title").rect();
|
||||
assert!(field.height() >= 30.0, "title field {field:?}");
|
||||
assert!(field.left() >= card.left() + 16.0, "title field {field:?} in {card:?}");
|
||||
assert!(field.right() <= card.right() - 16.0, "title field {field:?} in {card:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn password_prompt_field_is_tall_and_inset() {
|
||||
let mut h = Harness::builder().with_size(egui::vec2(1400.0, 900.0)).build_eframe(|_cc| {
|
||||
let mut app = PdfCraftApp::new();
|
||||
app.set_option("language", "en").unwrap();
|
||||
app.open_bytes("secret.pdf", None, protected()).unwrap();
|
||||
app
|
||||
});
|
||||
h.run_steps(4);
|
||||
let card = card(&h, "password");
|
||||
let field = h.get_by_role(Role::PasswordInput).rect();
|
||||
assert!(field.height() >= 30.0, "password field {field:?}");
|
||||
assert!(field.left() - card.left() >= 16.0, "password field {field:?} in {card:?}");
|
||||
assert!(card.right() - field.right() >= 16.0, "password field {field:?} in {card:?}");
|
||||
let heading = h.get_by_label("Password required").rect();
|
||||
assert!(heading.top() - card.top() >= 16.0, "heading inset {heading:?} in {card:?}");
|
||||
}
|
||||
|
|
@ -181,6 +181,9 @@ fn certifying_without_a_visible_signature() {
|
|||
h.get_by_label("Continue").click();
|
||||
h.run_steps(2);
|
||||
assert_eq!(h.state().digital_ids.len(), 1);
|
||||
// The dialog shrinks onto this step. egui centers an area from the previous frame's size,
|
||||
// so give the buttons a frame to land before clicking.
|
||||
h.run_steps(2);
|
||||
h.get_by_label("Continue").click();
|
||||
h.run_steps(2);
|
||||
h.get_by_label("Certify as \"Test Signer EC\"");
|
||||
|
|
|
|||
|
|
@ -281,6 +281,8 @@ fn initial_view_is_edited_and_honoured_on_open() {
|
|||
v.language = Some("de-DE".into());
|
||||
}
|
||||
h.run_steps(2);
|
||||
// The dialog's height settles a frame after it opens; egui centers from the previous size.
|
||||
h.run_steps(2);
|
||||
h.get_by_label("OK").click();
|
||||
h.run_steps(3);
|
||||
let s = h.state();
|
||||
|
|
@ -404,6 +406,8 @@ fn an_earlier_revision_opens_from_document_properties() {
|
|||
h.state_mut().dialog = Some(Dialog::Properties(pdfcraft_ui_egui::PropsTab::Advanced));
|
||||
h.run_steps(2);
|
||||
h.run_steps(2);
|
||||
// Let the dialog finish centering before the revision button is clicked.
|
||||
h.run_steps(2);
|
||||
h.get_by_label("View revision 1").click();
|
||||
h.run_steps(3);
|
||||
let s = h.state();
|
||||
|
|
|
|||
Loading…
Reference in New Issue