1 /*
2  * Copyright (c) 2023 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include "core/components_ng/animation/geometry_transition.h"
17 
18 #include "core/common/container.h"
19 #include "core/common/container_scope.h"
20 #include "core/components_ng/base/frame_node.h"
21 #include "core/components_ng/base/view_stack_processor.h"
22 #include "core/components_ng/property/border_property.h"
23 #include "core/components_ng/property/property.h"
24 #include "core/components_ng/layout/layout_property.h"
25 #include "core/pipeline_ng/pipeline_context.h"
26 
27 namespace OHOS::Ace::NG {
28 // Geometry transition is used for hero animation dealing with matched pair of inNode and outNode holding the
29 // same key. During geometry transition inNode starts with the size and position of outNode(inNode active),
30 // animates to the place where it should to be(inNode identity), meanwhile outNode starts with its own size
31 // and position(outNode identity), animates to the final size and position of inNode(outNode active). Although
32 // we have two transitions but these two transitions fit together perfectly, so the appearance looks like a
33 // single view move from its old position to its new position, thus visual focus guidance is completed.
GeometryTransition(const std::string & id,bool followWithoutTransition,bool doRegisterSharedTransition)34 GeometryTransition::GeometryTransition(
35     const std::string& id, bool followWithoutTransition, bool doRegisterSharedTransition) : id_(id),
36     followWithoutTransition_(followWithoutTransition), doRegisterSharedTransition_(doRegisterSharedTransition) {}
37 
IsInAndOutEmpty() const38 bool GeometryTransition::IsInAndOutEmpty() const
39 {
40     return !inNode_.Upgrade() && !outNode_.Upgrade();
41 }
42 
IsInAndOutValid() const43 bool GeometryTransition::IsInAndOutValid() const
44 {
45     return inNode_.Upgrade() && outNode_.Upgrade();
46 }
47 
IsRunning(const WeakPtr<FrameNode> & frameNode) const48 bool GeometryTransition::IsRunning(const WeakPtr<FrameNode>& frameNode) const
49 {
50     auto node = frameNode.Upgrade();
51     CHECK_NULL_RETURN(node && IsInAndOutValid(), false);
52     return node->GetLayoutPriority() != 0 && (node == inNode_ || node == outNode_);
53 }
54 
IsNodeInAndActive(const WeakPtr<FrameNode> & frameNode) const55 bool GeometryTransition::IsNodeInAndActive(const WeakPtr<FrameNode>& frameNode) const
56 {
57     return state_ == State::ACTIVE && hasInAnim_ && frameNode.Upgrade() == inNode_ && IsInAndOutValid();
58 }
59 
IsNodeInAndIdentity(const WeakPtr<FrameNode> & frameNode) const60 bool GeometryTransition::IsNodeInAndIdentity(const WeakPtr<FrameNode>& frameNode) const
61 {
62     return state_ == State::IDENTITY && hasInAnim_ && frameNode.Upgrade() == inNode_ && IsInAndOutValid();
63 }
64 
IsNodeOutAndActive(const WeakPtr<FrameNode> & frameNode) const65 bool GeometryTransition::IsNodeOutAndActive(const WeakPtr<FrameNode>& frameNode) const
66 {
67     return hasOutAnim_ && frameNode.Upgrade() == outNode_ && IsInAndOutValid();
68 }
69 
SwapInAndOut(bool condition)70 void GeometryTransition::SwapInAndOut(bool condition)
71 {
72     if (condition) {
73         std::swap(inNode_, outNode_);
74     }
75 }
76 
GetMatchedPair(bool isNodeIn) const77 std::pair<RefPtr<FrameNode>, RefPtr<FrameNode>> GeometryTransition::GetMatchedPair(bool isNodeIn) const
78 {
79     auto self = isNodeIn ? inNode_ : outNode_;
80     auto target = isNodeIn ? outNode_ : inNode_;
81     return { self.Upgrade(), target.Upgrade() };
82 }
83 
GetNodeAbsFrameRect(const RefPtr<FrameNode> & node,std::optional<OffsetF> parentPos) const84 RectF GeometryTransition::GetNodeAbsFrameRect(const RefPtr<FrameNode>& node, std::optional<OffsetF> parentPos) const
85 {
86     CHECK_NULL_RETURN(node, RectF());
87     auto renderContext = node->GetRenderContext();
88     CHECK_NULL_RETURN(renderContext, RectF());
89     auto parentGlobalOffset = parentPos.value_or(node->GetPaintRectGlobalOffsetWithTranslate(true).first);
90     auto paintRect = renderContext->GetPaintRectWithTransform();
91     return RectF(parentGlobalOffset + paintRect.GetOffset(), paintRect.GetSize());
92 }
93 
RecordOutNodeFrame()94 void GeometryTransition::RecordOutNodeFrame()
95 {
96     auto outNode = outNode_.Upgrade();
97     CHECK_NULL_VOID(outNode);
98     auto [val, err] = outNode->GetPaintRectGlobalOffsetWithTranslate(true);
99     outNodeParentPos_ = val;
100     outNodeParentHasScales_ = err;
101     auto outNodeAbsRect = GetNodeAbsFrameRect(outNode_.Upgrade(), outNodeParentPos_);
102     outNodePos_ = outNodeAbsRect.GetOffset();
103     outNodeSize_ = outNodeAbsRect.GetSize();
104 }
105 
MarkLayoutDirty(const RefPtr<FrameNode> & node,int32_t layoutPriority)106 void GeometryTransition::MarkLayoutDirty(const RefPtr<FrameNode>& node, int32_t layoutPriority)
107 {
108     CHECK_NULL_VOID(node && node->GetLayoutProperty());
109     if (layoutPriority) {
110         node->SetLayoutPriority(layoutPriority);
111     }
112     node->GetLayoutProperty()->CleanDirty();
113     node->MarkDirtyNode(PROPERTY_UPDATE_MEASURE_SELF_AND_CHILD);
114 }
115 
116 // Build should be called during node tree build phase dealing with node add/remove or appearing/disappearing
Build(const WeakPtr<FrameNode> & frameNode,bool isNodeIn)117 void GeometryTransition::Build(const WeakPtr<FrameNode>& frameNode, bool isNodeIn)
118 {
119     state_ = State::IDLE;
120     outNodeTargetAbsRect_.reset();
121     isSynced_ = false;
122     if (IsInAndOutEmpty()) {
123         hasInAnim_ = false;
124         hasOutAnim_ = false;
125     }
126     auto node = frameNode.Upgrade();
127     CHECK_NULL_VOID(node && node->GetRenderContext() && !id_.empty());
128     TAG_LOGD(AceLogTag::ACE_GEOMETRY_TRANSITION, "build node: %{public}d, direction: %{public}d, onTree: %{public}d, "
129         "removing: %{public}d, compid: %{public}s .", node->GetId(), isNodeIn, node->IsOnMainTree(),
130         node->IsRemoving(), node->GetInspectorId().value_or("").c_str());
131     if (!isNodeIn && (frameNode == inNode_ || frameNode == outNode_)) {
132         SwapInAndOut(frameNode == inNode_);
133         RecordOutNodeFrame();
134         hasOutAnim_ = true;
135     }
136     if (isNodeIn && (frameNode != inNode_)) {
137         auto inNode = inNode_.Upgrade();
138         bool replace = true;
139         if (inNode && !inNode->IsRemoving() && inNode->IsOnMainTree()) {
140             std::string id = node->GetInspectorId().value_or("");
141             replace = !id.empty() && id == inNode->GetInspectorId().value_or("");
142         }
143         SwapInAndOut(!replace);
144         inNode_ = frameNode;
145         bool isInAnimating = inNode && inNode->GetRenderContext() && inNode->GetRenderContext()->HasSandBox();
146         CHECK_NULL_VOID(!(replace && isInAnimating));
147         hasInAnim_ = true;
148     }
149     auto inNode = inNode_.Upgrade();
150     auto outNode = outNode_.Upgrade();
151     CHECK_NULL_VOID(inNode && outNode && (inNode != outNode));
152 
153     bool isImplicitAnimationOpen = AnimationUtils::IsImplicitAnimationOpen();
154     bool follow = false;
155     if (hasOutAnim_) {
156         if (!hasInAnim_) {
157             follow = OnFollowWithoutTransition(false);
158         }
159         hasOutAnim_ = isImplicitAnimationOpen || follow;
160         if (hasOutAnim_) {
161             MarkLayoutDirty(outNode, -1);
162         }
163     }
164     if (hasInAnim_ && !follow) {
165         if (!hasOutAnim_) {
166             follow = OnFollowWithoutTransition(true);
167         }
168         if (isImplicitAnimationOpen || follow) {
169             state_ = State::ACTIVE;
170             MarkLayoutDirty(inNode, 1);
171         } else {
172             hasInAnim_ = false;
173             inNode->SetLayoutPriority(0);
174             inNode->MarkDirtyNode(PROPERTY_UPDATE_MEASURE);
175             inNode->GetGeometryNode()->SetFrameSize(SizeF());
176         }
177     }
178 
179     TAG_LOGD(AceLogTag::ACE_GEOMETRY_TRANSITION, "inAnim: %{public}d, outAnim: %{public}d, follow: %{public}d, "
180         "inNode: %{public}d, %{public}s, outNode: %{public}d, %{public}s", hasInAnim_, hasOutAnim_, follow,
181         inNode->GetId(), inNode->GetTag().c_str(), outNode->GetId(), outNode->GetTag().c_str());
182 }
183 
184 // Update should be called during node update phase when node exists
Update(const WeakPtr<FrameNode> & which,const WeakPtr<FrameNode> & value)185 bool GeometryTransition::Update(const WeakPtr<FrameNode>& which, const WeakPtr<FrameNode>& value)
186 {
187     bool ret = true;
188     if (which.Upgrade() == inNode_.Upgrade()) {
189         inNode_ = value;
190     } else if (which.Upgrade() == outNode_.Upgrade()) {
191         outNode_ = value;
192     } else {
193         ret = false;
194     }
195     auto whichNode = which.Upgrade();
196     if (ret && whichNode && whichNode != value.Upgrade()) {
197         whichNode->SetLayoutPriority(0);
198     }
199     return ret;
200 }
201 
202 // Called before layout, perform layout constraints match modifications in active state to
203 // impact self and children's measure and layout.
WillLayout(const RefPtr<LayoutWrapper> & layoutWrapper)204 void GeometryTransition::WillLayout(const RefPtr<LayoutWrapper>& layoutWrapper)
205 {
206     CHECK_NULL_VOID(layoutWrapper && layoutWrapper->IsRootMeasureNode());
207     auto hostNode = layoutWrapper->GetHostNode();
208     if (IsNodeInAndActive(hostNode)) {
209         layoutPropertyIn_ = hostNode->GetLayoutProperty()->Clone();
210         ModifyLayoutConstraint(layoutWrapper, true);
211     } else if (IsNodeOutAndActive(hostNode) && !hasInAnim_) {
212         layoutPropertyOut_ = hostNode->GetLayoutProperty()->Clone();
213         ModifyLayoutConstraint(layoutWrapper, false);
214     }
215 }
216 
217 // Called after layout, perform final adjustments of geometry position
DidLayout(const RefPtr<LayoutWrapper> & layoutWrapper)218 void GeometryTransition::DidLayout(const RefPtr<LayoutWrapper>& layoutWrapper)
219 {
220     CHECK_NULL_VOID(layoutWrapper);
221     auto node = layoutWrapper->GetHostNode();
222     CHECK_NULL_VOID(node);
223     bool isRoot = layoutWrapper->IsRootMeasureNode();
224     std::optional<bool> direction;
225     if (isRoot && IsNodeInAndActive(node)) {
226         TAG_LOGD(AceLogTag::ACE_GEOMETRY_TRANSITION, "node: %{public}d in and active", node->GetId());
227         state_ = State::IDENTITY;
228         auto geometryNode = node->GetGeometryNode();
229         CHECK_NULL_VOID(geometryNode);
230         inNodeActiveFrameSize_ = geometryNode->GetFrameSize();
231         CHECK_NULL_VOID(layoutPropertyIn_);
232         layoutPropertyIn_->UpdatePropertyChangeFlag(PROPERTY_UPDATE_MEASURE);
233         node->SetLayoutProperty(layoutPropertyIn_);
234         layoutPropertyIn_.Reset();
235     } else if (IsNodeInAndIdentity(node)) {
236         TAG_LOGD(AceLogTag::ACE_GEOMETRY_TRANSITION, "node: %{public}d in and identity", node->GetId());
237         state_ = State::IDLE;
238         direction = true;
239         hasInAnim_ = false;
240     } else if (isRoot && IsNodeOutAndActive(node)) {
241         TAG_LOGD(AceLogTag::ACE_GEOMETRY_TRANSITION, "node: %{public}d out and active, dependency check: %{public}d",
242             node->GetId(), !hasInAnim_);
243         if (hasInAnim_) {
244             MarkLayoutDirty(node);
245             return;
246         }
247         hasOutAnim_ = false;
248         direction = false;
249     }
250     if (direction.has_value()) {
251         auto pipeline = PipelineContext::GetCurrentContext();
252         CHECK_NULL_VOID(pipeline);
253         pipeline->AddAfterLayoutTask([weak = WeakClaim(this), isNodeIn = direction.value()]() {
254             auto geometryTransition = weak.Upgrade();
255             CHECK_NULL_VOID(geometryTransition);
256             geometryTransition->SyncGeometry(isNodeIn);
257             if (!isNodeIn) {
258                 auto outNode = geometryTransition->outNode_.Upgrade();
259                 if (outNode && geometryTransition->layoutPropertyOut_) {
260                     outNode->SetLayoutProperty(geometryTransition->layoutPropertyOut_);
261                     geometryTransition->layoutPropertyOut_.Reset();
262                 }
263             }
264         });
265     }
266 }
267 
ModifyLayoutConstraint(const RefPtr<LayoutWrapper> & layoutWrapper,bool isNodeIn)268 void GeometryTransition::ModifyLayoutConstraint(const RefPtr<LayoutWrapper>& layoutWrapper, bool isNodeIn)
269 {
270     // outNode's frame is the target frame for active inNode to match,
271     // inNode's frame is the target frame for active outNode to match.
272     auto [self, target] = GetMatchedPair(isNodeIn);
273     CHECK_NULL_VOID(self && target && layoutWrapper);
274     // target's geometry is ensured ready to use because layout nodes are sorted to respect dependency,
275     // the order is active inNode, normal layout, active outNode.
276     auto layoutProperty = layoutWrapper->GetLayoutProperty();
277     auto targetGeometryNode = target->GetGeometryNode();
278     auto targetRenderContext = target->GetRenderContext();
279     CHECK_NULL_VOID(targetRenderContext && targetGeometryNode && layoutProperty);
280     SizeF size;
281     if (isNodeIn) {
282         staticNodeAbsRect_ =
283             target->IsRemoving() ? std::nullopt : std::optional<RectF>(target->GetTransformRectRelativeToWindow());
284         size = target->IsRemoving() ? outNodeSize_ : staticNodeAbsRect_->GetSize();
285     } else {
286         staticNodeAbsRect_ =
287             !staticNodeAbsRect_ ? std::nullopt : std::optional<RectF>(target->GetTransformRectRelativeToWindow());
288         size = staticNodeAbsRect_ ? staticNodeAbsRect_->GetSize() :
289             (inNodeAbsRect_ ? inNodeAbsRect_->GetSize() : targetGeometryNode->GetFrameSize());
290     }
291     auto targetSize = CalcSize(NG::CalcLength(size.Width()), NG::CalcLength(size.Height()));
292     layoutProperty->UpdateUserDefinedIdealSize(targetSize);
293     TAG_LOGD(AceLogTag::ACE_GEOMETRY_TRANSITION, "node: %{public}d modify size to: %{public}s",
294         self->GetId(), targetSize.ToString().c_str());
295     // if node has aspect ratio we'll ignore it in active state
296     auto& magicItemProperty = layoutProperty->GetMagicItemProperty();
297     if (magicItemProperty.HasAspectRatio()) {
298         magicItemProperty.ResetAspectRatio();
299     }
300 }
301 
SyncGeometry(bool isNodeIn)302 void GeometryTransition::SyncGeometry(bool isNodeIn)
303 {
304     auto [self, target] = GetMatchedPair(isNodeIn);
305     CHECK_NULL_VOID(self && target);
306     auto renderContext = self->GetRenderContext();
307     auto targetRenderContext = target->GetRenderContext();
308     auto geometryNode = self->GetGeometryNode();
309     auto pipeline = PipelineBase::GetCurrentContext();
310     CHECK_NULL_VOID(renderContext && targetRenderContext && geometryNode && pipeline);
311     // get own parent's global position, parent's transform is not taken into account other than translate
312     auto parentPos = self->IsRemoving() ? outNodeParentPos_ : self->GetPaintRectGlobalOffsetWithTranslate(true).first;
313     // get target's global position, target own transform is taken into account
314     auto targetRect = target->IsRemoving() ? RectF(outNodePos_, outNodeSize_) :
315         staticNodeAbsRect_.value_or(inNodeAbsRect_.value_or(GetNodeAbsFrameRect(target)));
316     auto targetPos = targetRect.GetOffset();
317     // adjust self's position to match with target's position, here we only need to adjust node self,
318     // its children's positions are still determined by layout process.
319     auto activeFrameRect = isNodeIn ? RectF(targetPos - parentPos, inNodeActiveFrameSize_) :
320                                       RectF(targetPos - parentPos, geometryNode->GetFrameSize());
321     auto activeCornerRadius = targetRenderContext->GetBorderRadius().value_or(BorderRadiusProperty());
322     auto cornerRadius = renderContext->GetBorderRadius().value_or(BorderRadiusProperty());
323     if (isNodeIn) {
324         self->SetLayoutPriority(0);
325         renderContext->SetFrameWithoutAnimation(activeFrameRect);
326         if (target->IsRemoving()) {
327             if (doRegisterSharedTransition_) {
328                 // notify backend for hierarchy processing
329                 renderContext->RegisterSharedTransition(targetRenderContext);
330             }
331         }
332     } else {
333         isSynced_ = true;
334         outNodeTargetAbsRect_ = targetRect;
335         if (staticNodeAbsRect_ && targetRenderContext->HasSandBox()) {
336             staticNodeAbsRect_.reset();
337             if (doRegisterSharedTransition_) {
338                 targetRenderContext->RegisterSharedTransition(renderContext);
339             }
340         }
341     }
342     auto propertyCallback = [&]() {
343         // sync geometry in active state
344         renderContext->SetBorderRadius(activeCornerRadius);
345         renderContext->SyncGeometryProperties(activeFrameRect);
346         // sync geometry in identity state for inNode
347         if (isNodeIn) {
348             renderContext->SetBorderRadius(cornerRadius);
349             renderContext->SyncGeometryProperties(RawPtr(geometryNode));
350         }
351         // draw self and children in sandbox which will not be affected by parent's transition
352         if (!isNodeIn && outNodeParentHasScales_) {
353             renderContext->SetSandBox(std::nullopt, true);
354         } else {
355             renderContext->SetSandBox(parentPos);
356         }
357     };
358     auto follow = followWithoutTransition_;
359     auto finishCallback = [follow, nodeWeak = WeakClaim(RawPtr(self))]() {
360         auto node = nodeWeak.Upgrade();
361         CHECK_NULL_VOID(node);
362         auto renderContext = node->GetRenderContext();
363         CHECK_NULL_VOID(renderContext);
364         renderContext->SetSandBox(std::nullopt);
365         TAG_LOGD(AceLogTag::ACE_GEOMETRY_TRANSITION, "node %{public}d animation completed", node->GetId());
366     };
367     if (!isNodeIn && inNodeAbsRect_) {
368         AnimationUtils::Animate(animationOption_, propertyCallback, finishCallback);
369         inNodeAbsRect_.reset();
370         animationOption_ = AnimationOption();
371     } else {
372         AnimationUtils::AnimateWithCurrentOptions(propertyCallback, finishCallback, false);
373     }
374 
375     TAG_LOGD(AceLogTag::ACE_GEOMETRY_TRANSITION, "node: %{public}d, parent: %{public}s, target: %{public}s, "
376         "active frame: %{public}s, identity frame: %{public}s, option: %{public}d",
377         self->GetId(), parentPos.ToString().c_str(), targetPos.ToString().c_str(), activeFrameRect.ToString().c_str(),
378         isNodeIn ? geometryNode->GetFrameRect().ToString().c_str() : "no log",
379         AnimationUtils::IsImplicitAnimationOpen());
380 }
381 
CreateHolderNode(const RefPtr<FrameNode> & node)382 RefPtr<FrameNode> CreateHolderNode(const RefPtr<FrameNode>& node)
383 {
384     CHECK_NULL_RETURN(node, nullptr);
385     auto newNode = FrameNode::CreateFrameNode(
386         node->GetTag(), ElementRegister::GetInstance()->MakeUniqueId(), AceType::MakeRefPtr<Pattern>());
387     newNode->SetGeometryNode(node->GetGeometryNode()->Clone());
388     auto frameSize = node->GetGeometryNode()->GetFrameSize();
389     newNode->GetLayoutProperty()->UpdateUserDefinedIdealSize(
390         CalcSize(CalcLength(frameSize.Width()), CalcLength(frameSize.Height())));
391     return newNode;
392 }
393 
SyncGeometryPropertiesAfterLayout(const RefPtr<FrameNode> & syncNode)394 void GeometryTransition::SyncGeometryPropertiesAfterLayout(const RefPtr<FrameNode>& syncNode)
395 {
396     CHECK_NULL_VOID(syncNode);
397     auto pipeline = PipelineContext::GetCurrentContext();
398     CHECK_NULL_VOID(pipeline);
399     pipeline->AddAfterLayoutTask(
400         [nodeWeak = WeakClaim(RawPtr(syncNode))]() {
401             auto node = nodeWeak.Upgrade();
402             CHECK_NULL_VOID(node);
403             auto renderContext = node->GetRenderContext();
404             CHECK_NULL_VOID(renderContext);
405             auto geometryNode = node->GetGeometryNode();
406             CHECK_NULL_VOID(geometryNode);
407             renderContext->SyncGeometryProperties(RawPtr(geometryNode));
408             renderContext->SetBorderRadius(renderContext->GetBorderRadius().value_or(BorderRadiusProperty()));
409         }, true);
410 }
411 
412 // For nodes without transition (still on the tree), but still need to follow the matched node which has
413 // transition (parameter is its transition direction).
OnFollowWithoutTransition(std::optional<bool> direction)414 bool GeometryTransition::OnFollowWithoutTransition(std::optional<bool> direction)
415 {
416     CHECK_NULL_RETURN(followWithoutTransition_, false);
417     if (!direction.has_value()) {
418         auto inNode = inNode_.Upgrade();
419         auto outNode = outNode_.Upgrade();
420         CHECK_NULL_RETURN(holder_ && inNode && outNode, false);
421         auto parent = holder_->GetParent();
422         auto inRenderContext = inNode->GetRenderContext();
423         auto outRenderContext = outNode->GetRenderContext();
424         CHECK_NULL_RETURN(parent && inRenderContext && outRenderContext, false);
425         parent->ReplaceChild(holder_, outNode);
426         parent->RemoveDisappearingChild(outNode);
427         parent->MarkDirtyNode(PROPERTY_UPDATE_MEASURE_SELF_AND_CHILD);
428         inRenderContext->UnregisterSharedTransition(outRenderContext);
429         hasOutAnim_ = false;
430         TAG_LOGD(AceLogTag::ACE_GEOMETRY_TRANSITION, "follow cancelled");
431         holder_ = nullptr;
432         SyncGeometryPropertiesAfterLayout(outNode);
433         return false;
434     }
435     if (direction.value()) {
436         auto outNode = outNode_.Upgrade();
437         CHECK_NULL_RETURN(outNode, false);
438         auto parent = outNode->GetParent();
439         CHECK_NULL_RETURN(parent, false);
440         holder_ = CreateHolderNode(outNode);
441         CHECK_NULL_RETURN(holder_, false);
442         RecordOutNodeFrame();
443         auto idx = parent->GetChildIndex(outNode);
444         parent->ReplaceChild(outNode, holder_);
445         parent->AddDisappearingChild(outNode, idx);
446         MarkLayoutDirty(outNode, -1);
447         hasOutAnim_ = true;
448         TAG_LOGD(AceLogTag::ACE_GEOMETRY_TRANSITION, "follow started");
449     } else {
450         auto inNode = inNode_.Upgrade();
451         CHECK_NULL_RETURN(inNode && inNode->GetGeometryNode() && holder_, false);
452         auto parent = holder_->GetParent();
453         CHECK_NULL_RETURN(parent, false);
454         parent->ReplaceChild(holder_, inNode);
455         parent->RemoveDisappearingChild(inNode);
456         state_ = State::ACTIVE;
457         MarkLayoutDirty(inNode, 1);
458         if (auto inNodeParent = inNode->GetAncestorNodeOfFrame()) {
459             MarkLayoutDirty(inNodeParent);
460         }
461         hasInAnim_ = true;
462         TAG_LOGD(AceLogTag::ACE_GEOMETRY_TRANSITION, "follow ended");
463         holder_ = nullptr;
464     }
465     return true;
466 }
467 
IsParent(const WeakPtr<FrameNode> & parent,const WeakPtr<FrameNode> & child) const468 bool GeometryTransition::IsParent(const WeakPtr<FrameNode>& parent, const WeakPtr<FrameNode>& child) const
469 {
470     CHECK_NULL_RETURN(parent.Upgrade() && child.Upgrade(), false);
471     RefPtr<UINode> node = child.Upgrade();
472     while (node != nullptr) {
473         if (AceType::DynamicCast<FrameNode>(node) == parent) {
474             return true;
475         }
476         node = node->GetParent();
477     }
478     return false;
479 }
480 
RecordAnimationOption(const WeakPtr<FrameNode> & trigger,const AnimationOption & option)481 void GeometryTransition::RecordAnimationOption(const WeakPtr<FrameNode>& trigger, const AnimationOption& option)
482 {
483     if (option.IsValid()) {
484         if (IsParent(trigger, inNode_)) {
485             animationOption_ = option;
486         }
487     } else if (NG::ViewStackProcessor::GetInstance()->GetImplicitAnimationOption().IsValid()) {
488         if (IsParent(trigger, inNode_)) {
489             animationOption_ = NG::ViewStackProcessor::GetInstance()->GetImplicitAnimationOption();
490         }
491     } else {
492         auto pipeline = PipelineBase::GetCurrentContext();
493         if (pipeline && pipeline->GetSyncAnimationOption().IsValid() && IsParent(trigger, inNode_)) {
494             animationOption_ = pipeline->GetSyncAnimationOption();
495         }
496     }
497 }
498 
AnimateWithSandBox(const OffsetF & inNodeParentPos,bool inNodeParentHasScales,const std::function<void ()> & propertyCallback,const AnimationOption & option)499 void GeometryTransition::AnimateWithSandBox(const OffsetF& inNodeParentPos, bool inNodeParentHasScales,
500     const std::function<void()>& propertyCallback, const AnimationOption& option)
501 {
502     auto inNode = inNode_.Upgrade();
503     CHECK_NULL_VOID(inNode);
504     auto inRenderContext = inNode->GetRenderContext();
505     CHECK_NULL_VOID(inRenderContext);
506     AnimationUtils::Animate(option, [&]() {
507         if (inRenderContext->HasSandBox()) {
508             auto parent = inNode->GetAncestorNodeOfFrame();
509             if (inNodeParentHasScales && parent) {
510                 inRenderContext->SetSandBox(parent->GetTransformRectRelativeToWindow().GetOffset());
511             } else {
512                 inRenderContext->SetSandBox(inNodeParentPos);
513             }
514         }
515         propertyCallback();
516     }, [nodeWeak = WeakClaim(RawPtr(inNode))]() {
517         auto node = nodeWeak.Upgrade();
518         CHECK_NULL_VOID(node);
519         auto renderContext = node->GetRenderContext();
520         CHECK_NULL_VOID(renderContext);
521         if (renderContext->HasSandBox()) {
522             renderContext->SetSandBox(std::nullopt);
523         }
524         TAG_LOGD(AceLogTag::ACE_GEOMETRY_TRANSITION, "node %{public}d resync animation completed", node->GetId());
525     });
526 }
527 
528 // during outNode animation is running target inNode's frame is changed, outNode needs to change as well to
529 // match tightly.
OnReSync(const WeakPtr<FrameNode> & trigger,const AnimationOption & option)530 void GeometryTransition::OnReSync(const WeakPtr<FrameNode>& trigger, const AnimationOption& option)
531 {
532     auto inNode = inNode_.Upgrade();
533     auto outNode = outNode_.Upgrade();
534     CHECK_NULL_VOID(isSynced_ && outNode && outNode->IsRemoving() && outNodeTargetAbsRect_ &&
535         outNodeTargetAbsRect_->IsValid() && inNode && inNode->IsOnMainTree());
536     auto inRenderContext = inNode->GetRenderContext();
537     auto outRenderContext = outNode->GetRenderContext();
538     CHECK_NULL_VOID(inRenderContext && outRenderContext);
539     if (trigger.Upgrade()) {
540         RecordAnimationOption(trigger, option);
541         return;
542     }
543     OffsetF inNodeParentPos;
544     bool inNodeParentHasScales = false;
545     if (!staticNodeAbsRect_) {
546         auto [val, err] = inNode->GetPaintRectGlobalOffsetWithTranslate(true);
547         inNodeParentPos = val;
548         inNodeParentHasScales = err;
549     }
550     auto inNodeAbsRect = staticNodeAbsRect_ || inNodeParentHasScales ?
551         inNode->GetTransformRectRelativeToWindow() : GetNodeAbsFrameRect(inNode, inNodeParentPos);
552     auto inNodeAbsRectOld = outNodeTargetAbsRect_.value();
553     bool sizeChanged = GreatNotEqual(std::fabs(inNodeAbsRect.Width() - inNodeAbsRectOld.Width()), 1.0f) ||
554         GreatNotEqual(std::fabs(inNodeAbsRect.Height() - inNodeAbsRectOld.Height()), 1.0f);
555     bool posChanged = GreatNotEqual(std::fabs(inNodeAbsRect.GetX() - inNodeAbsRectOld.GetX()), 1.0f) ||
556         GreatNotEqual(std::fabs(inNodeAbsRect.GetY() - inNodeAbsRectOld.GetY()), 1.0f);
557     CHECK_NULL_VOID(sizeChanged || posChanged);
558     auto animOption = animationOption_.IsValid() ? animationOption_ : AnimationOption(Curves::LINEAR, RESYNC_DURATION);
559     auto propertyCallback = [&]() {
560         if (!sizeChanged) {
561             auto activeFrameRect = RectF(inNodeAbsRect.GetOffset() - outNodeParentPos_, inNodeAbsRect.GetSize());
562             outRenderContext->SyncGeometryProperties(activeFrameRect);
563             outNodeTargetAbsRect_ = inNodeAbsRect;
564             animationOption_ = AnimationOption();
565         } else {
566             hasOutAnim_ = true;
567             inNodeAbsRect_ = inNodeAbsRect;
568             outNodeTargetAbsRect_.reset();
569             MarkLayoutDirty(outNode);
570             animationOption_ = animOption;
571         }
572     };
573     AnimateWithSandBox(inNodeParentPos, inNodeParentHasScales, propertyCallback, animOption);
574     TAG_LOGD(AceLogTag::ACE_GEOMETRY_TRANSITION, "outNode: %{public}d %{public}s resyncs to inNode: %{public}d "
575         "%{public}s, option: %{public}d, hasScales: %{public}d", outNode->GetId(), inNodeAbsRectOld.ToString().c_str(),
576         inNode->GetId(), inNodeAbsRect.ToString().c_str(), animOption.GetDuration(), inNodeParentHasScales);
577 }
578 
579 // if nodes with geometry transitions are added during layout, we need to execute additional layout in current frame
OnAdditionalLayout(const WeakPtr<FrameNode> & frameNode)580 bool GeometryTransition::OnAdditionalLayout(const WeakPtr<FrameNode>& frameNode)
581 {
582     bool ret = false;
583     auto node = frameNode.Upgrade();
584     CHECK_NULL_RETURN(node, false);
585     if (IsNodeInAndActive(frameNode)) {
586         auto parentNode = node->GetAncestorNodeOfFrame();
587         if (parentNode) {
588             MarkLayoutDirty(node);
589             MarkLayoutDirty(parentNode);
590             ret = true;
591         }
592     } else if (IsNodeOutAndActive(frameNode)) {
593         MarkLayoutDirty(node);
594         ret = true;
595     }
596     return ret;
597 }
598 
ToString() const599 std::string GeometryTransition::ToString() const
600 {
601     return std::string("in: ") + (inNode_.Upgrade() ? std::to_string(inNode_.Upgrade()->GetId()) : "null") +
602         std::string(", out: ") + (outNode_.Upgrade() ? std::to_string(outNode_.Upgrade()->GetId()) : "null");
603 }
604 } // namespace OHOS::Ace::NG
605