• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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)34 GeometryTransition::GeometryTransition(const std::string& id, bool followWithoutTransition) : id_(id),
35     followWithoutTransition_(followWithoutTransition) {}
36 
IsInAndOutEmpty() const37 bool GeometryTransition::IsInAndOutEmpty() const
38 {
39     return !inNode_.Upgrade() && !outNode_.Upgrade();
40 }
41 
IsInAndOutValid() const42 bool GeometryTransition::IsInAndOutValid() const
43 {
44     return inNode_.Upgrade() && outNode_.Upgrade();
45 }
46 
IsRunning(const WeakPtr<FrameNode> & frameNode) const47 bool GeometryTransition::IsRunning(const WeakPtr<FrameNode>& frameNode) const
48 {
49     auto node = frameNode.Upgrade();
50     CHECK_NULL_RETURN(node && IsInAndOutValid(), false);
51     return (node == inNode_ || node == outNode_) && node->GetLayoutPriority() != 0;
52 }
53 
IsNodeInAndActive(const WeakPtr<FrameNode> & frameNode) const54 bool GeometryTransition::IsNodeInAndActive(const WeakPtr<FrameNode>& frameNode) const
55 {
56     return IsInAndOutValid() && hasInAnim_ && frameNode.Upgrade() == inNode_ && state_ == State::ACTIVE;
57 }
58 
IsNodeInAndIdentity(const WeakPtr<FrameNode> & frameNode) const59 bool GeometryTransition::IsNodeInAndIdentity(const WeakPtr<FrameNode>& frameNode) const
60 {
61     return IsInAndOutValid() && hasInAnim_ && frameNode.Upgrade() == inNode_ && state_ == State::IDENTITY;
62 }
63 
IsNodeOutAndActive(const WeakPtr<FrameNode> & frameNode) const64 bool GeometryTransition::IsNodeOutAndActive(const WeakPtr<FrameNode>& frameNode) const
65 {
66     return IsInAndOutValid() && hasOutAnim_ && frameNode.Upgrade() == outNode_;
67 }
68 
SwapInAndOut(bool condition)69 void GeometryTransition::SwapInAndOut(bool condition)
70 {
71     if (condition) {
72         std::swap(inNode_, outNode_);
73     }
74 }
75 
GetMatchedPair(bool isNodeIn) const76 std::pair<RefPtr<FrameNode>, RefPtr<FrameNode>> GeometryTransition::GetMatchedPair(bool isNodeIn) const
77 {
78     auto self = isNodeIn ? inNode_ : outNode_;
79     auto target = isNodeIn ? outNode_ : inNode_;
80     return { self.Upgrade(), target.Upgrade() };
81 }
82 
GetNodeAbsFrameRect(const RefPtr<FrameNode> & node,std::optional<OffsetF> parentPos) const83 RectF GeometryTransition::GetNodeAbsFrameRect(const RefPtr<FrameNode>& node, std::optional<OffsetF> parentPos) const
84 {
85     CHECK_NULL_RETURN(node, RectF());
86     auto renderContext = node->GetRenderContext();
87     CHECK_NULL_RETURN(renderContext, RectF());
88     auto parentGlobalOffset = parentPos.value_or(node->GetPaintRectGlobalOffsetWithTranslate(true).first);
89     auto paintRect = renderContext->GetPaintRectWithTransform();
90     return RectF(parentGlobalOffset + paintRect.GetOffset(), paintRect.GetSize());
91 }
92 
RecordOutNodeFrame()93 void GeometryTransition::RecordOutNodeFrame()
94 {
95     auto outNode = outNode_.Upgrade();
96     CHECK_NULL_VOID(outNode);
97     auto [val, err] = outNode->GetPaintRectGlobalOffsetWithTranslate(true);
98     outNodeParentPos_ = val;
99     outNodeParentHasScales_ = err;
100     auto outNodeAbsRect = GetNodeAbsFrameRect(outNode_.Upgrade(), outNodeParentPos_);
101     outNodePos_ = outNodeAbsRect.GetOffset();
102     outNodeSize_ = outNodeAbsRect.GetSize();
103 }
104 
MarkLayoutDirty(const RefPtr<FrameNode> & node,int32_t layoutPriority)105 void GeometryTransition::MarkLayoutDirty(const RefPtr<FrameNode>& node, int32_t layoutPriority)
106 {
107     CHECK_NULL_VOID(node && node->GetLayoutProperty());
108     if (layoutPriority) {
109         node->SetLayoutPriority(layoutPriority);
110     }
111     node->GetLayoutProperty()->CleanDirty();
112     node->MarkDirtyNode(PROPERTY_UPDATE_MEASURE_SELF_AND_CHILD);
113 }
114 
115 // Build should be called during node tree build phase dealing with node add/remove or appearing/disappearing
Build(const WeakPtr<FrameNode> & frameNode,bool isNodeIn)116 void GeometryTransition::Build(const WeakPtr<FrameNode>& frameNode, bool isNodeIn)
117 {
118     state_ = State::IDLE;
119     outNodeTargetAbsRect_ = std::nullopt;
120     isSynced_ = false;
121     if (IsInAndOutEmpty()) {
122         hasInAnim_ = false;
123         hasOutAnim_ = false;
124     }
125     auto node = frameNode.Upgrade();
126     CHECK_NULL_VOID(node && node->GetRenderContext() && !id_.empty());
127     std::string id = node->GetInspectorId().value_or("");
128     TAG_LOGI(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(), id.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 = inNode && !inNode->IsRemoving() && inNode->IsOnMainTree() &&
139             (id.empty() || id != inNode->GetInspectorId().value_or("")) ? false : true;
140         SwapInAndOut(!replace);
141         inNode_ = frameNode;
142         bool isInAnimating = inNode && inNode->GetRenderContext() && inNode->GetRenderContext()->HasSandBox();
143         CHECK_NULL_VOID(!(replace && isInAnimating));
144         hasInAnim_ = true;
145     }
146     auto pipeline = PipelineBase::GetCurrentContext();
147     if (pipeline && pipeline->GetSyncAnimationOption().IsValid()) {
148         implicitAnimationOption_ = pipeline->GetSyncAnimationOption();
149     }
150     auto inNode = inNode_.Upgrade();
151     auto outNode = outNode_.Upgrade();
152     CHECK_NULL_VOID(IsInAndOutValid() && (inNode != outNode));
153 
154     bool isImplicitAnimationOpen = AnimationUtils::IsImplicitAnimationOpen();
155     bool follow = false;
156     if (hasOutAnim_) {
157         if (!hasInAnim_) {
158             follow = OnFollowWithoutTransition(false);
159         }
160         hasOutAnim_ = isImplicitAnimationOpen || follow;
161         if (hasOutAnim_) {
162             MarkLayoutDirty(outNode, -1);
163         }
164     }
165     if (hasInAnim_ && !follow) {
166         if (!hasOutAnim_) {
167             follow = OnFollowWithoutTransition(true);
168         }
169         if (isImplicitAnimationOpen || follow) {
170             state_ = State::ACTIVE;
171             MarkLayoutDirty(inNode, 1);
172         } else {
173             hasInAnim_ = false;
174             inNode->SetLayoutPriority(0);
175             inNode->MarkDirtyNode(PROPERTY_UPDATE_MEASURE);
176             inNode->GetGeometryNode()->SetFrameSize(SizeF());
177         }
178     }
179 
180     TAG_LOGI(AceLogTag::ACE_GEOMETRY_TRANSITION, "inAnim: %{public}d, outAnim: %{public}d, follow: %{public}d, "
181         "inNode: %{public}d, %{public}s, outNode: %{public}d, %{public}s", hasInAnim_, hasOutAnim_, follow,
182         inNode->GetId(), inNode->GetTag().c_str(), outNode->GetId(), outNode->GetTag().c_str());
183 }
184 
185 // Update should be called during node update phase when node exists
Update(const WeakPtr<FrameNode> & which,const WeakPtr<FrameNode> & value)186 bool GeometryTransition::Update(const WeakPtr<FrameNode>& which, const WeakPtr<FrameNode>& value)
187 {
188     bool ret = true;
189     if (which.Upgrade() == inNode_.Upgrade()) {
190         inNode_ = value;
191     } else if (which.Upgrade() == outNode_.Upgrade()) {
192         outNode_ = value;
193     } else {
194         ret = false;
195     }
196     return ret;
197 }
198 
199 // Called before layout, perform layout constraints match modifications in active state to
200 // impact self and children's measure and layout.
WillLayout(const RefPtr<LayoutWrapper> & layoutWrapper)201 void GeometryTransition::WillLayout(const RefPtr<LayoutWrapper>& layoutWrapper)
202 {
203     CHECK_NULL_VOID(layoutWrapper);
204     if (!layoutWrapper->IsRootMeasureNode()) {
205         return;
206     }
207     auto hostNode = layoutWrapper->GetHostNode();
208     if (IsNodeInAndActive(hostNode)) {
209         layoutPropertyIn_ = hostNode->GetLayoutProperty()->Clone();
210         ModifyLayoutConstraint(layoutWrapper, true);
211     } else if (IsNodeOutAndActive(hostNode)) {
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 = std::nullopt;
225 
226     if (isRoot && IsNodeInAndActive(node)) {
227         TAG_LOGD(AceLogTag::ACE_GEOMETRY_TRANSITION, "node: %{public}d in and active", node->GetId());
228         state_ = State::IDENTITY;
229         auto geometryNode = node->GetGeometryNode();
230         CHECK_NULL_VOID(geometryNode);
231         inNodeActiveFrameSize_ = geometryNode->GetFrameSize();
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         node->SetLayoutPriority(0);
239         direction = true;
240         hasInAnim_ = false;
241     } else if (isRoot && IsNodeOutAndActive(node)) {
242         TAG_LOGI(AceLogTag::ACE_GEOMETRY_TRANSITION, "node: %{public}d out and active, dependency check: %{public}d",
243             node->GetId(), !hasInAnim_);
244         hasOutAnim_ = false;
245         CHECK_NULL_VOID(!hasInAnim_);
246         direction = false;
247     }
248 
249     if (direction.has_value()) {
250         auto pipeline = AceType::DynamicCast<NG::PipelineContext>(PipelineBase::GetCurrentContext());
251         CHECK_NULL_VOID(pipeline);
252         pipeline->AddAfterLayoutTask([weak = WeakClaim(this), isNodeIn = direction.value()]() {
253             auto geometryTransition = weak.Upgrade();
254             CHECK_NULL_VOID(geometryTransition);
255             geometryTransition->SyncGeometry(isNodeIn);
256             if (!isNodeIn) {
257                 auto outNode = geometryTransition->outNode_.Upgrade();
258                 if (outNode && geometryTransition->layoutPropertyOut_) {
259                     outNode->SetLayoutProperty(geometryTransition->layoutPropertyOut_);
260                     geometryTransition->layoutPropertyOut_.Reset();
261                 }
262             }
263         });
264     }
265 }
266 
ModifyLayoutConstraint(const RefPtr<LayoutWrapper> & layoutWrapper,bool isNodeIn)267 void GeometryTransition::ModifyLayoutConstraint(const RefPtr<LayoutWrapper>& layoutWrapper, bool isNodeIn)
268 {
269     // outNode's frame is the target frame for active inNode to match,
270     // inNode's frame is the target frame for active outNode to match.
271     auto [self, target] = GetMatchedPair(isNodeIn);
272     CHECK_NULL_VOID(self);
273     CHECK_NULL_VOID(target);
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 targetGeometryNode = target->GetGeometryNode();
277     CHECK_NULL_VOID(targetGeometryNode);
278     auto targetRenderContext = target->GetRenderContext();
279     CHECK_NULL_VOID(targetRenderContext);
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     auto layoutProperty = layoutWrapper->GetLayoutProperty();
293     CHECK_NULL_VOID(layoutProperty);
294     layoutProperty->UpdateUserDefinedIdealSize(targetSize);
295     TAG_LOGD(AceLogTag::ACE_GEOMETRY_TRANSITION, "node: %{public}d modify size to: %{public}s",
296         self->GetId(), targetSize.ToString().c_str());
297     // if node has aspect ratio we'll ignore it in active state
298     auto& magicItemProperty = layoutProperty->GetMagicItemProperty();
299     if (magicItemProperty.HasAspectRatio()) {
300         magicItemProperty.ResetAspectRatio();
301     }
302 }
303 
SyncGeometry(bool isNodeIn)304 void GeometryTransition::SyncGeometry(bool isNodeIn)
305 {
306     auto [self, target] = GetMatchedPair(isNodeIn);
307     CHECK_NULL_VOID(self && target);
308     auto renderContext = self->GetRenderContext();
309     auto targetRenderContext = target->GetRenderContext();
310     auto geometryNode = self->GetGeometryNode();
311     auto pipeline = PipelineBase::GetCurrentContext();
312     CHECK_NULL_VOID(renderContext && targetRenderContext && geometryNode && pipeline);
313     auto taskExecutor = pipeline->GetTaskExecutor();
314     // get own parent's global position, parent's transform is not taken into account other than translate
315     auto parentPos = self->IsRemoving() ? outNodeParentPos_ : self->GetPaintRectGlobalOffsetWithTranslate(true).first;
316     // get target's global position, target own transform is taken into account
317     auto targetRect = target->IsRemoving() ? RectF(outNodePos_, outNodeSize_) :
318         staticNodeAbsRect_.value_or(inNodeAbsRect_.value_or(GetNodeAbsFrameRect(target)));
319     auto targetPos = targetRect.GetOffset();
320     // adjust self's position to match with target's position, here we only need to adjust node self,
321     // its children's positions are still determined by layout process.
322     auto activeFrameRect = isNodeIn ? RectF(targetPos - parentPos, inNodeActiveFrameSize_) :
323                                       RectF(targetPos - parentPos, geometryNode->GetFrameSize());
324     auto activeCornerRadius = targetRenderContext->GetBorderRadius().value_or(BorderRadiusProperty());
325     auto cornerRadius = renderContext->GetBorderRadius().value_or(BorderRadiusProperty());
326     if (isNodeIn) {
327         renderContext->SetFrameWithoutAnimation(activeFrameRect);
328         if (target->IsRemoving()) {
329             renderContext->RegisterSharedTransition(targetRenderContext); // notify backend for hierarchy processing
330         }
331     } else {
332         outNodeTargetAbsRect_ = targetRect;
333         if (staticNodeAbsRect_ && targetRenderContext->HasSandBox()) {
334             staticNodeAbsRect_ = std::nullopt;
335             targetRenderContext->RegisterSharedTransition(renderContext);
336         }
337         if (taskExecutor) {
338             taskExecutor->PostTask(
339                 [weakGT = WeakClaim(this)]() {
340                     auto geometryTransition = weakGT.Upgrade();
341                     CHECK_NULL_VOID(geometryTransition);
342                     geometryTransition->isSynced_ = true;
343                 }, TaskExecutor::TaskType::UI);
344         }
345     }
346     auto propertyCallback = [&]() {
347         // sync geometry in active state
348         renderContext->SetBorderRadius(activeCornerRadius);
349         renderContext->SyncGeometryProperties(activeFrameRect);
350         // sync geometry in identity state for inNode
351         if (isNodeIn) {
352             renderContext->SetBorderRadius(cornerRadius);
353             renderContext->SyncGeometryProperties(RawPtr(geometryNode));
354         }
355         // draw self and children in sandbox which will not be affected by parent's transition
356         if (!isNodeIn && outNodeParentHasScales_) {
357             renderContext->SetSandBox(std::nullopt, true);
358         } else {
359             renderContext->SetSandBox(parentPos);
360         }
361     };
362     auto finishCallback = [nodeWeak = WeakClaim(RawPtr(self))]() {
363         auto node = nodeWeak.Upgrade();
364         CHECK_NULL_VOID(node);
365         auto renderContext = node->GetRenderContext();
366         CHECK_NULL_VOID(renderContext);
367         renderContext->SetSandBox(std::nullopt);
368         TAG_LOGI(AceLogTag::ACE_GEOMETRY_TRANSITION, "node %{public}d animation completed", node->GetId());
369     };
370     bool isCurrentOptionValid = AnimationUtils::IsImplicitAnimationOpen();
371     if (!isNodeIn && inNodeAbsRect_) {
372         AnimationUtils::Animate(animationOption_, propertyCallback, finishCallback);
373         inNodeAbsRect_ = std::nullopt;
374         animationOption_ = AnimationOption();
375     } else if (isCurrentOptionValid) {
376         AnimationUtils::AnimateWithCurrentOptions(propertyCallback, finishCallback, false);
377     } else {
378         AnimationUtils::Animate(implicitAnimationOption_, propertyCallback, finishCallback);
379     }
380 
381     TAG_LOGI(AceLogTag::ACE_GEOMETRY_TRANSITION, "node: %{public}d, parent: %{public}s, target: %{public}s, "
382         "active frame: %{public}s, identity frame: %{public}s, option: %{public}d",
383         self->GetId(), parentPos.ToString().c_str(), targetPos.ToString().c_str(), activeFrameRect.ToString().c_str(),
384         isNodeIn ? geometryNode->GetFrameRect().ToString().c_str() : "no log", isCurrentOptionValid);
385 }
386 
CreateHolderNode(const RefPtr<FrameNode> & node)387 RefPtr<FrameNode> CreateHolderNode(const RefPtr<FrameNode>& node)
388 {
389     CHECK_NULL_RETURN(node, nullptr);
390     auto newNode = FrameNode::CreateFrameNode(
391         node->GetTag(), ElementRegister::GetInstance()->MakeUniqueId(), AceType::MakeRefPtr<Pattern>());
392     newNode->SetGeometryNode(node->GetGeometryNode()->Clone());
393     auto frameSize = node->GetGeometryNode()->GetFrameSize();
394     newNode->GetLayoutProperty()->UpdateUserDefinedIdealSize(
395         CalcSize(CalcLength(frameSize.Width()), CalcLength(frameSize.Height())));
396     return newNode;
397 }
398 
399 // For nodes without transition (still on the tree), but still need to follow the matched node which has
400 // transition (parameter is its transition direction).
OnFollowWithoutTransition(std::optional<bool> direction)401 bool GeometryTransition::OnFollowWithoutTransition(std::optional<bool> direction)
402 {
403     CHECK_NULL_RETURN(followWithoutTransition_, false);
404     if (!direction.has_value()) {
405         auto inNode = inNode_.Upgrade();
406         auto outNode = outNode_.Upgrade();
407         CHECK_NULL_RETURN(holder_ && inNode && outNode, false);
408         auto parent = holder_->GetParent();
409         auto inRenderContext = inNode->GetRenderContext();
410         auto outRenderContext = outNode->GetRenderContext();
411         CHECK_NULL_RETURN(parent && inRenderContext && outRenderContext, false);
412         parent->ReplaceChild(holder_, outNode);
413         parent->RemoveDisappearingChild(outNode);
414         parent->MarkDirtyNode(PROPERTY_UPDATE_MEASURE_SELF_AND_CHILD);
415         inRenderContext->UnregisterSharedTransition(outRenderContext);
416         hasOutAnim_ = false;
417         TAG_LOGI(AceLogTag::ACE_GEOMETRY_TRANSITION, "follow cancelled, node %{public}d to %{public}d",
418             holder_->GetId(), outNode->GetId());
419         holder_ = nullptr;
420         return false;
421     }
422     if (direction.value()) {
423         auto outNode = outNode_.Upgrade();
424         CHECK_NULL_RETURN(outNode, false);
425         auto parent = outNode->GetParent();
426         CHECK_NULL_RETURN(parent, false);
427         holder_ = CreateHolderNode(outNode);
428         CHECK_NULL_RETURN(holder_, false);
429         RecordOutNodeFrame();
430         auto idx = parent->GetChildIndex(outNode);
431         parent->ReplaceChild(outNode, holder_);
432         parent->AddDisappearingChild(outNode, idx);
433         MarkLayoutDirty(outNode, -1);
434         hasOutAnim_ = true;
435         TAG_LOGI(AceLogTag::ACE_GEOMETRY_TRANSITION, "follow started, node %{public}d to %{public}d",
436             outNode->GetId(), holder_->GetId());
437     } else {
438         auto inNode = inNode_.Upgrade();
439         CHECK_NULL_RETURN(inNode && inNode->GetGeometryNode() && holder_, false);
440         auto parent = holder_->GetParent();
441         CHECK_NULL_RETURN(parent, false);
442         parent->ReplaceChild(holder_, inNode);
443         parent->RemoveDisappearingChild(inNode);
444         inNode->SetLayoutPriority(0);
445         inNode->MarkDirtyNode(PROPERTY_UPDATE_MEASURE);
446         inNode->GetGeometryNode()->SetFrameSize(SizeF());
447         hasInAnim_ = false;
448         TAG_LOGI(AceLogTag::ACE_GEOMETRY_TRANSITION, "follow ended, node %{public}d to %{public}d",
449             holder_->GetId(), inNode->GetId());
450         holder_ = nullptr;
451     }
452     return true;
453 }
454 
IsParent(const WeakPtr<FrameNode> & parent,const WeakPtr<FrameNode> & child) const455 bool GeometryTransition::IsParent(const WeakPtr<FrameNode>& parent, const WeakPtr<FrameNode>& child) const
456 {
457     CHECK_NULL_RETURN(parent.Upgrade() && child.Upgrade(), false);
458     RefPtr<UINode> node = child.Upgrade();
459     while (node != nullptr) {
460         if (AceType::DynamicCast<FrameNode>(node) == parent) {
461             return true;
462         }
463         node = node->GetParent();
464     }
465     return false;
466 }
467 
RecordAnimationOption(const WeakPtr<FrameNode> & trigger,const AnimationOption & option)468 void GeometryTransition::RecordAnimationOption(const WeakPtr<FrameNode>& trigger, const AnimationOption& option)
469 {
470     if (option.IsValid()) {
471         if (IsParent(trigger, inNode_)) {
472             animationOption_ = option;
473         }
474     } else if (NG::ViewStackProcessor::GetInstance()->GetImplicitAnimationOption().IsValid()) {
475         if (IsParent(trigger, inNode_)) {
476             animationOption_ = NG::ViewStackProcessor::GetInstance()->GetImplicitAnimationOption();
477         }
478     } else {
479         auto pipeline = PipelineBase::GetCurrentContext();
480         if (pipeline && pipeline->GetSyncAnimationOption().IsValid() && IsParent(trigger, inNode_)) {
481             animationOption_ = pipeline->GetSyncAnimationOption();
482         }
483     }
484 }
485 
486 // during outNode animation is running target inNode's frame is changed, outNode needs to change as well to
487 // match tightly.
OnReSync(const WeakPtr<FrameNode> & trigger,const AnimationOption & option)488 void GeometryTransition::OnReSync(const WeakPtr<FrameNode>& trigger, const AnimationOption& option)
489 {
490     auto inNode = inNode_.Upgrade();
491     auto outNode = outNode_.Upgrade();
492     CHECK_NULL_VOID(isSynced_ && outNode && outNode->IsRemoving() && outNodeTargetAbsRect_ &&
493         outNodeTargetAbsRect_->IsValid() && inNode && inNode->IsOnMainTree());
494     auto inRenderContext = inNode->GetRenderContext();
495     auto outRenderContext = outNode->GetRenderContext();
496     CHECK_NULL_VOID(inRenderContext && outRenderContext);
497     if (trigger.Upgrade()) {
498         RecordAnimationOption(trigger, option);
499         return;
500     }
501     OffsetF inNodeParentPos;
502     bool inNodeParentHasScales = false;
503     if (!staticNodeAbsRect_) {
504         auto [val, err] = inNode->GetPaintRectGlobalOffsetWithTranslate(true);
505         inNodeParentPos = val;
506         inNodeParentHasScales = err;
507     }
508     auto inNodeAbsRect = staticNodeAbsRect_ || inNodeParentHasScales ?
509         inNode->GetTransformRectRelativeToWindow() : GetNodeAbsFrameRect(inNode, inNodeParentPos);
510     auto inNodeAbsRectOld = outNodeTargetAbsRect_.value();
511     bool sizeChanged = GreatNotEqual(std::fabs(inNodeAbsRect.Width() - inNodeAbsRectOld.Width()), 1.0f) ||
512         GreatNotEqual(std::fabs(inNodeAbsRect.Height() - inNodeAbsRectOld.Height()), 1.0f);
513     bool posChanged = GreatNotEqual(std::fabs(inNodeAbsRect.GetX() - inNodeAbsRectOld.GetX()), 1.0f) ||
514         GreatNotEqual(std::fabs(inNodeAbsRect.GetY() - inNodeAbsRectOld.GetY()), 1.0f);
515     CHECK_NULL_VOID(sizeChanged || posChanged);
516     auto animOption = animationOption_.IsValid() ? animationOption_ : AnimationOption(Curves::LINEAR, RESYNC_DURATION);
517     AnimationUtils::Animate(animOption, [&]() {
518         if (inRenderContext->HasSandBox()) {
519             auto parent = inNode->GetAncestorNodeOfFrame();
520             inNodeParentPos = inNodeParentHasScales && parent ?
521                 parent->GetTransformRectRelativeToWindow().GetOffset() : inNodeParentPos;
522             inRenderContext->SetSandBox(inNodeParentPos, true);
523         }
524         if (!sizeChanged) {
525             auto activeFrameRect = RectF(inNodeAbsRect.GetOffset() - outNodeParentPos_, inNodeAbsRect.GetSize());
526             outRenderContext->SyncGeometryProperties(activeFrameRect);
527             outNodeTargetAbsRect_ = inNodeAbsRect;
528             animationOption_ = AnimationOption();
529         } else {
530             hasOutAnim_ = true;
531             inNodeAbsRect_ = inNodeAbsRect;
532             outNodeTargetAbsRect_.reset();
533             MarkLayoutDirty(outNode);
534             animationOption_ = animOption;
535         }
536     }, nullptr);
537     TAG_LOGI(AceLogTag::ACE_GEOMETRY_TRANSITION, "outNode: %{public}d %{public}s resyncs to inNode: %{public}d "
538         "%{public}s, option: %{public}d, hasScales: %{public}d", outNode->GetId(), inNodeAbsRectOld.ToString().c_str(),
539         inNode->GetId(), inNodeAbsRect.ToString().c_str(), animOption.GetDuration(), inNodeParentHasScales);
540 }
541 
542 // if nodes with geometry transitions are added during layout, we need to execute additional layout in current frame
OnAdditionalLayout(const WeakPtr<FrameNode> & frameNode)543 bool GeometryTransition::OnAdditionalLayout(const WeakPtr<FrameNode>& frameNode)
544 {
545     bool ret = false;
546     auto node = frameNode.Upgrade();
547     CHECK_NULL_RETURN(node, false);
548     if (IsNodeInAndActive(frameNode)) {
549         auto parentNode = node->GetAncestorNodeOfFrame();
550         if (parentNode) {
551             MarkLayoutDirty(node);
552             MarkLayoutDirty(parentNode);
553             ret = true;
554         }
555     } else if (IsNodeOutAndActive(frameNode)) {
556         MarkLayoutDirty(node);
557         ret = true;
558     }
559     return ret;
560 }
561 
ToString() const562 std::string GeometryTransition::ToString() const
563 {
564     return std::string("in: ") + (inNode_.Upgrade() ? std::to_string(inNode_.Upgrade()->GetId()) : "null") +
565         std::string(", out: ") + (outNode_.Upgrade() ? std::to_string(outNode_.Upgrade()->GetId()) : "null");
566 }
567 } // namespace OHOS::Ace::NG
568