• 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.
34 namespace {
35     constexpr char OCCLUSION_SCENE[] = "_occlusion";
36 }
GeometryTransition(const std::string & id,bool followWithoutTransition,bool doRegisterSharedTransition)37 GeometryTransition::GeometryTransition(
38     const std::string& id, bool followWithoutTransition, bool doRegisterSharedTransition) : id_(id),
39     followWithoutTransition_(followWithoutTransition), doRegisterSharedTransition_(doRegisterSharedTransition) {}
40 
IsInAndOutEmpty() const41 bool GeometryTransition::IsInAndOutEmpty() const
42 {
43     return !inNode_.Upgrade() && !outNode_.Upgrade();
44 }
45 
IsInAndOutValid() const46 bool GeometryTransition::IsInAndOutValid() const
47 {
48     return inNode_.Upgrade() && outNode_.Upgrade();
49 }
50 
IsRunning(const WeakPtr<FrameNode> & frameNode) const51 bool GeometryTransition::IsRunning(const WeakPtr<FrameNode>& frameNode) const
52 {
53     auto node = frameNode.Upgrade();
54     CHECK_NULL_RETURN(node && IsInAndOutValid(), false);
55     return node->GetLayoutPriority() != 0 && (node == inNode_ || node == outNode_);
56 }
57 
IsNodeInAndActive(const WeakPtr<FrameNode> & frameNode) const58 bool GeometryTransition::IsNodeInAndActive(const WeakPtr<FrameNode>& frameNode) const
59 {
60     return state_ == State::ACTIVE && hasInAnim_ && frameNode.Upgrade() == inNode_ && IsInAndOutValid();
61 }
62 
IsNodeInAndIdentity(const WeakPtr<FrameNode> & frameNode) const63 bool GeometryTransition::IsNodeInAndIdentity(const WeakPtr<FrameNode>& frameNode) const
64 {
65     return state_ == State::IDENTITY && hasInAnim_ && frameNode.Upgrade() == inNode_ && IsInAndOutValid();
66 }
67 
IsNodeOutAndActive(const WeakPtr<FrameNode> & frameNode) const68 bool GeometryTransition::IsNodeOutAndActive(const WeakPtr<FrameNode>& frameNode) const
69 {
70     return hasOutAnim_ && frameNode.Upgrade() == outNode_ && IsInAndOutValid();
71 }
72 
SwapInAndOut(bool condition)73 void GeometryTransition::SwapInAndOut(bool condition)
74 {
75     if (condition) {
76         std::swap(inNode_, outNode_);
77     }
78 }
79 
GetMatchedPair(bool isNodeIn) const80 std::pair<RefPtr<FrameNode>, RefPtr<FrameNode>> GeometryTransition::GetMatchedPair(bool isNodeIn) const
81 {
82     auto self = isNodeIn ? inNode_ : outNode_;
83     auto target = isNodeIn ? outNode_ : inNode_;
84     return { self.Upgrade(), target.Upgrade() };
85 }
86 
GetNodeAbsFrameRect(const RefPtr<FrameNode> & node,std::optional<OffsetF> parentPos) const87 RectF GeometryTransition::GetNodeAbsFrameRect(const RefPtr<FrameNode>& node, std::optional<OffsetF> parentPos) const
88 {
89     CHECK_NULL_RETURN(node, RectF());
90     auto renderContext = node->GetRenderContext();
91     CHECK_NULL_RETURN(renderContext, RectF());
92     auto parentGlobalOffset = parentPos.value_or(node->GetPaintRectGlobalOffsetWithTranslate(true, true).first);
93     auto paintRect = renderContext->GetPaintRectWithTransform();
94     return RectF(parentGlobalOffset + paintRect.GetOffset(), paintRect.GetSize());
95 }
96 
RecordOutNodeFrame()97 void GeometryTransition::RecordOutNodeFrame()
98 {
99     auto outNode = outNode_.Upgrade();
100     CHECK_NULL_VOID(outNode);
101     auto [val, err] = outNode->GetPaintRectGlobalOffsetWithTranslate(true, true);
102     outNodeParentPos_ = val;
103     outNodeParentHasScales_ = err;
104     auto outNodeAbsRect = GetNodeAbsFrameRect(outNode_.Upgrade(), outNodeParentPos_);
105     outNodePos_ = outNodeAbsRect.GetOffset();
106     outNodeSize_ = outNodeAbsRect.GetSize();
107     outWindowBoundaryNode_ = GetWindowBoundaryNode(outNode);
108 }
109 
GetWindowBoundaryNode(const RefPtr<FrameNode> & node) const110 RefPtr<FrameNode> GeometryTransition::GetWindowBoundaryNode(const RefPtr<FrameNode>& node) const
111 {
112     CHECK_NULL_RETURN(node, nullptr);
113     auto parent = node;
114     auto topParent = parent;
115     while (parent) {
116         if (parent->IsWindowBoundary()) {
117             return parent;
118         }
119         parent = parent->GetAncestorNodeOfFrame(false);
120         if (parent) {
121             topParent = parent;
122         }
123     }
124     return topParent;
125 }
126 
MarkLayoutDirty(const RefPtr<FrameNode> & node,int32_t layoutPriority)127 void GeometryTransition::MarkLayoutDirty(const RefPtr<FrameNode>& node, int32_t layoutPriority)
128 {
129     CHECK_NULL_VOID(node && node->GetLayoutProperty());
130     if (layoutPriority) {
131         node->SetLayoutPriority(layoutPriority);
132     }
133     node->GetLayoutProperty()->CleanDirty();
134     node->MarkDirtyNode(PROPERTY_UPDATE_MEASURE_SELF_AND_CHILD);
135 }
136 
137 // Build should be called during node tree build phase dealing with node add/remove or appearing/disappearing
Build(const WeakPtr<FrameNode> & frameNode,bool isNodeIn)138 void GeometryTransition::Build(const WeakPtr<FrameNode>& frameNode, bool isNodeIn)
139 {
140     state_ = State::IDLE;
141     outNodeTargetAbsRect_.reset();
142     isSynced_ = false;
143     if (IsInAndOutEmpty()) {
144         hasInAnim_ = false;
145         hasOutAnim_ = false;
146     }
147     auto node = frameNode.Upgrade();
148     CHECK_NULL_VOID(node && node->GetRenderContext() && !id_.empty());
149     TAG_LOGD(AceLogTag::ACE_GEOMETRY_TRANSITION, "build node: %{public}d, direction: %{public}d, onTree: %{public}d, "
150         "removing: %{public}d, compid: %{public}s .", node->GetId(), isNodeIn, node->IsOnMainTree(),
151         node->IsRemoving(), node->GetInspectorId().value_or("").c_str());
152     if (!isNodeIn && (frameNode == inNode_ || frameNode == outNode_)) {
153         SwapInAndOut(frameNode == inNode_);
154         RecordOutNodeFrame();
155         hasOutAnim_ = true;
156     }
157     if (isNodeIn && (frameNode != inNode_)) {
158         auto inNode = inNode_.Upgrade();
159         bool replace = true;
160         if (inNode && !inNode->IsRemoving() && inNode->IsOnMainTree()) {
161             std::string id = node->GetInspectorId().value_or("");
162             replace = !id.empty() && id == inNode->GetInspectorId().value_or("");
163         }
164         SwapInAndOut(!replace);
165         inNode_ = frameNode;
166         bool isInAnimating = inNode && inNode->GetRenderContext() && inNode->GetRenderContext()->HasSandBox();
167         CHECK_NULL_VOID(!(replace && isInAnimating));
168         hasInAnim_ = true;
169     }
170     auto inNode = inNode_.Upgrade();
171     auto outNode = outNode_.Upgrade();
172     CHECK_NULL_VOID(inNode && outNode && (inNode != outNode));
173     auto pipeline = inNode->GetContextRefPtr();
174 
175     bool isImplicitAnimationOpen = AnimationUtils::IsImplicitAnimationOpen(pipeline);
176     bool follow = false;
177     if (hasOutAnim_) {
178         if (!hasInAnim_) {
179             follow = OnFollowWithoutTransition(false);
180         }
181         hasOutAnim_ = isImplicitAnimationOpen || follow;
182         if (hasOutAnim_) {
183             MarkLayoutDirty(outNode, -1);
184         }
185     }
186     if (hasInAnim_ && !follow) {
187         if (!hasOutAnim_) {
188             follow = OnFollowWithoutTransition(true);
189         }
190         if (isImplicitAnimationOpen || follow) {
191             state_ = State::ACTIVE;
192             MarkLayoutDirty(inNode, 1);
193         } else {
194             hasInAnim_ = false;
195             inNode->SetLayoutPriority(0);
196             inNode->MarkDirtyNode(PROPERTY_UPDATE_MEASURE);
197             inNode->GetGeometryNode()->SetFrameSize(SizeF());
198         }
199     }
200 
201     TAG_LOGD(AceLogTag::ACE_GEOMETRY_TRANSITION, "inAnim: %{public}d, outAnim: %{public}d, follow: %{public}d, "
202         "inNode: %{public}d, %{public}s, outNode: %{public}d, %{public}s", hasInAnim_, hasOutAnim_, follow,
203         inNode->GetId(), inNode->GetTag().c_str(), outNode->GetId(), outNode->GetTag().c_str());
204 
205     ACE_SCOPED_TRACE("ACE_GEOMETRY_TRANSITION, inAnim: %d, outAnim: %d, follow: %d, "
206         "inNode: %d, %s, outNode: %d, %s", hasInAnim_, hasOutAnim_, follow,
207         inNode->GetId(), inNode->GetTag().c_str(), outNode->GetId(), outNode->GetTag().c_str());
208 }
209 
210 // Update should be called during node update phase when node exists
Update(const WeakPtr<FrameNode> & which,const WeakPtr<FrameNode> & value)211 bool GeometryTransition::Update(const WeakPtr<FrameNode>& which, const WeakPtr<FrameNode>& value)
212 {
213     bool ret = true;
214     if (which.Upgrade() == inNode_.Upgrade()) {
215         inNode_ = value;
216     } else if (which.Upgrade() == outNode_.Upgrade()) {
217         outNode_ = value;
218     } else {
219         ret = false;
220     }
221     auto whichNode = which.Upgrade();
222     if (ret && whichNode && whichNode != value.Upgrade()) {
223         whichNode->SetLayoutPriority(0);
224     }
225     return ret;
226 }
227 
228 // Called before layout, perform layout constraints match modifications in active state to
229 // impact self and children's measure and layout.
WillLayout(const RefPtr<LayoutWrapper> & layoutWrapper)230 void GeometryTransition::WillLayout(const RefPtr<LayoutWrapper>& layoutWrapper)
231 {
232     CHECK_NULL_VOID(layoutWrapper && layoutWrapper->IsRootMeasureNode());
233     auto hostNode = layoutWrapper->GetHostNode();
234     if (IsNodeInAndActive(hostNode)) {
235         layoutPropertyIn_ = hostNode->GetLayoutProperty()->Clone();
236         ModifyLayoutConstraint(layoutWrapper, true);
237     } else if (IsNodeOutAndActive(hostNode) && !hasInAnim_) {
238         layoutPropertyOut_ = hostNode->GetLayoutProperty()->Clone();
239         ModifyLayoutConstraint(layoutWrapper, false);
240     }
241 }
242 
243 // Called after layout, perform final adjustments of geometry position
DidLayout(const RefPtr<LayoutWrapper> & layoutWrapper)244 void GeometryTransition::DidLayout(const RefPtr<LayoutWrapper>& layoutWrapper)
245 {
246     CHECK_NULL_VOID(layoutWrapper);
247     auto node = layoutWrapper->GetHostNode();
248     CHECK_NULL_VOID(node);
249     bool isRoot = layoutWrapper->IsRootMeasureNode();
250     std::optional<bool> direction;
251     if (isRoot && IsNodeInAndActive(node)) {
252         TAG_LOGD(AceLogTag::ACE_GEOMETRY_TRANSITION, "node: %{public}d in and active", node->GetId());
253         state_ = State::IDENTITY;
254         auto geometryNode = node->GetGeometryNode();
255         CHECK_NULL_VOID(geometryNode);
256         inNodeActiveFrameSize_ = geometryNode->GetFrameSize();
257         CHECK_NULL_VOID(layoutPropertyIn_);
258         layoutPropertyIn_->UpdatePropertyChangeFlag(PROPERTY_UPDATE_MEASURE);
259         node->SetLayoutProperty(layoutPropertyIn_);
260         layoutPropertyIn_.Reset();
261     } else if (IsNodeInAndIdentity(node)) {
262         TAG_LOGD(AceLogTag::ACE_GEOMETRY_TRANSITION, "node: %{public}d in and identity", node->GetId());
263         state_ = State::IDLE;
264         direction = true;
265         hasInAnim_ = false;
266     } else if (isRoot && IsNodeOutAndActive(node)) {
267         TAG_LOGD(AceLogTag::ACE_GEOMETRY_TRANSITION, "node: %{public}d out and active, dependency check: %{public}d",
268             node->GetId(), !hasInAnim_);
269         if (hasInAnim_) {
270             MarkLayoutDirty(node);
271             return;
272         }
273         hasOutAnim_ = false;
274         direction = false;
275     }
276     if (direction.has_value()) {
277         auto pipeline = PipelineContext::GetCurrentContext();
278         CHECK_NULL_VOID(pipeline);
279         pipeline->AddAfterLayoutTask([weak = WeakClaim(this), isNodeIn = direction.value()]() {
280             auto geometryTransition = weak.Upgrade();
281             CHECK_NULL_VOID(geometryTransition);
282             geometryTransition->SyncGeometry(isNodeIn);
283             if (!isNodeIn) {
284                 auto outNode = geometryTransition->outNode_.Upgrade();
285                 if (outNode && geometryTransition->layoutPropertyOut_) {
286                     outNode->SetLayoutProperty(geometryTransition->layoutPropertyOut_);
287                     geometryTransition->layoutPropertyOut_.Reset();
288                 }
289             }
290         });
291     }
292 }
293 
ModifyLayoutConstraint(const RefPtr<LayoutWrapper> & layoutWrapper,bool isNodeIn)294 void GeometryTransition::ModifyLayoutConstraint(const RefPtr<LayoutWrapper>& layoutWrapper, bool isNodeIn)
295 {
296     // outNode's frame is the target frame for active inNode to match,
297     // inNode's frame is the target frame for active outNode to match.
298     auto [self, target] = GetMatchedPair(isNodeIn);
299     CHECK_NULL_VOID(self && target && layoutWrapper);
300     // target's geometry is ensured ready to use because layout nodes are sorted to respect dependency,
301     // the order is active inNode, normal layout, active outNode.
302     auto layoutProperty = layoutWrapper->GetLayoutProperty();
303     auto targetGeometryNode = target->GetGeometryNode();
304     auto targetRenderContext = target->GetRenderContext();
305     CHECK_NULL_VOID(targetRenderContext && targetGeometryNode && layoutProperty);
306     SizeF size;
307     if (isNodeIn) {
308         staticNodeAbsRect_ =
309             target->IsRemoving() ? std::nullopt : std::optional<RectF>(target->GetTransformRectRelativeToWindow(true));
310         size = target->IsRemoving() ? outNodeSize_ : staticNodeAbsRect_->GetSize();
311     } else {
312         staticNodeAbsRect_ =
313             !staticNodeAbsRect_ ? std::nullopt : std::optional<RectF>(target->GetTransformRectRelativeToWindow(true));
314         size = staticNodeAbsRect_ ? staticNodeAbsRect_->GetSize() :
315             (inNodeAbsRect_ ? inNodeAbsRect_->GetSize() : targetGeometryNode->GetFrameSize());
316     }
317     auto targetSize = CalcSize(NG::CalcLength(size.Width()), NG::CalcLength(size.Height()));
318     layoutProperty->UpdateUserDefinedIdealSize(targetSize);
319     TAG_LOGD(AceLogTag::ACE_GEOMETRY_TRANSITION, "node: %{public}d modify size to: %{public}s",
320         self->GetId(), targetSize.ToString().c_str());
321     // if node has aspect ratio we'll ignore it in active state
322     auto& magicItemProperty = layoutProperty->GetMagicItemProperty();
323     if (magicItemProperty.HasAspectRatio()) {
324         magicItemProperty.ResetAspectRatio();
325     }
326 }
327 
HandleOcclusionScene(const RefPtr<FrameNode> & node,bool flag)328 void GeometryTransition::HandleOcclusionScene(const RefPtr<FrameNode>& node, bool flag)
329 {
330     CHECK_NULL_VOID(node);
331     if (node->GetInspectorId().value_or("").find(OCCLUSION_SCENE) == std::string::npos) {
332         return;
333     }
334     ACE_SCOPED_TRACE("occlusion contentNode id: %d, name: %s setSuccess",
335         node->GetId(), node->GetInspectorId().value_or("").c_str());
336     node->AddToOcclusionMap(flag);
337 }
338 
SyncGeometry(bool isNodeIn)339 void GeometryTransition::SyncGeometry(bool isNodeIn)
340 {
341     auto [self, target] = GetMatchedPair(isNodeIn);
342     CHECK_NULL_VOID(self && target);
343     auto renderContext = self->GetRenderContext();
344     auto targetRenderContext = target->GetRenderContext();
345     auto geometryNode = self->GetGeometryNode();
346     auto pipeline = PipelineBase::GetCurrentContext();
347     CHECK_NULL_VOID(renderContext && targetRenderContext && geometryNode && pipeline);
348     // get own parent's global position, parent's transform is not taken into account other than translate
349     auto parentPos = self->IsRemoving() ? outNodeParentPos_ :
350         self->GetPaintRectGlobalOffsetWithTranslate(true, true).first;
351     // get target's global position, target own transform is taken into account
352     auto targetRect = target->IsRemoving() ? RectF(outNodePos_, outNodeSize_) :
353         staticNodeAbsRect_.value_or(inNodeAbsRect_.value_or(GetNodeAbsFrameRect(target)));
354     auto targetPos = targetRect.GetOffset();
355     // adjust self's position to match with target's position, here we only need to adjust node self,
356     // its children's positions are still determined by layout process.
357     auto activeFrameRect = isNodeIn ? RectF(targetPos - parentPos, inNodeActiveFrameSize_) :
358                                       RectF(targetPos - parentPos, geometryNode->GetFrameSize());
359     auto activeCornerRadius = targetRenderContext->GetBorderRadius().value_or(BorderRadiusProperty());
360     auto cornerRadius = renderContext->GetBorderRadius().value_or(BorderRadiusProperty());
361     if (isNodeIn) {
362         self->SetLayoutPriority(0);
363         renderContext->SetFrameWithoutAnimation(activeFrameRect);
364         if (target->IsRemoving()) {
365             if (doRegisterSharedTransition_) {
366                 auto outWindowBoundaryNode = outWindowBoundaryNode_.Upgrade();
367                 auto isInSameWindow = outWindowBoundaryNode && outWindowBoundaryNode == GetWindowBoundaryNode(self);
368                 // notify backend for hierarchy processing
369                 renderContext->RegisterSharedTransition(targetRenderContext, isInSameWindow);
370             }
371         }
372     } else {
373         isSynced_ = true;
374         outNodeTargetAbsRect_ = targetRect;
375         if (staticNodeAbsRect_ && targetRenderContext->HasSandBox()) {
376             staticNodeAbsRect_.reset();
377             if (doRegisterSharedTransition_) {
378                 auto outWindowBoundaryNode = GetWindowBoundaryNode(self);
379                 auto isInSameWindow = outWindowBoundaryNode && outWindowBoundaryNode == GetWindowBoundaryNode(target);
380                 targetRenderContext->RegisterSharedTransition(renderContext, isInSameWindow);
381             }
382         }
383     }
384     static std::atomic<int32_t> traceTaskId = 0;
385     auto currentTraceTaskId = traceTaskId.fetch_add(1, std::memory_order_relaxed);
386     auto nodeId = self->GetId();
387     auto propertyCallback = [&]() {
388         // sync geometry in active state
389         renderContext->SetBorderRadius(activeCornerRadius);
390         renderContext->SyncGeometryProperties(activeFrameRect);
391         // sync geometry in identity state for inNode
392         if (isNodeIn) {
393             renderContext->SetBorderRadius(cornerRadius);
394             renderContext->SyncGeometryProperties(RawPtr(geometryNode));
395         }
396         // draw self and children in sandbox which will not be affected by parent's transition
397         if (!isNodeIn && outNodeParentHasScales_) {
398             renderContext->SetSandBox(std::nullopt, true);
399         } else {
400             renderContext->SetSandBox(parentPos);
401         }
402         std::string traceTag = "ACE_GEOMETRY_TRANSITION, node " + std::to_string(nodeId) + " animation";
403         AceAsyncTraceBeginCommercial(currentTraceTaskId, traceTag.c_str());
404     };
405     auto finishCallback = [currentTraceTaskId, nodeWeak = WeakClaim(RawPtr(self)), weak = WeakClaim(this)]() {
406         auto node = nodeWeak.Upgrade();
407         CHECK_NULL_VOID(node);
408         auto renderContext = node->GetRenderContext();
409         CHECK_NULL_VOID(renderContext);
410         renderContext->SetSandBox(std::nullopt);
411         TAG_LOGD(AceLogTag::ACE_GEOMETRY_TRANSITION, "node %{public}d animation completed", node->GetId());
412         std::string traceTag = "ACE_GEOMETRY_TRANSITION, node " + std::to_string(node->GetId()) + " animation";
413         AceAsyncTraceEndCommercial(currentTraceTaskId, traceTag.c_str());
414         auto occlusion = weak.Upgrade();
415         CHECK_NULL_VOID(occlusion);
416         occlusion->HandleOcclusionScene(node, false);
417     };
418     HandleOcclusionScene(self, true);
419     if (!isNodeIn && inNodeAbsRect_) {
420         AnimationUtils::Animate(animationOption_, propertyCallback, finishCallback, nullptr, pipeline);
421         inNodeAbsRect_.reset();
422         animationOption_ = AnimationOption();
423     } else {
424         AnimationUtils::AnimateWithCurrentOptions(propertyCallback, finishCallback, false, pipeline);
425     }
426 
427     ACE_SCOPED_TRACE("ACE_GEOMETRY_TRANSITION, node: %d, parent: %s, target: %s, "
428         "active frame: %s, identity frame: %s, option: %d",
429         self->GetId(), parentPos.ToString().c_str(), targetPos.ToString().c_str(), activeFrameRect.ToString().c_str(),
430         isNodeIn ? geometryNode->GetFrameRect().ToString().c_str() : "no log",
431         AnimationUtils::IsImplicitAnimationOpen(pipeline));
432 
433     TAG_LOGD(AceLogTag::ACE_GEOMETRY_TRANSITION, "node: %{public}d, parent: %{public}s, target: %{public}s, "
434         "active frame: %{public}s, identity frame: %{public}s, option: %{public}d",
435         self->GetId(), parentPos.ToString().c_str(), targetPos.ToString().c_str(), activeFrameRect.ToString().c_str(),
436         isNodeIn ? geometryNode->GetFrameRect().ToString().c_str() : "no log",
437         AnimationUtils::IsImplicitAnimationOpen(pipeline));
438 }
439 
CreateHolderNode(const RefPtr<FrameNode> & node)440 RefPtr<FrameNode> CreateHolderNode(const RefPtr<FrameNode>& node)
441 {
442     CHECK_NULL_RETURN(node, nullptr);
443     auto newNode = FrameNode::CreateFrameNode(
444         node->GetTag(), ElementRegister::GetInstance()->MakeUniqueId(), AceType::MakeRefPtr<Pattern>());
445     newNode->SetGeometryNode(node->GetGeometryNode()->Clone());
446     auto frameSize = node->GetGeometryNode()->GetFrameSize();
447     newNode->GetLayoutProperty()->UpdateUserDefinedIdealSize(
448         CalcSize(CalcLength(frameSize.Width()), CalcLength(frameSize.Height())));
449     return newNode;
450 }
451 
SyncGeometryPropertiesAfterLayout(const RefPtr<FrameNode> & syncNode)452 void GeometryTransition::SyncGeometryPropertiesAfterLayout(const RefPtr<FrameNode>& syncNode)
453 {
454     CHECK_NULL_VOID(syncNode);
455     auto pipeline = PipelineContext::GetCurrentContext();
456     CHECK_NULL_VOID(pipeline);
457     pipeline->AddAfterLayoutTask(
458         [nodeWeak = WeakClaim(RawPtr(syncNode))]() {
459             auto node = nodeWeak.Upgrade();
460             CHECK_NULL_VOID(node);
461             auto renderContext = node->GetRenderContext();
462             CHECK_NULL_VOID(renderContext);
463             auto geometryNode = node->GetGeometryNode();
464             CHECK_NULL_VOID(geometryNode);
465             renderContext->SyncGeometryProperties(RawPtr(geometryNode));
466             renderContext->SetBorderRadius(renderContext->GetBorderRadius().value_or(BorderRadiusProperty()));
467         }, true);
468 }
469 
470 // For nodes without transition (still on the tree), but still need to follow the matched node which has
471 // transition (parameter is its transition direction).
OnFollowWithoutTransition(std::optional<bool> direction)472 bool GeometryTransition::OnFollowWithoutTransition(std::optional<bool> direction)
473 {
474     CHECK_NULL_RETURN(followWithoutTransition_, false);
475     if (!direction.has_value()) {
476         auto inNode = inNode_.Upgrade();
477         auto outNode = outNode_.Upgrade();
478         CHECK_NULL_RETURN(holder_ && inNode && outNode, false);
479         auto parent = holder_->GetParent();
480         auto inRenderContext = inNode->GetRenderContext();
481         auto outRenderContext = outNode->GetRenderContext();
482         CHECK_NULL_RETURN(parent && inRenderContext && outRenderContext, false);
483         parent->ReplaceChild(holder_, outNode);
484         parent->RemoveDisappearingChild(outNode);
485         parent->MarkDirtyNode(PROPERTY_UPDATE_MEASURE_SELF_AND_CHILD);
486         inRenderContext->UnregisterSharedTransition(outRenderContext);
487         hasOutAnim_ = false;
488         TAG_LOGD(AceLogTag::ACE_GEOMETRY_TRANSITION, "follow cancelled");
489         holder_ = nullptr;
490         SyncGeometryPropertiesAfterLayout(outNode);
491         return false;
492     }
493     if (direction.value()) {
494         auto outNode = outNode_.Upgrade();
495         CHECK_NULL_RETURN(outNode, false);
496         auto parent = outNode->GetParent();
497         CHECK_NULL_RETURN(parent, false);
498         holder_ = CreateHolderNode(outNode);
499         CHECK_NULL_RETURN(holder_, false);
500         RecordOutNodeFrame();
501         auto idx = parent->GetChildIndex(outNode);
502         parent->ReplaceChild(outNode, holder_);
503         parent->AddDisappearingChild(outNode, idx);
504         MarkLayoutDirty(outNode, -1);
505         hasOutAnim_ = true;
506         TAG_LOGD(AceLogTag::ACE_GEOMETRY_TRANSITION, "follow started");
507     } else {
508         auto inNode = inNode_.Upgrade();
509         CHECK_NULL_RETURN(inNode && inNode->GetGeometryNode() && holder_, false);
510         auto parent = holder_->GetParent();
511         CHECK_NULL_RETURN(parent, false);
512         parent->ReplaceChild(holder_, inNode);
513         parent->RemoveDisappearingChild(inNode);
514         state_ = State::ACTIVE;
515         MarkLayoutDirty(inNode, 1);
516         if (auto inNodeParent = inNode->GetAncestorNodeOfFrame(false)) {
517             MarkLayoutDirty(inNodeParent);
518         }
519         hasInAnim_ = true;
520         TAG_LOGD(AceLogTag::ACE_GEOMETRY_TRANSITION, "follow ended");
521         holder_ = nullptr;
522     }
523     return true;
524 }
525 
IsParent(const WeakPtr<FrameNode> & parent,const WeakPtr<FrameNode> & child) const526 bool GeometryTransition::IsParent(const WeakPtr<FrameNode>& parent, const WeakPtr<FrameNode>& child) const
527 {
528     CHECK_NULL_RETURN(parent.Upgrade() && child.Upgrade(), false);
529     RefPtr<UINode> node = child.Upgrade();
530     while (node != nullptr) {
531         if (AceType::DynamicCast<FrameNode>(node) == parent) {
532             return true;
533         }
534         node = node->GetParent();
535     }
536     return false;
537 }
538 
RecordAnimationOption(const WeakPtr<FrameNode> & trigger,const AnimationOption & option)539 void GeometryTransition::RecordAnimationOption(const WeakPtr<FrameNode>& trigger, const AnimationOption& option)
540 {
541     if (option.IsValid()) {
542         if (IsParent(trigger, inNode_)) {
543             animationOption_ = option;
544         }
545     } else if (NG::ViewStackProcessor::GetInstance()->GetImplicitAnimationOption().IsValid()) {
546         if (IsParent(trigger, inNode_)) {
547             animationOption_ = NG::ViewStackProcessor::GetInstance()->GetImplicitAnimationOption();
548         }
549     } else {
550         auto pipeline = PipelineBase::GetCurrentContext();
551         if (pipeline && pipeline->GetSyncAnimationOption().IsValid() && IsParent(trigger, inNode_)) {
552             animationOption_ = pipeline->GetSyncAnimationOption();
553         }
554     }
555 }
556 
AnimateWithSandBox(const OffsetF & inNodeParentPos,bool inNodeParentHasScales,const std::function<void ()> & propertyCallback,const AnimationOption & option)557 void GeometryTransition::AnimateWithSandBox(const OffsetF& inNodeParentPos, bool inNodeParentHasScales,
558     const std::function<void()>& propertyCallback, const AnimationOption& option)
559 {
560     auto inNode = inNode_.Upgrade();
561     CHECK_NULL_VOID(inNode);
562     auto inRenderContext = inNode->GetRenderContext();
563     CHECK_NULL_VOID(inRenderContext);
564     auto pipeline = inNode->GetContextRefPtr();
565     AnimationUtils::Animate(option, [&]() {
566         if (inRenderContext->HasSandBox()) {
567             auto parent = inNode->GetAncestorNodeOfFrame(false);
568             if (inNodeParentHasScales && parent) {
569                 inRenderContext->SetSandBox(parent->GetTransformRectRelativeToWindow(true).GetOffset());
570             } else {
571                 inRenderContext->SetSandBox(inNodeParentPos);
572             }
573         }
574         propertyCallback();
575     }, [nodeWeak = WeakClaim(RawPtr(inNode))]() {
576         auto node = nodeWeak.Upgrade();
577         CHECK_NULL_VOID(node);
578         auto renderContext = node->GetRenderContext();
579         CHECK_NULL_VOID(renderContext);
580         if (renderContext->HasSandBox()) {
581             renderContext->SetSandBox(std::nullopt);
582         }
583         TAG_LOGD(AceLogTag::ACE_GEOMETRY_TRANSITION, "node %{public}d resync animation completed", node->GetId());
584     }, nullptr, pipeline);
585 }
586 
587 // during outNode animation is running target inNode's frame is changed, outNode needs to change as well to
588 // match tightly.
OnReSync(const WeakPtr<FrameNode> & trigger,const AnimationOption & option)589 void GeometryTransition::OnReSync(const WeakPtr<FrameNode>& trigger, const AnimationOption& option)
590 {
591     auto inNode = inNode_.Upgrade();
592     auto outNode = outNode_.Upgrade();
593     CHECK_NULL_VOID(isSynced_ && outNode && outNode->IsRemoving() && outNodeTargetAbsRect_ &&
594         outNodeTargetAbsRect_->IsValid() && inNode && inNode->IsOnMainTree());
595     auto inRenderContext = inNode->GetRenderContext();
596     auto outRenderContext = outNode->GetRenderContext();
597     CHECK_NULL_VOID(inRenderContext && outRenderContext);
598     if (trigger.Upgrade()) {
599         RecordAnimationOption(trigger, option);
600         return;
601     }
602     OffsetF inNodeParentPos;
603     bool inNodeParentHasScales = false;
604     if (!staticNodeAbsRect_) {
605         auto [val, err] = inNode->GetPaintRectGlobalOffsetWithTranslate(true, true);
606         inNodeParentPos = val;
607         inNodeParentHasScales = err;
608     }
609     auto inNodeAbsRect = staticNodeAbsRect_ || inNodeParentHasScales ?
610         inNode->GetTransformRectRelativeToWindow(true) : GetNodeAbsFrameRect(inNode, inNodeParentPos);
611     auto inNodeAbsRectOld = outNodeTargetAbsRect_.value();
612     bool sizeChanged = GreatNotEqual(std::fabs(inNodeAbsRect.Width() - inNodeAbsRectOld.Width()), 1.0f) ||
613         GreatNotEqual(std::fabs(inNodeAbsRect.Height() - inNodeAbsRectOld.Height()), 1.0f);
614     bool posChanged = GreatNotEqual(std::fabs(inNodeAbsRect.GetX() - inNodeAbsRectOld.GetX()), 1.0f) ||
615         GreatNotEqual(std::fabs(inNodeAbsRect.GetY() - inNodeAbsRectOld.GetY()), 1.0f);
616     CHECK_NULL_VOID(sizeChanged || posChanged);
617     auto animOption = animationOption_.IsValid() ? animationOption_ : AnimationOption(Curves::LINEAR, RESYNC_DURATION);
618     auto propertyCallback = [&]() {
619         if (!sizeChanged) {
620             auto activeFrameRect = RectF(inNodeAbsRect.GetOffset() - outNodeParentPos_, inNodeAbsRect.GetSize());
621             outRenderContext->SyncGeometryProperties(activeFrameRect);
622             outNodeTargetAbsRect_ = inNodeAbsRect;
623             animationOption_ = AnimationOption();
624         } else {
625             hasOutAnim_ = true;
626             inNodeAbsRect_ = inNodeAbsRect;
627             outNodeTargetAbsRect_.reset();
628             MarkLayoutDirty(outNode);
629             animationOption_ = animOption;
630         }
631     };
632     AnimateWithSandBox(inNodeParentPos, inNodeParentHasScales, propertyCallback, animOption);
633     TAG_LOGD(AceLogTag::ACE_GEOMETRY_TRANSITION, "outNode: %{public}d %{public}s resyncs to inNode: %{public}d "
634         "%{public}s, option: %{public}d, hasScales: %{public}d", outNode->GetId(), inNodeAbsRectOld.ToString().c_str(),
635         inNode->GetId(), inNodeAbsRect.ToString().c_str(), animOption.GetDuration(), inNodeParentHasScales);
636 }
637 
638 // if nodes with geometry transitions are added during layout, we need to execute additional layout in current frame
OnAdditionalLayout(const WeakPtr<FrameNode> & frameNode)639 bool GeometryTransition::OnAdditionalLayout(const WeakPtr<FrameNode>& frameNode)
640 {
641     bool ret = false;
642     auto node = frameNode.Upgrade();
643     CHECK_NULL_RETURN(node, false);
644     if (IsNodeInAndActive(frameNode)) {
645         auto parentNode = node->GetAncestorNodeOfFrame(false);
646         if (parentNode) {
647             MarkLayoutDirty(node);
648             MarkLayoutDirty(parentNode);
649             ret = true;
650         }
651     } else if (IsNodeOutAndActive(frameNode)) {
652         MarkLayoutDirty(node);
653         ret = true;
654     }
655     return ret;
656 }
657 
ToString() const658 std::string GeometryTransition::ToString() const
659 {
660     return std::string("in: ") + (inNode_.Upgrade() ? std::to_string(inNode_.Upgrade()->GetId()) : "null") +
661         std::string(", out: ") + (outNode_.Upgrade() ? std::to_string(outNode_.Upgrade()->GetId()) : "null");
662 }
663 } // namespace OHOS::Ace::NG
664