Ribbon: large-button labels end above the group titles (#653) (#699)

* #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>

* #653: large-button labels end above the group title row

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:
Dmitrii Khlapov 2026-10-12 12:13:17 +02:00 committed by GitHub
parent 3c8840556a
commit 4a1acb6c3f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 47 additions and 18 deletions

View File

@ -11,7 +11,7 @@ use wordcraft_engine::math_gallery::{BUILT_INS, STRUCTURES, SYMBOL_SETS, Templat
use crate::WordApp;
use crate::theme::{Tokens, regular, semibold};
use crate::widgets::{CONTENT_H, group, small};
use crate::widgets::{BIG_ICON_Y, BIG_LABEL_Y, CONTENT_H, LABEL_PITCH, group, small};
/// What each structure button shows.
fn button_picture(id: &str) -> &'static str {
@ -56,14 +56,14 @@ fn math_menu_button(ui: &mut Ui, app: &mut WordApp, picture: &str, label: &str,
if resp.hovered() {
ui.painter().rect_filled(r, 4.0, t.hover);
}
let pic = Rect::from_center_size(pos2(r.center().x, r.min.y + 20.0), vec2(40.0, 34.0));
let pic = Rect::from_center_size(pos2(r.center().x, r.min.y + BIG_ICON_Y), vec2(40.0, 32.0));
ui.painter().rect_filled(pic, 3.0, egui::Color32::WHITE);
paint_math(app, ui, pic, &format!("btn:{picture}"), picture, None, false, 12.0);
let lines: Vec<&str> = label.split('\n').collect();
let mut y = r.min.y + 43.0;
let mut y = r.min.y + BIG_LABEL_Y;
for (i, l) in lines.iter().enumerate() {
crate::widgets::label_line(ui, r.center().x, y, l, regular(11.5), t.text, i + 1 == lines.len());
y += 13.0;
y += LABEL_PITCH;
}
let resp = resp.on_hover_text(label.replace('\n', " "));
egui::Popup::menu(&resp).show(|ui| {
@ -152,8 +152,8 @@ pub fn insert_button(ui: &mut Ui, app: &mut WordApp) {
let label = tl!("Equation");
let w = (ui.ctx().fonts_mut(|f| f.layout_no_wrap(label.to_string(), regular(11.5), t.text).size().x) + 22.0).max(48.0);
let (r, _) = ui.allocate_exact_size(vec2(w, CONTENT_H), Sense::hover());
let top = Rect::from_min_max(r.min, pos2(r.max.x, r.min.y + 40.0));
let bottom = Rect::from_min_max(pos2(r.min.x, r.min.y + 40.0), r.max);
let top = Rect::from_min_max(r.min, pos2(r.max.x, r.min.y + BIG_LABEL_Y - 2.0));
let bottom = Rect::from_min_max(pos2(r.min.x, r.min.y + BIG_LABEL_Y - 2.0), r.max);
let rt = ui.interact(top, ui.id().with("eq_insert_top"), Sense::click());
let rb = ui.interact(bottom, ui.id().with("eq_insert_bottom"), Sense::click());
for (rr, resp) in [(top, &rt), (bottom, &rb)] {
@ -161,8 +161,14 @@ pub fn insert_button(ui: &mut Ui, app: &mut WordApp) {
ui.painter().rect_filled(rr, 4.0, t.hover);
}
}
crate::icons::paint(ui.painter(), Rect::from_center_size(pos2(r.center().x, r.min.y + 20.0), vec2(32.0, 32.0)), "equation", t.icon, t.accent);
crate::widgets::label_line(ui, r.center().x, r.min.y + 43.0, label, regular(11.5), t.text, true);
crate::icons::paint(
ui.painter(),
Rect::from_center_size(pos2(r.center().x, r.min.y + BIG_ICON_Y), vec2(32.0, 32.0)),
"equation",
t.icon,
t.accent,
);
crate::widgets::label_line(ui, r.center().x, r.min.y + BIG_LABEL_Y, label, regular(11.5), t.text, true);
if rt.on_hover_text(tl!("Insert a new equation (Alt+=)")).clicked() {
let _ = app.run("insert.equation", json!({}));
app.canvas.want_focus = true;

View File

@ -8,7 +8,17 @@ use crate::{WordApp, icons};
/// Height of the ribbon content area (without group labels).
pub const CONTENT_H: f32 = 66.0;
pub const LABEL_H: f32 = 16.0;
/// Height of the group title row under the content.
pub const LABEL_H: f32 = 18.0;
/// Large buttons: centre of the 32 px icon, top of the first label line, and the pitch and
/// height of a label line (`regular(11.5)`; a test checks the font's real line height).
pub const BIG_ICON_Y: f32 = 18.0;
pub const BIG_LABEL_Y: f32 = 36.0;
pub const LABEL_PITCH: f32 = 13.0;
pub const LABEL_LINE_H: f32 = 14.0;
// #653: a two-line label ends inside the content, above the group title row.
const _: () = assert!(BIG_LABEL_Y + LABEL_PITCH + LABEL_LINE_H <= CONTENT_H);
const _: () = assert!(BIG_ICON_Y + 16.0 <= BIG_LABEL_Y);
/// Shortcut text for a command (`⌘B` on macOS, `Ctrl+B` elsewhere): its first key in effect,
/// custom keys included (Customize Keyboard).
@ -154,14 +164,14 @@ fn big_button(ui: &mut Ui, app: &mut WordApp, icon: &str, label: &str, id: &str,
keytip_badge(ui, r, id, &label.replace('\n', " "));
let on = enabled(app, id);
bg(ui, r, &resp, checked, &t);
let ic = Rect::from_center_size(pos2(r.center().x, r.min.y + 20.0), vec2(32.0, 32.0));
let ic = Rect::from_center_size(pos2(r.center().x, r.min.y + BIG_ICON_Y), vec2(32.0, 32.0));
let (c, a) = if on { (t.icon, t.accent) } else { (t.text_disabled, t.text_disabled) };
icons::paint(ui.painter(), ic, icon, c, a);
let mut y = r.min.y + 43.0;
let mut y = r.min.y + BIG_LABEL_Y;
let lines: Vec<&str> = label.split('\n').collect();
for (i, l) in lines.iter().enumerate() {
label_line(ui, r.center().x, y, l, regular(11.5), if on { t.text } else { t.text_disabled }, menu && i + 1 == lines.len());
y += 13.0;
y += LABEL_PITCH;
}
let resp = tooltip(app, resp, &label.replace('\n', " "), id);
command_menu(&resp, app, id);
@ -263,12 +273,12 @@ 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, c, a);
icons::paint(ui.painter(), Rect::from_center_size(pos2(r.center().x, r.min.y + BIG_ICON_Y), 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;
let mut y = r.min.y + BIG_LABEL_Y;
for (i, l) in lines.iter().enumerate() {
label_line(ui, r.center().x, y, l, regular(11.5), text, i + 1 == lines.len());
y += 13.0;
y += LABEL_PITCH;
}
resp
} else {
@ -313,9 +323,10 @@ pub fn group_titled(ui: &mut Ui, id_title: &str, title: &str, launcher: Option<&
});
let r = inner.response.rect;
let r = Rect::from_min_max(start, pos2(r.max.x, start.y + CONTENT_H + LABEL_H));
ui.painter().text(pos2(r.center().x, r.max.y - 8.0), Align2::CENTER_CENTER, title, regular(11.0), t.group_label);
let title_y = r.max.y - LABEL_H / 2.0;
ui.painter().text(pos2(r.center().x, title_y), Align2::CENTER_CENTER, title, regular(11.0), t.group_label);
if let Some(cmd) = launcher {
let lr = Rect::from_center_size(pos2(r.max.x - 6.0, r.max.y - 8.0), vec2(11.0, 11.0));
let lr = Rect::from_center_size(pos2(r.max.x - 6.0, title_y), vec2(11.0, 11.0));
let resp = ui.interact(lr, ui.id().with(("launch", id_title)), Sense::click());
if resp.hovered() {
ui.painter().rect_filled(lr.expand(2.0), 2.0, t.hover);
@ -822,6 +833,17 @@ fn icon_text_button_with(ui: &mut Ui, icon: &str, text: &str, font: egui::FontId
mod tests {
use super::*;
/// #653: a large button's label line is no taller than `LABEL_LINE_H` in the interface font, so
/// two lines (checked at compile time) end above the group title row.
#[test]
fn big_button_label_lines_fit_their_slot() {
let ctx = egui::Context::default();
ctx.set_fonts(crate::theme::font_definitions(false, false));
ctx.run_ui(egui::RawInput::default(), |_| {}).drop_without_applying_deltas();
let h = ctx.fonts_mut(|f| f.layout_no_wrap("Page Number Mail Merge gjpqy".into(), crate::theme::regular(11.5), Color32::WHITE).size().y);
assert!(h <= LABEL_LINE_H, "label line is {h} px tall");
}
#[test]
fn drawn_buttons_fade_in_on_hover_and_darken_while_pressed() {
// #305's drawn replacements for ✕, 💬, ✎ and ➕ must react like egui's buttons.

View File

@ -1,6 +1,6 @@
# UI parity with Microsoft Word
> **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
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** trivial (ribbon labels clear the group titles, #653; previously 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 | Two-line labels on large ribbon buttons end above the group title row (icon and label moved up, title row 18 px; shared constants with a compile-time check) (#653) |
| 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) |