mirror of
https://github.com/aphrody-labs/wordcraft.git
synced 2026-10-13 19:26:53 +00:00
* #659: one drop-down marker (the dropdown icon) on every ribbon button Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * #658: menu buttons grey out while their command is disabled Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com>
This commit is contained in:
4 files changed
+146
-96
No files matched your search
@@ -272,20 +272,29 @@ pub fn show(app: &mut WordApp, ui: &mut Ui) {
|
||||
!latex,
|
||||
);
|
||||
small(ui, app, "formula", Some("LaTeX"), "Type equations in LaTeX", "equation.inputFormat", json!({"format": "latex"}), latex);
|
||||
crate::widgets::menu_button(ui, app, "replace", Some("Convert"), "Convert between built-up and linear form", false, |ui, app| {
|
||||
for (label, to, all) in [
|
||||
("Current - Professional", "professional", false),
|
||||
("Current - Linear", "linear", false),
|
||||
("All - Professional", "professional", true),
|
||||
("All - Linear", "linear", true),
|
||||
] {
|
||||
if ui.button(tl!(label)).clicked() {
|
||||
let _ = app.run("equation.convert", json!({"to": to, "all": all}));
|
||||
app.canvas.want_focus = true;
|
||||
ui.close();
|
||||
crate::widgets::menu_button(
|
||||
ui,
|
||||
app,
|
||||
"replace",
|
||||
Some("Convert"),
|
||||
"Convert between built-up and linear form",
|
||||
false,
|
||||
"equation.convert",
|
||||
|ui, app| {
|
||||
for (label, to, all) in [
|
||||
("Current - Professional", "professional", false),
|
||||
("Current - Linear", "linear", false),
|
||||
("All - Professional", "professional", true),
|
||||
("All - Linear", "linear", true),
|
||||
] {
|
||||
if ui.button(tl!(label)).clicked() {
|
||||
let _ = app.run("equation.convert", json!({"to": to, "all": all}));
|
||||
app.canvas.want_focus = true;
|
||||
ui.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
ui.vertical(|ui| {
|
||||
ui.spacing_mut().item_spacing = vec2(1.0, 0.0);
|
||||
@@ -339,14 +348,23 @@ pub fn show(app: &mut WordApp, ui: &mut Ui) {
|
||||
});
|
||||
ui.vertical(|ui| {
|
||||
ui.spacing_mut().item_spacing = vec2(1.0, 0.0);
|
||||
crate::widgets::menu_button(ui, app, "justify", Some("Justification"), "Justification of a display equation", false, |ui, app| {
|
||||
for (label, jc) in [("Left", "left"), ("Right", "right"), ("Centered", "center"), ("Centered as Group", "centerGroup")] {
|
||||
if ui.button(tl!(label)).clicked() {
|
||||
let _ = app.run("equation.justify", json!({"jc": jc}));
|
||||
ui.close();
|
||||
crate::widgets::menu_button(
|
||||
ui,
|
||||
app,
|
||||
"justify",
|
||||
Some("Justification"),
|
||||
"Justification of a display equation",
|
||||
false,
|
||||
"equation.justify",
|
||||
|ui, app| {
|
||||
for (label, jc) in [("Left", "left"), ("Right", "right"), ("Centered", "center"), ("Centered as Group", "centerGroup")] {
|
||||
if ui.button(tl!(label)).clicked() {
|
||||
let _ = app.run("equation.justify", json!({"jc": jc}));
|
||||
ui.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
if ui.button(tl!("Close Equation")).on_hover_text(tl!("Leave the equation (Esc)")).clicked() {
|
||||
let _ = app.run("equation.exit", json!({}));
|
||||
app.canvas.want_focus = true;
|
||||
|
||||
@@ -426,7 +426,7 @@ 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, |ui, app| {
|
||||
menu_button(ui, app, "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!({}));
|
||||
@@ -472,7 +472,7 @@ fn home(app: &mut WordApp, ui: &mut Ui) {
|
||||
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, |ui, app| {
|
||||
menu_button(ui, app, "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"}));
|
||||
@@ -506,7 +506,7 @@ fn home(app: &mut WordApp, ui: &mut Ui) {
|
||||
small(ui, app, "charborder", None, "Character Border", "format.border", json!({}), flag("border"));
|
||||
small(ui, app, "enclose", None, "Enclose Characters", "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, "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();
|
||||
@@ -624,7 +624,7 @@ fn home(app: &mut WordApp, ui: &mut Ui) {
|
||||
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);
|
||||
ui.add_space(4.0);
|
||||
menu_button(ui, app, "lineSpacing", None, "Line and Paragraph Spacing", false, |ui, app| {
|
||||
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}));
|
||||
}
|
||||
@@ -663,13 +663,13 @@ 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, |ui, app| {
|
||||
menu_button(ui, app, "find", Some("Find"), "Find", false, "edit.find", |ui, app| {
|
||||
mi(ui, app, "Find", "ui.dialog", json!({"name": "find"}));
|
||||
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, |ui, app| {
|
||||
menu_button(ui, app, "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!({}));
|
||||
@@ -687,7 +687,7 @@ fn home(app: &mut WordApp, ui: &mut Ui) {
|
||||
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, |ui, app| {
|
||||
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);
|
||||
@@ -695,7 +695,7 @@ fn insert(app: &mut WordApp, ui: &mut Ui) {
|
||||
});
|
||||
});
|
||||
group(ui, "Tables", None, app, |ui, app| {
|
||||
menu_button(ui, app, "table", Some("Table"), "Add a Table", true, |ui, app| {
|
||||
menu_button(ui, app, "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"}));
|
||||
@@ -711,7 +711,7 @@ 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, |ui, app| {
|
||||
menu_button(ui, app, "shapes", Some("Shapes"), "Shapes", true, "insert.shape", |ui, app| {
|
||||
for (l, k) in [
|
||||
("Rectangle", "rectangle"),
|
||||
("Rounded Rectangle", "roundedRectangle"),
|
||||
@@ -756,7 +756,7 @@ fn insert(app: &mut WordApp, ui: &mut Ui) {
|
||||
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, |ui, app| {
|
||||
menu_button(ui, app, "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"}));
|
||||
@@ -764,7 +764,7 @@ 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, |ui, app| {
|
||||
menu_button(ui, app, "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"}));
|
||||
@@ -772,7 +772,7 @@ 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, |ui, app| {
|
||||
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"}));
|
||||
@@ -783,7 +783,7 @@ fn insert(app: &mut WordApp, ui: &mut Ui) {
|
||||
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, |ui, app| {
|
||||
menu_button(ui, app, "quickParts", Some("Quick Parts"), "Quick Parts", false, "insert.quickParts", |ui, app| {
|
||||
let parts: Vec<String> = app.session.building_blocks.keys().cloned().collect();
|
||||
for name in parts {
|
||||
if ui.button(&name).clicked() {
|
||||
@@ -793,7 +793,7 @@ fn insert(app: &mut WordApp, ui: &mut Ui) {
|
||||
}
|
||||
mi(ui, app, "Field…", "ui.dialog", json!({"name": "field"}));
|
||||
});
|
||||
menu_button(ui, app, "wordArt", Some("WordArt"), "WordArt", false, |ui, app| {
|
||||
menu_button(ui, app, "wordArt", Some("WordArt"), "WordArt", false, "", |ui, app| {
|
||||
crate::wordart_menus::style_gallery(ui, app, "insert.wordArt")
|
||||
});
|
||||
small(ui, app, "dropCap", Some("Drop Cap"), "Drop Cap", "insert.dropCap", json!({}), false);
|
||||
@@ -806,7 +806,7 @@ fn insert(app: &mut WordApp, ui: &mut Ui) {
|
||||
});
|
||||
group(ui, "Symbols", None, app, |ui, app| {
|
||||
crate::equation_tab::insert_button(ui, app);
|
||||
menu_button(ui, app, "symbol", Some("Symbol"), "Symbol", true, |ui, app| {
|
||||
menu_button(ui, app, "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 [
|
||||
@@ -863,7 +863,7 @@ 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, "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);
|
||||
@@ -881,7 +881,7 @@ fn draw(app: &mut WordApp, ui: &mut Ui) {
|
||||
update_gallery_pen(app, custom, tool);
|
||||
}
|
||||
});
|
||||
menu_button(ui, app, "thickness", None, "Thickness", false, |ui, app| {
|
||||
menu_button(ui, app, "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())]);
|
||||
@@ -978,7 +978,7 @@ fn style_set_box(app: &mut WordApp, ui: &mut Ui) -> (Rect, Rect) {
|
||||
|
||||
fn design(app: &mut WordApp, ui: &mut Ui) {
|
||||
group(ui, "Themes", None, app, |ui, app| {
|
||||
menu_button(ui, app, "themes", Some("Themes"), "Themes", true, |ui, app| {
|
||||
menu_button(ui, app, "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}));
|
||||
}
|
||||
@@ -987,19 +987,19 @@ 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, |ui, app| {
|
||||
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, |ui, app| {
|
||||
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}));
|
||||
}
|
||||
});
|
||||
});
|
||||
stack(ui, |ui| {
|
||||
menu_button(ui, app, "paraSpacing", Some("Paragraph Spacing"), "Paragraph Spacing", false, |ui, app| {
|
||||
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"),
|
||||
@@ -1017,7 +1017,7 @@ fn design(app: &mut WordApp, ui: &mut Ui) {
|
||||
});
|
||||
});
|
||||
group(ui, "Page Background", None, app, |ui, app| {
|
||||
menu_button(ui, app, "watermark", Some("Watermark"), "Watermark", true, |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}));
|
||||
}
|
||||
@@ -1025,7 +1025,7 @@ fn design(app: &mut WordApp, ui: &mut Ui) {
|
||||
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, |ui, app| {
|
||||
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"));
|
||||
@@ -1038,7 +1038,7 @@ 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, |ui, app| {
|
||||
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)]
|
||||
@@ -1046,7 +1046,7 @@ fn layout(app: &mut WordApp, ui: &mut Ui) {
|
||||
mi_check(ui, app, label, dir == d, "layout.textDirection", json!({"value": d.ooxml()}));
|
||||
}
|
||||
});
|
||||
menu_button(ui, app, "margins", Some("Margins"), "Margins", true, |ui, app| {
|
||||
menu_button(ui, app, "margins", Some("Margins"), "Margins", true, "layout.margins", |ui, app| {
|
||||
for (l, k) in [
|
||||
("Normal 1\" all", "normal"),
|
||||
("Narrow 0.5\" all", "narrow"),
|
||||
@@ -1059,16 +1059,16 @@ fn layout(app: &mut WordApp, ui: &mut Ui) {
|
||||
}
|
||||
mi(ui, app, "Custom Margins…", "ui.dialog", json!({"name": "pageSetup"}));
|
||||
});
|
||||
menu_button(ui, app, "orientation", Some("Orientation"), "Orientation", true, |ui, app| {
|
||||
menu_button(ui, app, "orientation", Some("Orientation"), "Orientation", true, "layout.orientation", |ui, app| {
|
||||
mi(ui, app, "Portrait", "layout.orientation", json!({"value": "portrait"}));
|
||||
mi(ui, app, "Landscape", "layout.orientation", json!({"value": "landscape"}));
|
||||
});
|
||||
menu_button(ui, app, "size", Some("Size"), "Size", true, |ui, app| {
|
||||
menu_button(ui, app, "size", Some("Size"), "Size", true, "layout.size", |ui, app| {
|
||||
for (n, w, h) in wordcraft_geom::PAPER_SIZES {
|
||||
mi(ui, app, &format!("{n} {:.2}\" × {:.2}\"", w / 72.0, h / 72.0), "layout.size", json!({"name": n}));
|
||||
}
|
||||
});
|
||||
menu_button(ui, app, "columns", Some("Columns"), "Columns", true, |ui, app| {
|
||||
menu_button(ui, app, "columns", Some("Columns"), "Columns", true, "layout.columns", |ui, app| {
|
||||
mi(ui, app, "One", "layout.columns", json!({"count": 1}));
|
||||
mi(ui, app, "Two", "layout.columns", json!({"count": 2}));
|
||||
mi(ui, app, "Three", "layout.columns", json!({"count": 3}));
|
||||
@@ -1078,7 +1078,7 @@ fn layout(app: &mut WordApp, ui: &mut Ui) {
|
||||
mi(ui, app, "More Columns…", "ui.dialog", json!({"name": "columns"}));
|
||||
});
|
||||
stack(ui, |ui| {
|
||||
menu_button(ui, app, "breaks", Some("Breaks"), "Breaks", false, |ui, app| {
|
||||
menu_button(ui, app, "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"}));
|
||||
@@ -1089,7 +1089,7 @@ 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, |ui, app| {
|
||||
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")]
|
||||
{
|
||||
@@ -1099,7 +1099,7 @@ fn layout(app: &mut WordApp, ui: &mut Ui) {
|
||||
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, |ui, app| {
|
||||
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"}));
|
||||
@@ -1176,7 +1176,7 @@ 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, |ui, app| {
|
||||
menu_button(ui, app, "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"),
|
||||
@@ -1204,7 +1204,7 @@ fn wrap_menu(ui: &mut Ui, app: &mut WordApp) {
|
||||
}
|
||||
|
||||
fn rotate_menu(ui: &mut Ui, app: &mut WordApp, label: Option<&str>) {
|
||||
menu_button(ui, app, "rotate", label, "Rotate", false, |ui, app| {
|
||||
menu_button(ui, app, "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"}));
|
||||
@@ -1228,7 +1228,7 @@ fn rotation_field(ui: &mut Ui, app: &mut WordApp) {
|
||||
|
||||
/// Arrange › Group: Group (Shift+click objects to select several) and Ungroup.
|
||||
fn group_menu(ui: &mut Ui, app: &mut WordApp, label: Option<&str>) {
|
||||
menu_button(ui, app, "group", label, "Group", false, |ui, app| {
|
||||
menu_button(ui, app, "group", label, "Group", false, "arrange.group", |ui, app| {
|
||||
mi(ui, app, "Group", "arrange.group", json!({}));
|
||||
mi(ui, app, "Ungroup", "arrange.ungroup", json!({}));
|
||||
});
|
||||
@@ -1236,7 +1236,7 @@ fn group_menu(ui: &mut Ui, app: &mut WordApp, label: Option<&str>) {
|
||||
|
||||
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, |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();
|
||||
@@ -1244,7 +1244,7 @@ fn references(app: &mut WordApp, ui: &mut Ui) {
|
||||
mi(ui, app, "Remove Table of Contents", "references.removeToc", json!({}));
|
||||
});
|
||||
stack(ui, |ui| {
|
||||
menu_button(ui, app, "addText", Some("Add Text"), "Add Text", false, |ui, app| {
|
||||
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}));
|
||||
@@ -1266,7 +1266,7 @@ 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, "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 {
|
||||
@@ -1281,7 +1281,7 @@ fn references(app: &mut WordApp, ui: &mut Ui) {
|
||||
small(ui, app, "sources", Some("Manage Sources"), "Manage Sources", "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, |ui, app| {
|
||||
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}));
|
||||
}
|
||||
@@ -1338,7 +1338,7 @@ 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, |ui, app| {
|
||||
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"),
|
||||
@@ -1353,7 +1353,7 @@ fn mailings(app: &mut WordApp, ui: &mut Ui) {
|
||||
}
|
||||
});
|
||||
// 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, |ui, app| {
|
||||
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!({}));
|
||||
});
|
||||
@@ -1366,7 +1366,7 @@ 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, |ui, app| {
|
||||
menu_button(ui, app, "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"}));
|
||||
@@ -1424,7 +1424,7 @@ 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, "language", Some("Language"), "Language", true, "", language_menu);
|
||||
});
|
||||
group(ui, "Comments", None, app, |ui, app| {
|
||||
big(ui, app, "newComment", "New\nComment", "review.newComment", json!({}), false);
|
||||
@@ -1448,6 +1448,7 @@ fn review(app: &mut WordApp, ui: &mut Ui) {
|
||||
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"}));
|
||||
@@ -1457,11 +1458,11 @@ fn review(app: &mut WordApp, ui: &mut Ui) {
|
||||
});
|
||||
});
|
||||
group(ui, "Changes", None, app, |ui, app| {
|
||||
menu_button(ui, app, "accept", Some("Accept"), "Accept", true, |ui, app| {
|
||||
menu_button(ui, app, "accept", Some("Accept"), "Accept", true, "review.accept", |ui, app| {
|
||||
mi(ui, app, "Accept This Change", "review.accept", json!({}));
|
||||
mi(ui, app, "Accept All Changes", "review.acceptAll", json!({}));
|
||||
});
|
||||
menu_button(ui, app, "reject", Some("Reject"), "Reject", true, |ui, app| {
|
||||
menu_button(ui, app, "reject", Some("Reject"), "Reject", true, "review.reject", |ui, app| {
|
||||
mi(ui, app, "Reject Change", "review.reject", json!({}));
|
||||
mi(ui, app, "Reject All Changes", "review.rejectAll", json!({}));
|
||||
});
|
||||
@@ -1576,7 +1577,7 @@ fn view(app: &mut WordApp, ui: &mut Ui) {
|
||||
});
|
||||
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| {
|
||||
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("ui.theme", json!({"value": a.code()}));
|
||||
@@ -1708,7 +1709,7 @@ 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| {
|
||||
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)));
|
||||
@@ -1731,12 +1732,12 @@ fn smart_art_design(app: &mut WordApp, ui: &mut Ui) {
|
||||
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, "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, "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),
|
||||
@@ -1747,7 +1748,7 @@ 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, "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}));
|
||||
});
|
||||
@@ -1788,11 +1789,11 @@ 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, |ui, app| {
|
||||
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"));
|
||||
});
|
||||
menu_button(ui, app, "pen", Some("Shape Outline"), "Shape Outline", false, |ui, app| {
|
||||
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();
|
||||
@@ -1803,7 +1804,7 @@ fn shape_format(app: &mut WordApp, ui: &mut Ui) {
|
||||
}
|
||||
});
|
||||
});
|
||||
menu_button(ui, app, "shapeEffects", Some("Shape Effects"), "Shadow, glow and soft edges", false, |ui, app| {
|
||||
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)
|
||||
});
|
||||
});
|
||||
@@ -1811,7 +1812,7 @@ fn shape_format(app: &mut WordApp, ui: &mut Ui) {
|
||||
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, "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| {
|
||||
@@ -1823,16 +1824,16 @@ 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, "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));
|
||||
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| {
|
||||
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, "transform", Some("Text Effects"), "Text Effects", false, |ui, app| {
|
||||
menu_button(ui, app, "transform", Some("Text Effects"), "Text Effects", false, "", |ui, app| {
|
||||
crate::wordart_menus::effects_menu(ui, app, &theme, true)
|
||||
});
|
||||
});
|
||||
@@ -1877,7 +1878,7 @@ 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| {
|
||||
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),
|
||||
@@ -1887,7 +1888,7 @@ fn shape_text_group(app: &mut WordApp, ui: &mut Ui) {
|
||||
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, "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)}));
|
||||
}
|
||||
@@ -1941,7 +1942,7 @@ fn shape_effects_menu(ui: &mut Ui, app: &mut WordApp, theme: &[wordcraft_doc::Rg
|
||||
|
||||
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, |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)]
|
||||
{
|
||||
@@ -1951,7 +1952,7 @@ fn picture_format(app: &mut WordApp, ui: &mut Ui) {
|
||||
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, |ui, app| {
|
||||
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}));
|
||||
}
|
||||
@@ -1960,11 +1961,11 @@ fn picture_format(app: &mut WordApp, ui: &mut Ui) {
|
||||
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, |ui, app| {
|
||||
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, |ui, app| {
|
||||
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}));
|
||||
}
|
||||
@@ -1975,7 +1976,7 @@ fn picture_format(app: &mut WordApp, ui: &mut Ui) {
|
||||
});
|
||||
});
|
||||
group(ui, "Picture Styles", None, app, |ui, app| {
|
||||
menu_button(ui, app, "picture", Some("Styles"), "Frames and effects", true, |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"),
|
||||
@@ -1986,7 +1987,7 @@ fn picture_format(app: &mut WordApp, ui: &mut Ui) {
|
||||
mi(ui, app, l, "picture.style", json!({"style": s}));
|
||||
}
|
||||
});
|
||||
menu_button(ui, app, "picture", Some("Border"), "Picture border", true, |ui, app| {
|
||||
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}));
|
||||
}
|
||||
@@ -2132,7 +2133,7 @@ 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, |ui, app| {
|
||||
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"}));
|
||||
@@ -2153,7 +2154,7 @@ fn table_design(app: &mut WordApp, ui: &mut Ui) {
|
||||
});
|
||||
});
|
||||
group(ui, "Borders", None, app, |ui, app| {
|
||||
menu_button(ui, app, "borders", Some("Borders"), "Borders", true, |ui, app| {
|
||||
menu_button(ui, app, "borders", Some("Borders"), "Borders", true, "table.borders", |ui, app| {
|
||||
for (l, k) in [
|
||||
("All Borders", "all"),
|
||||
("Outside Borders", "outside"),
|
||||
@@ -2235,7 +2236,7 @@ fn cell_size_boxes(ui: &mut Ui, app: &mut WordApp) {
|
||||
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, |ui, app| {
|
||||
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 Row", "table.selectRow", json!({}));
|
||||
mi(ui, app, "Select Table", "table.selectTable", json!({}));
|
||||
@@ -2252,7 +2253,7 @@ 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, |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!({}));
|
||||
@@ -2273,7 +2274,7 @@ fn table_layout(app: &mut WordApp, ui: &mut Ui) {
|
||||
});
|
||||
});
|
||||
group(ui, "Cell Size", None, app, |ui, app| {
|
||||
menu_button(ui, app, "autofit", Some("AutoFit"), "AutoFit", true, |ui, app| {
|
||||
menu_button(ui, app, "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"}));
|
||||
@@ -2344,6 +2345,23 @@ mod tests {
|
||||
assert!(next_x >= gallery.right() + 8.0 - 0.01, "{next_x} {gallery:?}");
|
||||
}
|
||||
|
||||
/// #658: menu buttons grey out (and don't open) while the command their menu is about is
|
||||
/// disabled: the Table Layout menus outside a table; menus with no command stay on.
|
||||
#[test]
|
||||
fn menu_buttons_follow_their_command() {
|
||||
use crate::widgets::menu_enabled;
|
||||
let mut a = crate::WordApp::new(Session::new(wordcraft_doc::Document::from_text("Text")), crate::Services::default());
|
||||
for id in ["table.selectTable", "table.deleteTable", "table.autofit"] {
|
||||
assert!(!menu_enabled(&a, id), "{id} is off outside a table");
|
||||
}
|
||||
assert!(menu_enabled(&a, ""), "a menu without a command is always on");
|
||||
assert!(menu_enabled(&a, "insert.header"));
|
||||
a.run("insert.table", json!({"rows": 2, "cols": 2})).unwrap();
|
||||
for id in ["table.selectTable", "table.deleteTable", "table.autofit"] {
|
||||
assert!(menu_enabled(&a, id), "{id} is on inside a table");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn picture_format_tab_appears_when_picture_selected() {
|
||||
let mut s = Session::new(wordcraft_doc::Document::new());
|
||||
|
||||
@@ -231,6 +231,11 @@ pub fn split(
|
||||
});
|
||||
}
|
||||
|
||||
/// Whether a menu button whose menu is about command `id` is on ("" = always).
|
||||
pub fn menu_enabled(app: &WordApp, id: &str) -> bool {
|
||||
id.is_empty() || enabled(app, id)
|
||||
}
|
||||
|
||||
/// A button that only opens a menu (icon + label + drop-down marker).
|
||||
pub fn menu_button(
|
||||
ui: &mut Ui,
|
||||
@@ -239,10 +244,15 @@ pub fn menu_button(
|
||||
label: Option<&str>,
|
||||
tip: &str,
|
||||
big_btn: bool,
|
||||
id: &str,
|
||||
menu: impl FnOnce(&mut Ui, &mut WordApp),
|
||||
) {
|
||||
let (label, tip) = (label.map(|l| tl!(l)), tl!(tip));
|
||||
let t = Tokens::get(ui.ctx());
|
||||
// `id` names the command the menu is about (Header for the Header menu…); while it is
|
||||
// disabled the button greys out and doesn't open, like the other ribbon buttons. "" = always on.
|
||||
let on = menu_enabled(app, id);
|
||||
let (c, a, text) = if on { (t.icon, t.accent, t.text) } else { (t.text_disabled, t.text_disabled, t.text_disabled) };
|
||||
let resp = if big_btn {
|
||||
let galley_w = label
|
||||
.unwrap_or("")
|
||||
@@ -253,11 +263,11 @@ pub fn menu_button(
|
||||
let (r, resp) = ui.allocate_exact_size(vec2(w, CONTENT_H), Sense::click());
|
||||
keytip_badge(ui, r, "menu", tip);
|
||||
bg(ui, r, &resp, false, &t);
|
||||
icons::paint(ui.painter(), Rect::from_center_size(pos2(r.center().x, r.min.y + 20.0), vec2(32.0, 32.0)), icon, t.icon, t.accent);
|
||||
icons::paint(ui.painter(), Rect::from_center_size(pos2(r.center().x, r.min.y + 20.0), vec2(32.0, 32.0)), icon, c, a);
|
||||
let lines: Vec<&str> = label.unwrap_or("").split('\n').collect();
|
||||
let mut y = r.min.y + 43.0;
|
||||
for (i, l) in lines.iter().enumerate() {
|
||||
label_line(ui, r.center().x, y, l, regular(11.5), t.text, i + 1 == lines.len());
|
||||
label_line(ui, r.center().x, y, l, regular(11.5), text, i + 1 == lines.len());
|
||||
y += 13.0;
|
||||
}
|
||||
resp
|
||||
@@ -266,14 +276,17 @@ pub fn menu_button(
|
||||
let (r, resp) = ui.allocate_exact_size(vec2(24.0 + text_w + 10.0, 22.0), Sense::click());
|
||||
keytip_badge(ui, r, "menu", tip);
|
||||
bg(ui, r, &resp, false, &t);
|
||||
icons::paint(ui.painter(), Rect::from_center_size(pos2(r.min.x + 12.0, r.center().y), vec2(17.0, 17.0)), icon, t.icon, t.accent);
|
||||
icons::paint(ui.painter(), Rect::from_center_size(pos2(r.min.x + 12.0, r.center().y), vec2(17.0, 17.0)), icon, c, a);
|
||||
if let Some(l) = label {
|
||||
ui.painter().text(pos2(r.min.x + 24.0, r.center().y), Align2::LEFT_CENTER, l, regular(11.5), t.text);
|
||||
ui.painter().text(pos2(r.min.x + 24.0, r.center().y), Align2::LEFT_CENTER, l, regular(11.5), text);
|
||||
}
|
||||
icons::paint(ui.painter(), Rect::from_center_size(pos2(r.max.x - 6.0, r.center().y), vec2(DROPDOWN, DROPDOWN)), "dropdown", t.icon, t.accent);
|
||||
icons::paint(ui.painter(), Rect::from_center_size(pos2(r.max.x - 6.0, r.center().y), vec2(DROPDOWN, DROPDOWN)), "dropdown", c, c);
|
||||
resp
|
||||
};
|
||||
let resp = resp.on_hover_text(tip);
|
||||
if !on {
|
||||
return;
|
||||
}
|
||||
egui::Popup::menu(&resp).show(|ui| {
|
||||
ui.set_min_width(200.0);
|
||||
menu(ui, app);
|
||||
|
||||
+2
-1
@@ -1,6 +1,6 @@
|
||||
# UI parity with Microsoft Word
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** trivial (one drop-down marker, #659; previously minor: status bar language follows the caret and sets the proofing language; Review › Language is a menu; previously trivial: Customize Ribbon and the Quick Access Toolbar, #379; previously: major, first version; ribbon, dialogs, shortcuts and on-canvas interaction measured from `crates/ui-egui` and the command registry) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** trivial (menu buttons grey out, #658; previously trivial: one drop-down marker, #659; previously minor: status bar language follows the caret and sets the proofing language; Review › Language is a menu; previously trivial: Customize Ribbon and the Quick Access Toolbar, #379; previously: major, first version; ribbon, dialogs, shortcuts and on-canvas interaction measured from `crates/ui-egui` and the command registry) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4
|
||||
|
||||
How WordCraft looks and feels next to Word: ribbon, dialogs, panes, keyboard, mouse and touch.
|
||||
Typing behaviour has its own checklist ([`typing-parity.md`](typing-parity.md)). All assets are our
|
||||
@@ -69,6 +69,7 @@ own (`AGENTS.md`: no Microsoft iconography, ever).
|
||||
| 2026-10-11 | trivial | Text field and combo outlines reach 3:1 against their fill and surroundings in both themes (light #8A8A8A, dark #7A7A7A); a test pins the ratios (#657) |
|
||||
| 2026-10-11 | trivial | Font dialog: Asian text font and Latin text font boxes, each with its own list and type-to-find, shown for a Chinese/Japanese interface or East Asian text in the selection; OK sets only the fonts changed; `format.font {"eastAsia"}` (#505) |
|
||||
| 2026-10-11 | trivial | References › Table of Authorities: Insert Table of Authorities is labelled and joined by Update Table (`references.updateAuthorities`), as in the Index group (#661) |
|
||||
| 2026-10-11 | trivial | Menu buttons (Header, Page Number, Breaks, Wrap Text, table Select/Delete/AutoFit…) grey out and don't open while the command their menu is about is disabled, like the other ribbon buttons (#658) |
|
||||
| 2026-10-11 | trivial | One drop-down marker everywhere: large ribbon buttons, Equation and the Editing/Reviewing button draw the `dropdown` icon after their label, like split and menu buttons, instead of a text glyph (#659) |
|
||||
| 2026-10-11 | trivial | Dark theme: Styles, style-set and table-style previews sit on a dimmer page tone; with View › Switch Modes on, the Styles gallery previews the dark page (#663) |
|
||||
| 2026-10-11 | trivial | Dialog primary buttons, the Discord pill, the table eraser line and the Navigation search highlight take their colours from the theme in both themes (`widgets::primary_button`); a test bans the old fixed values (#656) |
|
||||
|
||||
Reference in new issue
Block a user