Merge pull request #2164 from TrevorBrown-dev/fix/sticky-text-size-2157

M12: a sticky's text keeps its size and the note grows taller (#2157)
This commit is contained in:
BFlat authored and GitHub committed 2026-11-12 09:37:23 +07:00
commit c2d61d36bb
6 files changed
+79 -44

No files matched your search

+64 -8
View File
@@ -7,7 +7,7 @@ use figcraft_doc::{
use figcraft_geom::Point;
use serde_json::{Value, json};
use crate::board::{route, sticky_font_size};
use crate::board::{STICKY_TEXT_SIZE, route};
use crate::collab_live_tests::Room;
use crate::tools::{PointerEvent, PointerKind, ToolId};
use crate::{EngineError, Session};
@@ -77,7 +77,7 @@ fn board_commands_refuse_on_design_files_and_prototyping_refuses_on_boards() {
}
#[test]
fn a_sticky_is_a_square_frame_whose_note_shrinks_as_it_grows() {
fn a_sticky_is_a_square_frame_whose_note_grows_taller_as_it_fills() {
let mut s = board();
s.prefs.user_name = "Ada".into();
let r = s.execute("board.sticky", &json!({"x": 10, "y": 20, "text": "Hi", "color": "blue"})).unwrap();
@@ -88,19 +88,75 @@ fn a_sticky_is_a_square_frame_whose_note_shrinks_as_it_grows() {
assert_eq!(n.fills[0].solid_color(), board_color("blue"));
assert!(n.layout.is_some() && !n.effects.is_empty());
assert_eq!(n.children.len(), 2, "the note and its author");
assert_eq!(node(&s, note).kind.text().unwrap().style.font_size, 24.0);
assert_eq!(node(&s, note).kind.text().unwrap().style.font_size, 18.0);
// A click on the note picks the sticky whole.
let doc = s.doc().unwrap();
assert_eq!(figcraft_doc::geometry::select_target(doc, note, &[], false), st);
// A long note takes a smaller size; a very long one grows the sticky past its square.
// A long note keeps the size; a very long one grows the sticky past its square.
let long = "word ".repeat(120);
s.execute("node.set", &json!({"id": note, "props": {"characters": long}})).unwrap();
assert_eq!(node(&s, note).kind.text().unwrap().style.font_size, sticky_font_size(&long));
assert!(sticky_font_size(&long) < 16.0);
assert_eq!(node(&s, note).kind.text().unwrap().style.font_size, STICKY_TEXT_SIZE);
assert!(node(&s, st).width == 200.0 && node(&s, st).height >= 200.0);
// Undo puts the short note and its size back.
// Undo puts the short note back.
s.execute("edit.undo", &json!({})).unwrap();
assert_eq!(node(&s, note).kind.text().unwrap().style.font_size, 24.0);
assert_eq!(node(&s, note).kind.text().unwrap().style.font_size, 18.0);
}
#[test]
fn a_new_sticky_starts_at_18() {
let mut s = board();
for (cmd, p) in [("board.sticky", json!({"x": 0, "y": 0, "text": "Hi"})), ("board.sticky", json!({"x": 300, "y": 0}))] {
let note = id(&s.execute(cmd, &p).unwrap(), "text");
assert_eq!(node(&s, note).kind.text().unwrap().style.font_size, 18.0);
}
let next = id(&s.execute("board.nextSticky", &json!({})).unwrap(), "text");
assert_eq!(node(&s, next).kind.text().unwrap().style.font_size, 18.0);
assert_eq!(STICKY_TEXT_SIZE, 18.0);
}
#[test]
fn a_sticky_keeps_its_text_size_as_text_is_added() {
// The note's growth needs text measured: without craft-fonts that is with a system font,
// scanned here rather than by whichever test happens to run first (#333).
figcraft_text::scan_system_fonts();
let mut s = board();
let r = s.execute("board.sticky", &json!({"x": 0, "y": 0, "text": "one"})).unwrap();
let (st, note) = (id(&r, "id"), id(&r, "text"));
let (height, text_height) = (node(&s, st).height, node(&s, note).height);
// Typing in the note: each step is a real edit that runs the normalize pass.
crate::board::edit_text(&mut s, st).unwrap();
for _ in 0..9 {
crate::textedit::key(&mut s, "Enter", false, false, false).unwrap();
crate::textedit::insert(&mut s, "another line").unwrap();
}
let ten = node(&s, note).kind.text().unwrap().clone();
assert_eq!(ten.characters.matches('\n').count(), 9, "ten lines");
assert_eq!(ten.style.font_size, 18.0);
assert!(node(&s, note).height > text_height, "ten lines make the text taller");
assert!(node(&s, st).height > height, "and the note");
assert_eq!(node(&s, st).child_layout.sizing_v, figcraft_doc::Sizing::Hug, "the note hugs its text");
let (w, h10) = (node(&s, st).width, node(&s, st).height);
crate::textedit::insert(&mut s, &"a long paragraph ".repeat(15)).unwrap();
crate::textedit::end(&mut s).unwrap();
let t = node(&s, note).kind.text().unwrap().clone();
assert!(t.characters.chars().count() >= 250);
assert_eq!(t.style.font_size, 18.0);
assert!(t.runs.iter().all(|r| r.style.font_size == 18.0));
assert_eq!(node(&s, st).width, w, "the note keeps its width");
assert!(node(&s, st).height > h10, "and grows taller");
}
#[test]
fn a_sticky_saved_with_another_text_size_keeps_it() {
let mut s = board();
let r = s.execute("board.sticky", &json!({"x": 0, "y": 0, "text": "old"})).unwrap();
let (st, note) = (id(&r, "id"), id(&r, "text"));
s.execute("node.set", &json!({"id": note, "props": {"fontSize": 11}})).unwrap();
assert_eq!(node(&s, note).kind.text().unwrap().style.font_size, 11.0);
s.execute("node.set", &json!({"id": note, "props": {"characters": "old and now much longer than it was"}})).unwrap();
assert_eq!(node(&s, note).kind.text().unwrap().style.font_size, 11.0, "an edit");
s.execute("node.set", &json!({"id": st, "props": {"x": 40}})).unwrap();
assert_eq!(node(&s, note).kind.text().unwrap().style.font_size, 11.0, "a move");
}
#[test]
+1 -1
View File
@@ -69,8 +69,8 @@ pub(crate) use edit::{copied_items, copy_map, rejoin};
use edit::{label_cmd, set_color, set_style};
pub(crate) use items::add_text;
pub use items::{CONNECTED_GAP, connected_place};
pub use items::{STICKY_TEXT_SIZE, edit_text, owner_of_text, text_of};
use items::{add_connected, ink_cmd, next_sticky, shape_cmd, stamp_cmd, sticky_cmd};
pub use items::{edit_text, owner_of_text, sticky_font_size, text_of};
pub(crate) use mindmap::settle_mind_maps;
pub(crate) use reshape::path_handle_at;
pub use reshape::selected_connector_handles;
+3 -16
View File
@@ -1,7 +1,6 @@
use figcraft_doc::RouteEnd;
use super::edit::set_font_size;
use super::items::{owner_of_text, shape_padding, sticky_font_size, text_of};
use super::items::shape_padding;
use super::{
Affine, BTreeSet, BezPath, BoardItem, Connector, Document, End, NodeId, NodeKind, PathEl, Point, Rect, Routing, Shape, VectorNetwork, is_board,
};
@@ -9,26 +8,14 @@ use super::{
// ---------------------------------------------------------------------------------------------
// Derived data (the normalize pass).
/// Before text is measured: a sticky's note takes the size its length calls for, and a resized
/// shape keeps its text inside its outline.
/// Before text is measured: a resized shape keeps its text inside its outline. A sticky's text
/// keeps whatever size it has (FigJam's size is the note's own setting).
pub(crate) fn settle_text(doc: &mut Document, before: &Document, touched: &BTreeSet<NodeId>) {
if !is_board(doc) {
return;
}
for &id in touched {
let Some(n) = doc.node(id) else { continue };
if let Some(owner) =
owner_of_text(doc, id).or(Some(id)).filter(|o| doc.node(*o).is_some_and(|k| matches!(k.board, Some(BoardItem::Sticky { .. }))))
&& let Some(t) = text_of(doc, owner)
&& let Some(NodeKind::Text(td)) = doc.node(t).map(|k| &k.kind)
{
let size = sticky_font_size(&td.characters);
let off = td.style.font_size != size || td.runs.iter().any(|r| r.style.font_size != size);
if off && let Some(NodeKind::Text(td)) = doc.node_mut(t).map(|k| &mut k.kind) {
set_font_size(td, size);
}
continue;
}
if let Some(BoardItem::Shape { shape }) = n.board
&& before.node(id).is_none_or(|b| (b.width, b.height) != (n.width, n.height))
{
+4 -13
View File
@@ -57,18 +57,9 @@ pub(crate) fn add_text(doc: &mut Document, parent: NodeId, text: &str, size: f64
Ok(id)
}
/// The text size a sticky's note gets: smaller the more it holds (by characters and lines, so
/// every copy of the file picks the same size whatever fonts it has).
pub fn sticky_font_size(text: &str) -> f64 {
let weight = text.chars().count() + 24 * text.matches('\n').count();
match weight {
0..=24 => 24.0,
25..=64 => 20.0,
65..=140 => 16.0,
141..=280 => 13.0,
_ => 11.0,
}
}
/// The text size of a new sticky's note, FigJam's "Small" default (measured in the web app,
/// 2026-11-09): it never changes as the note fills, the note grows taller instead.
pub const STICKY_TEXT_SIZE: f64 = 18.0;
/// A sticky note's layers: the frame, the note and (when known) its author.
fn make_sticky(doc: &mut Document, parent: NodeId, at: (f64, f64), text: &str, color: Color, author: &str) -> Result<(NodeId, NodeId)> {
@@ -89,7 +80,7 @@ fn make_sticky(doc: &mut Document, parent: NodeId, at: (f64, f64), text: &str, c
n.board = Some(BoardItem::Sticky { author: author.to_string() });
})?;
let inner = STICKY - 2.0 * STICKY_PAD;
let note = add_text(doc, id, text, sticky_font_size(text), INK, TextAlignH::Left, inner)?;
let note = add_text(doc, id, text, STICKY_TEXT_SIZE, INK, TextAlignH::Left, inner)?;
if !author.is_empty() {
let a = add_text(doc, id, author, 10.0, AUTHOR_INK, TextAlignH::Left, inner)?;
if let Some(n) = doc.node_mut(a) {