From 3e20488c1597ba977d737a2ee2c33482e09ef638 Mon Sep 17 00:00:00 2001 From: gossardla Date: Wed, 14 Oct 2026 22:55:27 +0200 Subject: [PATCH] Comments: an option to show only the selected text's comment (#1055) (#1066) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * test(engine): drop a needless drop() that clippy 1.97 rejects (drop_non_drop) * Comments: a click inside a selected balloon moves the caret in its text (#1013) - The canvas under the markup area also "contains" the pointer, so a press on the balloon counted as a click on the page and left it - Presses over a balloon card now belong to the card, in both the deselect check and the document click in mouse() - Test: press, drag and release inside the selected balloon; a click on the page still leaves it * Comments: an option to show only the selected text's comment (#1055) - Track Changes Options › Comments: "In the margin" (default, as in Word) or "Only for the selected text" - The second mode takes comments out of the margin column; the comment under the caret, or the clicked one, floats beside the page, over its edge in a narrow window - The floating card has its own foreground layer, so presses on it never reach the canvas - review.trackingOptions takes commentDisplay: margin|selected - comment_range_at_caret: the comment whose range holds the caret - Strings in all catalogs; tests for the option, the helper and the card's clicks * vi: comment display options Co-Authored-By: Claude Opus 5.5 --------- Co-authored-by: Brandon Thomas Co-authored-by: Claude Opus 5.5 --- ROADMAP.md | 4 +- crates/engine/src/cmd/review.rs | 46 +++++++++++++-- crates/layout/src/display.rs | 13 +++++ crates/ui-egui/src/canvas.rs | 70 ++++++++++++++++++----- crates/ui-egui/src/dialogs_lists.rs | 10 ++++ crates/ui-egui/src/i18n/bg.tsv | 2 + crates/ui-egui/src/i18n/cs.tsv | 2 + crates/ui-egui/src/i18n/de.tsv | 2 + crates/ui-egui/src/i18n/es.tsv | 2 + crates/ui-egui/src/i18n/et.tsv | 2 + crates/ui-egui/src/i18n/fr.tsv | 2 + crates/ui-egui/src/i18n/hu.tsv | 2 + crates/ui-egui/src/i18n/it.tsv | 2 + crates/ui-egui/src/i18n/ja.tsv | 2 + crates/ui-egui/src/i18n/pl.tsv | 2 + crates/ui-egui/src/i18n/pt-br.tsv | 2 + crates/ui-egui/src/i18n/ru.tsv | 2 + crates/ui-egui/src/i18n/sr-latn.tsv | 2 + crates/ui-egui/src/i18n/sr.tsv | 2 + crates/ui-egui/src/i18n/tr.tsv | 2 + crates/ui-egui/src/i18n/uk.tsv | 2 + crates/ui-egui/src/i18n/vi.tsv | 4 ++ crates/ui-egui/src/i18n/zh-hans.tsv | 2 + crates/ui-egui/src/i18n/zh-hant.tsv | 2 + crates/ui-egui/src/lib.rs | 89 +++++++++++++++++++++++++++++ docs/ui-parity.md | 3 +- 26 files changed, 255 insertions(+), 20 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index a567fee85..32b9a7dae 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -2,7 +2,7 @@ **Stage: alpha** · next: beta, ~15 points (60% → 75% ready for real work) and ~150–230 h away -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-14 · **Change:** trivial (progress log: clicking in a comment's text moves its caret, #1013; previously trivial: progress log: a press on the ribbon no longer scrolls the document, #1019; previously trivial: title rename and account settings; previously trivial: progress log: Down and Up no longer skip a table, #1007; previously trivial: progress log: privacy options on save, #1062; previously trivial: progress log: equation pictures in theme colours on Windows, #1002; previously trivial: symbol fonts, #776; previously trivial: the save prompt answers from the keyboard, #861; previously trivial: table column and row markers on the rulers, #700; previously trivial: table borders and the resize handle drag on the page, #217; previously trivial: formatting revisions landed, #41; previously trivial: Draw tab ink, #307, and Draw Table and Eraser, #303, landed: catalog 402/434; previously minor: readiness table with hours per audience; full number back to the additive weighted sum, 60%; mainstream and essentials numbers added; alpha gate checked; previously major: full re-measure against Word for Mac 16.113.4; restructured to the craftrules progress-docs standard) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4 +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-14 · **Change:** trivial (progress log: an option to show only the selected text's comment, #1055; previously trivial: progress log: clicking in a comment's text moves its caret, #1013; previously trivial: progress log: a press on the ribbon no longer scrolls the document, #1019; previously trivial: title rename and account settings; previously trivial: progress log: Down and Up no longer skip a table, #1007; previously trivial: progress log: privacy options on save, #1062; previously trivial: progress log: equation pictures in theme colours on Windows, #1002; previously trivial: symbol fonts, #776; previously trivial: the save prompt answers from the keyboard, #861; previously trivial: table column and row markers on the rulers, #700; previously trivial: table borders and the resize handle drag on the page, #217; previously trivial: formatting revisions landed, #41; previously trivial: Draw tab ink, #307, and Draw Table and Eraser, #303, landed: catalog 402/434; previously minor: readiness table with hours per audience; full number back to the additive weighted sum, 60%; mainstream and essentials numbers added; alpha gate checked; previously major: full re-measure against Word for Mac 16.113.4; restructured to the craftrules progress-docs standard) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4 > **Printing update:** 2026-10-12 · direct desktop printing through craft-print; details below. @@ -136,6 +136,7 @@ Ranked, to beta (detail and remaining milestones in [docs/roadmap.md](docs/roadm | Date | What landed | |---|---| +| 2026-10-12 | Comments can show only for the selected text (#1055): Track Changes Options › Comments › "Only for the selected text" drops the margin column, and the comment under the caret, or the clicked one, floats beside the page (over its edge in a narrow window). The default stays "In the margin", as in Word; `review.trackingOptions {"commentDisplay"}` for agents | | 2026-10-12 | Clicking inside a selected comment's balloon moves the caret in its text (#1013); the click used to leave the balloon, so the caret could only be moved with the arrow keys. A press on any balloon now belongs to the balloon, not the page behind it | | 2026-10-12 | Page background pictures and textures (Design › Page Color › Fill Effects) are drawn (tiled or stretched) and kept on save instead of being dropped from the package (#999); PDF export paints them too | | 2026-10-12 | Holding a click on the ribbon or toolbar no longer scrolls the document up (#1019): a double-click in the header or footer area left a text drag running, so the next press anywhere drove the edge auto-scroll; a drag now ends with the button, wherever it's released | @@ -336,6 +337,7 @@ Ranked, to beta (detail and remaining milestones in [docs/roadmap.md](docs/roadm | Date | Change | Summary | |---|---|---| +| 2026-10-12 | trivial | Progress log: an option to show only the selected text's comment (#1055) | | 2026-10-12 | trivial | Progress log: clicking in a comment's text moves its caret (#1013) | | 2026-10-12 | trivial | Progress log: a press on the ribbon no longer scrolls the document (#1019) | | 2026-10-12 | trivial | Progress log: Ctrl+drag copies an object (#1014) | diff --git a/crates/engine/src/cmd/review.rs b/crates/engine/src/cmd/review.rs index 4bfc95379..3ab67141e 100644 --- a/crates/engine/src/cmd/review.rs +++ b/crates/engine/src/cmd/review.rs @@ -43,7 +43,7 @@ pub fn specs() -> Vec { .pure(), CommandSpec::new("review.trackingOptions", "Track Changes Options", "Review › Tracking", tracking_options) .params( - r#"{"comments"?: bool, "ink"?: bool, "insertionsDeletions"?: bool, "formatting"?: bool, "balloons"?: "revisions|inline|commentsAndFormatting", "insertMark"?: "underline|doubleUnderline|bold|italic|strikethrough|colorOnly|none", "insertColor"?: "byAuthor" | "RRGGBB", "deleteMark"?: "strikethrough|doubleStrikethrough|hidden|caret|hash|underline|colorOnly", "deleteColor"?: "byAuthor" | "RRGGBB", "changedLines"?: "outside|left|right|none", "changedLinesColor"?: "auto" | "RRGGBB", "trackFormatting"?: bool} → the options (omit all to read them)"#, + r#"{"comments"?: bool, "ink"?: bool, "insertionsDeletions"?: bool, "formatting"?: bool, "balloons"?: "revisions|inline|commentsAndFormatting", "commentDisplay"?: "margin|selected", "insertMark"?: "underline|doubleUnderline|bold|italic|strikethrough|colorOnly|none", "insertColor"?: "byAuthor" | "RRGGBB", "deleteMark"?: "strikethrough|doubleStrikethrough|hidden|caret|hash|underline|colorOnly", "deleteColor"?: "byAuthor" | "RRGGBB", "changedLines"?: "outside|left|right|none", "changedLinesColor"?: "auto" | "RRGGBB", "trackFormatting"?: bool} → the options (omit all to read them)"#, ) .pure(), CommandSpec::new("review.wordCount", "Word Count", "Review › Proofing", word_count) @@ -166,8 +166,8 @@ fn edit_comment(s: &mut Session, v: &Value) -> CmdResult { Ok(json!({"id": id})) } -/// Comment anchored around the caret. -fn comment_at_caret(s: &Session) -> Option { +/// The innermost comment whose range holds the caret. +pub fn comment_range_at_caret(s: &Session) -> Option { let f = &s.sel.focus; let p = s.doc.para_at(f)?; let mut open: Vec = Vec::new(); @@ -181,7 +181,14 @@ fn comment_at_caret(s: &Session) -> Option { _ => {} } } - open.last().copied().or_else(|| { + open.last().copied() +} + +/// Comment anchored around the caret, else the first one in its paragraph. +fn comment_at_caret(s: &Session) -> Option { + let f = &s.sel.focus; + let p = s.doc.para_at(f)?; + comment_range_at_caret(s).or_else(|| { p.object_offsets().into_iter().find_map(|o| match p.object_at(o) { Some(InlineObject::CommentStart { id }) | Some(InlineObject::CommentEnd { id }) => Some(*id), _ => None, @@ -909,7 +916,7 @@ fn thesaurus(s: &mut Session, v: &Value) -> CmdResult { /// Track Changes Options: change the fields given, return them all. A per-user preference. fn tracking_options(s: &mut Session, v: &Value) -> CmdResult { use wordcraft_doc::Rgb; - use wordcraft_layout::display::{BalloonMode, ChangeBar, DeleteMark, InsertMark}; + use wordcraft_layout::display::{BalloonMode, ChangeBar, CommentDisplay, DeleteMark, InsertMark}; let mut m = s.prefs.markup.clone(); let color = |k: &str, auto: &str, cur: Option| -> Result, CmdError> { match p::str(v, k) { @@ -933,6 +940,9 @@ fn tracking_options(s: &mut Session, v: &Value) -> CmdResult { if let Some(x) = pick("balloons") { m.balloons = BalloonMode::parse(x).ok_or_else(|| CmdError::Params("`balloons`: revisions|inline|commentsAndFormatting".into()))?; } + if let Some(x) = pick("commentDisplay") { + m.comment_display = CommentDisplay::parse(x).ok_or_else(|| CmdError::Params("`commentDisplay`: margin|selected".into()))?; + } if let Some(x) = pick("insertMark") { m.insert_mark = InsertMark::parse(x).ok_or_else(|| CmdError::Params("unknown `insertMark`".into()))?; } @@ -962,6 +972,7 @@ pub fn tracking_json(m: &wordcraft_layout::display::MarkupOptions) -> Value { "insertionsDeletions": m.insertions_deletions, "formatting": m.formatting, "balloons": m.balloons.name(), + "commentDisplay": m.comment_display.name(), "insertMark": m.insert_mark.name(), "insertColor": hex(m.insert_color, "byAuthor"), "deleteMark": m.delete_mark.name(), @@ -1039,6 +1050,31 @@ mod tests { assert_eq!(s.run("review.trackingOptions", &json!({})).unwrap()["insertColor"], "00AA00"); } + /// #1055: comments show in the margin as in Word unless the option says otherwise. + #[test] + fn comment_display_defaults_to_margin_and_is_validated() { + let mut s = Session::new(wordcraft_doc::Document::new()); + assert_eq!(s.run("review.trackingOptions", &json!({})).unwrap()["commentDisplay"], "margin"); + assert_eq!(s.run("review.trackingOptions", &json!({"commentDisplay": "selected"})).unwrap()["commentDisplay"], "selected"); + assert_eq!(s.run("review.trackingOptions", &json!({})).unwrap()["commentDisplay"], "selected"); + assert!(s.run("review.trackingOptions", &json!({"commentDisplay": "never"})).is_err()); + assert_eq!(s.run("review.trackingOptions", &json!({"commentDisplay": "margin"})).unwrap()["commentDisplay"], "margin"); + } + + /// #1055: the caret's own comment is the one whose range holds it, not any in its paragraph. + #[test] + fn comment_range_at_caret_needs_the_caret_inside_the_range() { + let mut s = Session::new(wordcraft_doc::Document::from_text("one two three")); + s.run("select.text", &json!({"text": "two"})).unwrap(); + let id = s.run("review.newComment", &json!({"text": "note"})).unwrap()["id"].as_u64().unwrap() as u32; + s.run("select.text", &json!({"text": "two"})).unwrap(); + s.run("caret.left", &json!({})).unwrap(); + assert_eq!(super::comment_range_at_caret(&s), Some(id), "inside the commented text"); + s.run("select.text", &json!({"text": "three"})).unwrap(); + assert_eq!(super::comment_range_at_caret(&s), None, "elsewhere in the paragraph"); + assert_eq!(super::comment_at_caret(&s), Some(id), "the older lookup still falls back to the paragraph's comment"); + } + fn glyph_text(d: &[Draw]) -> String { d.iter().filter_map(|x| if let Draw::Glyphs { text, .. } = x { Some(text.as_str()) } else { None }).collect() } diff --git a/crates/layout/src/display.rs b/crates/layout/src/display.rs index ff0bed885..70843cf0e 100644 --- a/crates/layout/src/display.rs +++ b/crates/layout/src/display.rs @@ -259,6 +259,17 @@ named! { } } +named! { + /// How comments are shown beside the page. + CommentDisplay { + /// Every comment in the margin, as in Word. + #[default] + Margin = "margin", + /// Only the comment of the text under the caret (or the clicked one) floats beside the page. + Selected = "selected", + } +} + /// Track Changes Options: what markup shows and how revisions are drawn. A per-user preference, /// as in Word (saved with the interface settings, not the document). #[derive(Clone, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)] @@ -269,6 +280,7 @@ pub struct MarkupOptions { pub insertions_deletions: bool, pub formatting: bool, pub balloons: BalloonMode, + pub comment_display: CommentDisplay, pub insert_mark: InsertMark, /// `None` = by author. pub insert_color: Option, @@ -290,6 +302,7 @@ impl Default for MarkupOptions { insertions_deletions: true, formatting: true, balloons: BalloonMode::default(), + comment_display: CommentDisplay::default(), insert_mark: InsertMark::default(), insert_color: None, delete_mark: DeleteMark::default(), diff --git a/crates/ui-egui/src/canvas.rs b/crates/ui-egui/src/canvas.rs index 807541cc9..dc9d65315 100644 --- a/crates/ui-egui/src/canvas.rs +++ b/crates/ui-egui/src/canvas.rs @@ -8,6 +8,7 @@ use std::hash::{Hash, Hasher}; use egui::{Color32, Pos2, Rect, Sense, Stroke, TextureHandle, Ui, pos2, vec2}; use serde_json::json; use wordcraft_doc::{PartKind, Pos, StoryRef}; +use wordcraft_layout::display::CommentDisplay; use wordcraft_layout::{DocLayout, Page, Placed}; use crate::WordApp; @@ -317,6 +318,23 @@ fn on_page_gap(rects: &[Rect], bands: &[(f32, f32)], p: Pos2) -> bool { }) } +/// Width of the card that floats beside a page when comments show only for the selected text (points). +const FLOAT_WIDTH: f32 = 216.0; + +/// Comments show as a floating card rather than in a margin column (#1055): the option is on, +/// balloons are shown at all, and no other markup needs the column. +fn floating_comments(app: &WordApp) -> bool { + let s = &app.session; + let m = &s.prefs.markup; + s.balloons_shown() && m.comments && m.comment_display == CommentDisplay::Selected && !s.doc.comments.is_empty() && markup_width(app) <= 0.0 +} + +/// The comment shown when only the selected one is: the clicked balloon, else the comment whose +/// range holds the caret. +fn shown_comment(app: &WordApp) -> Option { + app.canvas.balloon.or_else(|| wordcraft_engine::cmd::review::comment_range_at_caret(&app.session)) +} + /// Width of the markup area beside each page for comment and revision balloons (points), or 0. pub fn markup_width(app: &WordApp) -> f32 { // Word shows comments either in balloons (contextual) or in the Comments pane (list); Track @@ -326,7 +344,7 @@ pub fn markup_width(app: &WordApp) -> f32 { return 0.0; } let m = &s.prefs.markup; - let comments = m.comments && !s.doc.comments.is_empty(); + let comments = m.comments && !s.doc.comments.is_empty() && m.comment_display == CommentDisplay::Margin; let [has_formats, has_moves, has_deletions] = match app.canvas.markup_facts.get() { Some((rev, facts)) if rev == (s.document_id(), s.rev()) => facts, _ => { @@ -1078,7 +1096,21 @@ pub fn show(app: &mut WordApp, ui: &mut Ui) { let near = clip.expand2(vec2(clip.width(), clip.height() * TEXTURE_KEEP_SCREENS)); app.canvas.textures.retain(|k, _| *k < n && geo.rects.get(*k).is_some_and(|r| r.translate(content_origin.to_vec2()).intersects(near))); app.canvas.page_rects = rects.clone(); - balloons(app, ui, &painter, &rects, &layout, geo.scale); + let view_rect = ui.clip_rect(); + if floating_comments(app) { + // Its own layer, so presses on the card never reach the canvas under it. + let ctx = ui.ctx().clone(); + egui::Area::new(egui::Id::new("floating_comment")).order(egui::Order::Foreground).fixed_pos(Pos2::ZERO).sense(Sense::hover()).show( + &ctx, + |aui| { + aui.set_clip_rect(view_rect); + let ap = aui.painter().clone(); + balloons(app, aui, &ap, &rects, &layout, geo.scale, view_rect); + }, + ); + } else { + balloons(app, ui, &painter, &rects, &layout, geo.scale, view_rect); + } // Find › Reading Highlight. let marks: Vec<(Pos, Pos)> = app.session.find_highlights().iter().take(5000).cloned().collect(); for (a, b) in &marks { @@ -1435,17 +1467,20 @@ pub fn show(app: &mut WordApp, ui: &mut Ui) { /// Comment balloons in the markup area right of each page, joined to their anchors, with each /// comment's replies under it. Clicking a balloon selects it, like Word: its text becomes editable /// in place, with Reply, Resolve and Delete (the same commands as the Comments pane). -fn balloons(app: &mut WordApp, ui: &mut Ui, painter: &egui::Painter, rects: &[Rect], layout: &DocLayout, scale: f32) { +fn balloons(app: &mut WordApp, ui: &mut Ui, painter: &egui::Painter, rects: &[Rect], layout: &DocLayout, scale: f32, view: Rect) { app.canvas.balloon_rects.clear(); let mw = markup_width(app); - if app.canvas.balloon.is_some_and(|id| mw <= 0.0 || !app.session.doc.comments.contains_key(&id)) { + let float = floating_comments(app); + if app.canvas.balloon.is_some_and(|id| (mw <= 0.0 && !float) || !app.session.doc.comments.contains_key(&id)) { deselect_balloon(app, ui.ctx()); } - if mw <= 0.0 { + if mw <= 0.0 && !float { return; } let t = Tokens::get(ui.ctx()); - let clip = ui.clip_rect(); + let clip = view; + let only_shown = app.session.prefs.markup.comment_display == CommentDisplay::Selected; + let shown = shown_comment(app); // Replies hang under the comment they answer rather than getting balloons of their own. let is_reply = |c: &wordcraft_doc::Comment| c.parent.is_some_and(|p| app.session.doc.comments.contains_key(&p)); let mut replies: HashMap> = HashMap::new(); @@ -1462,7 +1497,7 @@ fn balloons(app: &mut WordApp, ui: &mut Ui, painter: &egui::Painter, rects: &[Re let comments = if m.comments { wordcraft_engine::cmd::review::comment_list(&app.session) } else { Vec::new() }; for (id, pos) in comments { let Some(pos) = pos else { continue }; - if app.session.doc.comments.get(&id).is_some_and(is_reply) { + if app.session.doc.comments.get(&id).is_some_and(is_reply) || (only_shown && shown != Some(id)) { continue; } let Some(c) = layout.caret_on(&pos, app.session.page_hint) else { continue }; @@ -1496,7 +1531,7 @@ fn balloons(app: &mut WordApp, ui: &mut Ui, painter: &egui::Painter, rects: &[Re for (pi, pr) in rects.iter().enumerate() { let page_scale = layout.pages.get(pi).map_or(scale, |page| page_screen_scale(*pr, page, scale)); let area = Rect::from_min_max(pos2(pr.max.x, pr.min.y), pos2(pr.max.x + mw * page_scale, pr.max.y)); - if area.intersects(clip) { + if mw > 0.0 && area.intersects(clip) { painter.rect_filled(area, 0.0, Color32::from_rgb(0xF3, 0xF3, 0xF3)); painter.line_segment([area.left_top(), area.left_bottom()], Stroke::new(1.0, Color32::from_rgb(0xE0, 0xE0, 0xE0))); } @@ -1511,8 +1546,14 @@ fn balloons(app: &mut WordApp, ui: &mut Ui, painter: &egui::Painter, rects: &[Re } list.sort_by(|a, b| a.1.total_cmp(&b.1).then(a.0.total_cmp(&b.0))); let page_scale = layout.pages.get(pi).map_or(scale, |page| page_screen_scale(*pr, page, scale)); - let x0 = pr.max.x + 10.0; - let w = (mw * page_scale - 20.0).max(60.0); + // Beside the page in the column; floating, beside it when the view has room, else over its edge. + let (x0, w, over_page) = if mw > 0.0 { + (pr.max.x + 10.0, (mw * page_scale - 20.0).max(60.0), false) + } else { + let w = (FLOAT_WIDTH * page_scale - 20.0).max(60.0); + let beside = pr.max.x + 10.0; + if beside + w <= view.max.x - 8.0 { (beside, w, false) } else { ((view.max.x - w - 8.0).max(view.min.x + 8.0), w, true) } + }; let mut next_y = pr.min.y; for (ax, ay, item, pos) in list.iter() { let id = match item { @@ -1557,7 +1598,8 @@ fn balloons(app: &mut WordApp, ui: &mut Ui, painter: &egui::Painter, rects: &[Re let fs = (11.0 * page_scale / PX_PER_PT).clamp(8.0, 16.0); let thread: Vec = replies.get(id).cloned().unwrap_or_default(); let anchor = pos2(pr.min.x + ax * page_scale, pr.min.y + ay * page_scale); - let top = (anchor.y - 12.0).max(next_y); + // Over the page the card goes under the commented line, not on it. + let top = (anchor.y + if over_page { 4.0 } else { -12.0 }).max(next_y); let active = app.canvas.balloon == Some(*id); // Lay out a read-only balloon's text: the comment, then each reply under a rule. let mut lines: Vec<(std::sync::Arc, f32)> = Vec::new(); @@ -1584,8 +1626,10 @@ fn balloons(app: &mut WordApp, ui: &mut Ui, painter: &egui::Painter, rects: &[Re let selected = active || (app.session.sel.focus.path == pos.path && app.session.sel.focus.story == pos.story); // Leader: from the anchor along the text to the page edge, then to the card. let lead = Stroke::new(if selected { 1.5 } else { 1.0 }, color.linear_multiply(if selected { 1.0 } else { 0.7 })); - dashed(painter, anchor, pos2(pr.max.x, anchor.y), lead); - painter.line_segment([pos2(pr.max.x, anchor.y), pos2(x0, top + 10.0)], lead); + if !over_page { + dashed(painter, anchor, pos2(pr.max.x, anchor.y), lead); + painter.line_segment([pos2(pr.max.x, anchor.y), pos2(x0, top + 10.0)], lead); + } let card = if active { active_balloon(app, ui, painter, *id, &thread, card, fs, color) } else { diff --git a/crates/ui-egui/src/dialogs_lists.rs b/crates/ui-egui/src/dialogs_lists.rs index 9d6b8dd0a..24baf130a 100644 --- a/crates/ui-egui/src/dialogs_lists.rs +++ b/crates/ui-egui/src/dialogs_lists.rs @@ -351,6 +351,7 @@ pub struct TrackForm { pub insertions_deletions: bool, pub formatting: bool, pub balloons: String, + pub comment_display: String, pub insert_mark: String, /// Hex, empty = by author. pub insert_color: String, @@ -374,6 +375,7 @@ impl TrackForm { insertions_deletions: b("insertionsDeletions"), formatting: b("formatting"), balloons: s("balloons"), + comment_display: s("commentDisplay"), insert_mark: s("insertMark"), insert_color: c("insertColor"), delete_mark: s("deleteMark"), @@ -393,6 +395,7 @@ impl TrackForm { "insertionsDeletions": self.insertions_deletions, "formatting": self.formatting, "balloons": self.balloons, + "commentDisplay": self.comment_display, "insertMark": self.insert_mark, "insertColor": or(&self.insert_color, "byAuthor"), "deleteMark": self.delete_mark, @@ -472,6 +475,10 @@ pub fn track_options(app: &mut WordApp, ui: &mut Ui, f: &mut TrackForm) -> bool ] { ui.radio_value(&mut f.balloons, v.to_string(), tl!(l)); } + heading(ui, "Comments"); + for (v, l) in [("margin", "In the margin"), ("selected", "Only for the selected text")] { + ui.radio_value(&mut f.comment_display, v.to_string(), tl!(l)); + } heading(ui, "Advanced"); egui::Grid::new("tco_advanced").num_columns(3).spacing(vec2(10.0, 6.0)).show(ui, |ui| { ui.label(tl!("Insertions:")); @@ -574,6 +581,8 @@ mod tests { assert_eq!(a.run("review.trackingOptions", json!({})).unwrap(), json!({"pending": "trackChangesOptions"})); let Some(Dialog::TrackOptions { form }) = &mut a.dialog else { panic!("dialog") }; assert_eq!((form.insert_mark.as_str(), form.balloons.as_str(), form.insert_color.as_str()), ("underline", "commentsAndFormatting", "")); + assert_eq!(form.comment_display, "margin", "comments stay in the margin, as in Word, unless chosen otherwise (#1055)"); + form.comment_display = "selected".into(); form.insert_mark = "doubleUnderline".into(); form.changed_lines = "right".into(); form.changed_lines_color = "FF0000".into(); @@ -582,6 +591,7 @@ mod tests { a.run("review.trackingOptions", params).unwrap(); let m = &a.session.prefs.markup; assert_eq!(m.insert_mark, wordcraft_layout::display::InsertMark::DoubleUnderline); + assert_eq!(m.comment_display, wordcraft_layout::display::CommentDisplay::Selected); assert_eq!(m.changed_lines_color, Some(wordcraft_doc::Rgb(0xFF, 0, 0))); // Saved and restored with the preferences. let saved = a.prefs(); diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index 105207382..fb8c4f3e1 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -87,6 +87,7 @@ Enter formatting for number: Въведете форматиране за номера: Follow number with: След номера: Formatting Форматиране + In the margin На полето Include level number from: Включване на номер на ниво от: Ink Мастило Insertions and Deletions Вмъквания и изтривания @@ -100,6 +101,7 @@ Number alignment: Подравняване на номера: Number format Формат на номера Number style for this level: Стил на номериране за това ниво: + Only for the selected text Само за избрания текст Outside border Външна граница Restart list after: Рестартиране на списъка след: Right border Дясна граница diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index fb0fe633f..22522d5a5 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -538,6 +538,7 @@ Immersive Asistivní čtení Immersive Reader Asistivní čtečka Immersive\nReader Asistivní\nčtečka + In the margin Na okraji Include level number from: Zahrnout číslo úrovně z: Increase Font Size Zvětšit velikost písma Increase Indent Zvětšit odsazení @@ -789,6 +790,7 @@ OK OK One Jeden One Page Jedna stránka + Only for the selected text Pouze pro vybraný text Only web (http, https) and email (mailto) links can be followed Otevřít lze pouze webové (http, https) a e-mailové (mailto) odkazy Open Otevřít OpenDocument Text (*.odt) Text OpenDocument (*.odt) diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index f59d53bd3..7c2a1aec2 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -92,6 +92,7 @@ Enter formatting for number: Formatierung für die Nummer eingeben: Follow number with: Der Nummer folgt: Formatting Formatierung + In the margin Am Rand Include level number from: Ebenennummer übernehmen aus: Ink Freihand Insertions and Deletions Einfügungen und Löschungen @@ -110,6 +111,7 @@ Number alignment: Nummernausrichtung: Number format Nummernformat Number style for this level: Nummernstil für diese Ebene: + Only for the selected text Nur für den ausgewählten Text Outside border Äußere Rahmenlinie Reopen Erneut öffnen Restart list after: Liste neu starten nach: diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index 4d91b4c42..b7cbeb495 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -96,6 +96,7 @@ Ignore web and file addresses Omitir direcciones web y de archivo Ignore words in capitals Omitir palabras en mayúsculas Ignore words with numbers Omitir palabras con números + In the margin En el margen Inches Pulgadas Formatting exceptions Excepciones de formato Include level number from: Incluir número de nivel de: @@ -123,6 +124,7 @@ Number alignment: Alineación del número: Number format Formato de número Number style for this level: Estilo de número para este nivel: + Only for the selected text Solo para el texto seleccionado Outside border Borde exterior Overtype Sobrescribir Overtype (typing replaces the characters after the caret) Sobrescribir (lo que escribes reemplaza los caracteres tras el cursor) diff --git a/crates/ui-egui/src/i18n/et.tsv b/crates/ui-egui/src/i18n/et.tsv index c897c8a22..fc157f690 100644 --- a/crates/ui-egui/src/i18n/et.tsv +++ b/crates/ui-egui/src/i18n/et.tsv @@ -337,6 +337,7 @@ Immersive Süvenemine Immersive Reader Süvaluger Immersive\nReader Süva-\nluger + In the margin Veerisel Inches Tollid Increase Font Size Suurenda fondi suurust Increase Indent Suurenda taanet @@ -504,6 +505,7 @@ Office 2003 Default Office 2003 vaikevalik One Üks One Page Üks leht + Only for the selected text Ainult valitud teksti jaoks Open Ava OpenDocument Text (*.odt) OpenDocumenti tekst (*.odt) Optional Hyphen Valikuline poolitusmärk diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index 2802433da..c7b32bd1b 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -86,6 +86,7 @@ Follow number with: Faire suivre le numéro de : Formatting Mise en forme Formatting exceptions Exceptions de mise en forme + In the margin Dans la marge Include level number from: Inclure le numéro de niveau de : Ink Encre Insertions and Deletions Insertions et suppressions @@ -101,6 +102,7 @@ Number alignment: Alignement des numéros : Number format Format des numéros Number style for this level: Style de numéros pour ce niveau : + Only for the selected text Uniquement pour le texte sélectionné Outside border Bordure extérieure Overtype Refrappe Outside: Extérieur : diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index 32f06d9a6..2cc1eb01d 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -328,6 +328,7 @@ Immersive Elmélyült Immersive Reader Elmélyült olvasás Immersive\nReader Elmélyült\nolvasás + In the margin A margón Increase Font Size Betűméret növelése Increase Indent Behúzás növelése Indent Behúzás @@ -482,6 +483,7 @@ OK OK One Egy One Page Egy oldal + Only for the selected text Csak a kijelölt szövegnél Open Megnyitás OpenDocument Text (*.odt) OpenDocument szöveg (*.odt) Optional Hyphen Feltételes kötőjel diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 1edcdbcd7..6a36c0b88 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -85,6 +85,7 @@ Enter formatting for number: Formato del numero: Follow number with: Dopo il numero: Formatting Formattazione + In the margin A margine Include level number from: Includi numero di livello da: Ink Penna Insertions and Deletions Inserimenti ed eliminazioni @@ -98,6 +99,7 @@ Number alignment: Allineamento numero: Number format Formato numero Number style for this level: Stile numero per questo livello: + Only for the selected text Solo per il testo selezionato Outside border Bordo esterno Restart list after: Riavvia elenco dopo: Right border Bordo destro diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 4c120c1f7..0b4455135 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -94,6 +94,7 @@ Ignore web and file addresses Web アドレスとファイル パスを無視する Ignore words in capitals すべて大文字の単語を無視する Ignore words with numbers 数字を含む単語を無視する + In the margin 余白に表示 Inches インチ Formatting exceptions 書式の例外 Include level number from: 含めるレベルの番号: @@ -127,6 +128,7 @@ Number alignment: 番号の配置: Number format 番号書式 Number style for this level: このレベルの番号の種類: + Only for the selected text 選択したテキストのみ Outside border 外側の罫線 Make WordCraft the default for Word documents… WordCraft を Word 文書の既定のアプリにする… Opens .docx, .doc, .rtf and .odt files and Word templates with WordCraft from now on. 今後、.docx、.doc、.rtf、.odt ファイルと Word テンプレートを WordCraft で開きます。 diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index 9661adf1b..db33226f7 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -322,6 +322,7 @@ Immersive Czytanie immersyjne Immersive Reader Czytnik immersyjny Immersive\nReader Czytnik\nimmersyjny + In the margin Na marginesie Increase Font Size Zwiększ rozmiar czcionki Increase Indent Zwiększ wcięcie Indent Wcięcie @@ -472,6 +473,7 @@ Office 2003 Default Domyślny pakietu Office 2003 One Jedna One Page Jedna strona + Only for the selected text Tylko dla zaznaczonego tekstu Open Otwórz OpenDocument Text (*.odt) Tekst OpenDocument (*.odt) Optional Hyphen Łącznik opcjonalny diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index 349fb8bd3..4279d0a4b 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -97,6 +97,7 @@ Ignore web and file addresses Ignorar endereços da Web e de arquivos Ignore words in capitals Ignorar palavras em maiúsculas Ignore words with numbers Ignorar palavras com números + In the margin Na margem Inches Polegadas Formatting exceptions Exceções de formatação Include level number from: Incluir número do nível: @@ -124,6 +125,7 @@ Number alignment: Alinhamento do número: Number format Formato do número Number style for this level: Estilo de número deste nível: + Only for the selected text Somente para o texto selecionado Outside border Borda externa Overtype Sobrescrever Overtype (typing replaces the characters after the caret) Sobrescrever (o que você digita substitui os caracteres após o cursor) diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index 1b7bfe6e4..e1b80a1fe 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -189,6 +189,8 @@ Delete Style Удалить стиль Delete Table Удалить таблицу Design Дизайн + In the margin На полях + Only for the selected text Только для выделенного текста No document is open. Create a document or open one first. Нет открытого документа. Сначала создайте или откройте документ. Search Поиск Title Bar Строка заголовка diff --git a/crates/ui-egui/src/i18n/sr-latn.tsv b/crates/ui-egui/src/i18n/sr-latn.tsv index eb49545c7..934e9b01b 100644 --- a/crates/ui-egui/src/i18n/sr-latn.tsv +++ b/crates/ui-egui/src/i18n/sr-latn.tsv @@ -121,6 +121,7 @@ Ignore web and file addresses Zanemari veb-adrese i putanje datoteka Ignore words in capitals Zanemari reči pisane velikim slovima Ignore words with numbers Zanemari reči sa brojevima + In the margin Na margini Inches Inči Formatting exceptions Izuzeci formatiranja Include level number from: Uključi broj nivoa: @@ -149,6 +150,7 @@ Number alignment: Poravnanje broja: Number format Format broja Number style for this level: Stil broja za ovaj nivo: + Only for the selected text Samo za izabrani tekst Outside border Spoljna ivica In Default apps, choose WordCraft for the document types you want it to open. U podrazumevanim aplikacijama izaberite WordCraft za vrste dokumenata koje želite da otvara. Make WordCraft the default for Word documents… Postavi WordCraft kao podrazumevanu aplikaciju za Word dokumente… diff --git a/crates/ui-egui/src/i18n/sr.tsv b/crates/ui-egui/src/i18n/sr.tsv index 8a5137902..7c6e3c2cc 100644 --- a/crates/ui-egui/src/i18n/sr.tsv +++ b/crates/ui-egui/src/i18n/sr.tsv @@ -125,6 +125,7 @@ Ignore web and file addresses Занемари веб-адресе и путање датотека Ignore words in capitals Занемари речи писане великим словима Ignore words with numbers Занемари речи са бројевима + In the margin На маргини Inches Инчи Formatting exceptions Изузеци форматирања Include level number from: Укључи број нивоа: @@ -153,6 +154,7 @@ Number alignment: Поравнање броја: Number format Формат броја Number style for this level: Стил броја за овај ниво: + Only for the selected text Само за изабрани текст Outside border Спољна ивица In Default apps, choose WordCraft for the document types you want it to open. У подразумеваним апликацијама изаберите WordCraft за врсте докумената које желите да отвара. Make WordCraft the default for Word documents… Постави WordCraft као подразумевану апликацију за Word документе… diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index a00f2e996..b8c384e20 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -326,6 +326,7 @@ Immersive Odaklı Immersive Reader Odaklı Okuyucu Immersive\nReader Odaklı\nOkuyucu + In the margin Kenarda Increase Font Size Yazı Tipi Boyutunu Büyüt Increase Indent Girintiyi Artır Indent Girinti @@ -482,6 +483,7 @@ OK Tamam One Bir One Page Bir Sayfa + Only for the selected text Yalnızca seçili metin için Open Aç OpenDocument Text (*.odt) OpenDocument Metni (*.odt) Optional Hyphen İsteğe Bağlı Kısa Çizgi diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index 7abb7d00c..40b722110 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -99,6 +99,7 @@ Ignore web and file addresses Пропускати веб-адреси та шляхи до файлів Ignore words in capitals Пропускати слова з великих літер Ignore words with numbers Пропускати слова з цифрами + In the margin На полях Inches Дюйми Formatting exceptions Винятки форматування Include level number from: Додати номер рівня: @@ -126,6 +127,7 @@ Number alignment: Вирівнювання номера: Number format Формат номера Number style for this level: Стиль номера для цього рівня: + Only for the selected text Лише для виділеного тексту Outside border Зовнішня межа Overtype Заміна Overtype (typing replaces the characters after the caret) Заміна (введений текст замінює символи після курсора) diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index b38a4091b..3f25067d1 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -2556,3 +2556,7 @@ Hunspell .aff and .dic files here (pl_PL, pt_BR, it_IT, hr_HR, tr_TR) are used before the system's. Các tệp Hunspell .aff và .dic trong thư mục này (pl_PL, pt_BR, it_IT, hr_HR, tr_TR) được dùng trước các tệp của hệ thống. {language}: {file} {language}: {file} {language}: no dictionary found {language}: không tìm thấy từ điển + +# Show only the selected text's comment (#1066) + In the margin Trong lề + Only for the selected text Chỉ cho văn bản được chọn diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index 5717aa1b6..2ac870a1c 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -106,6 +106,7 @@ Ignore web and file addresses 忽略网址和文件路径 Ignore words in capitals 忽略全部大写的单词 Ignore words with numbers 忽略包含数字的单词 + In the margin 在页边 Inches 英寸 Formatting exceptions 格式例外 Include level number from: 包含的级别编号来自: @@ -139,6 +140,7 @@ Number alignment: 编号对齐方式: Number format 编号格式 Number style for this level: 此级别的编号样式: + Only for the selected text 仅针对所选文字 Outside border 外侧框线 Make WordCraft the default for Word documents… 将 WordCraft 设为 Word 文档的默认应用… Opens .docx, .doc, .rtf and .odt files and Word templates with WordCraft from now on. 今后用 WordCraft 打开 .docx、.doc、.rtf、.odt 文件和 Word 模板。 diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index f3aa21b86..a4eec04a6 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -95,6 +95,7 @@ Ignore web and file addresses 忽略網址和檔案路徑 Ignore words in capitals 忽略全部大寫的單字 Ignore words with numbers 忽略包含數字的單字 + In the margin 在頁邊 Inches 英寸 Formatting exceptions 格式例外 Include level number from: 包含的層級編號來自: @@ -128,6 +129,7 @@ Number alignment: 編號對齊方式: Number format 編號格式 Number style for this level: 此層級的編號樣式: + Only for the selected text 僅針對所選文字 Outside border 外側框線 Make WordCraft the default for Word documents… 將 WordCraft 設為 Word 文件的預設應用程式… Opens .docx, .doc, .rtf and .odt files and Word templates with WordCraft from now on. 今後以 WordCraft 開啟 .docx、.doc、.rtf、.odt 檔案和 Word 範本。 diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index 6f030d301..8837be67b 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -3214,6 +3214,95 @@ mod tests { assert_eq!(a.canvas.balloon, None, "a click on the page leaves the balloon"); } + /// #1055: with "only for the selected text" the margin column is gone and just the comment + /// under the caret floats, beside the page in a wide window and over its edge in a narrow one. + #[test] + fn selected_comment_floats_and_keeps_its_clicks() { + for width in [1700.0_f32, 1000.0] { + let ctx = egui::Context::default(); + let mut a = app(); + a.run("text.insert", json!({"text": "one two three. ".repeat(60)})).unwrap(); + a.run("select.text", json!({"text": "two"})).unwrap(); + a.run("review.newComment", json!({"text": "A comment with a few words in it"})).unwrap(); + a.session.view.comments_pane = false; + assert!(canvas::markup_width(&a) > 0.0, "the margin is the default, as in Word"); + a.run("review.trackingOptions", json!({"commentDisplay": "selected"})).unwrap(); + assert_eq!(canvas::markup_width(&a), 0.0, "no margin column any more"); + let screen = egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(width, 700.0)); + let mut t = 0.0; + let mut frame = |a: &mut WordApp, events: Vec| { + t += 1.0 / 60.0; + let input = egui::RawInput { events, time: Some(t), screen_rect: Some(screen), ..Default::default() }; + ctx.run_ui(input, |ui| { + a.logic(ui.ctx()); + a.ui(ui); + }) + .drop_without_applying_deltas(); + }; + let btn = |pos, pressed| egui::Event::PointerButton { pos, button: egui::PointerButton::Primary, pressed, modifiers: Default::default() }; + for _ in 0..30 { + frame(&mut a, Vec::new()); + } + // The caret is in the text after the first comment: not inside its range. + a.run("caret.docStart", json!({})).unwrap(); + for _ in 0..5 { + frame(&mut a, Vec::new()); + } + assert!(a.canvas.balloon_rects.is_empty(), "width {width}: no comment under the caret, no card"); + // Inside the commented word. + a.run("select.text", json!({"text": "two"})).unwrap(); + a.run("caret.left", json!({})).unwrap(); + for _ in 0..5 { + frame(&mut a, Vec::new()); + } + assert_eq!(a.canvas.balloon_rects.len(), 1, "width {width}: the comment under the caret shows"); + let (id, card) = a.canvas.balloon_rects[0]; + let page = a.canvas.page_rects.first().copied().unwrap(); + assert_eq!( + card.left() < page.right(), + width < 1100.0, + "width {width}: beside the page when the window has room, else over its edge: {card:?} {page:?}" + ); + if width < 1100.0 { + let line = a.session.layout().caret_on(&a.session.sel.focus, a.session.page_hint).expect("caret laid out"); + let bottom = page.min.y + (line.top + line.height) * canvas::PX_PER_PT; + assert!(card.top() >= bottom, "width {width}: over the page the card sits under the commented line: {card:?} vs {bottom}"); + } + // The #1013 flow on the floating card: click, press inside, drag, release. + let (caret, scroll) = (a.session.sel.clone(), a.canvas.scroll_offset); + frame(&mut a, vec![egui::Event::PointerMoved(card.center())]); + frame(&mut a, vec![btn(card.center(), true)]); + frame(&mut a, vec![btn(card.center(), false)]); + for _ in 0..5 { + frame(&mut a, Vec::new()); + } + assert_eq!(a.canvas.balloon, Some(id), "width {width}: a click on the card selects it"); + assert_eq!(a.session.sel.focus.path, caret.focus.path, "width {width}: the document's caret stays in its paragraph"); + let (_, card) = *a.canvas.balloon_rects.iter().find(|(i, _)| *i == id).unwrap(); + let (from, to) = (card.left_top() + egui::vec2(30.0, 30.0), card.left_top() + egui::vec2(80.0, 30.0)); + let caret = a.session.sel.clone(); + frame(&mut a, vec![egui::Event::PointerMoved(from)]); + frame(&mut a, vec![btn(from, true)]); + for _ in 0..3 { + frame(&mut a, vec![egui::Event::PointerMoved(to)]); + } + assert!(!a.canvas.drag_selecting(), "width {width}: the press didn't start a drag in the document"); + frame(&mut a, vec![btn(to, false)]); + assert_eq!(a.canvas.balloon, Some(id), "width {width}: still selected"); + assert_eq!(a.session.sel, caret, "width {width}: the document's caret didn't move"); + assert_eq!(a.canvas.scroll_offset, scroll, "width {width}: nothing scrolled"); + // A click on the page text leaves the card, and the comment goes away with the caret. + let body = egui::pos2(page.left() + 120.0, page.top() + 40.0 + 150.0); + frame(&mut a, vec![egui::Event::PointerMoved(body)]); + frame(&mut a, vec![btn(body, true)]); + frame(&mut a, vec![btn(body, false)]); + for _ in 0..5 { + frame(&mut a, Vec::new()); + } + assert_eq!(a.canvas.balloon, None, "width {width}: a click on the page leaves the card"); + } + } + /// #1019: a double-click in the header area returned before the release ended the drag, so /// a later press held on the ribbon edge-scrolled the document up. #[test] diff --git a/docs/ui-parity.md b/docs/ui-parity.md index e4926439f..922698858 100644 --- a/docs/ui-parity.md +++ b/docs/ui-parity.md @@ -1,6 +1,6 @@ # UI parity with Microsoft Word -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-14 · **Change:** trivial (Ctrl+drag copies a picture, shape or text box, #1014; previously trivial: the mini toolbar scrolls under the ribbon, not over it; previously trivial: the mini toolbar steps aside for the ribbon; previously trivial: selection bar in the left margin, #856; previously trivial: table column and row markers on the rulers, #700; previously trivial: table borders and the resize handle drag on the page, #217; 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-14 · **Change:** trivial (Track Changes Options › Comments: show only the selected text's comment, beyond Word, #1055; previously trivial: Ctrl+drag copies a picture, shape or text box, #1014; previously trivial: the mini toolbar scrolls under the ribbon, not over it; previously trivial: the mini toolbar steps aside for the ribbon; previously trivial: selection bar in the left margin, #856; previously trivial: table column and row markers on the rulers, #700; previously trivial: table borders and the resize handle drag on the page, #217; 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 @@ -51,6 +51,7 @@ Document title opens Rename Document; the account badge opens Options › Genera | Date | Change | Summary | |---|---|---| +| 2026-10-12 | trivial | Track Changes Options › Comments: "In the margin" (default, as in Word) or "Only for the selected text", an option Word doesn't have (#1055). In the second mode the margin column is gone and only the comment under the caret, or the clicked one, floats beside the page (over its edge in a narrow window); comment ranges stay shaded in the text. `review.trackingOptions {"commentDisplay": "margin"\|"selected"}` | | 2026-10-12 | trivial | On-canvas objects: Ctrl+drag copies a picture, shape or text box (`arrange.copyTo`, #1014) | | 2026-10-12 | trivial | Cross-reference dialog: reference type (heading, bookmark, equation, figure, table), what to insert and the item; the ribbon button opened nothing before (#916); modal dialog count 55 | | 2026-10-12 | trivial | Document title opens Rename Document; the account badge opens Options › General for user name and initials. |