Repository navigation
feat(layers): per-layer move and scale limits - #910
Merged
Merged
Conversation
Add LayerInteraction.bounds, a function of the main editor's body size that returns the area a layer stays inside, and LayerInteraction.minScale and maxScale, which replace the scale limits of the layer's type for that layer. A dragged layer stops at the edge of its bounds and follows the finger again once the finger is back where it stopped. Pinched, rotated or scaled with the selection button at an edge, it is pushed inwards, does not grow past the bounds and moves back as it shrinks within the same gesture. A layer larger than its bounds is shrunk to fit, but not below its minScale. The layer is measured as it is drawn: at its scale and turned by its rotation. A helper line or guide the bounds move the layer off is hidden. The desktop scroll and keyboard shortcuts keep the limits too, and LayerInteractionManager.applyLayerLimits applies them to a layer changed by code. minScale and maxScale are exported with the layer (minified as n and x); bounds, a function, is not. Importing a history now merges each step's interaction settings over the previous step's instead of replacing them, so a step that changes one setting no longer resets the others.
The line was placed from where the previous step left the layer and then moved by the whole push of this step, so it drifted off a layer the bounds pushed. It now moves by how far the layer moved since the line was placed. Also note that resizing a text box ignores the bounds and shorten the changelog entry.
2 tasks done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Problem. A host had no way to keep one layer inside part of the canvas, or to give one layer its own scale range. Divine's poll, quiz and slider stickers are tap targets in the published video, so they must stay clear of the feed's buttons and caption and must not get tiny. Divine snaps a poll back into its safe area after release, which feels odd on a phone, and clamping from
MainEditorCallbacks.onScaleUpdatelands a frame late: the callback runs before the editor applies the step, so the step overwrites the clamp. Scale limits only existed per layer type (stickerEditor.minScale/maxScalefor everyWidgetLayer).Change. Three optional fields on
LayerInteraction, next toenableMove,enableScaleand the other switches:bounds(Rect Function(Size editorBodySize)?): the area the layer stays inside, in the body's logical pixels from its top left corner, likeTextEditorConfigs.layerBounds. It is called on every gesture step, so it can follow whatever it depends on, such as the export's aspect ratio.minScale/maxScale(double?): replace the limits of the layer's type for this layer, in either direction.LayerInteractionManager.applyLayerLimitsapplies them to a layer changed by code, once it is laid out.How the layer behaves with
bounds:enableScale: false, but never below itsminScale. One that still does not fit is centered on that side.Import/export.
minScaleandmaxScaleare exported only when set (minifiedn/x), so older states load unchanged.boundsis a function and is not exported; a host sets it again after an import. A history step whose only change is a differentboundsfunction writes no interaction diff.Fix found on the way. On import, a history step's interaction diff replaced the previous step's whole interaction map, so a step that changed one setting reset the others to their defaults. The diffs are now merged over the previous step. This also corrects older exports.
Not changed. Without the new fields nothing behaves differently: the drag path still adds each step to
layer.offset, and the type-wide scale limits apply as before.LayerInteractionis still imported fromcore/models/layers/layer_interaction.dartand is not added to the barrel, because Divine imports that file directly and an extra export would flag it as an unnecessary import.Limits. Text and emoji glyph sizes are rounded, so while one is pinched its edge can be a fraction of a pixel off. Widget layers scale exactly through their
FittedBox. Each layer of a multi-selection stops at its own bounds.CHANGELOG 14.23.0. The example app has a new Layers → Move and Scale Limits page.
Tests.
layer_interaction_test: defaults,toMap/fromMap(plain and minified), a map without the new keys,toMapFromReferencewith changed and removed limits,copyWith, equality, andLayer.toMapFromReferenceskipping a bounds-only change.layer_interaction_history_test: a three-step history keeps every step's interaction through export and import (plain and minified), and a 6.0.0 history without the limits loads, with a one-key diff keeping the other settings.layer_interaction_limits_test, in a running editor:applyLayerLimitsworks on a layer moved by code;minScale.flutter analyzeanddart formatare clean, and all 1306 tests pass.Related Issue: Closes #none; needed for Divine's poll, quiz and slider stickers.
Type of Change