diff --git a/ROADMAP.md b/ROADMAP.md index c58a58439..6cf56660f 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -136,6 +136,7 @@ Ranked, to beta (detail and remaining milestones in [docs/roadmap.md](docs/roadm ## Progress log +- 2026-10-25: Text Highlight Color shows the last colour as the button's bar in the ribbon and mini toolbar; after No Color it shows the pen without a misleading yellow bar, and the button removes highlighting (#4087). - 2026-10-23: DOCX: a document whose default font size is unset keeps its size when saved and reopened. The size was left out of the file, and the reader read the missing size as 10 pt while the document resolved it as 11 pt; the file now says 11 pt (`DEFAULT_FONT_SIZE`). Files from other programs without a size are read as before. - 2026-10-23: Insert › Table: hovering the grid shows the table on the page where it would go, as Word's Live Preview does, and the preview follows the pointer from square to square. It is laid out on a copy of the document, so nothing changes, and Undo has nothing new, until a square is clicked (#3931). - 2026-10-22: Mail merge (#3028): Preview Results has First Record and Last Record buttons and a box with the previewed record number; type a number and press Enter to go there (past either end stops at the first or last record). Finish & Merge is now a menu. Edit Individual Documents… and Print Documents… ask for all records, the current record, or a From and To range by record number, and Print Documents opens Print for the merged document. Scripts get `mailings.first`, `mailings.last`, `mailings.goToRecord`, `mailings.printDocuments` and a `records` choice on `mailings.finish`; a range with no records in it is refused instead of leaving an empty document. @@ -597,6 +598,7 @@ Footer page-table height and body wrapping are corrected. Other pagination gaps | Date | Change | Summary | |---|---|---| +| 2026-10-25 | trivial | Progress log: Text Highlight Color buttons show the remembered colour, or no colour bar after No Color (#4087) | | 2026-10-23 | trivial | Progress log: interface translations from storytold/craft-i18n through the `craft-i18n` crate | | 2026-10-22 | trivial | Progress log: mail merge record navigation and record ranges for Finish & Merge and Print Documents (#3028) | | 2026-10-23 | trivial | Progress log: a relationship the markup names without the prefix is read as it wrote (#3411 follow-up) | diff --git a/crates/ui-egui/src/icons.rs b/crates/ui-egui/src/icons.rs index ac039e7ed..2b9326c25 100644 --- a/crates/ui-egui/src/icons.rs +++ b/crates/ui-egui/src/icons.rs @@ -160,6 +160,10 @@ pub fn paint(p: &Painter, r: Rect, name: &str, c: Color32, accent: Color32) { pen.line(&[(5.0, 12.0), (3.5, 15.0), (8.0, 15.0)]); pen.frect(2.0, 16.5, 18.0, 19.0, yellow); } + "highlightNoColor" => { + pen.fill(&[(5.0, 12.0), (11.0, 4.0), (14.0, 7.0), (8.0, 15.0)], c); + pen.line(&[(5.0, 12.0), (3.5, 15.0), (8.0, 15.0)]); + } "fontcolor" => { pen.text(10.0, 9.0, 14.0, "A", c, false); pen.frect(2.0, 16.5, 18.0, 19.0, red); @@ -1617,3 +1621,26 @@ pub fn library(p: &Painter, r: Rect, icon: &wordcraft_render::icon_lib::Icon, c: } } } + +#[cfg(test)] +mod tests { + use super::*; + + fn has_default_highlight_bar(name: &str) -> bool { + let ctx = egui::Context::default(); + let output = ctx.run_ui(egui::RawInput::default(), |ui| { + paint(ui.painter(), Rect::from_min_size(Pos2::ZERO, egui::vec2(20.0, 20.0)), name, Color32::BLACK, Color32::BLACK); + }); + let yellow = Color32::from_rgb(0xF2, 0xC8, 0x11); + let found = output.shapes.iter().any(|shape| matches!(&shape.shape, Shape::Rect(rect) if rect.fill == yellow)); + output.drop_without_applying_deltas(); + found + } + + /// #4087: No Color uses the pen-only icon, not the ordinary icon's yellow fallback bar. + #[test] + fn no_colour_highlight_icon_has_no_yellow_bar() { + assert!(has_default_highlight_bar("highlight")); + assert!(!has_default_highlight_bar("highlightNoColor")); + } +} diff --git a/crates/ui-egui/src/mini_toolbar.rs b/crates/ui-egui/src/mini_toolbar.rs index aaff581ef..5aff43cc8 100644 --- a/crates/ui-egui/src/mini_toolbar.rs +++ b/crates/ui-egui/src/mini_toolbar.rs @@ -278,8 +278,8 @@ fn content(app: &mut WordApp, ui: &mut egui::Ui, t: &Tokens) { sep(ui, t); let hl = app.canvas.last_highlight.clone(); - let sw = highlight_button_swatch(&hl); - widgets::split(ui, app, "highlight", "Text Highlight Color", "format.highlight", json!({"color": hl}), false, sw, |ui, app| { + let (icon, sw) = highlight_button_appearance(&hl); + widgets::split(ui, app, icon, "Text Highlight Color", "format.highlight", json!({"color": hl}), false, sw, |ui, app| { highlight_menu(ui, app); }); let fc = app.canvas.last_font_color.clone(); @@ -304,13 +304,16 @@ fn sep(ui: &mut egui::Ui, t: &Tokens) { p.vline(r.center().x, r.y_range().shrink(4.0), egui::Stroke::new(1.0, t.border)); } +/// The split-button icon and colour bar for the remembered highlight choice. +pub(crate) fn highlight_button_appearance(name: &str) -> (&'static str, Option) { + let highlight = wordcraft_doc::props::Highlight::from_ooxml(name); + let icon = if highlight == wordcraft_doc::props::Highlight::None { "highlightNoColor" } else { "highlight" }; + (icon, highlight.rgb().map(crate::theme::c32)) +} + /// Highlight colour grid and No Color, shared by the ribbon and the mini toolbar. The pick, /// No Color too, becomes the split button's colour, as in Word: after No Color a click on the /// button removes highlighting (#2105). -pub(crate) fn highlight_button_swatch(name: &str) -> Option { - wordcraft_doc::props::Highlight::from_ooxml(name).rgb().map(crate::theme::c32) -} - pub(crate) fn highlight_menu(ui: &mut egui::Ui, app: &mut WordApp) { egui::Grid::new("mini_hl").spacing(Vec2::new(3.0, 3.0)).show(ui, |ui| { for (i, h) in wordcraft_doc::props::Highlight::ALL.iter().enumerate().skip(1) { @@ -354,9 +357,10 @@ mod tests { use super::*; #[test] - fn highlight_button_swatch_follows_the_picked_colour() { - assert_eq!(highlight_button_swatch("cyan"), Some(egui::Color32::from_rgb(0, 255, 255))); - assert_eq!(highlight_button_swatch("none"), None); + fn highlight_button_appearance_follows_the_picked_colour() { + assert_eq!(highlight_button_appearance("cyan"), ("highlight", Some(egui::Color32::from_rgb(0, 255, 255)))); + assert_eq!(highlight_button_appearance("none"), ("highlightNoColor", None)); + assert_eq!(highlight_button_appearance("unknown"), ("highlightNoColor", None)); } fn vp() -> Rect { diff --git a/crates/ui-egui/src/ribbon.rs b/crates/ui-egui/src/ribbon.rs index 276950305..b67a30ef7 100644 --- a/crates/ui-egui/src/ribbon.rs +++ b/crates/ui-egui/src/ribbon.rs @@ -1156,11 +1156,11 @@ fn home(app: &mut WordApp, ui: &mut Ui) { mi(ui, app, "Double Strikethrough", "format.doubleStrikethrough", json!({})); }); let hl = app.canvas.last_highlight.clone(); - let sw = crate::mini_toolbar::highlight_button_swatch(&hl); + let (icon, sw) = crate::mini_toolbar::highlight_button_appearance(&hl); split( ui, app, - "highlight", + icon, "Text Highlight Color", "format.highlight", json!({"color": hl}), diff --git a/crates/ui-egui/src/widgets.rs b/crates/ui-egui/src/widgets.rs index 4d6bbc4bf..b56a13852 100644 --- a/crates/ui-egui/src/widgets.rs +++ b/crates/ui-egui/src/widgets.rs @@ -320,7 +320,9 @@ pub fn split( icons::paint(ui.painter(), ic, icon, c, a); if let Some(sw) = swatch { let sw = if on { sw } else { t.text_disabled }; - ui.painter().rect_filled(Rect::from_min_max(pos2(r.min.x + 4.0, r.max.y - 5.0), pos2(r.max.x - 4.0, r.max.y - 2.0)), 0.0, sw); + let bar = Rect::from_min_max(pos2(r.min.x + 4.0, r.max.y - 5.0), pos2(r.max.x - 4.0, r.max.y - 2.0)); + ui.painter().rect_filled(bar, 0.0, sw); + ui.painter().rect_stroke(bar, 0.0, Stroke::new(0.5, t.border), egui::StrokeKind::Inside); } icons::paint(ui.painter(), Rect::from_center_size(ar.center(), vec2(DROPDOWN, DROPDOWN)), "dropdown", c, a); let resp = tooltip(app, resp, tip, id); diff --git a/docs/gaps.md b/docs/gaps.md index 0553a8ce0..dbc7e86a5 100644 --- a/docs/gaps.md +++ b/docs/gaps.md @@ -125,6 +125,7 @@ Footer page-table height and body wrapping are corrected. Other pagination gaps | Date | Change | Summary | |---|---|---| | 2026-10-22 | minor | #8 narrowed: Spanish proofing (es-ES, es-MX, es-419, es-UY spelling from the installed Hunspell dictionaries, Uruguayan `vos` forms, generic and opening-mark checks, #3360, #3553) | +| 2026-10-25 | trivial | Text Highlight Color buttons show the remembered colour and no bar after No Color (#4087); no ranked gap or readiness estimate changes | | 2026-10-22 | trivial | Gap 10: group connector rerouting preserves rotated/flipped free and unresolved endpoints under nonuniform scaling, with safe degenerate-dimension and display-size-cap handling; routing around shapes and readiness estimates unchanged | | 2026-10-21 | trivial | The ranked gap list held two rows each for #19, #20 and #21; the stale copies are gone, so each gap appears once, and the #20 row carries the measured figure (#3411) | | 2026-10-21 | trivial | #20 narrowed: a document with many relationships opens in the time its part takes (#3411) | diff --git a/docs/ui-parity.md b/docs/ui-parity.md index 90ca04e79..7cae0f006 100644 --- a/docs/ui-parity.md +++ b/docs/ui-parity.md @@ -76,6 +76,7 @@ Footnote and endnote editing keeps body and note text at normal opacity, on ligh | Date | Change | Summary | |---|---|---| +| 2026-10-25 | trivial | Text Highlight Color shows the remembered colour on the ribbon and mini-toolbar buttons; No Color leaves the pen without a misleading yellow bar (#4087) | | 2026-10-23 | trivial | Insert › Table: hovering the grid shows the table on the page, at the caret, as Word's Live Preview does; it follows the pointer and is laid out on a copy, so the document and Undo change only when a square is clicked (#3931) | | 2026-10-22 | trivial | Mail merge: First Record, Last Record, a record number box, and record choices for Edit Individual Documents and Print Documents (#3028) | | 2026-10-22 | trivial | While editing a header, a single click in the footer (or the reverse, or another page's own header) edits that one with the caret where clicked; the click was placed in the header being edited, and the view scrolled back to it, so a double-click on the footer rarely landed |