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