diff --git a/crates/ui-egui/src/ribbon/arrange.rs b/crates/ui-egui/src/ribbon/arrange.rs index 998d44905..df446d10e 100644 --- a/crates/ui-egui/src/ribbon/arrange.rs +++ b/crates/ui-egui/src/ribbon/arrange.rs @@ -90,9 +90,16 @@ pub(super) fn rotation_field(ui: &mut Ui, app: &mut WordApp) { } } +/// The command the Group menu button follows: Ungroup while a group is selected (Group is off +/// then, as one object has nothing to group), Group otherwise (#4229). +pub(super) fn group_menu_command(app: &WordApp) -> &'static str { + if crate::widgets::enabled(app, "arrange.ungroup") { "arrange.ungroup" } else { "arrange.group" } +} + /// 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, crate::widgets::MenuButtonOptions::new("group", label, "Group", false, "arrange.group"), |ui, app| { + let id = group_menu_command(app); + menu_button(ui, app, crate::widgets::MenuButtonOptions::new("group", label, "Group", false, id), |ui, app| { mi(ui, app, "Group", "arrange.group", json!({})); mi(ui, app, "Ungroup", "arrange.ungroup", json!({})); }); diff --git a/crates/ui-egui/src/ribbon/tests.rs b/crates/ui-egui/src/ribbon/tests.rs index d2e138a4d..edbe0d3b2 100644 --- a/crates/ui-egui/src/ribbon/tests.rs +++ b/crates/ui-egui/src/ribbon/tests.rs @@ -1,3 +1,4 @@ +use super::arrange::group_menu_command; use super::*; use wordcraft_engine::Session; @@ -117,6 +118,34 @@ fn menu_buttons_follow_their_command() { } } +/// #4229: with a group selected the Group button stays on, so Ungroup in its menu can be +/// reached; with nothing selected it is off, and with two objects it offers Group. +#[test] +fn the_group_menu_stays_on_for_a_selected_group() { + use crate::widgets::{enabled, menu_enabled}; + let mut a = crate::WordApp::new( + Session::new(wordcraft_doc::Document::from_text( + "one +two +three", + )), + crate::Services::default(), + ); + assert!(!menu_enabled(&a, group_menu_command(&a)), "nothing selected"); + a.run("insert.shape", json!({"kind": "rectangle", "width": 80, "height": 40})).unwrap(); + a.run("arrange.bounds", json!({"page": 0, "x": 100, "y": 150})).unwrap(); + a.run("select.collapse", json!({"end": true})).unwrap(); + a.run("insert.shape", json!({"kind": "ellipse", "width": 80, "height": 40})).unwrap(); + a.run("arrange.bounds", json!({"page": 0, "x": 300, "y": 150})).unwrap(); + a.run("select.addObject", json!({"index": 0})).unwrap(); + assert_eq!(group_menu_command(&a), "arrange.group"); + assert!(menu_enabled(&a, group_menu_command(&a)), "two objects to group"); + a.run("arrange.group", json!({})).unwrap(); + assert!(!enabled(&a, "arrange.group"), "one group: nothing to group"); + assert_eq!(group_menu_command(&a), "arrange.ungroup"); + assert!(menu_enabled(&a, group_menu_command(&a)), "the menu reaches Ungroup"); +} + /// #1206: Margins, Orientation, Size and Columns mark the caret section's setting, as Word /// does. Every value differs from a new document's, so a default can't satisfy it. #[test] diff --git a/docs/ui-parity.md b/docs/ui-parity.md index e6806755f..66dc806f8 100644 --- a/docs/ui-parity.md +++ b/docs/ui-parity.md @@ -88,6 +88,7 @@ Labels → New Document with “One label per recipient” includes all filtered | 2026-10-29 | trivial | Window sizing uses the screen rectangle when the inner rectangle is missing, empty or not finite; an Equation symbol tooltip shows a missing glyph's linear name or code point once instead of twice | | 2026-10-29 | trivial | Displayed document titles and suggested file names omit NUL characters (X11 panicked on them) and use the localized untitled name when nothing remains (#3634) | | 2026-10-29 | trivial | Recipient labels continue across product-sized sheets instead of stopping after the first sheet (#4880). Filtered/sorted order, product geometry and single-label mode retained; readiness estimates unchanged. | +| 2026-10-26 | trivial | Layout › Arrange › Group stays on while a group is selected, so Ungroup in its menu can be reached; it was greyed out because one object has nothing to group (#4229) | | 2026-10-22 | trivial | View › Window › New Window opens another window on the document, focused and cascaded; two windows on one document are titled `Name:1` and `Name:2` (one window: the plain name). Each window keeps its own selection, scroll position, zoom, view mode, ruler and panes; the text, undo history, unsaved state and file are the document's, so an edit or Undo in either window shows in both, and a caret in text the other window deleted moves to the nearest place left. Closing one of a document's windows asks nothing; closing its last asks about unsaved changes, and quitting asks once per document. Switch Windows lists every window. The web app keeps one window | | 2026-10-22 | trivial | One native window per open document on the desktop (the web app keeps one window): New and Open open the document in a window of its own, focused and cascaded from the last; Open replaces an untouched blank document in its window and brings an already-open file's window forward; each window keeps its own ribbon tab, File page, dialogs, panes and composition; View › Window › Switch Windows lists the windows and brings the chosen one forward; File › Close and the window's close button close the window (asking about unsaved changes) and the window focused before comes forward, while the last window stays open with no document; quitting asks Save / Don't Save / Cancel about each document in its own window | | 2026-10-22 | trivial | Several open documents in one window (one window per document comes next): View › Window › Switch Windows lists the open documents, the shown one marked and unsaved ones dotted, and shows the one chosen, with its own scroll position, zoom, comment drafts and AutoSave file; File › New, Open, Envelopes, Labels, Finish & Merge and personal templates open beside the open documents without asking about unsaved changes; File › Close shows the next open document; the window's close button asks Save / Don't Save / Cancel about each document with unsaved changes, showing it, and Cancel keeps the app open. Untitled documents are numbered (Document2) in the interface language |