/* * Copyright (c) 2024 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "animation_state.h" #include #include #include #include #include #include #include META_BEGIN_NAMESPACE() namespace Internal { bool AnimationState::Initialize(AnimationStateParams&& params) { params_ = BASE_NS::move(params); auto owner = GetOwner(); if (!owner) { CORE_LOG_F("AnimationState: Invalid target animation"); return false; } auto controllerProp = owner->Controller(); if (!controllerProp) { return false; } if (!controllerProp->GetValue().lock()) { // No controller set, init to default controllerProp->SetValue(META_NS::GetAnimationController()); } updateTotalDuration_ = MakeCallback(this, &AnimationState::UpdateTotalDuration); if (auto timed = interface_cast(owner)) { timed->Duration()->OnChanged()->AddHandler(updateTotalDuration_, uintptr_t(this)); } auto updateController = MakeCallback(this, &AnimationState::UpdateController); controllerProp->OnChanged()->AddHandler(updateController); state_.clock_ = META_NS::GetObjectRegistry().Create(META_NS::ClassId::ManualClock); CORE_ASSERT(state_.clock_); UpdateTotalDuration(); UpdateController(); return true; } void AnimationState::Uninitialize() { if (auto timed = interface_pointer_cast(GetOwner())) { timed->Duration()->OnChanged()->RemoveHandler(uintptr_t(this)); } } void AnimationState::NotifyEvaluationNeeded() const { if (auto internal = interface_pointer_cast(GetOwner())) { internal->OnEvaluationNeeded(); } } void AnimationState::NotifyStateChanged(const IAnimationInternal::AnimationStateChangedInfo& info) const { if (auto internal = interface_pointer_cast(GetOwner())) { internal->OnAnimationStateChanged(info); // Need also evaluation internal->OnEvaluationNeeded(); } } void AnimationState::UpdateController() { IAnimation::Ptr animation = GetOwner(); if (!animation) { CORE_LOG_E("Invalid target animation"); return; } auto oldController = controller_.lock(); auto newController = animation->Controller()->GetValue().lock(); if (oldController != newController) { if (oldController) { oldController->RemoveAnimation(animation); } if (newController) { newController->AddAnimation(animation); } controller_ = newController; } } void AnimationState::ResetClock() { state_.ResetLastTick(); state_.SetTime(TimeSpan::Zero()); } AnimationState::StepStatus AnimationState::Step(const IClock::ConstPtr& clock) { if (!IsRunning()) { return {}; } const auto time = clock ? clock->GetTime() : TimeSpan::Zero(); float progress = static_cast(state_.Tick(time).ToMilliseconds()) / static_cast(state_.GetBaseDuration().ToMilliseconds()); return Move(IAnimationInternal::MoveParams::FromProgress(progress)); } constexpr IAnimationInternal::AnimationTargetState GetTargetState(const IAnimationInternal::MoveParams& move) noexcept { using AnimationTargetState = IAnimationInternal::AnimationTargetState; const auto& step = move.step; const auto& state = move.state; if (state == AnimationTargetState::UNDEFINED) { // Figure out target state based on step data automatically const float progress = step.progress; const bool reverse = step.reverse; if (progress >= 1.f) { return reverse ? AnimationTargetState::RUNNING : AnimationTargetState::FINISHED; } if (progress <= 0.f) { return reverse ? AnimationTargetState::FINISHED : AnimationTargetState::RUNNING; } return AnimationTargetState::RUNNING; } // Just go to the state defined by the caller return state; } AnimationState::StepStatus AnimationState::Move(const IAnimationInternal::MoveParams& move) { using AnimationTargetState = IAnimationInternal::AnimationTargetState; auto animationState = GetTargetState(move); const auto& step = move.step; float progress = step.progress; if (state_.shouldInit_) { state_.loops = state_.duration.loopCount; state_.shouldInit_ = false; } if (animationState == AnimationTargetState::FINISHED) { // Check if we need to loop if (state_.loops && (state_.loops < 0 || --state_.loops)) { animationState = AnimationTargetState::RUNNING; const auto overflow = progress - BASE_NS::Math::floor(progress); state_.SetTime(overflow * state_.GetBaseDuration()); if (overflow > 0.f) { // If progress based on clock would be e.g. 1.2, jump to 0.2 to not jank the animation progress = overflow; } } } AnimationState::StepStatus status; if (progress = BASE_NS::Math::clamp01(progress); progress != GetProgress()) { SetProgress(progress); status.changed = true; } status.changed |= SetState(animationState); status.state = state_.animationState_; status.progress = GetProgress(); if (status.changed) { NotifyEvaluationNeeded(); } return status; } void AnimationState::Seek(float position) { auto animation = GetOwner(); if (!animation) { CORE_LOG_E("Invalid target animation"); return; } position = BASE_NS::Math::clamp01(position); state_.ResetLastTick(); const auto seekedTime = state_.GetBaseDuration().ToSecondsFloat() * position; state_.SetTime(TimeSpan::Seconds(seekedTime)); auto state = state_.animationState_; if (position >= 1.f) { state = IAnimationInternal::AnimationTargetState::FINISHED; } Move(IAnimationInternal::MoveParams::FromProgress(position, state)); } bool AnimationState::Pause() { return SetState(IAnimationInternal::AnimationTargetState::PAUSED); } bool AnimationState::Start() { return SetState(IAnimationInternal::AnimationTargetState::RUNNING); } bool AnimationState::Stop() { return Move(IAnimationInternal::MoveParams::FromProgress(0.f, IAnimationInternal::AnimationTargetState::STOPPED)) .StatusChanged(); } bool AnimationState::Finish() { return Move(IAnimationInternal::MoveParams::FromProgress(1.f, IAnimationInternal::AnimationTargetState::FINISHED)) .StatusChanged(); } bool AnimationState::Restart() { if (Stop()) { return Start(); } return false; } IAnimation::Ptr AnimationState::GetOwner() const noexcept { return params_.owner.lock(); } bool AnimationState::IsRunning() const noexcept { return GetValue(params_.runningProperty, false); } bool AnimationState::IsPaused() const noexcept { return state_.animationState_ == IAnimationInternal::AnimationTargetState::PAUSED; } void AnimationState::SetRunning(bool running) noexcept { SetValue(params_.runningProperty, running); } float AnimationState::GetProgress() const noexcept { return GetValue(params_.progressProperty, 0.f); } void AnimationState::SetProgress(float progress) noexcept { SetValue(params_.progressProperty, progress); } bool AnimationState::SetState(IAnimationInternal::AnimationTargetState state) { using AnimationTargetState = IAnimationInternal::AnimationTargetState; const auto previous = state_.animationState_; if (previous == state) { return false; } if (const auto owner = GetOwner()) { bool notifyStarted = false; switch (state) { case AnimationTargetState::RUNNING: if (previous != AnimationTargetState::PAUSED) { state_.shouldInit_ = true; notifyStarted = true; if (previous == AnimationTargetState::FINISHED) { SetProgress(0.f); ResetClock(); } } break; case AnimationTargetState::PAUSED: state_.ResetLastTick(); break; case AnimationTargetState::FINISHED: [[fallthrough]]; case AnimationTargetState::STOPPED: ResetClock(); break; default: CORE_LOG_E("Invalid target state for animation: AnimationTargetState::UNDEFINED"); ResetClock(); break; } SetRunning(state == AnimationTargetState::RUNNING); state_.animationState_ = state; IAnimationInternal::AnimationStateChangedInfo info; info.source = GetOwner(); info.state = state; info.previous = previous; NotifyStateChanged(info); if (state == AnimationTargetState::FINISHED) { Invoke(owner->OnFinished()); } if (notifyStarted) { Invoke(owner->OnStarted()); } return true; } return false; } BASE_NS::vector AnimationState::GetModifiers() const { if (!modifierCache_.HasTarget()) { // Do not create an attachment container unless one has already been created by someone if (const auto attach = interface_pointer_cast(params_.owner)) { if (const auto container = attach->GetAttachmentContainer(false)) { modifierCache_.SetTarget( container, { "", TraversalType::NO_HIERARCHY, { IAnimationModifier::UID }, true }); } } } return modifierCache_.FindAll(); } IAnimationModifier::StepData AnimationState::ApplyStepModifiers(float progress) const { IAnimationModifier::StepData step(progress); for (auto&& mod : GetModifiers()) { mod->ProcessOnStep(step); } return step; } IAnimationModifier::DurationData AnimationState::ApplyDurationModifiers(TimeSpan duration) const { using DurationData = IAnimationModifier::DurationData; DurationData durationData; durationData.duration = duration; for (auto&& mod : GetModifiers()) { DurationData data = durationData; if (mod->ProcessOnGetDuration(data)) { durationData = data; } } return durationData; } TimeSpan AnimationState::GetAnimationBaseDuration() const { if (auto timed = interface_cast(GetOwner())) { return GetValue(timed->Duration()); } CORE_LOG_W("Cannot update total duration of an animation that does not implement ITimedAnimation"); return TimeSpan::Zero(); } void AnimationState::UpdateTotalDuration() { if (!params_.totalDuration) { return; } auto durationData = ApplyDurationModifiers(GetAnimationBaseDuration()); state_.duration = durationData; state_.totalDuration = durationData.loopCount > 0 ? durationData.duration * durationData.loopCount : TimeSpan::Infinite(); SetValue(params_.totalDuration, state_.totalDuration); } bool AnimationState::Attach(const IObject::Ptr& attachment, const IObject::Ptr& dataContext) { bool success = false; if (auto attach = interface_pointer_cast(params_.owner)) { if (const auto attachments = interface_cast(attach->GetAttachmentContainer(true))) { if (const auto modifier = interface_cast(attachment)) { if (success = attachments->Attach(attachment, dataContext); success) { if (auto notifyChanged = interface_cast(modifier)) { notifyChanged->OnChanged()->AddHandler(updateTotalDuration_, uintptr_t(this)); } } UpdateTotalDuration(); } else { // Attaching something else than a modifier return attachments->Attach(attachment, dataContext); } } } return success; } bool AnimationState::Detach(const IObject::Ptr& attachment) { bool success = false; if (auto attach = interface_pointer_cast(params_.owner)) { success = attach->Detach(attachment); if (const auto modifier = interface_cast(attachment)) { if (auto notifyChanged = interface_cast(modifier)) { notifyChanged->OnChanged()->RemoveHandler(uintptr_t(this)); } } UpdateTotalDuration(); } return success; } // PropertyAnimationState IInterpolator::Ptr PropertyAnimationState::GetInterpolator() const { return interpolator_; } bool PropertyAnimationState::SetInterpolator(const TypeId& id) { interpolator_ = id != TypeId {} ? GetObjectRegistry().CreateInterpolator(id) : nullptr; return interpolator_ != nullptr; } AnyReturnValue PropertyAnimationState::EvaluateValue(const EvaluationData& data) const { if (!data.IsValid()) { return AnyReturn::FAIL; } auto step = ApplyStepModifiers(data.progress); auto progress = step.progress; if (progress <= 0.f) { return data.target->CopyFrom(*data.from); } if (progress >= 1.f) { return data.target->CopyFrom(*data.to); } if (data.curve) { progress = data.curve->Transform(progress); } if (interpolator_) { return interpolator_->Interpolate(*data.target, *data.from, *data.to, progress); } CORE_LOG_W("No interpolator set for animation state"); return AnyReturn::FAIL; } } // namespace Internal META_END_NAMESPACE()