mirror of
https://github.com/storytold/wordcraft.git
synced 2026-11-02 16:30:05 -06:00
* 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 <noreply@anthropic.com> --------- Co-authored-by: Brandon Thomas <echelon@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
Brandon Thomas
parent
77da439d0b
commit
3e20488c15
+3
-1
@@ -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) |
|
||||
|
||||
@@ -43,7 +43,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
.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<u32> {
|
||||
/// The innermost comment whose range holds the caret.
|
||||
pub fn comment_range_at_caret(s: &Session) -> Option<u32> {
|
||||
let f = &s.sel.focus;
|
||||
let p = s.doc.para_at(f)?;
|
||||
let mut open: Vec<u32> = Vec::new();
|
||||
@@ -181,7 +181,14 @@ fn comment_at_caret(s: &Session) -> Option<u32> {
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
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<u32> {
|
||||
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<Rgb>| -> Result<Option<Rgb>, 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()
|
||||
}
|
||||
|
||||
@@ -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<Rgb>,
|
||||
@@ -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(),
|
||||
|
||||
@@ -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<u32> {
|
||||
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<u32, Vec<u32>> = 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<u32> = 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<egui::Galley>, 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 {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 Дясна граница
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 7 and column 25.
|
@@ -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)
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 7 and column 25.
|
@@ -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:
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 25.
|
@@ -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)
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 25.
|
@@ -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
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 460 and column 13.
|
@@ -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 :
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 7 and column 25.
|
@@ -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
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 25.
|
@@ -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
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 25.
|
@@ -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 で開きます。
|
||||
|
||||
|
@@ -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
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 25.
|
@@ -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)
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 25.
|
@@ -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 Строка заголовка
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 7 and column 25.
|
@@ -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…
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 25.
|
@@ -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 документе…
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 11 and column 25.
|
@@ -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
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 441 and column 13.
|
@@ -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) Заміна (введений текст замінює символи після курсора)
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 31.
|
@@ -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
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 7 and column 25.
|
@@ -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 模板。
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 7 and column 25.
|
@@ -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 範本。
|
||||
|
||||
|
@@ -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<egui::Event>| {
|
||||
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]
|
||||
|
||||
+2
-1
@@ -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. |
|
||||
|
||||
Reference in New Issue
Block a user