M1: preserve fractional corner smoothing across drags

This commit is contained in:
Carbocode
2026-10-29 11:41:54 +01:00
parent 9dab7669cb
commit bc354430ef
4 changed files with 39 additions and 7 deletions
+3 -3
View File
@@ -2,7 +2,7 @@
**Stage: alpha** · next: beta, ~26 points and ~162–292 h away (one Opus 5.5 agent; ~33–67 h wall-clock with 5 agents)
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-29 · **Change:** minor (Corner smoothing: a visible icon supports dragging the percentage with the shared field gesture; `.fig` import keeps the file's saved line breaks while the text and its box are unedited (#1236); Download a missing free font from the Missing fonts window (`font.download`, #754); the web build's wasm back under its 24 MiB gate (#1472); shadows of images with transparent pixels follow their alpha (#1289); Crop image: Object ▸ Crop image, the right-click menu and the image fill row enter crop mode (`image.crop`, #1464); offline Penpot 2.x import/export (#1143); optional batch continue-on-error with per-step outcomes (#1386); the Design panel's sections on one grid (#858); FigJam outer shape outlines and sticky body fallback beside author text (#1341, #1397); images placed at their own size, 2x screenshots in points (#1360); the toolbar on one 32 pt grid, without its Resources button (#1210); M4: zero-length round-capped dashes draw dots; dash expansion uses the existing safety budget; extracted from #1143; Arabic draft catalog; scaled instances keep their scale when their main is rebuilt (#922); .fig embedded images are copied once per blob, not once per hash (#1059); page ruler guides survive save and reopen (#1476); colour picker LCH and OKLCH models; non-ASCII layer names transliterate in code export (#1235); branches in live sessions and branch history entries, gap 17; plugin widgets take text input and a property menu (plugin API 4); boards: templates and tables; boards: a shared timer and voting sessions; boards: the dot grid, connector ends dragged to re-attach, curved connectors; Installed weights of an embedded font family (#1246); `.sketch` import, best effort (Sketch 48 and newer); CJK line breaking: JIS X 4051's line-start and line-end sets completed with the halfwidth small kana, the halfwidth and spacing (semi-)voiced sound marks, the iteration marks 〱–〵 and the halfwidth parentheses, with behaviour cases for each; Adobe Fonts found on macOS (#298); egui 0.37 hidden-capture deadline contract; Dev Mode measurements kept on the canvas and a read-only layers panel; boards: a board area row and progress entry; community fixes in #507, #510, #529 and #530: auto-save, a text box by dragging, batch step references, removing someone from a live session, Russian and Turkish catalogs reviewed, Czech and Indonesian drafts, the Japanese draft completed, zoom previews, the web clipboard, Alt-drag snapping and Cmd+D repeat, recovery copies, crash and rollback fixes, colour emoji, `.fig` import fixes; earlier changes are in the revision history below) · **Target:** Figma Design (desktop app 126.10.7)
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-29 · **Change:** minor (Corner smoothing: a visible icon supports dragging the percentage with the shared field gesture, preserving fractional values across drags; `.fig` import keeps the file's saved line breaks while the text and its box are unedited (#1236); Download a missing free font from the Missing fonts window (`font.download`, #754); the web build's wasm back under its 24 MiB gate (#1472); shadows of images with transparent pixels follow their alpha (#1289); Crop image: Object ▸ Crop image, the right-click menu and the image fill row enter crop mode (`image.crop`, #1464); offline Penpot 2.x import/export (#1143); optional batch continue-on-error with per-step outcomes (#1386); the Design panel's sections on one grid (#858); FigJam outer shape outlines and sticky body fallback beside author text (#1341, #1397); images placed at their own size, 2x screenshots in points (#1360); the toolbar on one 32 pt grid, without its Resources button (#1210); M4: zero-length round-capped dashes draw dots; dash expansion uses the existing safety budget; extracted from #1143; Arabic draft catalog; scaled instances keep their scale when their main is rebuilt (#922); .fig embedded images are copied once per blob, not once per hash (#1059); page ruler guides survive save and reopen (#1476); colour picker LCH and OKLCH models; non-ASCII layer names transliterate in code export (#1235); branches in live sessions and branch history entries, gap 17; plugin widgets take text input and a property menu (plugin API 4); boards: templates and tables; boards: a shared timer and voting sessions; boards: the dot grid, connector ends dragged to re-attach, curved connectors; Installed weights of an embedded font family (#1246); `.sketch` import, best effort (Sketch 48 and newer); CJK line breaking: JIS X 4051's line-start and line-end sets completed with the halfwidth small kana, the halfwidth and spacing (semi-)voiced sound marks, the iteration marks 〱–〵 and the halfwidth parentheses, with behaviour cases for each; Adobe Fonts found on macOS (#298); egui 0.37 hidden-capture deadline contract; Dev Mode measurements kept on the canvas and a read-only layers panel; boards: a board area row and progress entry; community fixes in #507, #510, #529 and #530: auto-save, a text box by dragging, batch step references, removing someone from a live session, Russian and Turkish catalogs reviewed, Czech and Indonesian drafts, the Japanese draft completed, zoom previews, the web clipboard, Alt-drag snapping and Cmd+D repeat, recovery copies, crash and rollback fixes, colour emoji, `.fig` import fixes; earlier changes are in the revision history below) · **Target:** Figma Design (desktop app 126.10.7)
FigCraft is a clean-room, open-source, pure-Rust reimplementation of the Figma design workflow,
native on macOS, Windows, Linux and FreeBSD and in the browser, fully drivable by agents through
@@ -132,7 +132,7 @@ Ranked; detail and milestones in [`docs/roadmap.md`](docs/roadmap.md).
## Progress log
- **2026-10-29** · M1: Corner smoothing has an original, code-drawn icon with the same 24 pt drag target as the other number fields. Dragging it changes the percentage live, Shift and Alt use the shared rates, the value stays within 0–100%, and the gesture is one undo step. The field is named for screen readers. Regression: `corner_individual_tests`. Numbers unchanged.
- **2026-10-29** · M1: Corner smoothing has an original, code-drawn icon with the same 24 pt drag target as the other number fields. Dragging it changes the percentage live, successive drags preserve fractional percentages, Shift and Alt use the shared rates, the value stays within 0–100%, and the gesture is one undo step. The field is named for screen readers. Regression: `corner_individual_tests`. Numbers unchanged.
- **2026-10-28** · M3, #922: an instance scaled with the Scale tool (or `transform.scale`) keeps its layers' scaled size and points when its main is rebuilt. The scaled layers' new size and place were read as what constraints give (an ancestor resized in the same edit), so no override was kept, and adding a layer to the main put a 14 px icon's 24 px triangle back. The Scale tool now records them as `size` and `position` overrides on layers inside instances; strokes already kept theirs. The report's own sequence (clearing effects on the component holding the icon) no longer reproduces on main. Regressions for a scaled instance, one scaled with the tool (with undo) and a scaled icon nested in a component and its instances. Numbers unchanged.
- **2026-10-28** · M6, #1059: `.fig` import copies each image carried inside the message once per blob, shared by every hash that names it, instead of once per hash: a file pointing many hashes at one large blob no longer multiplies it in memory (100 hashes on a 250 MB blob were 25 GB). Regression test. Numbers unchanged.
@@ -357,7 +357,7 @@ Ranked; detail and milestones in [`docs/roadmap.md`](docs/roadmap.md).
| Date | Change | Summary |
|---|---|---|
| 2026-10-29 | minor | M1: Corner smoothing gets an original code-drawn icon, the shared 24 pt scrub target and an accessible name. Live percentage changes, Shift/Alt rates, 0–100% clamping and one-step undo/redo have headless pointer regression coverage (`corner_individual_tests`). Numbers unchanged |
| 2026-10-29 | minor | M1: Corner smoothing gets an original code-drawn icon, the shared 24 pt scrub target and an accessible name. Live percentage changes, fractional baselines across consecutive drags, Shift/Alt rates, 0–100% clamping and one-step undo/redo have headless pointer regression coverage (`corner_individual_tests`). Numbers unchanged |
| 2026-10-29 | minor | M6, #1236 (gap 1): imported `.fig` text keeps the line breaks the file saved with it (`derivedTextData`), so a font whose advances differ slightly from the file's (another build of the family, a stand-in) no longer wraps a word early or splits one in a narrow box. The breaks hold only while the box width and everything that moves a break are as saved (characters, fonts, sizes, spacing, case, axes, features, lists, paragraph settings; paints, links and vertical settings don't count), and are given up where a line would come out more than 3% (+1 px) wider than its box; they are kept in `.figcraft`. The line breaker was checked and is unchanged: trailing spaces and the letter spacing after a line's last glyph already stay out of the fit test, box widths aren't rounded and the tolerance is 1e-6 px, so the differences come from the fonts. Synthetic tests in `saved_breaks_tests` and `fig::text_wrap_tests`; not checked on real files. Numbers unchanged |
| 2026-10-29 | minor | #754, #1025: the Missing fonts window offers Download for a family the suite's font store has (Google Fonts, Noto: craft-libs' `craft-font-store`, its pinned indexes compiled in). The first Download asks the user to allow downloading fonts (off until then; the setting is the Craft apps' shared one); the files download on a thread of their own, each checked against the index's size and sha256 before it is installed into the shared font folder, which the font scan now reads; the row shows progress or why it failed, and once installed the file's text fits the real font and the family leaves the list. `font.download {family}` and `font.downloads` are commands, but no command turns downloading on; agents install only while the user has it on. Desktop only. No cancel, no turning it off from FigCraft, no substitute picker. Tests: `fonts::download::tests`, `missing_fonts_tests`. Numbers unchanged |
| 2026-10-29 | minor | Web wasm size (#1472): the release wasm is 23,915,213 bytes (22.8 MiB), down from 25,982,540 (24.8 MiB), 1.2 MiB under the gate. Catalog rows hold their plural forms behind a reference (two words, not eight) and the browser build compiles with wasm SIMD, so the glyph rasterizer's scalar fallback is left out; the 24 MiB gate in `packaging/web/package.sh` is restored. The browser now needs WebAssembly SIMD (Safari 16.4 or later). Numbers unchanged. |
@@ -107,6 +107,38 @@ fn corner_smoothing_icon_scrubs_with_modifiers_and_one_undo_step() {
}
}
/// Fine drags keep their fractional baseline across gestures, including a later plain drag.
#[test]
fn consecutive_smoothing_drags_preserve_fractional_percentages() {
let (mut u, r) = scene();
u.app.run("node.set", json!({"ids": [r], "props": {"cornerSmoothing": 0.5}}));
let toggle = named_button(&mut u, "Individual corners");
click(&mut u, toggle);
let history = u.app.session.undo_depth().0;
let smoothing = |u: &Ui| u.app.session.doc().unwrap().node(r).unwrap().corners.smoothing;
for (step, (dx, modifiers, want)) in
[(16.0, Modifiers::ALT, 0.516), (16.0, Modifiers::ALT, 0.532), (8.0, Modifiers::NONE, 0.612)].into_iter().enumerate()
{
let from = u.ctx.read_response(Id::unique("smoothing").with("lead")).unwrap().rect.center();
u.frame_with(vec![Event::PointerMoved(from)], modifiers);
u.frame_with(vec![Event::PointerButton { pos: from, button: egui::PointerButton::Primary, pressed: true, modifiers }], modifiers);
for k in 1..=4 {
u.frame_with(vec![Event::PointerMoved(pos2(from.x + dx * k as f32 / 4.0, from.y))], modifiers);
}
u.frame_with(
vec![Event::PointerButton { pos: pos2(from.x + dx, from.y), button: egui::PointerButton::Primary, pressed: false, modifiers }],
modifiers,
);
u.frame(vec![]);
assert!((smoothing(&u) - want).abs() < 1e-9, "gesture {step}: expected {want}, got {}", smoothing(&u));
assert_eq!(u.app.session.undo_depth().0, history + step + 1);
}
for want in [0.532, 0.516, 0.5] {
u.app.session.undo().unwrap();
assert!((smoothing(&u) - want).abs() < 1e-9, "undo restores the precise value");
}
}
/// The fields sit at their corners, and typing in the bottom right one sets only that corner in a
/// single undo step (#1002: the bottom row showed bottom right on the left).
#[test]
+1 -1
View File
@@ -113,7 +113,7 @@ pub(super) fn appearance(app: &mut FigApp, ui: &mut Ui, doc: &Document, sel: &[N
}
});
}
let sm = common(doc, sel, |n| (n.corners.smoothing * 100.0).round());
let sm = common(doc, sel, |n| n.corners.smoothing * 100.0);
widgets::sublabels(ui, tr("Corner smoothing"), None);
row(ui, 32.0, |ui| {
let e = widgets::number_field(ui, Id::unique("smoothing"), Lead::Icon(Icon::CornerSmoothing, tr("Corner smoothing")), sm, "%", col, 1.0);
+3 -3
View File
File diff suppressed because one or more lines are too long