From ac2cc0c4ef3fd77ab5dd36d9ea1c20a9fc3c97e6 Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Wed, 28 Oct 2026 01:06:48 +0000 Subject: [PATCH] Refactor ui ribbon for strict Clippy checks --- crates/ui-egui/src/ribbon/arrange.rs | 10 +- crates/ui-egui/src/ribbon/charts.rs | 48 ++-- crates/ui-egui/src/ribbon/common.rs | 4 +- crates/ui-egui/src/ribbon/design.rs | 128 ++++++---- crates/ui-egui/src/ribbon/draw.rs | 8 +- crates/ui-egui/src/ribbon/home.rs | 300 +++++++++++++++--------- crates/ui-egui/src/ribbon/insert.rs | 118 ++++++---- crates/ui-egui/src/ribbon/language.rs | 2 +- crates/ui-egui/src/ribbon/layout.rs | 112 ++++++--- crates/ui-egui/src/ribbon/mailings.rs | 98 +++++--- crates/ui-egui/src/ribbon/picture.rs | 168 +++++++------ crates/ui-egui/src/ribbon/references.rs | 148 +++++++++--- crates/ui-egui/src/ribbon/review.rs | 91 +++++-- crates/ui-egui/src/ribbon/shape.rs | 119 ++++++---- crates/ui-egui/src/ribbon/table.rs | 171 ++++++++++---- crates/ui-egui/src/ribbon/tabs.rs | 2 +- crates/ui-egui/src/ribbon/underline.rs | 2 +- crates/ui-egui/src/ribbon/view.rs | 85 ++++--- 18 files changed, 1078 insertions(+), 536 deletions(-) diff --git a/crates/ui-egui/src/ribbon/arrange.rs b/crates/ui-egui/src/ribbon/arrange.rs index efd442564..998d44905 100644 --- a/crates/ui-egui/src/ribbon/arrange.rs +++ b/crates/ui-egui/src/ribbon/arrange.rs @@ -1,6 +1,6 @@ use super::common::mi; use super::language::mi_check; -use super::*; +use super::{Ui, WordApp, json, menu_button}; /// Word's text wrapping styles in menu order: label, model value, `arrange.wrap` parameter. pub(crate) const WRAP_STYLES: [(&str, wordcraft_doc::para::Wrap, &str); 7] = { @@ -40,7 +40,7 @@ pub(super) fn wrap_menu(ui: &mut Ui, app: &mut WordApp) { let (wrap, side) = wordcraft_engine::cmd::objects::selected(&app.session) .and_then(|(_, o)| o.frame().map(|(_, _, f)| (f.wrap, f.wrap_text))) .unwrap_or_default(); - menu_button(ui, app, "wrapText", Some("Wrap\nText"), "Wrap Text", true, "arrange.wrapText", |ui, app| { + menu_button(ui, app, crate::widgets::MenuButtonOptions::new("wrapText", Some("Wrap\nText"), "Wrap Text", true, "arrange.wrapText"), |ui, app| { use wordcraft_doc::para::{Wrap, WrapText}; for (l, w, v) in [ ("In Line with Text", Wrap::Inline, "inline"), @@ -68,7 +68,7 @@ pub(super) fn wrap_menu(ui: &mut Ui, app: &mut WordApp) { } pub(super) fn rotate_menu(ui: &mut Ui, app: &mut WordApp, label: Option<&str>) { - menu_button(ui, app, "rotate", label, "Rotate", false, "arrange.rotate", |ui, app| { + menu_button(ui, app, crate::widgets::MenuButtonOptions::new("rotate", label, "Rotate", false, "arrange.rotate"), |ui, app| { mi(ui, app, "Rotate Right 90°", "arrange.rotate", json!({"direction": "right"})); mi(ui, app, "Rotate Left 90°", "arrange.rotate", json!({"direction": "left"})); mi(ui, app, "Flip Vertical", "arrange.rotate", json!({"direction": "flipVertical"})); @@ -92,7 +92,7 @@ pub(super) fn rotation_field(ui: &mut Ui, app: &mut WordApp) { /// Arrange › Group: Group (Shift+click objects to select several) and Ungroup. pub(super) fn group_menu(ui: &mut Ui, app: &mut WordApp, label: Option<&str>) { - menu_button(ui, app, "group", label, "Group", false, "arrange.group", |ui, app| { + menu_button(ui, app, crate::widgets::MenuButtonOptions::new("group", label, "Group", false, "arrange.group"), |ui, app| { mi(ui, app, "Group", "arrange.group", json!({})); mi(ui, app, "Ungroup", "arrange.ungroup", json!({})); }); @@ -101,7 +101,7 @@ pub(super) fn group_menu(ui: &mut Ui, app: &mut WordApp, label: Option<&str>) { /// Arrange › Align: horizontal position relative to the margin (#3242 — a plain button ran /// `arrange.align` with no `value`, which always centers; a menu reaches Left and Right too). pub(super) fn align_menu(ui: &mut Ui, app: &mut WordApp, label: Option<&str>) { - menu_button(ui, app, "align", label, "Align", false, "arrange.align", |ui, app| { + menu_button(ui, app, crate::widgets::MenuButtonOptions::new("align", label, "Align", false, "arrange.align"), |ui, app| { mi(ui, app, "Align Left", "arrange.align", json!({"value": "left"})); mi(ui, app, "Align Center", "arrange.align", json!({"value": "center"})); mi(ui, app, "Align Right", "arrange.align", json!({"value": "right"})); diff --git a/crates/ui-egui/src/ribbon/charts.rs b/crates/ui-egui/src/ribbon/charts.rs index b0a7d6304..82324300f 100644 --- a/crates/ui-egui/src/ribbon/charts.rs +++ b/crates/ui-egui/src/ribbon/charts.rs @@ -1,6 +1,6 @@ use super::common::{graphic_size, stack}; use super::language::mi_check; -use super::*; +use super::{Rect, Sense, Stroke, Tokens, Ui, WordApp, big, group, json, menu_button, pos2, small, vec2}; /// SmartArt Design: the selected graphic's items (Add Shape, Promote, Demote, Text Pane), its /// layout (a gallery of previews), its colours and Reset. @@ -14,9 +14,16 @@ pub(super) fn smart_art_design(app: &mut WordApp, ui: &mut Ui) { big(ui, app, "addShape", "Add\nShape", "smartArt.addShape", current.map_or(json!({}), |i| json!({"after": i})), false); stack(ui, |ui| { let idx = current.map_or(json!({}), |i| json!({"index": i})); - small(ui, app, "promote", Some("Promote"), "Promote", "smartArt.promote", idx.clone(), false); - small(ui, app, "demote", Some("Demote"), "Demote", "smartArt.demote", idx, false); - small(ui, app, "textPane", Some("Text Pane"), "Text Pane", "smartArt.textPane", json!({}), app.session.view.smart_art_pane); + small(ui, app, "promote", Some("Promote"), "Promote", crate::widgets::ButtonAction::new("smartArt.promote", idx.clone(), false)); + small(ui, app, "demote", Some("Demote"), "Demote", crate::widgets::ButtonAction::new("smartArt.demote", idx, false)); + small( + ui, + app, + "textPane", + Some("Text Pane"), + "Text Pane", + crate::widgets::ButtonAction::new("smartArt.textPane", json!({}), app.session.view.smart_art_pane), + ); }); }); group(ui, "Layouts", None, app, |ui, app| { @@ -44,18 +51,23 @@ pub(super) fn smart_art_design(app: &mut WordApp, ui: &mut Ui) { }); }); group(ui, "SmartArt Styles", None, app, |ui, app| { - menu_button(ui, app, "smartArtColors", Some("Change\nColors"), "Change Colors", true, "", |ui, app| { - for colors in SmartArtColors::ALL { - let on = g.colors == colors; - let resp = ui.add(egui::Button::new(crate::dialogs_smart_art::colors_label(colors)).selected(on).min_size(vec2(200.0, 24.0))); - let sw = Rect::from_min_size(pos2(resp.rect.right() - 74.0, resp.rect.center().y - 6.0), vec2(66.0, 12.0)); - crate::dialogs_smart_art::swatches(ui.painter(), sw, colors, &theme); - if resp.clicked() { - let _ = app.run("smartArt.colors", json!({"variant": colors.id()})); - ui.close(); + menu_button( + ui, + app, + crate::widgets::MenuButtonOptions::new("smartArtColors", Some("Change\nColors"), "Change Colors", true, ""), + |ui, app| { + for colors in SmartArtColors::ALL { + let on = g.colors == colors; + let resp = ui.add(egui::Button::new(crate::dialogs_smart_art::colors_label(colors)).selected(on).min_size(vec2(200.0, 24.0))); + let sw = Rect::from_min_size(pos2(resp.rect.right() - 74.0, resp.rect.center().y - 6.0), vec2(66.0, 12.0)); + crate::dialogs_smart_art::swatches(ui.painter(), sw, colors, &theme); + if resp.clicked() { + let _ = app.run("smartArt.colors", json!({"variant": colors.id()})); + ui.close(); + } } - } - }); + }, + ); }); group(ui, "Reset", None, app, |ui, app| { big(ui, app, "resetGraphic", "Reset\nGraphic", "smartArt.reset", json!({}), false); @@ -68,12 +80,12 @@ pub(super) fn smart_art_design(app: &mut WordApp, ui: &mut Ui) { pub(super) fn chart_design(app: &mut WordApp, ui: &mut Ui) { let Some(chart) = wordcraft_engine::cmd::chart::selected_chart(&app.session) else { return }; group(ui, "Chart Layouts", None, app, |ui, app| { - menu_button(ui, app, "chartTitle", Some("Chart Title"), "Chart Title", true, "", |ui, app| { + menu_button(ui, app, crate::widgets::MenuButtonOptions::new("chartTitle", Some("Chart Title"), "Chart Title", true, ""), |ui, app| { mi_check(ui, app, "None", chart.title.is_none(), "chart.title", json!({"text": null})); let text = chart.title.clone().unwrap_or_else(|| tl!("Chart Title").to_string()); mi_check(ui, app, "Above Chart", chart.title.is_some(), "chart.title", json!({"text": text})); }); - menu_button(ui, app, "legend", Some("Legend"), "Legend", true, "", |ui, app| { + menu_button(ui, app, crate::widgets::MenuButtonOptions::new("legend", Some("Legend"), "Legend", true, ""), |ui, app| { mi_check(ui, app, "None", chart.legend.is_none(), "chart.legend", json!({"pos": "none"})); for (label, pos) in [ ("Right", wordcraft_doc::chart::LegendPos::Right), @@ -84,7 +96,7 @@ pub(super) fn chart_design(app: &mut WordApp, ui: &mut Ui) { mi_check(ui, app, label, chart.legend == Some(pos), "chart.legend", json!({"pos": pos.id()})); } }); - menu_button(ui, app, "dataLabels", Some("Data Labels"), "Data Labels", true, "", |ui, app| { + menu_button(ui, app, crate::widgets::MenuButtonOptions::new("dataLabels", Some("Data Labels"), "Data Labels", true, ""), |ui, app| { mi_check(ui, app, "None", !chart.data_labels, "chart.dataLabels", json!({"on": false})); mi_check(ui, app, "Show", chart.data_labels, "chart.dataLabels", json!({"on": true})); }); diff --git a/crates/ui-egui/src/ribbon/common.rs b/crates/ui-egui/src/ribbon/common.rs index 651b9c90b..244967d8f 100644 --- a/crates/ui-egui/src/ribbon/common.rs +++ b/crates/ui-egui/src/ribbon/common.rs @@ -1,5 +1,5 @@ use super::language::mi_check; -use super::*; +use super::{Align2, Rect, Sense, Tokens, Ui, Value, WordApp, big, group, json, small, vec2}; /// Custom groups (Customize Ribbon, #379): up to three commands as large buttons, more as small /// labelled buttons in columns of three. Each shows the command's own icon and label. @@ -17,7 +17,7 @@ pub(super) fn custom_groups(app: &mut WordApp, ui: &mut Ui, groups: &[wordcraft_ for column in ids.chunks(3) { stack(ui, |ui| { for (id, label) in column { - small(ui, app, crate::widgets::command_icon(id), Some(label), label, id, json!({}), false); + small(ui, app, crate::widgets::command_icon(id), Some(label), label, crate::widgets::ButtonAction::new(id, json!({}), false)); } }); } diff --git a/crates/ui-egui/src/ribbon/design.rs b/crates/ui-egui/src/ribbon/design.rs index 809eb6ce0..8208c8631 100644 --- a/crates/ui-egui/src/ribbon/design.rs +++ b/crates/ui-egui/src/ribbon/design.rs @@ -1,5 +1,5 @@ use super::common::{mi, mi_text, stack}; -use super::*; +use super::{CONTENT_H, Rect, Sense, Target, Ui, WordApp, big, group, json, menu_button, small, vec2}; /// The Design tab's style set gallery in a box of its own, sized from its thumbnails and clipped /// to it, with a gap on each side: the Colors and Fonts column can't be drawn over, or cut by, @@ -17,7 +17,7 @@ pub(super) fn style_set_box(app: &mut WordApp, ui: &mut Ui) -> (Rect, Rect) { pub(super) fn design(app: &mut WordApp, ui: &mut Ui) { group(ui, "Themes", None, app, |ui, app| { - menu_button(ui, app, "themes", Some("Themes"), "Themes", true, "design.themes", |ui, app| { + menu_button(ui, app, crate::widgets::MenuButtonOptions::new("themes", Some("Themes"), "Themes", true, "design.themes"), |ui, app| { for (name, ..) in wordcraft_engine::cmd::design::THEMES { mi(ui, app, name, "design.theme", json!({"name": name})); } @@ -26,55 +26,93 @@ pub(super) fn design(app: &mut WordApp, ui: &mut Ui) { group(ui, "Document Formatting", None, app, |ui, app| { style_set_box(app, ui); stack(ui, |ui| { - menu_button(ui, app, "colors", Some("Colors"), "Theme Colors", false, "design.themeColors", |ui, app| { - for (name, ..) in wordcraft_engine::cmd::design::THEMES { - mi(ui, app, name, "design.themeColors", json!({"name": name})); - } - }); - menu_button(ui, app, "fonts", Some("Fonts"), "Theme Fonts", false, "design.themeFonts", |ui, app| { - for (name, h, b, _) in wordcraft_engine::cmd::design::THEMES { - mi_text(ui, app, &format!("{}: {h} / {b}", tl!(name)), "design.themeFonts", json!({"heading": h, "body": b})); - } - }); + menu_button( + ui, + app, + crate::widgets::MenuButtonOptions::new("colors", Some("Colors"), "Theme Colors", false, "design.themeColors"), + |ui, app| { + for (name, ..) in wordcraft_engine::cmd::design::THEMES { + mi(ui, app, name, "design.themeColors", json!({"name": name})); + } + }, + ); + menu_button( + ui, + app, + crate::widgets::MenuButtonOptions::new("fonts", Some("Fonts"), "Theme Fonts", false, "design.themeFonts"), + |ui, app| { + for (name, h, b, _) in wordcraft_engine::cmd::design::THEMES { + mi_text(ui, app, &format!("{}: {h} / {b}", tl!(name)), "design.themeFonts", json!({"heading": h, "body": b})); + } + }, + ); }); stack(ui, |ui| { - menu_button(ui, app, "paraSpacing", Some("Paragraph Spacing"), "Paragraph Spacing", false, "design.paragraphSpacing", |ui, app| { - for (l, v) in [ - ("Default", "default"), - ("No Paragraph Space", "none"), - ("Compact", "compact"), - ("Tight", "tight"), - ("Open", "open"), - ("Relaxed", "relaxed"), - ("Double", "double"), - ] { - // "Open" here is roomy spacing, not the file verb: only a `spacing` row - // translates it, else it stays English. - if l == "Open" { - mi_text(ui, app, crate::i18n::get("spacing", l).unwrap_or(l), "design.paragraphSpacing", json!({"value": v})); - continue; + menu_button( + ui, + app, + crate::widgets::MenuButtonOptions::new( + "paraSpacing", + Some("Paragraph Spacing"), + "Paragraph Spacing", + false, + "design.paragraphSpacing", + ), + |ui, app| { + for (l, v) in [ + ("Default", "default"), + ("No Paragraph Space", "none"), + ("Compact", "compact"), + ("Tight", "tight"), + ("Open", "open"), + ("Relaxed", "relaxed"), + ("Double", "double"), + ] { + // "Open" here is roomy spacing, not the file verb: only a `spacing` row + // translates it, else it stays English. + if l == "Open" { + mi_text(ui, app, crate::i18n::get("spacing", l).unwrap_or(l), "design.paragraphSpacing", json!({"value": v})); + continue; + } + mi(ui, app, l, "design.paragraphSpacing", json!({"value": v})); } - mi(ui, app, l, "design.paragraphSpacing", json!({"value": v})); - } - }); - small(ui, app, "effectsDesign", Some("Effects"), "Effects", "design.effects", json!({}), false); - small(ui, app, "setDefault", Some("Set as Default"), "Set as Default", "design.setDefault", json!({}), false); + }, + ); + small(ui, app, "effectsDesign", Some("Effects"), "Effects", crate::widgets::ButtonAction::new("design.effects", json!({}), false)); + small( + ui, + app, + "setDefault", + Some("Set as Default"), + "Set as Default", + crate::widgets::ButtonAction::new("design.setDefault", json!({}), false), + ); }); }); group(ui, "Page Background", None, app, |ui, app| { - menu_button(ui, app, "watermark", Some("Watermark"), "Watermark", true, "design.watermark", |ui, app| { - for w in ["DRAFT", "CONFIDENTIAL", "DO NOT COPY", "SAMPLE", "ASAP", "URGENT"] { - mi(ui, app, w, "design.watermark", json!({"text": w})); - } - ui.separator(); - mi(ui, app, "Custom Watermark…", "ui.dialog", json!({"name": "watermark"})); - mi(ui, app, "Remove Watermark", "design.watermark", json!({"remove": true})); - }); - menu_button(ui, app, "pageColor", Some("Page\nColor"), "Page Color", true, "design.pageColor", |ui, app| { - mi(ui, app, "No Color", "design.pageColor", json!({"color": null})); - let theme = app.session.doc.settings.theme_colors.clone(); - crate::colors::menu(ui, app, &theme, Target::cmd("design.pageColor")); - }); + menu_button( + ui, + app, + crate::widgets::MenuButtonOptions::new("watermark", Some("Watermark"), "Watermark", true, "design.watermark"), + |ui, app| { + for w in ["DRAFT", "CONFIDENTIAL", "DO NOT COPY", "SAMPLE", "ASAP", "URGENT"] { + mi(ui, app, w, "design.watermark", json!({"text": w})); + } + ui.separator(); + mi(ui, app, "Custom Watermark…", "ui.dialog", json!({"name": "watermark"})); + mi(ui, app, "Remove Watermark", "design.watermark", json!({"remove": true})); + }, + ); + menu_button( + ui, + app, + crate::widgets::MenuButtonOptions::new("pageColor", Some("Page\nColor"), "Page Color", true, "design.pageColor"), + |ui, app| { + mi(ui, app, "No Color", "design.pageColor", json!({"color": null})); + let theme = app.session.doc.settings.theme_colors.clone(); + crate::colors::menu(ui, app, &theme, Target::cmd("design.pageColor")); + }, + ); big(ui, app, "pageBorders", "Page\nBorders", "design.pageBorders", json!({}), false); }); } diff --git a/crates/ui-egui/src/ribbon/draw.rs b/crates/ui-egui/src/ribbon/draw.rs index 69877e542..8b89c2eb0 100644 --- a/crates/ui-egui/src/ribbon/draw.rs +++ b/crates/ui-egui/src/ribbon/draw.rs @@ -1,6 +1,6 @@ use super::common::{mi, stack}; use super::language::mi_check; -use super::*; +use super::{CONTENT_H, Rect, Sense, Stroke, Target, Tokens, Ui, WordApp, big, big_toggle, group, icons, json, menu_button, pos2, split, vec2}; pub(super) fn draw(app: &mut WordApp, ui: &mut Ui) { use wordcraft_doc::freeform::InkTool; @@ -25,7 +25,7 @@ pub(super) fn draw(app: &mut WordApp, ui: &mut Ui) { for (i, pen) in pens.iter().enumerate() { gallery_pen(ui, app, i, *pen, custom == Some(i)); } - menu_button(ui, app, "addPen", Some("Add\nPen"), "Add Pen", true, "", |ui, app| { + menu_button(ui, app, crate::widgets::MenuButtonOptions::new("addPen", Some("Add\nPen"), "Add Pen", true, ""), |ui, app| { for (label, kind) in [("Pen", InkTool::Pen), ("Pencil", InkTool::Pencil), ("Highlighter", InkTool::Highlighter)] { // A new pen starts as that kind's current colour and thickness. let cur = app.session.view.draw.settings(kind); @@ -34,7 +34,7 @@ pub(super) fn draw(app: &mut WordApp, ui: &mut Ui) { }); stack(ui, |ui| { let sw = Some(crate::theme::c32(set.color)); - split(ui, app, "fontcolor", "Color", pen_command(tool), json!({}), false, sw, |ui, app| { + split(ui, app, "fontcolor", "Color", crate::widgets::ButtonAction::new(pen_command(tool), json!({}), false), sw, |ui, app| { let theme = app.session.doc.settings.theme_colors.clone(); // With ink lasso-selected the colour goes to the selection, else to the pen. let cmd = if lasso_on { "draw.lassoColor" } else { pen_command(tool) }; @@ -43,7 +43,7 @@ pub(super) fn draw(app: &mut WordApp, ui: &mut Ui) { update_gallery_pen(app, custom, tool); } }); - menu_button(ui, app, "thickness", None, "Thickness", false, "draw.pen", |ui, app| { + menu_button(ui, app, crate::widgets::MenuButtonOptions::new("thickness", None, "Thickness", false, "draw.pen"), |ui, app| { let widths: &[f32] = if tool == InkTool::Highlighter { &[4.0, 8.0, 12.0, 18.0, 24.0] } else { &[0.5, 1.0, 1.5, 2.5, 3.5, 5.0] }; for w in widths { let label = crate::i18n::fmt(tl!("{n} pt"), &[("n", &w.to_string())]); diff --git a/crates/ui-egui/src/ribbon/home.rs b/crates/ui-egui/src/ribbon/home.rs index d2d3c233d..051e5e998 100644 --- a/crates/ui-egui/src/ribbon/home.rs +++ b/crates/ui-egui/src/ribbon/home.rs @@ -1,13 +1,14 @@ use super::common::{mi, mi_text, stack}; use super::underline::underline_menu; -use super::*; +use super::{Remember, Stroke, Target, Tokens, Ui, Value, WordApp, big, group, json, menu_button, small, split, vec2}; /// The Format Painter button. A click arms it for one use (blue border only); a double-click keeps /// it on across several uses (full checked look), like Word. Esc or another click turns it off. fn format_painter_button(ui: &mut Ui, app: &mut WordApp) { let sticky = matches!(app.session.painter, Some((_, _, true))); let armed = app.session.painter.is_some(); - let resp = small(ui, app, "painter", Some("Format"), "Format Painter", "edit.formatPainter", json!({}), sticky); + let resp = + small(ui, app, "painter", Some("Format"), "Format Painter", crate::widgets::ButtonAction::new("edit.formatPainter", json!({}), sticky)); if resp.double_clicked() { painter_double_click(app); } else if armed && !sticky { @@ -29,15 +30,15 @@ pub(super) fn home(app: &mut WordApp, ui: &mut Ui) { let st = app.session.run("format.state", &json!({})).unwrap_or_default(); let flag = |k: &str| st.get(k).and_then(Value::as_bool).unwrap_or(false); group(ui, "Clipboard", Some("edit.clipboardPane"), app, |ui, app| { - menu_button(ui, app, "paste", Some("Paste"), "Paste (⌘V)", true, "edit.paste", |ui, app| { + menu_button(ui, app, crate::widgets::MenuButtonOptions::new("paste", Some("Paste"), "Paste (⌘V)", true, "edit.paste"), |ui, app| { mi(ui, app, "Paste", "edit.paste", json!({})); mi(ui, app, "Keep Text Only", "edit.pasteText", json!({})); mi(ui, app, "Merge Formatting", "edit.pasteMerge", json!({})); mi(ui, app, "Paste Special…", "edit.pasteSpecial", json!({})); }); stack(ui, |ui| { - small(ui, app, "cut", Some("Cut"), "Cut", "edit.cut", json!({}), false); - small(ui, app, "copy", Some("Copy"), "Copy", "edit.copy", json!({}), false); + small(ui, app, "cut", Some("Cut"), "Cut", crate::widgets::ButtonAction::new("edit.cut", json!({}), false)); + small(ui, app, "copy", Some("Copy"), "Copy", crate::widgets::ButtonAction::new("edit.copy", json!({}), false)); format_painter_button(ui, app); }); }); @@ -49,7 +50,18 @@ pub(super) fn home(app: &mut WordApp, ui: &mut Ui) { let fams = app.previews.families(); let recent: Vec = app.ui.recent_fonts.iter().map(|f| app.previews.font_shown(f)).collect(); let prev = app.previews.font_preview_fn(); - if let Some(f) = crate::widgets::font_combo_with_recent_hint(ui, "font", 150.0, &font, &fams, &recent, Some(&*prev), tl!("Font")) { + if let Some(f) = crate::widgets::font_combo_with_recent_hint( + ui, + crate::widgets::FontComboOptions { + id: "font", + width: 150.0, + current: &font, + items: &fams, + recent: &recent, + preview: Some(&*prev), + tip: tl!("Font"), + }, + ) { let _ = if f == crate::widgets::ADD_FONT { app.run("font.add", json!({})) } else if f == crate::widgets::LOAD_SYSTEM_FONTS { @@ -72,30 +84,45 @@ pub(super) fn home(app: &mut WordApp, ui: &mut Ui) { let _ = app.run("format.size", json!({"size": x})); } ui.add_space(3.0); - small(ui, app, "grow", None, "Increase Font Size", "format.growFont", json!({}), false); - small(ui, app, "shrink", None, "Decrease Font Size", "format.shrinkFont", json!({}), false); - menu_button(ui, app, "case", None, "Change Case", false, "format.changeCase", |ui, app| { + small(ui, app, "grow", None, "Increase Font Size", crate::widgets::ButtonAction::new("format.growFont", json!({}), false)); + small(ui, app, "shrink", None, "Decrease Font Size", crate::widgets::ButtonAction::new("format.shrinkFont", json!({}), false)); + menu_button(ui, app, crate::widgets::MenuButtonOptions::new("case", None, "Change Case", false, "format.changeCase"), |ui, app| { mi(ui, app, "Sentence case.", "format.changeCase", json!({"mode": "sentence"})); mi(ui, app, "lowercase", "format.changeCase", json!({"mode": "lower"})); mi(ui, app, "UPPERCASE", "format.changeCase", json!({"mode": "upper"})); mi(ui, app, "Capitalize Each Word", "format.changeCase", json!({"mode": "title"})); mi(ui, app, "tOGGLE cASE", "format.changeCase", json!({"mode": "toggle"})); }); - small(ui, app, "clear", None, "Clear All Formatting", "format.clear", json!({}), false); - small(ui, app, "phonetic", None, "Phonetic Guide", "format.phonetic", json!({}), false); + small(ui, app, "clear", None, "Clear All Formatting", crate::widgets::ButtonAction::new("format.clear", json!({}), false)); + small(ui, app, "phonetic", None, "Phonetic Guide", crate::widgets::ButtonAction::new("format.phonetic", json!({}), false)); }); ui.add_space(3.0); crate::widgets::row(ui, |ui| { - small(ui, app, "bold", None, "Bold", "format.bold", json!({}), flag("bold")); - small(ui, app, "italic", None, "Italic", "format.italic", json!({}), flag("italic")); - split(ui, app, "underline", "Underline", "format.underline", json!({}), flag("underline"), None, underline_menu); - small(ui, app, "strike", None, "Strikethrough", "format.strikethrough", json!({}), flag("strike")); - small(ui, app, "subscript", None, "Subscript", "format.subscript", json!({}), flag("subscript")); - small(ui, app, "superscript", None, "Superscript", "format.superscript", json!({}), flag("superscript")); - small(ui, app, "charborder", None, "Character Border", "format.border", json!({}), flag("border")); - small(ui, app, "enclose", None, "Enclose Characters", "format.enclose", json!({}), false); + small(ui, app, "bold", None, "Bold", crate::widgets::ButtonAction::new("format.bold", json!({}), flag("bold"))); + small(ui, app, "italic", None, "Italic", crate::widgets::ButtonAction::new("format.italic", json!({}), flag("italic"))); + split( + ui, + app, + "underline", + "Underline", + crate::widgets::ButtonAction::new("format.underline", json!({}), flag("underline")), + None, + underline_menu, + ); + small(ui, app, "strike", None, "Strikethrough", crate::widgets::ButtonAction::new("format.strikethrough", json!({}), flag("strike"))); + small(ui, app, "subscript", None, "Subscript", crate::widgets::ButtonAction::new("format.subscript", json!({}), flag("subscript"))); + small( + ui, + app, + "superscript", + None, + "Superscript", + crate::widgets::ButtonAction::new("format.superscript", json!({}), flag("superscript")), + ); + small(ui, app, "charborder", None, "Character Border", crate::widgets::ButtonAction::new("format.border", json!({}), flag("border"))); + small(ui, app, "enclose", None, "Enclose Characters", crate::widgets::ButtonAction::new("format.enclose", json!({}), false)); ui.add_space(4.0); - menu_button(ui, app, "effects", None, "Text Effects and Typography", false, "", |ui, app| { + menu_button(ui, app, crate::widgets::MenuButtonOptions::new("effects", None, "Text Effects and Typography", false, ""), |ui, app| { let theme = app.session.doc.settings.theme_colors.clone(); crate::wordart_menus::style_gallery(ui, app, "format.textEffects"); ui.separator(); @@ -117,19 +144,25 @@ pub(super) fn home(app: &mut WordApp, ui: &mut Ui) { app, icon, "Text Highlight Color", - "format.highlight", - json!({"color": color}), - false, + crate::widgets::ButtonAction::new("format.highlight", json!({"color": color}), false), sw, crate::mini_toolbar::highlight_menu, ); let fc = app.canvas.last_font_color.clone(); let sw = wordcraft_doc::Rgb::parse(&fc).map(crate::theme::c32); - split(ui, app, "fontcolor", "Font Color", "format.color", json!({"color": fc}), false, sw, |ui, app| { - mi(ui, app, "Automatic", "format.color", json!({"color": "auto"})); - let theme = app.session.doc.settings.theme_colors.clone(); - crate::colors::menu(ui, app, &theme, Target::cmd("format.color").remember(Remember::FontColor)); - }); + split( + ui, + app, + "fontcolor", + "Font Color", + crate::widgets::ButtonAction::new("format.color", json!({"color": fc}), false), + sw, + |ui, app| { + mi(ui, app, "Automatic", "format.color", json!({"color": "auto"})); + let theme = app.session.doc.settings.theme_colors.clone(); + crate::colors::menu(ui, app, &theme, Target::cmd("format.color").remember(Remember::FontColor)); + }, + ); }); }); }); @@ -141,7 +174,7 @@ pub(super) fn home(app: &mut WordApp, ui: &mut Ui) { let lists = wordcraft_engine::cmd::para::list_buttons(&app.session.doc, rp.as_ref().and_then(|r| r.numbering)); stack(ui, |ui| { crate::widgets::row(ui, |ui| { - split(ui, app, "bullets", "Bullets", "para.bullets", json!({}), lists.bullets, None, |ui, app| { + split(ui, app, "bullets", "Bullets", crate::widgets::ButtonAction::new("para.bullets", json!({}), lists.bullets), None, |ui, app| { ui.label(egui::RichText::new(tl!("Bullet Library")).small().weak()); ui.horizontal(|ui| { for c in ["•", "○", "▪", "◆", "➢", "✓", "–"] { @@ -162,98 +195,135 @@ pub(super) fn home(app: &mut WordApp, ui: &mut Ui) { }); mi(ui, app, "None", "para.bullets", json!({"off": true})); }); - split(ui, app, "numbering", "Numbering", "para.numbering", json!({}), lists.numbering, None, |ui, app| { - for (l, k) in [ - ("1. 2. 3.", "numbered"), - ("1) 2) 3)", "numberedParen"), - ("I. II. III.", "outline"), - ("A. B. C.", "upperLetter"), - ("a) b) c)", "lowerLetter"), - ("i. ii. iii.", "lowerRoman"), - ] { - mi(ui, app, l, "para.numbering", json!({"kind": k})); - } - ui.separator(); - mi(ui, app, "Restart at 1", "para.restartNumbering", json!({})); - mi(ui, app, "None", "para.numbering", json!({"off": true})); - }); - split(ui, app, "multilevel", "Multilevel List", "para.multilevel", json!({}), lists.multilevel, None, |ui, app| { - mi(ui, app, "1. 1.1. 1.1.1.", "para.multilevel", json!({"kind": "legal"})); - mi(ui, app, "I. A. 1. a.", "para.multilevel", json!({"kind": "outline"})); - ui.separator(); - // Lists linked to the heading styles: the headings carry the numbers. Each item - // reads as its first three levels, number then (translated) style name. - for (numbers, k) in [ - (["1", "1.1", "1.1.1"], "headings"), - (["Article I.", "Section 1.01", "(a)"], "article"), - (["I.", "A.", "1."], "outlineHeadings"), - (["Chapter 1", "", ""], "chapter"), - ] { - let text = numbers - .iter() - .zip(["Heading 1", "Heading 2", "Heading 3"]) - .map(|(n, h)| if n.is_empty() { tl!(h).to_string() } else { format!("{} {}", tl!(n), tl!(h)) }) - .collect::>() - .join(" / "); - mi_text(ui, app, &text, "list.multilevelPreset", json!({"preset": k})); - } - ui.separator(); - mi(ui, app, "Define New Multilevel List…", "list.define", json!({})); - ui.separator(); - for lv in 0..5u64 { - mi(ui, app, &format!("Change to Level {}", lv + 1), "para.listLevel", json!({"level": lv})); - } - }); + split( + ui, + app, + "numbering", + "Numbering", + crate::widgets::ButtonAction::new("para.numbering", json!({}), lists.numbering), + None, + |ui, app| { + for (l, k) in [ + ("1. 2. 3.", "numbered"), + ("1) 2) 3)", "numberedParen"), + ("I. II. III.", "outline"), + ("A. B. C.", "upperLetter"), + ("a) b) c)", "lowerLetter"), + ("i. ii. iii.", "lowerRoman"), + ] { + mi(ui, app, l, "para.numbering", json!({"kind": k})); + } + ui.separator(); + mi(ui, app, "Restart at 1", "para.restartNumbering", json!({})); + mi(ui, app, "None", "para.numbering", json!({"off": true})); + }, + ); + split( + ui, + app, + "multilevel", + "Multilevel List", + crate::widgets::ButtonAction::new("para.multilevel", json!({}), lists.multilevel), + None, + |ui, app| { + mi(ui, app, "1. 1.1. 1.1.1.", "para.multilevel", json!({"kind": "legal"})); + mi(ui, app, "I. A. 1. a.", "para.multilevel", json!({"kind": "outline"})); + ui.separator(); + // Lists linked to the heading styles: the headings carry the numbers. Each item + // reads as its first three levels, number then (translated) style name. + for (numbers, k) in [ + (["1", "1.1", "1.1.1"], "headings"), + (["Article I.", "Section 1.01", "(a)"], "article"), + (["I.", "A.", "1."], "outlineHeadings"), + (["Chapter 1", "", ""], "chapter"), + ] { + let text = numbers + .iter() + .zip(["Heading 1", "Heading 2", "Heading 3"]) + .map(|(n, h)| if n.is_empty() { tl!(h).to_string() } else { format!("{} {}", tl!(n), tl!(h)) }) + .collect::>() + .join(" / "); + mi_text(ui, app, &text, "list.multilevelPreset", json!({"preset": k})); + } + ui.separator(); + mi(ui, app, "Define New Multilevel List…", "list.define", json!({})); + ui.separator(); + for lv in 0..5u64 { + mi(ui, app, &format!("Change to Level {}", lv + 1), "para.listLevel", json!({"level": lv})); + } + }, + ); ui.add_space(4.0); - small(ui, app, "outdent", None, "Decrease Indent", "para.outdent", json!({}), false); - small(ui, app, "indent", None, "Increase Indent", "para.indent", json!({}), false); + small(ui, app, "outdent", None, "Decrease Indent", crate::widgets::ButtonAction::new("para.outdent", json!({}), false)); + small(ui, app, "indent", None, "Increase Indent", crate::widgets::ButtonAction::new("para.indent", json!({}), false)); ui.add_space(4.0); - small(ui, app, "sort", None, "Sort", "para.sort", json!({}), false); + small(ui, app, "sort", None, "Sort", crate::widgets::ButtonAction::new("para.sort", json!({}), false)); let marks = app.session.view.marks; - small(ui, app, "pilcrow", None, "Show/Hide ¶", "view.marks", json!({}), marks); + small(ui, app, "pilcrow", None, "Show/Hide ¶", crate::widgets::ButtonAction::new("view.marks", json!({}), marks)); }); ui.add_space(3.0); crate::widgets::row(ui, |ui| { use wordcraft_doc::Align as A; - small(ui, app, "alignLeft", None, "Align Left", "para.alignLeft", json!({}), align == A::Left); - small(ui, app, "alignCenter", None, "Center", "para.alignCenter", json!({}), align == A::Center); - small(ui, app, "alignRight", None, "Align Right", "para.alignRight", json!({}), align == A::Right); - small(ui, app, "justify", None, "Justify", "para.justify", json!({}), align == A::Justify); + small(ui, app, "alignLeft", None, "Align Left", crate::widgets::ButtonAction::new("para.alignLeft", json!({}), align == A::Left)); + small(ui, app, "alignCenter", None, "Center", crate::widgets::ButtonAction::new("para.alignCenter", json!({}), align == A::Center)); + small(ui, app, "alignRight", None, "Align Right", crate::widgets::ButtonAction::new("para.alignRight", json!({}), align == A::Right)); + small(ui, app, "justify", None, "Justify", crate::widgets::ButtonAction::new("para.justify", json!({}), align == A::Justify)); ui.add_space(4.0); - small(ui, app, "textLtr", None, "Left-to-Right Text Direction", "para.ltr", json!({}), !rtl); - small(ui, app, "textRtl", None, "Right-to-Left Text Direction", "para.rtl", json!({}), rtl); + small(ui, app, "textLtr", None, "Left-to-Right Text Direction", crate::widgets::ButtonAction::new("para.ltr", json!({}), !rtl)); + small(ui, app, "textRtl", None, "Right-to-Left Text Direction", crate::widgets::ButtonAction::new("para.rtl", json!({}), rtl)); ui.add_space(4.0); - menu_button(ui, app, "lineSpacing", None, "Line and Paragraph Spacing", false, "para.lineSpacing", |ui, app| { - for v in [1.0, 1.15, 1.5, 2.0, 2.5, 3.0] { - mi(ui, app, &format!("{v}"), "para.lineSpacing", json!({"value": v})); - } - ui.separator(); - mi(ui, app, "Add Space Before Paragraph", "para.addSpaceBefore", json!({})); - mi(ui, app, "Remove Space After Paragraph", "para.removeSpaceAfter", json!({})); - mi(ui, app, "Line Spacing Options…", "para.dialog", json!({})); - }); - split(ui, app, "shading", "Shading", "para.shading", json!({"color": app.canvas.last_shading.clone()}), false, None, |ui, app| { - mi(ui, app, "No Color", "para.shading", json!({"color": null})); - let theme = app.session.doc.settings.theme_colors.clone(); - crate::colors::menu(ui, app, &theme, Target::cmd("para.shading").remember(Remember::Shading)); - }); - split(ui, app, "borders", "Borders", "para.borders", json!({"kind": "bottom"}), false, None, |ui, app| { - for (l, k) in [ - ("Bottom Border", "bottom"), - ("Top Border", "top"), - ("Left Border", "left"), - ("Right Border", "right"), - ("No Border", "none"), - ("All Borders", "all"), - ("Outside Borders", "outside"), - ("Inside Borders", "inside"), - ] { - mi(ui, app, l, "para.borders", json!({"kind": k})); - } - ui.separator(); - mi(ui, app, "Horizontal Line", "insert.horizontalLine", json!({})); - mi(ui, app, "Borders and Shading…", "para.borders", json!({})); - }); + menu_button( + ui, + app, + crate::widgets::MenuButtonOptions::new("lineSpacing", None, "Line and Paragraph Spacing", false, "para.lineSpacing"), + |ui, app| { + for v in [1.0, 1.15, 1.5, 2.0, 2.5, 3.0] { + mi(ui, app, &format!("{v}"), "para.lineSpacing", json!({"value": v})); + } + ui.separator(); + mi(ui, app, "Add Space Before Paragraph", "para.addSpaceBefore", json!({})); + mi(ui, app, "Remove Space After Paragraph", "para.removeSpaceAfter", json!({})); + mi(ui, app, "Line Spacing Options…", "para.dialog", json!({})); + }, + ); + split( + ui, + app, + "shading", + "Shading", + crate::widgets::ButtonAction::new("para.shading", json!({"color": app.canvas.last_shading.clone()}), false), + None, + |ui, app| { + mi(ui, app, "No Color", "para.shading", json!({"color": null})); + let theme = app.session.doc.settings.theme_colors.clone(); + crate::colors::menu(ui, app, &theme, Target::cmd("para.shading").remember(Remember::Shading)); + }, + ); + split( + ui, + app, + "borders", + "Borders", + crate::widgets::ButtonAction::new("para.borders", json!({"kind": "bottom"}), false), + None, + |ui, app| { + for (l, k) in [ + ("Bottom Border", "bottom"), + ("Top Border", "top"), + ("Left Border", "left"), + ("Right Border", "right"), + ("No Border", "none"), + ("All Borders", "all"), + ("Outside Borders", "outside"), + ("Inside Borders", "inside"), + ] { + mi(ui, app, l, "para.borders", json!({"kind": k})); + } + ui.separator(); + mi(ui, app, "Horizontal Line", "insert.horizontalLine", json!({})); + mi(ui, app, "Borders and Shading…", "para.borders", json!({})); + }, + ); }); }); }); @@ -262,14 +332,14 @@ pub(super) fn home(app: &mut WordApp, ui: &mut Ui) { }); group(ui, "Editing", None, app, |ui, app| { stack(ui, |ui| { - menu_button(ui, app, "find", Some("Find"), "Find", false, "edit.find", |ui, app| { + menu_button(ui, app, crate::widgets::MenuButtonOptions::new("find", Some("Find"), "Find", false, "edit.find"), |ui, app| { // As in Word: Find searches in the Navigation pane; Advanced Find is the dialog (#712). mi(ui, app, "Find", "edit.find", json!({})); mi(ui, app, "Advanced Find…", "edit.advancedFind", json!({})); mi(ui, app, "Go To…", "ui.dialog", json!({"name": "goto"})); }); - small(ui, app, "replace", Some("Replace"), "Replace", "ui.dialog", json!({"name": "replace"}), false); - menu_button(ui, app, "select", Some("Select"), "Select", false, "select.all", |ui, app| { + small(ui, app, "replace", Some("Replace"), "Replace", crate::widgets::ButtonAction::new("ui.dialog", json!({"name": "replace"}), false)); + menu_button(ui, app, crate::widgets::MenuButtonOptions::new("select", Some("Select"), "Select", false, "select.all"), |ui, app| { mi(ui, app, "Select All", "select.all", json!({})); mi(ui, app, "Select Paragraph", "select.paragraph", json!({})); mi(ui, app, "Select Sentence", "select.sentence", json!({})); diff --git a/crates/ui-egui/src/ribbon/insert.rs b/crates/ui-egui/src/ribbon/insert.rs index be84c1d67..36a5a1dd8 100644 --- a/crates/ui-egui/src/ribbon/insert.rs +++ b/crates/ui-egui/src/ribbon/insert.rs @@ -1,19 +1,24 @@ use super::common::{mi, mi_icon, stack}; use super::language::mi_check; -use super::*; +use super::{Ui, WordApp, big, group, json, menu_button, small}; pub(super) fn insert(app: &mut WordApp, ui: &mut Ui) { group(ui, "Pages", None, app, |ui, app| { stack(ui, |ui| { - menu_button(ui, app, "coverPage", Some("Cover Page"), "Cover Page", false, "insert.coverPage", |ui, app| { - mi(ui, app, "Studio cover", "insert.coverPage", json!({})); - }); - small(ui, app, "blankPage", Some("Blank Page"), "Blank Page", "insert.blankPage", json!({}), false); - small(ui, app, "pageBreak", Some("Page Break"), "Page Break", "insert.pageBreak", json!({}), false); + menu_button( + ui, + app, + crate::widgets::MenuButtonOptions::new("coverPage", Some("Cover Page"), "Cover Page", false, "insert.coverPage"), + |ui, app| { + mi(ui, app, "Studio cover", "insert.coverPage", json!({})); + }, + ); + small(ui, app, "blankPage", Some("Blank Page"), "Blank Page", crate::widgets::ButtonAction::new("insert.blankPage", json!({}), false)); + small(ui, app, "pageBreak", Some("Page Break"), "Page Break", crate::widgets::ButtonAction::new("insert.pageBreak", json!({}), false)); }); }); group(ui, "Tables", None, app, |ui, app| { - menu_button(ui, app, "table", Some("Table"), "Add a Table", true, "insert.table", |ui, app| { + menu_button(ui, app, crate::widgets::MenuButtonOptions::new("table", Some("Table"), "Add a Table", true, "insert.table"), |ui, app| { crate::dialogs::table_grid_picker(ui, app); ui.separator(); mi(ui, app, "Insert Table…", "ui.dialog", json!({"name": "insertTable"})); @@ -29,7 +34,7 @@ pub(super) fn insert(app: &mut WordApp, ui: &mut Ui) { }); group(ui, "Illustrations", None, app, |ui, app| { big(ui, app, "picture", "Pictures", "insert.picture", json!({}), false); - menu_button(ui, app, "shapes", Some("Shapes"), "Shapes", true, "insert.shape", |ui, app| { + menu_button(ui, app, crate::widgets::MenuButtonOptions::new("shapes", Some("Shapes"), "Shapes", true, "insert.shape"), |ui, app| { for (l, k) in [ ("Rectangle", "rectangle"), ("Rounded Rectangle", "roundedRectangle"), @@ -59,23 +64,37 @@ pub(super) fn insert(app: &mut WordApp, ui: &mut Ui) { } }); stack(ui, |ui| { - small(ui, app, "icons", Some("Icons"), "Icons", "insert.icon", json!({}), false); - small(ui, app, "smartArt", Some("SmartArt"), "SmartArt", "insert.smartArt", json!({}), false); - small(ui, app, "chart", Some("Chart"), "Chart", "insert.chart", json!({}), false); + small(ui, app, "icons", Some("Icons"), "Icons", crate::widgets::ButtonAction::new("insert.icon", json!({}), false)); + small(ui, app, "smartArt", Some("SmartArt"), "SmartArt", crate::widgets::ButtonAction::new("insert.smartArt", json!({}), false)); + small(ui, app, "chart", Some("Chart"), "Chart", crate::widgets::ButtonAction::new("insert.chart", json!({}), false)); }); }); group(ui, "Links", None, app, |ui, app| { stack(ui, |ui| { - small(ui, app, "link", Some("Link"), "Link", "ui.dialog", json!({"name": "link"}), false); - small(ui, app, "bookmark", Some("Bookmark"), "Bookmark", "ui.dialog", json!({"name": "bookmark"}), false); - small(ui, app, "crossRef", Some("Cross-reference"), "Cross-reference", "insert.crossReference", json!({}), false); + small(ui, app, "link", Some("Link"), "Link", crate::widgets::ButtonAction::new("ui.dialog", json!({"name": "link"}), false)); + small( + ui, + app, + "bookmark", + Some("Bookmark"), + "Bookmark", + crate::widgets::ButtonAction::new("ui.dialog", json!({"name": "bookmark"}), false), + ); + small( + ui, + app, + "crossRef", + Some("Cross-reference"), + "Cross-reference", + crate::widgets::ButtonAction::new("insert.crossReference", json!({}), false), + ); }); }); group(ui, "Comments", None, app, |ui, app| { big(ui, app, "newComment", "Comment", "review.newComment", json!({}), false); }); group(ui, "Header & Footer", None, app, |ui, app| { - menu_button(ui, app, "header", Some("Header"), "Header", true, "insert.header", |ui, app| { + menu_button(ui, app, crate::widgets::MenuButtonOptions::new("header", Some("Header"), "Header", true, "insert.header"), |ui, app| { mi(ui, app, "Blank", "insert.header", json!({"preset": "blank"})); mi(ui, app, "Blank (Three Columns)", "insert.header", json!({"preset": "blankThree"})); mi(ui, app, "Document Title", "insert.header", json!({"preset": "title"})); @@ -83,7 +102,7 @@ pub(super) fn insert(app: &mut WordApp, ui: &mut Ui) { mi(ui, app, "Edit Header", "insert.editHeader", json!({})); mi(ui, app, "Remove Header", "insert.removeHeader", json!({})); }); - menu_button(ui, app, "footer", Some("Footer"), "Footer", true, "insert.footer", |ui, app| { + menu_button(ui, app, crate::widgets::MenuButtonOptions::new("footer", Some("Footer"), "Footer", true, "insert.footer"), |ui, app| { mi(ui, app, "Blank", "insert.footer", json!({"preset": "blank"})); mi(ui, app, "Blank (Three Columns)", "insert.footer", json!({"preset": "blankThree"})); mi(ui, app, "Page Number", "insert.footer", json!({"preset": "pageNumber"})); @@ -91,45 +110,60 @@ pub(super) fn insert(app: &mut WordApp, ui: &mut Ui) { mi(ui, app, "Edit Footer", "insert.editFooter", json!({})); mi(ui, app, "Remove Footer", "insert.removeFooter", json!({})); }); - menu_button(ui, app, "pageNumber", Some("Page\nNumber"), "Page Number", true, "insert.pageNumber", |ui, app| { - mi(ui, app, "Top of Page", "insert.pageNumber", json!({"position": "top", "align": "right"})); - mi(ui, app, "Bottom of Page", "insert.pageNumber", json!({"position": "bottom", "align": "center"})); - mi(ui, app, "Page X of Y", "insert.pageNumber", json!({"position": "bottom", "format": "x of y"})); - mi(ui, app, "Current Position", "insert.pageNumber", json!({"position": "current"})); - mi(ui, app, "Format Page Numbers…", "ui.dialog", json!({"name": "pageNumbers"})); - }); + menu_button( + ui, + app, + crate::widgets::MenuButtonOptions::new("pageNumber", Some("Page\nNumber"), "Page Number", true, "insert.pageNumber"), + |ui, app| { + mi(ui, app, "Top of Page", "insert.pageNumber", json!({"position": "top", "align": "right"})); + mi(ui, app, "Bottom of Page", "insert.pageNumber", json!({"position": "bottom", "align": "center"})); + mi(ui, app, "Page X of Y", "insert.pageNumber", json!({"position": "bottom", "format": "x of y"})); + mi(ui, app, "Current Position", "insert.pageNumber", json!({"position": "current"})); + mi(ui, app, "Format Page Numbers…", "ui.dialog", json!({"name": "pageNumbers"})); + }, + ); }); group(ui, "Text", None, app, |ui, app| { big(ui, app, "textBox", "Text\nBox", "insert.textBox", json!({}), false); stack(ui, |ui| { - menu_button(ui, app, "quickParts", Some("Quick Parts"), "Quick Parts", false, "insert.quickParts", |ui, app| { - // This session's building blocks, then the document's own Quick Parts. - let mut parts: Vec = app.session.building_blocks.keys().cloned().collect(); - let mut seen: std::collections::HashSet = parts.iter().cloned().collect(); - let glossary = app.session.doc.glossary.iter().filter(|e| e.gallery == wordcraft_doc::QUICK_PARTS_GALLERY); - parts.extend(glossary.filter(|e| seen.insert(e.name.clone())).map(|e| e.name.clone())); - for name in parts { - if ui.button(&name).clicked() { - let _ = app.run("insert.quickParts", json!({"insert": name})); - ui.close(); + menu_button( + ui, + app, + crate::widgets::MenuButtonOptions::new("quickParts", Some("Quick Parts"), "Quick Parts", false, "insert.quickParts"), + |ui, app| { + // This session's building blocks, then the document's own Quick Parts. + let mut parts: Vec = app.session.building_blocks.keys().cloned().collect(); + let mut seen: std::collections::HashSet = parts.iter().cloned().collect(); + let glossary = app.session.doc.glossary.iter().filter(|e| e.gallery == wordcraft_doc::QUICK_PARTS_GALLERY); + parts.extend(glossary.filter(|e| seen.insert(e.name.clone())).map(|e| e.name.clone())); + for name in parts { + if ui.button(&name).clicked() { + let _ = app.run("insert.quickParts", json!({"insert": name})); + ui.close(); + } } - } - mi(ui, app, "Field…", "ui.dialog", json!({"name": "field"})); - }); - menu_button(ui, app, "wordArt", Some("WordArt"), "WordArt", false, "", |ui, app| { + mi(ui, app, "Field…", "ui.dialog", json!({"name": "field"})); + }, + ); + menu_button(ui, app, crate::widgets::MenuButtonOptions::new("wordArt", Some("WordArt"), "WordArt", false, ""), |ui, app| { crate::wordart_menus::style_gallery(ui, app, "insert.wordArt") }); - menu_button(ui, app, "dropCap", Some("Drop Cap"), "Drop Cap", false, "insert.dropCap", drop_cap_items); + menu_button( + ui, + app, + crate::widgets::MenuButtonOptions::new("dropCap", Some("Drop Cap"), "Drop Cap", false, "insert.dropCap"), + drop_cap_items, + ); }); stack(ui, |ui| { - small(ui, app, "signature", None, "Signature Line", "insert.signatureLine", json!({}), false); - small(ui, app, "dateTime", None, "Date & Time", "insert.dateTime", json!({}), false); - small(ui, app, "object", None, "Object", "insert.object", json!({}), false); + small(ui, app, "signature", None, "Signature Line", crate::widgets::ButtonAction::new("insert.signatureLine", json!({}), false)); + small(ui, app, "dateTime", None, "Date & Time", crate::widgets::ButtonAction::new("insert.dateTime", json!({}), false)); + small(ui, app, "object", None, "Object", crate::widgets::ButtonAction::new("insert.object", json!({}), false)); }); }); group(ui, "Symbols", None, app, |ui, app| { crate::equation_tab::insert_button(ui, app); - menu_button(ui, app, "symbol", Some("Symbol"), "Symbol", true, "insert.symbol", |ui, app| { + menu_button(ui, app, crate::widgets::MenuButtonOptions::new("symbol", Some("Symbol"), "Symbol", true, "insert.symbol"), |ui, app| { // Recently used symbols first (their font on hover), then common ones. let mut items: Vec<(String, String)> = app.ui.recent_symbols.iter().map(|r| (r.ch.clone(), r.font.clone())).collect(); for c in [ diff --git a/crates/ui-egui/src/ribbon/language.rs b/crates/ui-egui/src/ribbon/language.rs index 56b5f182c..ab47bd5ad 100644 --- a/crates/ui-egui/src/ribbon/language.rs +++ b/crates/ui-egui/src/ribbon/language.rs @@ -1,4 +1,4 @@ -use super::*; +use super::{Ui, Value, WordApp, json, vec2}; /// The proofing languages the Language menu offers: tag and English name (translated when drawn). pub(crate) const PROOFING_LANGUAGES: [(&str, &str); 16] = [ diff --git a/crates/ui-egui/src/ribbon/layout.rs b/crates/ui-egui/src/ribbon/layout.rs index 1d4484f67..0d4318235 100644 --- a/crates/ui-egui/src/ribbon/layout.rs +++ b/crates/ui-egui/src/ribbon/layout.rs @@ -1,7 +1,7 @@ use super::arrange::{align_menu, group_menu, rotate_menu, wrap_menu}; use super::common::{mi, size_menu, stack}; use super::language::mi_check; -use super::*; +use super::{MeasureExt, Ui, WordApp, big, group, json, menu_button, small, vec2}; // Layout › Margins, Orientation, Size and Columns: the caret section's setting has a check // mark, as Text Direction's does (#1206). @@ -52,20 +52,32 @@ pub(super) fn layout(app: &mut WordApp, ui: &mut Ui) { group(ui, "Page Setup", Some("layout.pageSetup"), app, |ui, app| { // Text Direction works on what the caret is in: table cells, a text box, else the section. let dir = wordcraft_engine::cmd::page::text_direction_at(&app.session); - menu_button(ui, app, "textDirection", Some("Text\nDirection"), "Text Direction", true, "layout.textDirection", |ui, app| { - use wordcraft_doc::props::TextDirection; - for (label, d) in - [("Horizontal", TextDirection::Horizontal), ("Rotate all text 90°", TextDirection::Down), ("Rotate all text 270°", TextDirection::Up)] - { - mi_check(ui, app, label, dir == d, "layout.textDirection", json!({"value": d.ooxml()})); - } - }); - menu_button(ui, app, "margins", Some("Margins"), "Margins", true, "layout.margins", margins_items); - menu_button(ui, app, "orientation", Some("Orientation"), "Orientation", true, "layout.orientation", orientation_items); - menu_button(ui, app, "size", Some("Size"), "Size", true, "layout.size", size_menu); - menu_button(ui, app, "columns", Some("Columns"), "Columns", true, "layout.columns", columns_items); + menu_button( + ui, + app, + crate::widgets::MenuButtonOptions::new("textDirection", Some("Text\nDirection"), "Text Direction", true, "layout.textDirection"), + |ui, app| { + use wordcraft_doc::props::TextDirection; + for (label, d) in [ + ("Horizontal", TextDirection::Horizontal), + ("Rotate all text 90°", TextDirection::Down), + ("Rotate all text 270°", TextDirection::Up), + ] { + mi_check(ui, app, label, dir == d, "layout.textDirection", json!({"value": d.ooxml()})); + } + }, + ); + menu_button(ui, app, crate::widgets::MenuButtonOptions::new("margins", Some("Margins"), "Margins", true, "layout.margins"), margins_items); + menu_button( + ui, + app, + crate::widgets::MenuButtonOptions::new("orientation", Some("Orientation"), "Orientation", true, "layout.orientation"), + orientation_items, + ); + menu_button(ui, app, crate::widgets::MenuButtonOptions::new("size", Some("Size"), "Size", true, "layout.size"), size_menu); + menu_button(ui, app, crate::widgets::MenuButtonOptions::new("columns", Some("Columns"), "Columns", true, "layout.columns"), columns_items); stack(ui, |ui| { - menu_button(ui, app, "breaks", Some("Breaks"), "Breaks", false, "layout.break", |ui, app| { + menu_button(ui, app, crate::widgets::MenuButtonOptions::new("breaks", Some("Breaks"), "Breaks", false, "layout.break"), |ui, app| { ui.label(egui::RichText::new(tl!("Page Breaks")).strong()); mi(ui, app, "Page", "layout.break", json!({"kind": "page"})); mi(ui, app, "Column", "layout.break", json!({"kind": "column"})); @@ -76,23 +88,36 @@ pub(super) fn layout(app: &mut WordApp, ui: &mut Ui) { mi(ui, app, "Even Page", "layout.break", json!({"kind": "evenPage"})); mi(ui, app, "Odd Page", "layout.break", json!({"kind": "oddPage"})); }); - menu_button(ui, app, "lineNumbers", Some("Line Numbers"), "Line Numbers", false, "layout.lineNumbers", |ui, app| { - for (l, v) in - [("None", "none"), ("Continuous", "continuous"), ("Restart Each Page", "restartPage"), ("Restart Each Section", "restartSection")] - { - mi(ui, app, l, "layout.lineNumbers", json!({"value": v})); - } - ui.separator(); - mi(ui, app, "Line Numbering Options…", "ui.dialog", json!({"name": "lineNumbers"})); - }); + menu_button( + ui, + app, + crate::widgets::MenuButtonOptions::new("lineNumbers", Some("Line Numbers"), "Line Numbers", false, "layout.lineNumbers"), + |ui, app| { + for (l, v) in [ + ("None", "none"), + ("Continuous", "continuous"), + ("Restart Each Page", "restartPage"), + ("Restart Each Section", "restartSection"), + ] { + mi(ui, app, l, "layout.lineNumbers", json!({"value": v})); + } + ui.separator(); + mi(ui, app, "Line Numbering Options…", "ui.dialog", json!({"name": "lineNumbers"})); + }, + ); let hy = app.session.doc.settings.auto_hyphenation; - menu_button(ui, app, "hyphenation", Some("Hyphenation"), "Hyphenation", false, "layout.hyphenation", |ui, app| { - mi(ui, app, if hy { "✓ Automatic" } else { "Automatic" }, "layout.hyphenation", json!({"value": true})); - mi(ui, app, if hy { "None" } else { "✓ None" }, "layout.hyphenation", json!({"value": false})); - mi(ui, app, "Manual…", "ui.dialog", json!({"name": "manualHyphenation"})); - ui.separator(); - mi(ui, app, "Hyphenation Options…", "ui.dialog", json!({"name": "hyphenation"})); - }); + menu_button( + ui, + app, + crate::widgets::MenuButtonOptions::new("hyphenation", Some("Hyphenation"), "Hyphenation", false, "layout.hyphenation"), + |ui, app| { + mi(ui, app, if hy { "✓ Automatic" } else { "Automatic" }, "layout.hyphenation", json!({"value": true})); + mi(ui, app, if hy { "None" } else { "✓ None" }, "layout.hyphenation", json!({"value": false})); + mi(ui, app, "Manual…", "ui.dialog", json!({"name": "manualHyphenation"})); + ui.separator(); + mi(ui, app, "Hyphenation Options…", "ui.dialog", json!({"name": "hyphenation"})); + }, + ); }); }); group(ui, "Paragraph", Some("para.dialog"), app, |ui, app| { @@ -147,9 +172,30 @@ pub(super) fn layout(app: &mut WordApp, ui: &mut Ui) { big(ui, app, "position", "Position", "arrange.position", json!({}), false); wrap_menu(ui, app); stack(ui, |ui| { - small(ui, app, "bringForward", Some("Bring Forward"), "Bring Forward", "arrange.bringForward", json!({}), false); - small(ui, app, "sendBackward", Some("Send Backward"), "Send Backward", "arrange.sendBackward", json!({}), false); - small(ui, app, "selectionPane", Some("Selection Pane"), "Selection Pane", "arrange.selectionPane", json!({}), false); + small( + ui, + app, + "bringForward", + Some("Bring Forward"), + "Bring Forward", + crate::widgets::ButtonAction::new("arrange.bringForward", json!({}), false), + ); + small( + ui, + app, + "sendBackward", + Some("Send Backward"), + "Send Backward", + crate::widgets::ButtonAction::new("arrange.sendBackward", json!({}), false), + ); + small( + ui, + app, + "selectionPane", + Some("Selection Pane"), + "Selection Pane", + crate::widgets::ButtonAction::new("arrange.selectionPane", json!({}), false), + ); }); stack(ui, |ui| { align_menu(ui, app, None); diff --git a/crates/ui-egui/src/ribbon/mailings.rs b/crates/ui-egui/src/ribbon/mailings.rs index e76ed3833..bd37f4c44 100644 --- a/crates/ui-egui/src/ribbon/mailings.rs +++ b/crates/ui-egui/src/ribbon/mailings.rs @@ -1,6 +1,6 @@ use super::common::{mi, stack}; use super::language::mi_check; -use super::*; +use super::{Ui, WordApp, big, group, json, menu_button, small}; pub(super) fn mailings(app: &mut WordApp, ui: &mut Ui) { group(ui, "Create", None, app, |ui, app| { @@ -9,25 +9,35 @@ pub(super) fn mailings(app: &mut WordApp, ui: &mut Ui) { }); group(ui, "Start Mail Merge", None, app, |ui, app| { // The kinds of merge document the engine makes, the current one checked. - menu_button(ui, app, "mailMerge", Some("Start Mail\nMerge"), "Start Mail Merge", true, "mailings.start", |ui, app| { - let kind = app.session.merge.kind.clone(); - for (label, k) in [ - ("Letters", "letters"), - ("E-mail Messages", "emails"), - ("Envelopes", "envelopes"), - ("Labels", "labels"), - ("Directory", "directory"), - ("Normal Word Document", "normal"), - ] { - let current = kind == k || (k == "normal" && kind.is_empty()); - mi_check(ui, app, label, current, "mailings.start", json!({"kind": k})); - } - }); + menu_button( + ui, + app, + crate::widgets::MenuButtonOptions::new("mailMerge", Some("Start Mail\nMerge"), "Start Mail Merge", true, "mailings.start"), + |ui, app| { + let kind = app.session.merge.kind.clone(); + for (label, k) in [ + ("Letters", "letters"), + ("E-mail Messages", "emails"), + ("Envelopes", "envelopes"), + ("Labels", "labels"), + ("Directory", "directory"), + ("Normal Word Document", "normal"), + ] { + let current = kind == k || (k == "normal" && kind.is_empty()); + mi_check(ui, app, label, current, "mailings.start", json!({"kind": k})); + } + }, + ); // The command needs data, so the button offers the two ways to give it (#240). - menu_button(ui, app, "recipients", Some("Select\nRecipients"), "Select Recipients", true, "mailings.recipients", |ui, app| { - mi(ui, app, "Type a New List…", "ui.dialog", json!({"name": "newRecipientList"})); - mi(ui, app, "Use an Existing List…", "ui.openRecipientList", json!({})); - }); + menu_button( + ui, + app, + crate::widgets::MenuButtonOptions::new("recipients", Some("Select\nRecipients"), "Select Recipients", true, "mailings.recipients"), + |ui, app| { + mi(ui, app, "Type a New List…", "ui.dialog", json!({"name": "newRecipientList"})); + mi(ui, app, "Use an Existing List…", "ui.openRecipientList", json!({})); + }, + ); big(ui, app, "editRecipients", "Edit\nRecipient List", "mailings.editRecipients", json!({}), false); }); group(ui, "Write & Insert Fields", None, app, |ui, app| { @@ -37,34 +47,60 @@ pub(super) fn mailings(app: &mut WordApp, ui: &mut Ui) { big(ui, app, "mergeField", "Insert Merge\nField", "mailings.insertField", json!({}), false); stack(ui, |ui| { // The merge rules the engine knows; If and Skip Record If ask for their condition. - menu_button(ui, app, "rules", Some("Rules"), "Rules", false, "mailings.rules", |ui, app| { + menu_button(ui, app, crate::widgets::MenuButtonOptions::new("rules", Some("Rules"), "Rules", false, "mailings.rules"), |ui, app| { mi(ui, app, "If…Then…Else…", "mailings.rules", json!({"rule": "IF"})); mi(ui, app, "Merge Record #", "mailings.rules", json!({"rule": "MERGEREC"})); mi(ui, app, "Next Record", "mailings.rules", json!({"rule": "NEXT"})); mi(ui, app, "Skip Record If…", "mailings.rules", json!({"rule": "SKIPIF"})); }); - small(ui, app, "matchFields", Some("Match Fields"), "Match Fields", "mailings.matchFields", json!({}), false); + small( + ui, + app, + "matchFields", + Some("Match Fields"), + "Match Fields", + crate::widgets::ButtonAction::new("mailings.matchFields", json!({}), false), + ); }); }); group(ui, "Preview Results", None, app, |ui, app| { big(ui, app, "preview", "Preview\nResults", "mailings.preview", json!({}), false); stack(ui, |ui| { crate::widgets::row(ui, |ui| { - small(ui, app, "first", None, "First Record", "mailings.first", json!({}), false); - small(ui, app, "previous", None, "Previous Record", "mailings.previous", json!({}), false); + small(ui, app, "first", None, "First Record", crate::widgets::ButtonAction::new("mailings.first", json!({}), false)); + small(ui, app, "previous", None, "Previous Record", crate::widgets::ButtonAction::new("mailings.previous", json!({}), false)); record_box(ui, app); - small(ui, app, "next", None, "Next Record", "mailings.next", json!({}), false); - small(ui, app, "last", None, "Last Record", "mailings.last", json!({}), false); + small(ui, app, "next", None, "Next Record", crate::widgets::ButtonAction::new("mailings.next", json!({}), false)); + small(ui, app, "last", None, "Last Record", crate::widgets::ButtonAction::new("mailings.last", json!({}), false)); }); - small(ui, app, "find", Some("Find Recipient"), "Find Recipient", "mailings.findRecipient", json!({}), false); - small(ui, app, "checkErrors", Some("Check for Errors"), "Check for Errors", "mailings.checkErrors", json!({}), false); + small( + ui, + app, + "find", + Some("Find Recipient"), + "Find Recipient", + crate::widgets::ButtonAction::new("mailings.findRecipient", json!({}), false), + ); + small( + ui, + app, + "checkErrors", + Some("Check for Errors"), + "Check for Errors", + crate::widgets::ButtonAction::new("mailings.checkErrors", json!({}), false), + ); }); }); group(ui, "Finish", None, app, |ui, app| { - menu_button(ui, app, "finish", Some("Finish &\nMerge"), "Finish & Merge", true, "mailings.finish", |ui, app| { - mi(ui, app, "Edit Individual Documents…", "mailings.finish", json!({})); - mi(ui, app, "Print Documents…", "mailings.printDocuments", json!({})); - }); + menu_button( + ui, + app, + crate::widgets::MenuButtonOptions::new("finish", Some("Finish &\nMerge"), "Finish & Merge", true, "mailings.finish"), + |ui, app| { + mi(ui, app, "Edit Individual Documents…", "mailings.finish", json!({})); + mi(ui, app, "Print Documents…", "mailings.printDocuments", json!({})); + }, + ); }); } diff --git a/crates/ui-egui/src/ribbon/picture.rs b/crates/ui-egui/src/ribbon/picture.rs index e49831ca2..4f527edf5 100644 --- a/crates/ui-egui/src/ribbon/picture.rs +++ b/crates/ui-egui/src/ribbon/picture.rs @@ -1,81 +1,117 @@ use super::arrange::{align_menu, group_menu, rotate_menu, rotation_field, wrap_menu}; use super::common::{esc_cancelled, mi, stack}; use super::shape::alt_text_group; -use super::*; +use super::{MeasureExt, Target, Ui, WordApp, big, group, json, menu_button, small}; /// Draws the Picture Format tab's Adjust, Picture Styles, Accessibility (Alt Text), Arrange and /// Size groups. pub(super) fn picture_format(app: &mut WordApp, ui: &mut Ui) { group(ui, "Adjust", None, app, |ui, app| { - menu_button(ui, app, "picture", Some("Corrections"), "Brightness, contrast and sharpness", true, "picture.corrections", |ui, app| { - for (l, b, c) in - [("Brighter +20%", 20.0, 0.0), ("Darker −20%", -20.0, 0.0), ("More Contrast +20%", 0.0, 20.0), ("Less Contrast −20%", 0.0, -20.0)] - { - mi(ui, app, l, "picture.corrections", json!({"brightness": b, "contrast": c})); - } - ui.separator(); - mi(ui, app, "Sharpen", "picture.corrections", json!({"sharpen": 40.0})); - mi(ui, app, "Soften", "picture.corrections", json!({"sharpen": -40.0})); - }); - menu_button(ui, app, "picture", Some("Color"), "Saturation and recolor", true, "picture.color", |ui, app| { - for (l, v) in [("Full Saturation 100%", 100.0), ("Muted 50%", 50.0), ("Gray 0%", 0.0), ("Vivid 200%", 200.0)] { - mi(ui, app, l, "picture.color", json!({"mode": "saturation", "saturation": v})); - } - ui.separator(); - mi(ui, app, "Grayscale", "picture.color", json!({"mode": "grayscale"})); - mi(ui, app, "Sepia", "picture.color", json!({"mode": "sepia"})); - mi(ui, app, "Washout", "picture.color", json!({"mode": "washout"})); - }); - menu_button(ui, app, "icons", Some("Icon Color"), "Recolor a one-color picture, such as an icon", true, "picture.iconColor", |ui, app| { - let theme = app.session.doc.settings.theme_colors.clone(); - crate::colors::menu(ui, app, &theme, Target::cmd("picture.iconColor")); - }); - menu_button(ui, app, "picture", Some("Transparency"), "Picture transparency", true, "picture.transparency", |ui, app| { - for v in [0.0, 15.0, 30.0, 50.0, 65.0, 80.0, 95.0] { - mi(ui, app, &format!("{v:.0}%"), "picture.transparency", json!({"percent": v})); - } - ui.separator(); - // An exact value, dragged with a live preview: absolute from the original - // pixels, one undo step for the whole drag (same pattern as Size › W/H). - // The current value isn't stored anywhere, so the slider reopens where it - // was last left for this picture. - let (media, off) = match wordcraft_engine::cmd::objects::selected(&app.session) { - Some((pos, wordcraft_doc::para::InlineObject::Image { media, .. })) => (media, pos.off), - _ => (String::new(), 0), - }; - let key = egui::Id::unique(("picture_transparency", media, off)); - let mut v = ui.data(|d| d.get_temp::(key)).unwrap_or(0.0); - let r = ui.add(egui::DragValue::new(&mut v).speed(1.0).range(0.0..=100.0).suffix("%")); - if r.changed() { - ui.data_mut(|d| d.insert_temp(key, v)); - if r.dragged() && !r.drag_started() { - app.session.join_next_undo(); + menu_button( + ui, + app, + crate::widgets::MenuButtonOptions::new("picture", Some("Corrections"), "Brightness, contrast and sharpness", true, "picture.corrections"), + |ui, app| { + for (l, b, c) in + [("Brighter +20%", 20.0, 0.0), ("Darker −20%", -20.0, 0.0), ("More Contrast +20%", 0.0, 20.0), ("Less Contrast −20%", 0.0, -20.0)] + { + mi(ui, app, l, "picture.corrections", json!({"brightness": b, "contrast": c})); } - let _ = app.run("picture.transparency", json!({"percent": v})); - } - }); + ui.separator(); + mi(ui, app, "Sharpen", "picture.corrections", json!({"sharpen": 40.0})); + mi(ui, app, "Soften", "picture.corrections", json!({"sharpen": -40.0})); + }, + ); + menu_button( + ui, + app, + crate::widgets::MenuButtonOptions::new("picture", Some("Color"), "Saturation and recolor", true, "picture.color"), + |ui, app| { + for (l, v) in [("Full Saturation 100%", 100.0), ("Muted 50%", 50.0), ("Gray 0%", 0.0), ("Vivid 200%", 200.0)] { + mi(ui, app, l, "picture.color", json!({"mode": "saturation", "saturation": v})); + } + ui.separator(); + mi(ui, app, "Grayscale", "picture.color", json!({"mode": "grayscale"})); + mi(ui, app, "Sepia", "picture.color", json!({"mode": "sepia"})); + mi(ui, app, "Washout", "picture.color", json!({"mode": "washout"})); + }, + ); + menu_button( + ui, + app, + crate::widgets::MenuButtonOptions::new( + "icons", + Some("Icon Color"), + "Recolor a one-color picture, such as an icon", + true, + "picture.iconColor", + ), + |ui, app| { + let theme = app.session.doc.settings.theme_colors.clone(); + crate::colors::menu(ui, app, &theme, Target::cmd("picture.iconColor")); + }, + ); + menu_button( + ui, + app, + crate::widgets::MenuButtonOptions::new("picture", Some("Transparency"), "Picture transparency", true, "picture.transparency"), + |ui, app| { + for v in [0.0, 15.0, 30.0, 50.0, 65.0, 80.0, 95.0] { + mi(ui, app, &format!("{v:.0}%"), "picture.transparency", json!({"percent": v})); + } + ui.separator(); + // An exact value, dragged with a live preview: absolute from the original + // pixels, one undo step for the whole drag (same pattern as Size › W/H). + // The current value isn't stored anywhere, so the slider reopens where it + // was last left for this picture. + let (media, off) = match wordcraft_engine::cmd::objects::selected(&app.session) { + Some((pos, wordcraft_doc::para::InlineObject::Image { media, .. })) => (media, pos.off), + _ => (String::new(), 0), + }; + let key = egui::Id::unique(("picture_transparency", media, off)); + let mut v = ui.data(|d| d.get_temp::(key)).unwrap_or(0.0); + let r = ui.add(egui::DragValue::new(&mut v).speed(1.0).range(0.0..=100.0).suffix("%")); + if r.changed() { + ui.data_mut(|d| d.insert_temp(key, v)); + if r.dragged() && !r.drag_started() { + app.session.join_next_undo(); + } + let _ = app.run("picture.transparency", json!({"percent": v})); + } + }, + ); stack(ui, |ui| { - small(ui, app, "picture", Some("Change"), "Change Picture", "ui.changePicture", json!({}), false); - small(ui, app, "picture", Some("Reset"), "Reset Picture", "picture.reset", json!({}), false); + small(ui, app, "picture", Some("Change"), "Change Picture", crate::widgets::ButtonAction::new("ui.changePicture", json!({}), false)); + small(ui, app, "picture", Some("Reset"), "Reset Picture", crate::widgets::ButtonAction::new("picture.reset", json!({}), false)); }); }); group(ui, "Picture Styles", None, app, |ui, app| { - menu_button(ui, app, "picture", Some("Styles"), "Frames and effects", true, "picture.style", |ui, app| { - for (l, s) in [ - ("Simple Frame", "simpleFrame"), - ("Thick Frame", "thickFrame"), - ("Rounded", "rounded"), - ("Soft Edge", "softEdge"), - ("Shadow", "shadow"), - ] { - mi(ui, app, l, "picture.style", json!({"style": s})); - } - }); - menu_button(ui, app, "picture", Some("Border"), "Picture border", true, "picture.border", |ui, app| { - for (l, w) in [("Thin", 2), ("Medium", 4), ("Thick", 8)] { - mi(ui, app, l, "picture.border", json!({"width": w})); - } - }); + menu_button( + ui, + app, + crate::widgets::MenuButtonOptions::new("picture", Some("Styles"), "Frames and effects", true, "picture.style"), + |ui, app| { + for (l, s) in [ + ("Simple Frame", "simpleFrame"), + ("Thick Frame", "thickFrame"), + ("Rounded", "rounded"), + ("Soft Edge", "softEdge"), + ("Shadow", "shadow"), + ] { + mi(ui, app, l, "picture.style", json!({"style": s})); + } + }, + ); + menu_button( + ui, + app, + crate::widgets::MenuButtonOptions::new("picture", Some("Border"), "Picture border", true, "picture.border"), + |ui, app| { + for (l, w) in [("Thin", 2), ("Medium", 4), ("Thick", 8)] { + mi(ui, app, l, "picture.border", json!({"width": w})); + } + }, + ); }); alt_text_group(ui, app); group(ui, "Arrange", None, app, |ui, app| { @@ -131,7 +167,7 @@ pub(super) fn picture_format(app: &mut WordApp, ui: &mut Ui) { ui.add_space(2.0); crate::widgets::row(ui, |ui| { let cropping = crate::crop::active(app).is_some(); - small(ui, app, "crop", Some("Crop"), "Crop", "ui.crop", json!({}), cropping); + small(ui, app, "crop", Some("Crop"), "Crop", crate::widgets::ButtonAction::new("ui.crop", json!({}), cropping)); for (i, side) in ["L", "T", "R", "B"].iter().enumerate() { ui.label(egui::RichText::new(tl!(side)).small()); let mut v = crop0[i] * 100.0; diff --git a/crates/ui-egui/src/ribbon/references.rs b/crates/ui-egui/src/ribbon/references.rs index 3ffc2d4a8..588a5d202 100644 --- a/crates/ui-egui/src/ribbon/references.rs +++ b/crates/ui-egui/src/ribbon/references.rs @@ -1,32 +1,63 @@ use super::common::{mi, mi_text, stack}; use super::language::mi_check; -use super::*; +use super::{Ui, WordApp, big, group, json, menu_button, small}; pub(super) fn references(app: &mut WordApp, ui: &mut Ui) { group(ui, "Table of Contents", None, app, |ui, app| { - menu_button(ui, app, "toc", Some("Table of\nContents"), "Table of Contents", true, "references.toc", |ui, app| { - mi(ui, app, "Automatic Table 1 (3 levels)", "references.toc", json!({"levels": 3})); - mi(ui, app, "Automatic Table 2 (2 levels)", "references.toc", json!({"levels": 2, "title": "Table of Contents"})); - ui.separator(); - mi(ui, app, "Custom Table of Contents…", "ui.dialog", json!({"name": "toc"})); - mi(ui, app, "Remove Table of Contents", "references.removeToc", json!({})); - }); + menu_button( + ui, + app, + crate::widgets::MenuButtonOptions::new("toc", Some("Table of\nContents"), "Table of Contents", true, "references.toc"), + |ui, app| { + mi(ui, app, "Automatic Table 1 (3 levels)", "references.toc", json!({"levels": 3})); + mi(ui, app, "Automatic Table 2 (2 levels)", "references.toc", json!({"levels": 2, "title": "Table of Contents"})); + ui.separator(); + mi(ui, app, "Custom Table of Contents…", "ui.dialog", json!({"name": "toc"})); + mi(ui, app, "Remove Table of Contents", "references.removeToc", json!({})); + }, + ); stack(ui, |ui| { - menu_button(ui, app, "addText", Some("Add Text"), "Add Text", false, "references.addText", |ui, app| { - mi(ui, app, "Do Not Show in TOC", "references.addText", json!({"level": 0})); - for l in 1..=3u64 { - mi(ui, app, &format!("Level {l}"), "references.addText", json!({"level": l})); - } - }); - small(ui, app, "updateTable", Some("Update Table"), "Update Table", "references.updateToc", json!({}), false); + menu_button( + ui, + app, + crate::widgets::MenuButtonOptions::new("addText", Some("Add Text"), "Add Text", false, "references.addText"), + |ui, app| { + mi(ui, app, "Do Not Show in TOC", "references.addText", json!({"level": 0})); + for l in 1..=3u64 { + mi(ui, app, &format!("Level {l}"), "references.addText", json!({"level": l})); + } + }, + ); + small( + ui, + app, + "updateTable", + Some("Update Table"), + "Update Table", + crate::widgets::ButtonAction::new("references.updateToc", json!({}), false), + ); }); }); group(ui, "Footnotes", Some("references.noteOptions"), app, |ui, app| { big(ui, app, "footnote", "Insert\nFootnote", "references.footnote", json!({}), false); stack(ui, |ui| { - small(ui, app, "endnote", Some("Insert Endnote"), "Insert Endnote", "references.endnote", json!({}), false); - small(ui, app, "nextFootnote", Some("Next Footnote"), "Next Footnote", "references.nextFootnote", json!({}), false); - small(ui, app, "showNotes", Some("Show Notes"), "Show Notes", "references.notes", json!({}), false); + small( + ui, + app, + "endnote", + Some("Insert Endnote"), + "Insert Endnote", + crate::widgets::ButtonAction::new("references.endnote", json!({}), false), + ); + small( + ui, + app, + "nextFootnote", + Some("Next Footnote"), + "Next Footnote", + crate::widgets::ButtonAction::new("references.nextFootnote", json!({}), false), + ); + small(ui, app, "showNotes", Some("Show Notes"), "Show Notes", crate::widgets::ButtonAction::new("references.notes", json!({}), false)); }); }); group(ui, "Research", None, app, |ui, app| { @@ -34,7 +65,7 @@ pub(super) fn references(app: &mut WordApp, ui: &mut Ui) { }); group(ui, "Citations & Bibliography", None, app, |ui, app| { // The document's sources to cite, then a new one (Create Source, #402). - menu_button(ui, app, "citation", Some("Insert\nCitation"), "Insert Citation", true, "", |ui, app| { + menu_button(ui, app, crate::widgets::MenuButtonOptions::new("citation", Some("Insert\nCitation"), "Insert Citation", true, ""), |ui, app| { let sources: Vec<(String, String)> = app.session.doc.sources.iter().map(|s| (s.tag.clone(), format!("{} — {}", s.author, s.title))).collect(); for (tag, label) in sources { @@ -46,15 +77,34 @@ pub(super) fn references(app: &mut WordApp, ui: &mut Ui) { mi(ui, app, "Add New Source…", "ui.dialog", json!({"name": "createSource"})); }); stack(ui, |ui| { - small(ui, app, "sources", Some("Manage Sources"), "Manage Sources", "references.sources", json!({}), false); + small( + ui, + app, + "sources", + Some("Manage Sources"), + "Manage Sources", + crate::widgets::ButtonAction::new("references.sources", json!({}), false), + ); let current = app.session.bib_style.clone(); let label = crate::i18n::fmt(tl!("Style: {style}"), &[("style", ¤t)]); - menu_button(ui, app, "styles", Some(&label), "Citation Style", false, "references.citationStyle", |ui, app| { - for st in &wordcraft_engine::cmd::citations::STYLES { - mi_check(ui, app, st.name, st.name == current, "references.citationStyle", json!({"style": st.name})); - } - }); - small(ui, app, "bibliography", Some("Bibliography"), "Bibliography", "references.bibliography", json!({}), false); + menu_button( + ui, + app, + crate::widgets::MenuButtonOptions::new("styles", Some(&label), "Citation Style", false, "references.citationStyle"), + |ui, app| { + for st in &wordcraft_engine::cmd::citations::STYLES { + mi_check(ui, app, st.name, st.name == current, "references.citationStyle", json!({"style": st.name})); + } + }, + ); + small( + ui, + app, + "bibliography", + Some("Bibliography"), + "Bibliography", + crate::widgets::ButtonAction::new("references.bibliography", json!({}), false), + ); }); }); group(ui, "Captions", None, app, |ui, app| { @@ -66,19 +116,38 @@ pub(super) fn references(app: &mut WordApp, ui: &mut Ui) { "tableOfFigures", Some("Insert Table of Figures"), "Insert Table of Figures", - "references.tableOfFigures", - json!({}), - false, + crate::widgets::ButtonAction::new("references.tableOfFigures", json!({}), false), + ); + small( + ui, + app, + "update", + Some("Update Table"), + "Update Table of Figures", + crate::widgets::ButtonAction::new("references.updateFigures", json!({}), false), + ); + small( + ui, + app, + "crossRef", + Some("Cross-reference"), + "Cross-reference", + crate::widgets::ButtonAction::new("insert.crossReference", json!({}), false), ); - small(ui, app, "update", Some("Update Table"), "Update Table of Figures", "references.updateFigures", json!({}), false); - small(ui, app, "crossRef", Some("Cross-reference"), "Cross-reference", "insert.crossReference", json!({}), false); }); }); group(ui, "Index", None, app, |ui, app| { big(ui, app, "markEntry", "Mark\nEntry", "references.markEntry", json!({}), false); stack(ui, |ui| { - small(ui, app, "index", Some("Insert Index"), "Insert Index", "references.index", json!({}), false); - small(ui, app, "update", Some("Update Index"), "Update Index", "references.updateIndex", json!({}), false); + small(ui, app, "index", Some("Insert Index"), "Insert Index", crate::widgets::ButtonAction::new("references.index", json!({}), false)); + small( + ui, + app, + "update", + Some("Update Index"), + "Update Index", + crate::widgets::ButtonAction::new("references.updateIndex", json!({}), false), + ); }); }); group(ui, "Table of Authorities", None, app, |ui, app| { @@ -90,11 +159,16 @@ pub(super) fn references(app: &mut WordApp, ui: &mut Ui) { "tableOfAuthorities", Some("Insert Table of Authorities"), "Insert Table of Authorities", - "references.tableOfAuthorities", - json!({}), - false, + crate::widgets::ButtonAction::new("references.tableOfAuthorities", json!({}), false), + ); + small( + ui, + app, + "update", + Some("Update Table"), + "Update Table", + crate::widgets::ButtonAction::new("references.updateAuthorities", json!({}), false), ); - small(ui, app, "update", Some("Update Table"), "Update Table", "references.updateAuthorities", json!({}), false); }); }); } diff --git a/crates/ui-egui/src/ribbon/review.rs b/crates/ui-egui/src/ribbon/review.rs index 3ae92576b..663e71e8a 100644 --- a/crates/ui-egui/src/ribbon/review.rs +++ b/crates/ui-egui/src/ribbon/review.rs @@ -1,13 +1,20 @@ use super::common::{mi, stack}; use super::language::language_menu; -use super::*; +use super::{Ui, WordApp, big, big_toggle, group, json, menu_button, small}; pub(super) fn review(app: &mut WordApp, ui: &mut Ui) { group(ui, "Proofing", None, app, |ui, app| { big(ui, app, "spelling", "Spelling &\nGrammar", "review.spelling", json!({}), false); stack(ui, |ui| { - small(ui, app, "thesaurus", Some("Thesaurus"), "Thesaurus", "review.thesaurus", json!({}), false); - small(ui, app, "wordCount", Some("Word Count"), "Word Count", "ui.dialog", json!({"name": "wordCount"}), false); + small(ui, app, "thesaurus", Some("Thesaurus"), "Thesaurus", crate::widgets::ButtonAction::new("review.thesaurus", json!({}), false)); + small( + ui, + app, + "wordCount", + Some("Word Count"), + "Word Count", + crate::widgets::ButtonAction::new("ui.dialog", json!({"name": "wordCount"}), false), + ); }); }); group(ui, "Speech", None, app, |ui, app| { @@ -18,14 +25,28 @@ pub(super) fn review(app: &mut WordApp, ui: &mut Ui) { }); group(ui, "Language", None, app, |ui, app| { big(ui, app, "translate", "Translate", "review.translate", json!({}), false); - menu_button(ui, app, "language", Some("Language"), "Language", true, "", language_menu); + menu_button(ui, app, crate::widgets::MenuButtonOptions::new("language", Some("Language"), "Language", true, ""), language_menu); }); group(ui, "Comments", None, app, |ui, app| { big(ui, app, "newComment", "New\nComment", "review.newComment", json!({}), false); stack(ui, |ui| { - small(ui, app, "deleteComment", Some("Delete"), "Delete Comment", "review.deleteComment", json!({}), false); - small(ui, app, "prevComment", Some("Previous"), "Previous Comment", "review.previousComment", json!({}), false); - small(ui, app, "nextComment", Some("Next"), "Next Comment", "review.nextComment", json!({}), false); + small( + ui, + app, + "deleteComment", + Some("Delete"), + "Delete Comment", + crate::widgets::ButtonAction::new("review.deleteComment", json!({}), false), + ); + small( + ui, + app, + "prevComment", + Some("Previous"), + "Previous Comment", + crate::widgets::ButtonAction::new("review.previousComment", json!({}), false), + ); + small(ui, app, "nextComment", Some("Next"), "Next Comment", crate::widgets::ButtonAction::new("review.nextComment", json!({}), false)); }); let shown = app.session.view.comments_pane; big(ui, app, "showComments", "Show\nComments", "view.commentsPane", json!({"value": !shown}), false); @@ -38,35 +59,51 @@ pub(super) fn review(app: &mut WordApp, ui: &mut Ui) { menu_button( ui, app, - "markup", - Some(if app.session.view.show_markup { "All Markup" } else { "No Markup" }), - "Display for Review", - false, - "review.markup", + crate::widgets::MenuButtonOptions::new( + "markup", + Some(if app.session.view.show_markup { "All Markup" } else { "No Markup" }), + "Display for Review", + false, + "review.markup", + ), |ui, app| { mi(ui, app, "All Markup", "review.markup", json!({"value": "all"})); mi(ui, app, "No Markup", "review.markup", json!({"value": "noMarkup"})); }, ); - small(ui, app, "reviewingPane", Some("Reviewing Pane"), "Reviewing Pane", "review.changes", json!({}), false); + small( + ui, + app, + "reviewingPane", + Some("Reviewing Pane"), + "Reviewing Pane", + crate::widgets::ButtonAction::new("review.changes", json!({}), false), + ); }); }); group(ui, "Changes", None, app, |ui, app| { - menu_button(ui, app, "accept", Some("Accept"), "Accept", true, "review.accept", |ui, app| { + menu_button(ui, app, crate::widgets::MenuButtonOptions::new("accept", Some("Accept"), "Accept", true, "review.accept"), |ui, app| { mi(ui, app, "Accept and Move to Next", "review.accept", json!({"next": true})); mi(ui, app, "Accept This Change", "review.accept", json!({})); mi(ui, app, "Accept All Changes", "review.acceptAll", json!({})); mi(ui, app, "Accept All Changes and Stop Tracking", "review.acceptAll", json!({"stopTracking": true})); }); - menu_button(ui, app, "reject", Some("Reject"), "Reject", true, "review.reject", |ui, app| { + menu_button(ui, app, crate::widgets::MenuButtonOptions::new("reject", Some("Reject"), "Reject", true, "review.reject"), |ui, app| { mi(ui, app, "Reject and Move to Next", "review.reject", json!({"next": true})); mi(ui, app, "Reject Change", "review.reject", json!({})); mi(ui, app, "Reject All Changes", "review.rejectAll", json!({})); mi(ui, app, "Reject All Changes and Stop Tracking", "review.rejectAll", json!({"stopTracking": true})); }); stack(ui, |ui| { - small(ui, app, "prevChange", Some("Previous"), "Previous Change", "review.previousChange", json!({}), false); - small(ui, app, "nextChange", Some("Next"), "Next Change", "review.nextChange", json!({}), false); + small( + ui, + app, + "prevChange", + Some("Previous"), + "Previous Change", + crate::widgets::ButtonAction::new("review.previousChange", json!({}), false), + ); + small(ui, app, "nextChange", Some("Next"), "Next Change", crate::widgets::ButtonAction::new("review.nextChange", json!({}), false)); }); }); group(ui, "Compare", None, app, |ui, app| { @@ -108,14 +145,28 @@ pub(super) fn developer(app: &mut WordApp, ui: &mut Ui) { for row in rows { crate::widgets::row(ui, |ui| { for (icon, tip, id) in row { - small(ui, app, icon, None, tip, id, json!({}), false); + small(ui, app, icon, None, tip, crate::widgets::ButtonAction::new(id, json!({}), false)); } }); } }); stack(ui, |ui| { - small(ui, app, "designMode", Some("Design Mode"), "Design Mode", "developer.designMode", json!({}), design); - small(ui, app, "properties", Some("Properties"), "Properties", "developer.properties", json!({}), false); + small( + ui, + app, + "designMode", + Some("Design Mode"), + "Design Mode", + crate::widgets::ButtonAction::new("developer.designMode", json!({}), design), + ); + small( + ui, + app, + "properties", + Some("Properties"), + "Properties", + crate::widgets::ButtonAction::new("developer.properties", json!({}), false), + ); }); }); } diff --git a/crates/ui-egui/src/ribbon/shape.rs b/crates/ui-egui/src/ribbon/shape.rs index 728e0187a..aa30f0cb6 100644 --- a/crates/ui-egui/src/ribbon/shape.rs +++ b/crates/ui-egui/src/ribbon/shape.rs @@ -1,7 +1,7 @@ use super::arrange::{align_menu, rotate_menu, rotation_field, wrap_menu}; use super::common::{mi, stack}; use super::language::mi_check; -use super::*; +use super::{Target, Ui, WordApp, big, group, json, menu_button, small, vec2}; /// Shape Fill › Gradient: No Gradient, Light and Dark Variations of the shape's colour, More /// Gradients… (the Format Shape pane). @@ -53,32 +53,45 @@ pub(super) fn shape_format(app: &mut WordApp, ui: &mut Ui) { let theme = app.session.doc.settings.theme_colors.clone(); group(ui, "Shape Styles", None, app, |ui, app| { stack(ui, |ui| { - menu_button(ui, app, "shading", Some("Shape Fill"), "Shape Fill", false, "shape.fill", |ui, app| { - mi(ui, app, "No Color", "shape.fill", json!({"color": null})); - crate::colors::menu(ui, app, &theme, Target::cmd("shape.fill")); - ui.separator(); - ui.menu_button(tl!("Gradient"), |ui| gradient_menu(ui, app, fill.as_ref())); - }); - menu_button(ui, app, "pen", Some("Shape Outline"), "Shape Outline", false, "shape.outline", |ui, app| { - mi(ui, app, "No Color", "shape.outline", json!({"color": null})); - crate::colors::menu(ui, app, &theme, Target::cmd("shape.outline")); - ui.separator(); - ui.menu_button(tl!("Weight"), |ui| { - let color = stroke.unwrap_or(wordcraft_doc::Rgb::BLACK).hex(); - for w in [0.25, 0.5, 0.75, 1.0, 1.5, 2.25, 3.0, 4.5, 6.0] { - mi(ui, app, &format!("{w} pt"), "shape.outline", json!({"color": color, "width": w})); - } - }); - }); - menu_button(ui, app, "shapeEffects", Some("Shape Effects"), "Shadow, glow and soft edges", false, "shape.effects", |ui, app| { - shape_effects_menu(ui, app, &theme, glow_size) - }); + menu_button( + ui, + app, + crate::widgets::MenuButtonOptions::new("shading", Some("Shape Fill"), "Shape Fill", false, "shape.fill"), + |ui, app| { + mi(ui, app, "No Color", "shape.fill", json!({"color": null})); + crate::colors::menu(ui, app, &theme, Target::cmd("shape.fill")); + ui.separator(); + ui.menu_button(tl!("Gradient"), |ui| gradient_menu(ui, app, fill.as_ref())); + }, + ); + menu_button( + ui, + app, + crate::widgets::MenuButtonOptions::new("pen", Some("Shape Outline"), "Shape Outline", false, "shape.outline"), + |ui, app| { + mi(ui, app, "No Color", "shape.outline", json!({"color": null})); + crate::colors::menu(ui, app, &theme, Target::cmd("shape.outline")); + ui.separator(); + ui.menu_button(tl!("Weight"), |ui| { + let color = stroke.unwrap_or(wordcraft_doc::Rgb::BLACK).hex(); + for w in [0.25, 0.5, 0.75, 1.0, 1.5, 2.25, 3.0, 4.5, 6.0] { + mi(ui, app, &format!("{w} pt"), "shape.outline", json!({"color": color, "width": w})); + } + }); + }, + ); + menu_button( + ui, + app, + crate::widgets::MenuButtonOptions::new("shapeEffects", Some("Shape Effects"), "Shadow, glow and soft edges", false, "shape.effects"), + |ui, app| shape_effects_menu(ui, app, &theme, glow_size), + ); }); }); alt_text_group(ui, app); if wordcraft_engine::cmd::canvas::selected_canvas(&app.session).is_some() { group(ui, "Drawing Canvas", None, app, |ui, app| { - menu_button(ui, app, "canvas", Some("Drawing\nCanvas"), "Drawing Canvas", true, "", |ui, app| { + menu_button(ui, app, crate::widgets::MenuButtonOptions::new("canvas", Some("Drawing\nCanvas"), "Drawing Canvas", true, ""), |ui, app| { mi(ui, app, "Fit", "canvas.fit", json!({})); mi(ui, app, "Expand", "canvas.expand", json!({})); ui.menu_button(tl!("Scale Drawing"), |ui| { @@ -90,16 +103,21 @@ pub(super) fn shape_format(app: &mut WordApp, ui: &mut Ui) { }); } group(ui, "WordArt Styles", None, app, |ui, app| { - menu_button(ui, app, "wordArt", Some("Styles"), "WordArt Styles", true, "", |ui, app| { + menu_button(ui, app, crate::widgets::MenuButtonOptions::new("wordArt", Some("Styles"), "WordArt Styles", true, ""), |ui, app| { crate::wordart_menus::style_gallery(ui, app, "wordArt.style") }); stack(ui, |ui| { - menu_button(ui, app, "textFill", Some("Text Fill"), "Text Fill", false, "", |ui, app| crate::wordart_menus::fill_menu(ui, app, &theme)); - let outline = crate::wordart_menus::current_outline(app); - menu_button(ui, app, "textOutline", Some("Text Outline"), "Text Outline", false, "", |ui, app| { - crate::wordart_menus::outline_menu(ui, app, &theme, outline) + menu_button(ui, app, crate::widgets::MenuButtonOptions::new("textFill", Some("Text Fill"), "Text Fill", false, ""), |ui, app| { + crate::wordart_menus::fill_menu(ui, app, &theme) }); - menu_button(ui, app, "transform", Some("Text Effects"), "Text Effects", false, "", |ui, app| { + let outline = crate::wordart_menus::current_outline(app); + menu_button( + ui, + app, + crate::widgets::MenuButtonOptions::new("textOutline", Some("Text Outline"), "Text Outline", false, ""), + |ui, app| crate::wordart_menus::outline_menu(ui, app, &theme, outline), + ); + menu_button(ui, app, crate::widgets::MenuButtonOptions::new("transform", Some("Text Effects"), "Text Effects", false, ""), |ui, app| { crate::wordart_menus::effects_menu(ui, app, &theme, true) }); }); @@ -146,26 +164,45 @@ fn shape_text_group(app: &mut WordApp, ui: &mut Ui) { let body = target.and_then(|(_, id)| app.session.doc.parts.get(&id).map(|p| p.body)).unwrap_or_default(); group(ui, "Text", None, app, |ui, app| { stack(ui, |ui| { - menu_button(ui, app, "textDirection", Some("Text Direction"), "Text Direction", false, "", |ui, app| { - for (label, vert) in [ - ("Horizontal", TextVert::Horz), - ("Rotate all text 90°", TextVert::Vert), - ("Rotate all text 270°", TextVert::Vert270), - ("Stacked", TextVert::Stacked), - ] { - mi_check(ui, app, label, body.vert == vert, "shape.textDirection", json!({"dir": vert.ooxml()})); - } - }); - menu_button(ui, app, "alignText", Some("Align Text"), "Align Text", false, "", |ui, app| { + menu_button( + ui, + app, + crate::widgets::MenuButtonOptions::new("textDirection", Some("Text Direction"), "Text Direction", false, ""), + |ui, app| { + for (label, vert) in [ + ("Horizontal", TextVert::Horz), + ("Rotate all text 90°", TextVert::Vert), + ("Rotate all text 270°", TextVert::Vert270), + ("Stacked", TextVert::Stacked), + ] { + mi_check(ui, app, label, body.vert == vert, "shape.textDirection", json!({"dir": vert.ooxml()})); + } + }, + ); + menu_button(ui, app, crate::widgets::MenuButtonOptions::new("alignText", Some("Align Text"), "Align Text", false, ""), |ui, app| { for (label, anchor) in [("Top", VAlign::Top), ("Middle", VAlign::Center), ("Bottom", VAlign::Bottom)] { mi_check(ui, app, label, body.anchor == anchor, "shape.alignText", json!({"anchor": wordcraft_doc::props::anchor_ooxml(anchor)})); } }); if body.next.is_some() { - small(ui, app, "textBoxUnlink", Some("Break Link"), "Break Link", "shape.breakLink", json!({}), false); + small( + ui, + app, + "textBoxUnlink", + Some("Break Link"), + "Break Link", + crate::widgets::ButtonAction::new("shape.breakLink", json!({}), false), + ); } else { let waiting = app.canvas.link_from.is_some(); - small(ui, app, "textBoxLink", Some("Create Link"), "Create Link", "ui.linkTextBox", json!({}), waiting); + small( + ui, + app, + "textBoxLink", + Some("Create Link"), + "Create Link", + crate::widgets::ButtonAction::new("ui.linkTextBox", json!({}), waiting), + ); } }); }); diff --git a/crates/ui-egui/src/ribbon/table.rs b/crates/ui-egui/src/ribbon/table.rs index 39746e35f..237e8660d 100644 --- a/crates/ui-egui/src/ribbon/table.rs +++ b/crates/ui-egui/src/ribbon/table.rs @@ -1,5 +1,5 @@ use super::common::{mi, stack}; -use super::*; +use super::{MeasureExt, Remember, Target, Ui, WordApp, big, group, json, menu_button, small, split, vec2}; pub(super) fn table_design(app: &mut WordApp, ui: &mut Ui) { let look = app.session.sel.focus.path.cell().and_then(|(tp, _, _)| app.session.doc.table(app.session.sel.focus.story, &tp).map(|t| t.props.look)); @@ -60,28 +60,41 @@ pub(super) fn table_design(app: &mut WordApp, ui: &mut Ui) { let can_modify = current.is_some(); // Built-in table styles can't be deleted. let can_delete = current == Some(false); - menu_button(ui, app, "styles", Some("Styles"), "Table Styles", false, "table.style", |ui, app| { - mi(ui, app, "New Table Style…", "ui.dialog", json!({"name": "newTableStyle"})); - if ui.add_enabled(can_modify, egui::Button::new(tl!("Modify Table Style…")).min_size(vec2(200.0, 0.0))).clicked() { - let _ = app.run("ui.dialog", json!({"name": "modifyTableStyle"})); - ui.close(); - } - if ui.add_enabled(can_delete, egui::Button::new(tl!("Delete Table Style")).min_size(vec2(200.0, 0.0))).clicked() { - if let Err(e) = app.run("table.deleteStyle", json!({})) { - app.status(e); + menu_button( + ui, + app, + crate::widgets::MenuButtonOptions::new("styles", Some("Styles"), "Table Styles", false, "table.style"), + |ui, app| { + mi(ui, app, "New Table Style…", "ui.dialog", json!({"name": "newTableStyle"})); + if ui.add_enabled(can_modify, egui::Button::new(tl!("Modify Table Style…")).min_size(vec2(200.0, 0.0))).clicked() { + let _ = app.run("ui.dialog", json!({"name": "modifyTableStyle"})); + ui.close(); } - ui.close(); - } - }); - split(ui, app, "shading", "Shading", "table.shading", json!({"color": app.canvas.last_shading.clone()}), false, None, |ui, app| { - mi(ui, app, "No Color", "table.shading", json!({"color": null})); - let theme = app.session.doc.settings.theme_colors.clone(); - crate::colors::menu(ui, app, &theme, Target::cmd("table.shading").remember(Remember::Shading)); - }); + if ui.add_enabled(can_delete, egui::Button::new(tl!("Delete Table Style")).min_size(vec2(200.0, 0.0))).clicked() { + if let Err(e) = app.run("table.deleteStyle", json!({})) { + app.status(e); + } + ui.close(); + } + }, + ); + split( + ui, + app, + "shading", + "Shading", + crate::widgets::ButtonAction::new("table.shading", json!({"color": app.canvas.last_shading.clone()}), false), + None, + |ui, app| { + mi(ui, app, "No Color", "table.shading", json!({"color": null})); + let theme = app.session.doc.settings.theme_colors.clone(); + crate::colors::menu(ui, app, &theme, Target::cmd("table.shading").remember(Remember::Shading)); + }, + ); }); }); group(ui, "Borders", None, app, |ui, app| { - menu_button(ui, app, "borders", Some("Borders"), "Borders", true, "table.borders", |ui, app| { + menu_button(ui, app, crate::widgets::MenuButtonOptions::new("borders", Some("Borders"), "Borders", true, "table.borders"), |ui, app| { for (l, k) in [ ("All Borders", "all"), ("Outside Borders", "outside"), @@ -163,15 +176,34 @@ fn cell_size_boxes(ui: &mut Ui, app: &mut WordApp) { pub(super) fn table_layout(app: &mut WordApp, ui: &mut Ui) { group(ui, "Table", None, app, |ui, app| { stack(ui, |ui| { - menu_button(ui, app, "selectTable", Some("Select"), "Select", false, "table.selectTable", |ui, app| { - mi(ui, app, "Select Cell", "table.selectCell", json!({})); - mi(ui, app, "Select Column", "table.selectColumn", json!({})); - mi(ui, app, "Select Row", "table.selectRow", json!({})); - mi(ui, app, "Select Table", "table.selectTable", json!({})); - }); + menu_button( + ui, + app, + crate::widgets::MenuButtonOptions::new("selectTable", Some("Select"), "Select", false, "table.selectTable"), + |ui, app| { + mi(ui, app, "Select Cell", "table.selectCell", json!({})); + mi(ui, app, "Select Column", "table.selectColumn", json!({})); + mi(ui, app, "Select Row", "table.selectRow", json!({})); + mi(ui, app, "Select Table", "table.selectTable", json!({})); + }, + ); let g = app.session.view.table_gridlines; - small(ui, app, "gridlines", Some("View Gridlines"), "View Gridlines", "table.viewGridlines", json!({}), g); - small(ui, app, "properties", Some("Properties"), "Table Properties", "table.properties", json!({}), false); + small( + ui, + app, + "gridlines", + Some("View Gridlines"), + "View Gridlines", + crate::widgets::ButtonAction::new("table.viewGridlines", json!({}), g), + ); + small( + ui, + app, + "properties", + Some("Properties"), + "Table Properties", + crate::widgets::ButtonAction::new("table.properties", json!({}), false), + ); }); }); group(ui, "Draw", None, app, |ui, app| { @@ -181,36 +213,76 @@ pub(super) fn table_layout(app: &mut WordApp, ui: &mut Ui) { crate::widgets::big_toggle(ui, app, "eraser", "Eraser", "table.eraser", tool == Some(TableTool::Erase)); }); group(ui, "Rows & Columns", None, app, |ui, app| { - menu_button(ui, app, "deleteTable", Some("Delete"), "Delete", true, "table.deleteTable", |ui, app| { - mi(ui, app, "Delete Cells", "table.deleteCells", json!({})); - mi(ui, app, "Delete Columns", "table.deleteColumn", json!({})); - mi(ui, app, "Delete Rows", "table.deleteRow", json!({})); - mi(ui, app, "Delete Table", "table.deleteTable", json!({})); - }); + menu_button( + ui, + app, + crate::widgets::MenuButtonOptions::new("deleteTable", Some("Delete"), "Delete", true, "table.deleteTable"), + |ui, app| { + mi(ui, app, "Delete Cells", "table.deleteCells", json!({})); + mi(ui, app, "Delete Columns", "table.deleteColumn", json!({})); + mi(ui, app, "Delete Rows", "table.deleteRow", json!({})); + mi(ui, app, "Delete Table", "table.deleteTable", json!({})); + }, + ); big(ui, app, "insertAbove", "Insert\nAbove", "table.insertRowAbove", json!({}), false); stack(ui, |ui| { - small(ui, app, "insertBelow", Some("Insert Below"), "Insert Below", "table.insertRowBelow", json!({}), false); - small(ui, app, "insertLeft", Some("Insert Left"), "Insert Left", "table.insertColumnLeft", json!({}), false); - small(ui, app, "insertRight", Some("Insert Right"), "Insert Right", "table.insertColumnRight", json!({}), false); + small( + ui, + app, + "insertBelow", + Some("Insert Below"), + "Insert Below", + crate::widgets::ButtonAction::new("table.insertRowBelow", json!({}), false), + ); + small( + ui, + app, + "insertLeft", + Some("Insert Left"), + "Insert Left", + crate::widgets::ButtonAction::new("table.insertColumnLeft", json!({}), false), + ); + small( + ui, + app, + "insertRight", + Some("Insert Right"), + "Insert Right", + crate::widgets::ButtonAction::new("table.insertColumnRight", json!({}), false), + ); }); }); group(ui, "Merge", None, app, |ui, app| { stack(ui, |ui| { - small(ui, app, "merge", Some("Merge Cells"), "Merge Cells", "table.merge", json!({}), false); - small(ui, app, "splitCells", Some("Split Cells"), "Split Cells", "table.split", json!({}), false); - small(ui, app, "splitTable", Some("Split Table"), "Split Table", "table.splitTable", json!({}), false); + small(ui, app, "merge", Some("Merge Cells"), "Merge Cells", crate::widgets::ButtonAction::new("table.merge", json!({}), false)); + small(ui, app, "splitCells", Some("Split Cells"), "Split Cells", crate::widgets::ButtonAction::new("table.split", json!({}), false)); + small(ui, app, "splitTable", Some("Split Table"), "Split Table", crate::widgets::ButtonAction::new("table.splitTable", json!({}), false)); }); }); group(ui, "Cell Size", None, app, |ui, app| { - menu_button(ui, app, "autofit", Some("AutoFit"), "AutoFit", true, "table.autofit", |ui, app| { + menu_button(ui, app, crate::widgets::MenuButtonOptions::new("autofit", Some("AutoFit"), "AutoFit", true, "table.autofit"), |ui, app| { mi(ui, app, "AutoFit Contents", "table.autofit", json!({"mode": "contents"})); mi(ui, app, "AutoFit Window", "table.autofit", json!({"mode": "window"})); mi(ui, app, "Fixed Column Width", "table.autofit", json!({"mode": "fixed"})); }); cell_size_boxes(ui, app); stack(ui, |ui| { - small(ui, app, "distributeRows", Some("Distribute Rows"), "Distribute Rows", "table.distributeRows", json!({}), false); - small(ui, app, "distributeCols", Some("Distribute Columns"), "Distribute Columns", "table.distributeColumns", json!({}), false); + small( + ui, + app, + "distributeRows", + Some("Distribute Rows"), + "Distribute Rows", + crate::widgets::ButtonAction::new("table.distributeRows", json!({}), false), + ); + small( + ui, + app, + "distributeCols", + Some("Distribute Columns"), + "Distribute Columns", + crate::widgets::ButtonAction::new("table.distributeColumns", json!({}), false), + ); }); }); group(ui, "Alignment", None, app, |ui, app| { @@ -221,7 +293,7 @@ pub(super) fn table_layout(app: &mut WordApp, ui: &mut Ui) { [("bottomLeft", "Align Bottom Left"), ("bottomCenter", "Align Bottom Center"), ("bottomRight", "Align Bottom Right")], ] { for (v, tip) in row { - small(ui, app, "cellAlign", None, tip, "table.cellAlign", json!({"value": v}), false); + small(ui, app, "cellAlign", None, tip, crate::widgets::ButtonAction::new("table.cellAlign", json!({"value": v}), false)); } ui.end_row(); } @@ -232,9 +304,16 @@ pub(super) fn table_layout(app: &mut WordApp, ui: &mut Ui) { group(ui, "Data", None, app, |ui, app| { big(ui, app, "sort", "Sort", "table.sort", json!({}), false); stack(ui, |ui| { - small(ui, app, "repeatHeader", Some("Repeat Header Rows"), "Repeat Header Rows", "table.repeatHeader", json!({}), false); - small(ui, app, "toText", Some("Convert to Text"), "Convert to Text", "table.toText", json!({}), false); - small(ui, app, "formula", Some("Formula"), "Formula", "table.formula", json!({}), false); + small( + ui, + app, + "repeatHeader", + Some("Repeat Header Rows"), + "Repeat Header Rows", + crate::widgets::ButtonAction::new("table.repeatHeader", json!({}), false), + ); + small(ui, app, "toText", Some("Convert to Text"), "Convert to Text", crate::widgets::ButtonAction::new("table.toText", json!({}), false)); + small(ui, app, "formula", Some("Formula"), "Formula", crate::widgets::ButtonAction::new("table.formula", json!({}), false)); }); }); } diff --git a/crates/ui-egui/src/ribbon/tabs.rs b/crates/ui-egui/src/ribbon/tabs.rs index 9ca7f5bce..2763c2e4a 100644 --- a/crates/ui-egui/src/ribbon/tabs.rs +++ b/crates/ui-egui/src/ribbon/tabs.rs @@ -1,4 +1,4 @@ -use super::*; +use super::{Stroke, Tokens, Ui, WordApp, json, semibold}; pub const TABS: [&str; 13] = wordcraft_engine::ribbon::TABS; diff --git a/crates/ui-egui/src/ribbon/underline.rs b/crates/ui-egui/src/ribbon/underline.rs index d3c852107..ae84bba86 100644 --- a/crates/ui-egui/src/ribbon/underline.rs +++ b/crates/ui-egui/src/ribbon/underline.rs @@ -1,5 +1,5 @@ use super::common::mi; -use super::*; +use super::{Rect, Stroke, Target, Tokens, Ui, WordApp, json, pos2, vec2}; /// The underline styles Home › Underline offers as line samples: (`format.underline` style, name). pub(super) const UNDERLINE_STYLES: [(&str, &str); 8] = [ diff --git a/crates/ui-egui/src/ribbon/view.rs b/crates/ui-egui/src/ribbon/view.rs index 730c97393..e2fa6671d 100644 --- a/crates/ui-egui/src/ribbon/view.rs +++ b/crates/ui-egui/src/ribbon/view.rs @@ -1,5 +1,5 @@ use super::common::stack; -use super::*; +use super::{Ui, WordApp, big, big_toggle, group, json, menu_button, small, vec2}; /// Draws the View tab's Views, Immersive, Page Movement, Show, Zoom, Dark Mode, Window and Macros /// and Templates groups. @@ -10,8 +10,8 @@ pub(super) fn view(app: &mut WordApp, ui: &mut Ui) { big(ui, app, "printLayout", "Print\nLayout", "view.printLayout", json!({}), false); big(ui, app, "webLayout", "Web\nLayout", "view.webLayout", json!({}), false); stack(ui, |ui| { - small(ui, app, "outline", Some("Outline"), "Outline", "view.outline", json!({}), false); - small(ui, app, "draft", Some("Draft"), "Draft", "view.draft", json!({}), false); + small(ui, app, "outline", Some("Outline"), "Outline", crate::widgets::ButtonAction::new("view.outline", json!({}), false)); + small(ui, app, "draft", Some("Draft"), "Draft", crate::widgets::ButtonAction::new("view.draft", json!({}), false)); }); }); group(ui, "Immersive", None, app, |ui, app| { @@ -50,25 +50,44 @@ pub(super) fn view(app: &mut WordApp, ui: &mut Ui) { big(ui, app, "zoom100", "100%", "view.zoom100", json!({}), false); // Step the zoom up and down by 10% (issue #67), from whatever the page shows now. stack(ui, |ui| { - small(ui, app, "zoomIn", Some("Zoom In"), "Zoom In", "view.zoomIn", json!({}), false); - small(ui, app, "zoomOut", Some("Zoom Out"), "Zoom Out", "view.zoomOut", json!({}), false); + small(ui, app, "zoomIn", Some("Zoom In"), "Zoom In", crate::widgets::ButtonAction::new("view.zoomIn", json!({}), false)); + small(ui, app, "zoomOut", Some("Zoom Out"), "Zoom Out", crate::widgets::ButtonAction::new("view.zoomOut", json!({}), false)); }); stack(ui, |ui| { - small(ui, app, "onePage", Some("One Page"), "One Page", "view.onePage", json!({}), v.fit == "onePage"); - small(ui, app, "multiplePages", Some("Multiple Pages"), "Multiple Pages", "view.multiplePages", json!({}), v.multi_page); - small(ui, app, "pageWidth", Some("Page Width"), "Page Width", "view.pageWidth", json!({}), v.fit == "pageWidth"); + small(ui, app, "onePage", Some("One Page"), "One Page", crate::widgets::ButtonAction::new("view.onePage", json!({}), v.fit == "onePage")); + small( + ui, + app, + "multiplePages", + Some("Multiple Pages"), + "Multiple Pages", + crate::widgets::ButtonAction::new("view.multiplePages", json!({}), v.multi_page), + ); + small( + ui, + app, + "pageWidth", + Some("Page Width"), + "Page Width", + crate::widgets::ButtonAction::new("view.pageWidth", json!({}), v.fit == "pageWidth"), + ); }); }); group(ui, "Dark Mode", None, app, |ui, app| { big(ui, app, "darkMode", "Switch\nModes", "view.darkMode", json!({}), false); - menu_button(ui, app, "interfaceTheme", Some("Interface\nTheme"), "Interface Theme", true, "", |ui, app| { - for a in crate::theme::Appearance::ALL { - if ui.add(egui::Button::selectable(app.ui.theme == a, tl!(a.label())).min_size(vec2(200.0, 0.0))).clicked() { - let _ = app.run("view.interfaceTheme", json!({"value": a.code()})); - ui.close(); + menu_button( + ui, + app, + crate::widgets::MenuButtonOptions::new("interfaceTheme", Some("Interface\nTheme"), "Interface Theme", true, ""), + |ui, app| { + for a in crate::theme::Appearance::ALL { + if ui.add(egui::Button::selectable(app.ui.theme == a, tl!(a.label())).min_size(vec2(200.0, 0.0))).clicked() { + let _ = app.run("view.interfaceTheme", json!({"value": a.code()})); + ui.close(); + } } - } - }); + }, + ); }); group(ui, "Window", None, app, |ui, app| { big(ui, app, "newWindow", "New\nWindow", "view.newWindow", json!({}), false); @@ -77,11 +96,7 @@ pub(super) fn view(app: &mut WordApp, ui: &mut Ui) { menu_button( ui, app, - "switchWindows", - Some("Switch\nWindows"), - "Switch Windows", - true, - "view.switchWindows", + crate::widgets::MenuButtonOptions::new("switchWindows", Some("Switch\nWindows"), "Switch Windows", true, "view.switchWindows"), crate::documents::switch_windows_menu, ); // The Full Screen button carries its name for assistive technology (and for the @@ -110,8 +125,15 @@ pub(super) fn outlining(app: &mut WordApp, ui: &mut Ui) { group(ui, "Outline Tools", None, app, |ui, app| { stack(ui, |ui| { crate::widgets::row(ui, |ui| { - small(ui, app, "outlineTop", None, "Promote to Heading 1", "outline.promoteToHeading1", json!({}), false); - small(ui, app, "outlinePromote", None, "Promote", "outline.promote", json!({}), false); + small( + ui, + app, + "outlineTop", + None, + "Promote to Heading 1", + crate::widgets::ButtonAction::new("outline.promoteToHeading1", json!({}), false), + ); + small(ui, app, "outlinePromote", None, "Promote", crate::widgets::ButtonAction::new("outline.promote", json!({}), false)); let levels: Vec = (1..=BODY).map(name).collect(); if let Some(v) = crate::widgets::combo_hint(ui, "outlineLevel", 100.0, &name(level), &levels, None, tl!("Outline Level")) && let Some(i) = levels.iter().position(|x| *x == v) @@ -119,15 +141,22 @@ pub(super) fn outlining(app: &mut WordApp, ui: &mut Ui) { let l = if i + 1 >= usize::from(BODY) { json!("body") } else { json!(i + 1) }; let _ = app.run("outline.level", json!({ "level": l })); } - small(ui, app, "outlineDemote", None, "Demote", "outline.demote", json!({}), false); - small(ui, app, "outlineBody", None, "Demote to Body Text", "outline.demoteToBody", json!({}), false); + small(ui, app, "outlineDemote", None, "Demote", crate::widgets::ButtonAction::new("outline.demote", json!({}), false)); + small( + ui, + app, + "outlineBody", + None, + "Demote to Body Text", + crate::widgets::ButtonAction::new("outline.demoteToBody", json!({}), false), + ); }); ui.add_space(3.0); crate::widgets::row(ui, |ui| { - small(ui, app, "outlineUp", None, "Move Up", "outline.moveUp", json!({}), false); - small(ui, app, "outlineDown", None, "Move Down", "outline.moveDown", json!({}), false); - small(ui, app, "outlineExpand", None, "Expand", "outline.expand", json!({}), false); - small(ui, app, "outlineCollapse", None, "Collapse", "outline.collapse", json!({}), false); + small(ui, app, "outlineUp", None, "Move Up", crate::widgets::ButtonAction::new("outline.moveUp", json!({}), false)); + small(ui, app, "outlineDown", None, "Move Down", crate::widgets::ButtonAction::new("outline.moveDown", json!({}), false)); + small(ui, app, "outlineExpand", None, "Expand", crate::widgets::ButtonAction::new("outline.expand", json!({}), false)); + small(ui, app, "outlineCollapse", None, "Collapse", crate::widgets::ButtonAction::new("outline.collapse", json!({}), false)); ui.add_space(6.0); ui.label(egui::RichText::new(tl!("Show Level")).size(11.5)); let mut shown: Vec = vec![tl!("All Levels").to_string()];