Skip to content

Commit 78dfc08

Browse files
committed
Large changes:
* Added rotatedTopLeftCorner field in BaseNode. * Updated NodeProcessor to utilize cached rotatedTopLeftCorner to reduce recursive calls & computaiton. * Fixed a crash in undo manager related to variants. * Fixed another crash in undo related to sorting id order. * Optimized position_manager id sorting and node processing step. * Added right-click to open suggestions menu. * Disabled node json viewer for more than 5 nodes. * Fixed bad rotation undo for rotation controls. * Removed padding-clearing when rotation is added. It's now preserved to not destroy layouts. * Disabled margin/padding rendering for rotated scenarios. * * Added more contrast to rainbow pastel colors.
1 parent ef82f6b commit 78dfc08

File tree

2 files changed

+196
-51
lines changed

2 files changed

+196
-51
lines changed

lib/src/api/node_processor.dart

Lines changed: 183 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ extension BaseNodeUpdateExtension on BaseNode {
106106
bool recursivelyCalculateChildrenGlobalBoxes = true,
107107
Vec? globalParentBoundingBoxPos,
108108
bool forceUpdateEdgePins = false,
109+
bool updatingSortedNodeList = false,
109110
}) =>
110111
NodeProcessor.updateNode(
111112
node: this,
@@ -122,6 +123,7 @@ extension BaseNodeUpdateExtension on BaseNode {
122123
recursivelyCalculateChildrenGlobalBoxes,
123124
globalParentBoundingBoxPos: globalParentBoundingBoxPos,
124125
forceUpdateEdgePins: forceUpdateEdgePins,
126+
updatingSortedNodeList: updatingSortedNodeList,
125127
);
126128

127129
/// Sets rotation for this node.
@@ -132,24 +134,17 @@ extension BaseNodeUpdateExtension on BaseNode {
132134

133135
/// Updates rotation for this node recursively for its children as well if
134136
/// [updateChildren] is true.
135-
void updateNodeRotation(int newRotationDegrees,
136-
{bool updateChildren = true}) {
137+
void updateNodeRotation(
138+
int newRotationDegrees, {
139+
bool updateChildren = true,
140+
}) {
137141
setNodeRotation(newRotationDegrees);
138142

139-
if (updateChildren) {
140-
NodeProcessor.updateGlobalRotation(
141-
node: this, newRotationDegrees: newRotationDegrees);
142-
} else {
143-
if (id == kRootNode) {
144-
globalRotationDegrees = newRotationDegrees;
145-
globalRotationRadians = newRotationDegrees * pi / 180;
146-
} else {
147-
final BaseNode parent = NodeProcessor.getNode(parentID);
148-
globalRotationDegrees =
149-
parent.globalRotationDegrees + newRotationDegrees;
150-
globalRotationRadians = globalRotationDegrees * pi / 180;
151-
}
152-
}
143+
NodeProcessor.updateGlobalRotation(
144+
node: this,
145+
newRotationDegrees: newRotationDegrees,
146+
updateChildren: updateChildren,
147+
);
153148
}
154149
}
155150

@@ -204,33 +199,36 @@ class NodeProcessor {
204199
static void updateGlobalRotation({
205200
required BaseNode node,
206201
required int newRotationDegrees,
202+
bool updateChildren = true,
207203
}) {
208204
if (node.id == kRootNode) {
209205
node.globalRotationDegrees = newRotationDegrees;
210206
node.globalRotationRadians = newRotationDegrees * pi / 180;
207+
node._rotatedTopLeftCorner = node.middleBoxLocal.topLeft;
211208
} else {
212209
final BaseNode parent = getNode(node.parentID);
210+
213211
node.globalRotationDegrees =
214212
parent.globalRotationDegrees + newRotationDegrees;
215213
node.globalRotationRadians = node.globalRotationDegrees * pi / 180;
216-
}
217214

218-
for (var childId in node.childrenOrEmpty) {
219-
final child = getNode(childId);
220-
updateGlobalRotation(
221-
node: child, newRotationDegrees: child.rotationDegrees);
215+
node._rotatedTopLeftCorner = calculateGlobalRotatedBoxTopLeft(
216+
node.id,
217+
boundaryType: NodeBoundaryType.middleBox,
218+
unrotate: false,
219+
updatingSortedNodeList: true,
220+
);
222221
}
223-
}
224222

225-
/// Switches the parent node of this node from [oldParent] to [newParent].
226-
static void switchParent({
227-
required BaseNode node,
228-
required BaseNode newParent,
229-
required BaseNode oldParent,
230-
}) {
231-
oldParent.childrenOrEmpty.remove(node.id);
232-
oldParent.childrenOrEmpty.add(node.id);
233-
node.parentID = newParent.id;
223+
if (updateChildren) {
224+
for (final String childId in node.childrenOrEmpty) {
225+
final child = getNode(childId);
226+
updateGlobalRotation(
227+
node: child,
228+
newRotationDegrees: child.rotationDegrees,
229+
);
230+
}
231+
}
234232
}
235233

236234
/// [updateNode] is responsible for updating the node and its children when
@@ -248,6 +246,11 @@ class NodeProcessor {
248246
/// children are updated recursively. If it is set to false, only the node is
249247
/// updated. This parameter is used during nodes initialization. Since all the
250248
/// nodes are being laid out, there's no need for recursive updates.
249+
///
250+
/// if [updatingSortedNodeList] is true, then you are currently updating a
251+
/// list of nodes one-by-one order from parent to child order. If this is
252+
/// the case, then we can make important assumptions that can help optimize
253+
/// and avoid recursive computation.
251254
static void updateNode({
252255
required BaseNode node,
253256
EdgeInsetsModel? margin,
@@ -262,6 +265,7 @@ class NodeProcessor {
262265
bool recursivelyCalculateChildrenGlobalBoxes = true,
263266
Vec? globalParentBoundingBoxPos,
264267
bool forceUpdateEdgePins = false,
268+
bool updatingSortedNodeList = false,
265269
}) {
266270
final bool marginChanged = margin != null && margin != node.margin;
267271
final bool paddingChanged = padding != null && padding != node.padding;
@@ -349,46 +353,58 @@ class NodeProcessor {
349353
}
350354
}
351355

352-
if (globalParentBoundingBoxPos == null) {
353-
_computeGlobalAndRotatedBoxes(
354-
node,
355-
recursivelyCalculateChildren: recursivelyCalculateChildrenGlobalBoxes,
356-
);
357-
} else {
358-
_computeGlobalAndRotatedBoxes(
359-
node,
360-
recursivelyCalculateChildren: false,
361-
);
362-
}
356+
// If a performLayout was run, the children do not need calculation
357+
// because they will be updated next.
358+
_computeGlobalAndRotatedBoxes(
359+
node,
360+
recursivelyCalculateChildren:
361+
!performLayoutRan && recursivelyCalculateChildrenGlobalBoxes,
362+
updatingSortedNodeList: updatingSortedNodeList,
363+
);
363364

364365
_computeInnerBoxLocal(node);
365366
}
366367

367368
static void _computeGlobalAndRotatedBoxes(
368369
BaseNode node, {
369370
bool recursivelyCalculateChildren = true,
371+
bool updatingSortedNodeList = false,
370372
}) {
371373
// Order matters.
372374
// Middle and inner boxes depend on outer.
373375
// Rotated boxes depend on all of them.
374-
_computeOuterBoxGlobal(node);
376+
_computeOuterBoxGlobal(
377+
node,
378+
updatingSortedNodeList: updatingSortedNodeList,
379+
);
375380
_computeMiddleBoxGlobal(node);
376381
_computeInnerBoxGlobal(node);
377-
_computeRotatedBoxes(node);
382+
383+
_computeRotatedBoxes(
384+
node,
385+
updatingSortedNodeList: updatingSortedNodeList,
386+
);
378387

379388
if (recursivelyCalculateChildren) {
380389
for (final String childID in node.childrenOrEmpty) {
381390
final BaseNode childNode = getNode(childID);
382-
_computeGlobalAndRotatedBoxes(childNode);
391+
_computeGlobalAndRotatedBoxes(
392+
childNode,
393+
updatingSortedNodeList: updatingSortedNodeList,
394+
);
383395
}
384396
}
385397
}
386398

387-
static void _computeOuterBoxGlobal(BaseNode node) {
388-
final Vec globalBoundingTopLeft = getGlobalRotatedBoxTopLeft(
399+
static void _computeOuterBoxGlobal(
400+
BaseNode node, {
401+
bool updatingSortedNodeList = false,
402+
}) {
403+
final Vec globalBoundingTopLeft = calculateGlobalRotatedBoxTopLeft(
389404
node.id,
390405
unrotate: true,
391406
boundaryType: NodeBoundaryType.outerBox,
407+
updatingSortedNodeList: updatingSortedNodeList,
392408
);
393409
node._outerBoxGlobal = OuterNodeBox(
394410
globalBoundingTopLeft.x,
@@ -422,7 +438,10 @@ class NodeProcessor {
422438
);
423439
}
424440

425-
static void _computeRotatedBoxes(BaseNode node) {
441+
static void _computeRotatedBoxes(
442+
BaseNode node, {
443+
bool updatingSortedNodeList = false,
444+
}) {
426445
final RectC middleRotatedBox = getRotatedBoundingBoxAroundSelf(
427446
node.middleBoxLocal.x,
428447
node.middleBoxLocal.y,
@@ -449,8 +468,11 @@ class NodeProcessor {
449468
);
450469

451470
// GLOBAL
452-
final middleUnrotatedGlobalTopLeft = getGlobalRotatedBoxTopLeft(node.id,
453-
boundaryType: NodeBoundaryType.middleBox);
471+
final middleUnrotatedGlobalTopLeft = calculateGlobalRotatedBoxTopLeft(
472+
node.id,
473+
boundaryType: NodeBoundaryType.middleBox,
474+
updatingSortedNodeList: updatingSortedNodeList,
475+
);
454476

455477
final RectC middleGlobalRotatedBox = getRotatedBoundingBoxAroundSelf(
456478
middleUnrotatedGlobalTopLeft.x,
@@ -480,10 +502,16 @@ class NodeProcessor {
480502

481503
/// [unrotate] will unrotate the top-left corner of the rotated box to the
482504
/// top-left corner of the bounding box.
483-
static Vec getGlobalRotatedBoxTopLeft(
505+
///
506+
/// if [updatingSortedNodeList] is true, then you are currently updating a
507+
/// list of nodes one-by-one order from parent to child order. If this is
508+
/// the case, then we can make important assumptions that can help optimize
509+
/// and avoid recursive computation.
510+
static Vec calculateGlobalRotatedBoxTopLeft(
484511
String id, {
485512
bool unrotate = true,
486513
required NodeBoundaryType boundaryType,
514+
bool updatingSortedNodeList = false,
487515
}) {
488516
assert(
489517
!boundaryType.isRotatedBox,
@@ -495,6 +523,14 @@ class NodeProcessor {
495523

496524
if (id == kRootNode) return currentVec;
497525

526+
if (updatingSortedNodeList) {
527+
return calculateSortedGlobalRotatedBoxTopLeft(
528+
id,
529+
unrotate: unrotate,
530+
boundaryType: boundaryType,
531+
);
532+
}
533+
498534
// First collect the node's and all of it's ancestor's id.
499535
final List<BaseNode> parents = [];
500536
String currentId = id;
@@ -590,4 +626,100 @@ class NodeProcessor {
590626

591627
return unrotated;
592628
}
629+
630+
/// [unrotate] will unrotate the top-left corner of the rotated box to the
631+
/// top-left corner of the bounding box.
632+
///
633+
/// This function is used when you are updating a list of nodes one-by-one
634+
/// order from parent to child order. If this is the case, then we can make
635+
/// important assumptions that can help optimize and avoid recursive
636+
/// computation.
637+
static Vec calculateSortedGlobalRotatedBoxTopLeft(
638+
String id, {
639+
bool unrotate = true,
640+
required NodeBoundaryType boundaryType,
641+
}) {
642+
assert(
643+
!boundaryType.isRotatedBox,
644+
"This function is computing the rotated part for you. You can't give it a rotated node boundary type",
645+
);
646+
647+
// Our main offset were guiding to the target id.
648+
Vec currentVec = Vec.zero;
649+
650+
if (id == kRootNode) return currentVec;
651+
652+
final node = getNode(id);
653+
final parent = getNode(node.parentID);
654+
655+
final NodeBox parentBox = parent.id == kRootNode
656+
? parent.basicBoxLocal
657+
: boundaryType.getGlobalBoxForNode(parent);
658+
final NodeBox nodeBox = boundaryType.getLocalBoxForNode(node);
659+
660+
currentVec = Vec.zero;
661+
662+
if (node.globalRotationDegrees == 0) {
663+
currentVec = parentBox is OuterNodeBox
664+
? parentBox.innerTopLeft
665+
: parentBox.topLeft;
666+
667+
currentVec += nodeBox.topLeft;
668+
} else {
669+
currentVec = parent.rotatedTopLeftCorner;
670+
671+
final RectC rect =
672+
(nodeBox is OuterNodeBox ? nodeBox.innerRect : nodeBox.rect);
673+
674+
// Returns local box top left after rotation.
675+
// This will be the point of the top-left drag handle of this box.
676+
// IE: The literal visually rotated top left corner.
677+
//
678+
// The x/y is NOT yet translated inside the parent's space.
679+
// That's the next step.
680+
Vec rotated = calculateRotatedBoxTopLeftAroundSelf(
681+
radians: node.rotationRadians,
682+
x1: rect.left,
683+
y1: rect.top,
684+
x2: rect.right,
685+
y2: rect.bottom,
686+
);
687+
688+
if (boundaryType == NodeBoundaryType.outerBox) {
689+
if (parent.rotationDegrees == 0) {
690+
currentVec += parent.outerBoxLocal.edgeTopLeft;
691+
}
692+
}
693+
694+
// Converting it to global.
695+
rotated = localRotatedVecToGlobalVec(
696+
localX: rotated.x,
697+
localY: rotated.y,
698+
parentX: currentVec.x,
699+
parentY: currentVec.y,
700+
radians: parent.globalRotationRadians,
701+
);
702+
703+
currentVec = rotated;
704+
}
705+
706+
// If our target happens to be orthogonal w global.
707+
if (node.globalRotationDegrees == 0 || !unrotate) return currentVec;
708+
709+
// Otherwise were pointing to an already rotated node which is not too
710+
// useful for most callers so we make it orthogonal w global.
711+
final SizeC size =
712+
(nodeBox is OuterNodeBox ? nodeBox.innerSize : nodeBox.size);
713+
714+
final Vec unrotated = calculateUnrotatedVecFromRotatedVec(
715+
x: currentVec.x,
716+
y: currentVec.y,
717+
width: size.width,
718+
height: size.height,
719+
radians: node.globalRotationRadians,
720+
) -
721+
(nodeBox is OuterNodeBox ? nodeBox.edgeTopLeft : Vec.zero);
722+
723+
return unrotated;
724+
}
593725
}

lib/src/api/nodes/base_node.dart

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,18 @@ abstract class BaseNode with SerializableMixin, EquatableMixin {
267267
@JsonKey(includeFromJson: false, includeToJson: false)
268268
RotatedNodeBox get outerRotatedBoxGlobal => _outerRotatedBoxGlobal;
269269

270+
late Vec _rotatedTopLeftCorner;
271+
272+
/// The top left corner of the middle box. This is not
273+
/// [middleBoxGlobal.topLeft], rather the visual top left corner of the
274+
/// visually rotated box instead of the point of the bounding box containing
275+
/// the rotated box.
276+
///
277+
/// This value is calculated at runtime and is used as a means of caching
278+
/// the value for performance reasons.
279+
@JsonKey(includeFromJson: false, includeToJson: false)
280+
Vec get rotatedTopLeftCorner => _rotatedTopLeftCorner;
281+
270282
EdgeInsetsModel _margin;
271283

272284
/// The margin applied outside the node.
@@ -391,6 +403,7 @@ abstract class BaseNode with SerializableMixin, EquatableMixin {
391403
_outerBoxGlobal.innerWidth,
392404
_outerBoxGlobal.innerHeight,
393405
);
406+
_rotatedTopLeftCorner = middleBoxLocal.topLeft;
394407

395408
_resolvedConstraints = _constraints;
396409

0 commit comments

Comments
 (0)