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