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