Skip to content

feat(main-editor): resizable text boxes - #898

Merged
hm21 merged 4 commits into
hm21:stablefrom
saif-ellafi:feat/text-layer-box
Oct 21, 2026
Merged

hm21 merged 4 commits into
hm21:stablefrom
saif-ellafi:feat/text-layer-box

Conversation

@saif-ellafi

@saif-ellafi saif-ellafi commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

This pull request has been AI assisted, I have tested and designed the solution manually. I hope you like the approach.

As it is, this is a backend component only, to enable clients implement this as necessary, existing and default functionality is not affected. An example has been included.

Description

Adds an optional fixed-size text box for TextLayer, like a text field on a form. A boxed layer lays its text out inside a box of a set size instead of growing with the text. Its edges can be dragged to resize the box without scaling the text. Everything is opt-in: layers without a box, and editors that don't enable the handles, behave exactly as before.

Model

  • New TextLayerBox (size, verticalAlign, overflow) on TextLayer.box. It is nullable and defaults to null.
    • size is in the layer's own units, before Layer.scale, the same as maxTextWidth. The box therefore scales together with the layer.
    • verticalAlign: top / center / bottom, for text shorter than the box.
    • overflow: clip, or shrink. With shrink, the font is scaled down until the text fits, to at least minShrinkFactor (25%), and anything still left over is clipped.
  • The box is serialized through toMap / fromMap / toMapFromReference (minified key tb) and carried by copyWith, layer copies and debugFillProperties. Older exports, which have no box key, load as unboxed layers.
  • Helpers:
    • TextLayerBox.resize(...) returns the new size and offset for a handle drag. It keeps the opposite edge fixed and accounts for rotation, flips, scale and layerFractionalOffset.
    • TextLayerBox.fit(...) sizes a box around some text so that it looks unchanged.

Rendering

  • RoundedBackgroundText / RoundedBackgroundPainter take an optional box. With a box:
    • the text wraps at the box width;
    • the widget is exactly the box size;
    • the background is one rectangle filling the box;
    • content is clipped to the box;
    • the whole box is hit-testable.
  • Without a box, nothing changes.

Interaction

  • New LayerInteractionConfigs.enableTextBoxResize (default false). When it is on, the default selection overlay shows edge handles on boxed text layers.
  • Pressing a handle doesn't consume the pointer. The press goes through to the editor's existing scale gesture, the same way the rotate/scale button works. The drag is measured from the press position, and each drag is one undo step.
  • Long-press handling is suppressed while a handle is held, so pausing mid-drag doesn't deselect the layer.
  • New public TextLayerBoxHandles widget, plus the LayerItemInteractions.textBoxResizeDown / textBoxResizeUp callbacks (no-ops by default), so custom overlayChildBuilders can show their own handles.

Text editor

  • TextEditorState.boxEnabled / setBoxEnabled(bool) let a custom UI turn thtor gains no new built-in button.
    • Turning it on for an unboxed layer fits a box around the text as it currently looks, so nothing visibly moves.
    • An existing box keeps its size when the text is edited.
    • Turning it off removes the box.

Tests

New tests cover:

  • the model, including serialization and resize math under rotation, flips and
  • box rendering, including vertical alignment, clip/shrink and hit-testing;
  • the handles widget, checking that the press passes through;
  • the text editor box on/off behaviour and fit;
  • end-to-end resizing in a real editor, including holding a handle past the lod-drag.

Related Issue: Related to #411 and #446 (text box feature requests).

Type of Change

  • ✨ New feature (non-breaking change which adds functionality)
  • 🛠️ Bug fix (non-breaking change which fixes an issue)
  • ❌ Breaking change (fix or feature that would cause existing functionali
  • 🧹 Code refactor
  • ✅ Build configuration change
  • 📝 Documentation
  • 🗑️ Chore

saif-ellafi and others added 4 commits October 20, 2026 18:53
…effects

- A box handle on a layer with keyframes kept the edge opposite it only on the rest offset: the resize now starts from where the keyframes show the layer and writes the keyframe at the playhead, like the other transform gestures.
- TextLayerBox.toMap rounds its size to maxDecimalPlaces, which toMap and toMapFromReference pass on; fromMap also reads sizes written as text.
- A box that shrinks its text shrinks the outline and the shadows with it.
- Add the 14.15.0 CHANGELOG entry and version.
@hm21

hm21 commented Oct 21, 2026 •

Copy link
Copy Markdown
Owner

Thanks a lot for the PR, @saif-ellafi! 🙌 I tested it in the example and in the editor, and everything worked flawlessly.

I added a small follow-up commit on top:

  • Resizing the box of a layer with keyframes (video editor) now keeps the opposite edge where the keyframes show the layer and writes the keyframe at the playhead, like the other transform gestures.
  • The box size is rounded to maxDecimalPlaces on export, and TextLayerBox.fromMap also reads sizes written as text.
  • With overflow: shrink, the outline and the shadows now shrink together with the text.
  • CHANGELOG entry and version bump to 14.15.0.

It will be released in 14.15.0.

@hm21
hm21 merged commit 8ba2d9a into hm21:stable Oct 21, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants