Fix paragraph alignment focus (#690)

* Fix paragraph alignment focus

Fixes #686

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Review fixes for #690: every ribbon command button returns focus to the document

Instead of four hard-coded alignment ids, big, small and split buttons and ribbon menu
items hand keyboard focus back to the canvas after running any engine command (ui.*
commands open dialogs and panes, which take focus themselves). The test also clicks Bold.
The ui-parity.md note moves into the change log.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
authored and GitHub committed 2026-10-12 05:32:58 -04:00
1 parent 25048c8b9c
commit de8a3daf31
4 files changed
+59 -6

No files matched your search

+41
View File
@@ -1676,6 +1676,7 @@ pub fn now_ms() -> f64 {
#[cfg(test)]
mod tests {
use super::*;
use egui_kittest::kittest::Queryable;
fn mod_key(key: egui::Key) -> egui::Event {
egui::Event::Key { key, physical_key: None, pressed: true, repeat: false, modifiers: egui::Modifiers::COMMAND }
@@ -2641,6 +2642,46 @@ mod tests {
assert_eq!(reported.get(), Some(true), "the host was told before the frame ended");
}
#[test]
fn paragraph_alignment_keeps_the_canvas_focused_for_typing() {
use wordcraft_doc::Align;
let mut h = egui_kittest::Harness::builder().with_size(egui::vec2(1440.0, 900.0)).build_ui_state(
|ui, app: &mut WordApp| {
let ctx = ui.ctx().clone();
app.logic(&ctx);
app.ui(ui);
},
typed(),
);
for _ in 0..6 {
h.step();
}
for (label, alignment, text) in
[("Align Left", Align::Left, " left"), ("Center", Align::Center, " center"), ("Align Right", Align::Right, " right")]
{
h.get_by_role_and_label(egui::accesskit::Role::Button, label).click();
h.step();
assert!(h.state().canvas.focused, "{label}: the canvas keeps keyboard focus");
let paragraph = h.state().session.doc.para_at(&h.state().session.sel.focus).expect("the caret remains in its paragraph");
assert_eq!(h.state().session.doc.styles.resolve_para(&paragraph.props).align, alignment);
h.event(egui::Event::Text(text.into()));
h.step();
assert!(body_text(h.state()).ends_with(text), "{label}: typing continues in the document");
}
// Every command button does the same, not only alignment: Bold, then type.
h.get_by_role_and_label(egui::accesskit::Role::Button, "Bold").click();
h.step();
assert!(h.state().canvas.focused, "Bold: the canvas keeps keyboard focus");
h.event(egui::Event::Text(" bold".into()));
h.step();
assert!(body_text(h.state()).ends_with(" bold"), "Bold: typing continues in the document");
}
/// #330: Word's shortcuts work from the keyboard — Ctrl+Alt+1 (⌘⌥1) applies Heading 1, and
/// an Alt shortcut doesn't also type the character its key would produce.
#[test]
+2 -2
View File
@@ -337,7 +337,7 @@ fn mi_text(ui: &mut Ui, app: &mut WordApp, text: &str, id: &str, params: Value)
let enabled = crate::widgets::enabled(app, id);
let resp = ui.add_enabled(enabled, egui::Button::new(text).shortcut_text(sc).min_size(vec2(200.0, 0.0)));
if resp.clicked() {
let _ = app.run(id, params);
crate::widgets::run_from_ribbon(app, id, params);
ui.close();
}
}
@@ -390,7 +390,7 @@ fn mi_check(ui: &mut Ui, app: &mut WordApp, label: &str, checked: bool, id: &str
let enabled = crate::widgets::enabled(app, id);
let resp = ui.add_enabled(enabled, egui::Button::new((mark, tl!(label))).selected(checked).min_size(vec2(200.0, 0.0)));
if resp.clicked() {
let _ = app.run(id, params);
crate::widgets::run_from_ribbon(app, id, params);
ui.close();
}
}
+14 -3
View File
@@ -105,6 +105,16 @@ pub(crate) fn bg(ui: &Ui, r: Rect, resp: &Response, checked: bool, t: &Tokens) {
}
}
/// Runs a ribbon button's command, then hands keyboard focus back to the document so typing
/// continues without another click, as in Word (#686). Interface commands (`ui.*`) open dialogs,
/// panes and tabs that take focus themselves, so they keep it.
pub(crate) fn run_from_ribbon(app: &mut WordApp, id: &str, params: Value) {
let _ = app.run(id, params);
if !id.starts_with("ui.") {
app.canvas.want_focus = true;
}
}
/// A large ribbon button: 32 px icon over a (possibly two-line) label.
pub fn big(ui: &mut Ui, app: &mut WordApp, icon: &str, label: &str, id: &str, params: Value, menu: bool) -> Response {
big_button(ui, app, icon, label, id, params, menu, false)
@@ -139,7 +149,7 @@ fn big_button(ui: &mut Ui, app: &mut WordApp, icon: &str, label: &str, id: &str,
let resp = tooltip(app, resp, &label.replace('\n', " "), id);
command_menu(&resp, app, id);
if resp.clicked() && on && !menu {
let _ = app.run(id, params);
run_from_ribbon(app, id, params);
}
resp
}
@@ -162,8 +172,9 @@ pub fn small(ui: &mut Ui, app: &mut WordApp, icon: &str, label: Option<&str>, ti
let resp = tooltip(app, resp, tip, id);
command_menu(&resp, app, id);
if resp.clicked() && on {
let _ = app.run(id, params);
run_from_ribbon(app, id, params);
}
resp.widget_info(|| egui::WidgetInfo::labeled(egui::WidgetType::Button, on, tip));
resp
}
@@ -195,7 +206,7 @@ pub fn split(
let resp = tooltip(app, resp, tip, id);
command_menu(&resp, app, id);
if resp.clicked() {
let _ = app.run(id, params);
run_from_ribbon(app, id, params);
}
egui::Popup::menu(&aresp).show(|ui| {
ui.set_min_width(180.0);
+2 -1
View File
@@ -1,6 +1,6 @@
# UI parity with Microsoft Word
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** 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 buttons return keyboard focus to the document, #686; previously trivial: screen readers can read the document canvas, #488; 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
@@ -43,6 +43,7 @@ own (`AGENTS.md`: no Microsoft iconography, ever).
| Date | Change | Summary |
|---|---|---|
| 2026-10-11 | trivial | Ribbon buttons that run a command (alignment, Bold, styles…) hand keyboard focus back to the document, so typing continues without another click; they also report their name and enabled state to screen readers (#686) |
| 2026-10-11 | trivial | Tables on the page: column and row borders and the resize handle drag, with a dashed guide and one undo step per drag (#217) |
| 2026-10-11 | trivial | Animation effects › Use system setting also reads macOS (Reduce motion) and the Linux/BSD desktops (settings portal: the cross-desktop reduced-motion preference, GNOME's switch, KDE's animation speed; the GTK and KDE settings files where no portal answers), and follows a change without a restart |
| 2026-10-11 | trivial | Animation effects setting in File › Options › Accessibility (Use system setting, On, Off), kept between runs: the app eased wheel scrolling, zooming and its fades whatever the system's setting was; off, they land at once. Follows the system's setting on Windows and in browsers; `ui.animations` for agents |