/* * MIT License * Copyright (c) 2021 _VIFEXTech * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include "PageManager.h" #include "PM_Log.h" /** * @brief Enter a new page, replace the old page * @param name: The name of the page to enter * @param stash: Parameters passed to the new page * @retval Return true if successful */ bool PageManager::Replace(const char* name, const PageBase::Stash_t* stash) { /* Check whether the animation of switching pages is being executed */ if (!SwitchAnimStateCheck()) { return false; } /* Check whether the stack is repeatedly pushed */ if (FindPageInStack(name) != nullptr) { PM_LOG_ERROR("Page(%s) was multi push", name); return false; } /* Check if the page is registered in the page pool */ PageBase* base = FindPageInPool(name); if (base == nullptr) { PM_LOG_ERROR("Page(%s) was not install", name); return false; } /* Get the top page of the stack */ PageBase* top = GetStackTop(); if (top == nullptr) { PM_LOG_ERROR("Stack top is NULL"); return false; } /* Force disable cache */ top->priv.IsCached = false; /* Synchronous automatic cache configuration */ base->priv.IsDisableAutoCache = base->priv.ReqDisableAutoCache; /* Remove current page */ _PageStack.pop(); /* Push into the stack */ _PageStack.push(base); PM_LOG_INFO("Page(%s) replace Page(%s) (stash = 0x%p)", name, top->_Name, stash); /* Page switching execution */ return SwitchTo(base, true, stash); } /** * @brief Enter a new page, the old page is pushed onto the stack * @param name: The name of the page to enter * @param stash: Parameters passed to the new page * @retval Return true if successful */ bool PageManager::Push(const char* name, const PageBase::Stash_t* stash) { /* Check whether the animation of switching pages is being executed */ if (!SwitchAnimStateCheck()) { return false; } /* Check whether the stack is repeatedly pushed */ if (FindPageInStack(name) != nullptr) { PM_LOG_ERROR("Page(%s) was multi push", name); return false; } /* Check if the page is registered in the page pool */ PageBase* base = FindPageInPool(name); if (base == nullptr) { PM_LOG_ERROR("Page(%s) was not install", name); return false; } /* Synchronous automatic cache configuration */ base->priv.IsDisableAutoCache = base->priv.ReqDisableAutoCache; /* Push into the stack */ _PageStack.push(base); PM_LOG_INFO("Page(%s) push >> [Screen] (stash = 0x%p)", name, stash); /* Page switching execution */ return SwitchTo(base, true, stash); } /** * @brief Pop the current page * @param None * @retval Return true if successful */ bool PageManager::Pop() { /* Check whether the animation of switching pages is being executed */ if (!SwitchAnimStateCheck()) { return false; } /* Get the top page of the stack */ PageBase* top = GetStackTop(); if (top == nullptr) { PM_LOG_WARN("Page stack is empty, cat't pop"); return false; } /* Whether to turn off automatic cache */ if (!top->priv.IsDisableAutoCache) { PM_LOG_INFO("Page(%s) has auto cache, cache disabled", top->_Name); top->priv.IsCached = false; } PM_LOG_INFO("Page(%s) pop << [Screen]", top->_Name); /* Page popup */ _PageStack.pop(); /* Get the next page */ top = GetStackTop(); /* Page switching execution */ return SwitchTo(top, false, nullptr);; } /** * @brief Page switching * @param newNode: Pointer to new page * @param isEnterAct: Whether it is a ENTER action * @param stash: Parameters passed to the new page * @retval Return true if successful */ bool PageManager::SwitchTo(PageBase* newNode, bool isEnterAct, const PageBase::Stash_t* stash) { if (newNode == nullptr) { PM_LOG_ERROR("newNode is nullptr"); return false; } /* Whether page switching has been requested */ if (_AnimState.IsSwitchReq) { PM_LOG_WARN("Page switch busy, reqire(%s) is ignore", newNode->_Name); return false; } _AnimState.IsSwitchReq = true; /* Is there a parameter to pass */ if (stash != nullptr) { PM_LOG_INFO("stash is detect, %s >> stash(0x%p) >> %s", GetPagePrevName(), stash, newNode->_Name); void* buffer = nullptr; if (newNode->priv.Stash.ptr == nullptr) { buffer = lv_mem_alloc(stash->size); if (buffer == nullptr) { PM_LOG_ERROR("stash malloc failed"); } else { PM_LOG_INFO("stash(0x%p) malloc[%d]", buffer, stash->size); } } else if(newNode->priv.Stash.size == stash->size) { buffer = newNode->priv.Stash.ptr; PM_LOG_INFO("stash(0x%p) is exist", buffer); } if (buffer != nullptr) { memcpy(buffer, stash->ptr, stash->size); PM_LOG_INFO("stash memcpy[%d] 0x%p >> 0x%p", stash->size, stash->ptr, buffer); newNode->priv.Stash.ptr = buffer; newNode->priv.Stash.size = stash->size; } } /* Record current page */ _PageCurrent = newNode; /* If the current page has a cache */ if (_PageCurrent->priv.IsCached) { /* Direct display, no need to load */ PM_LOG_INFO("Page(%s) has cached, appear driectly", _PageCurrent->_Name); _PageCurrent->priv.State = PageBase::PAGE_STATE_WILL_APPEAR; } else { /* Load page */ _PageCurrent->priv.State = PageBase::PAGE_STATE_LOAD; } if (_PagePrev != nullptr) { _PagePrev->priv.Anim.IsEnter = false; } _PageCurrent->priv.Anim.IsEnter = true; _AnimState.IsEntering = isEnterAct; if (_AnimState.IsEntering) { /* Update the animation configuration according to the current page */ SwitchAnimTypeUpdate(_PageCurrent); } /* Update the state machine of the previous page */ StateUpdate(_PagePrev); /* Update the state machine of the current page */ StateUpdate(_PageCurrent); /* Move the layer, move the new page to the front */ if (_AnimState.IsEntering) { PM_LOG_INFO("Page ENTER is detect, move Page(%s) to foreground", _PageCurrent->_Name); if (_PagePrev)lv_obj_move_foreground(_PagePrev->_root); lv_obj_move_foreground(_PageCurrent->_root); } else { PM_LOG_INFO("Page EXIT is detect, move Page(%s) to foreground", GetPagePrevName()); lv_obj_move_foreground(_PageCurrent->_root); if (_PagePrev)lv_obj_move_foreground(_PagePrev->_root); } return true; } /** * @brief Force the end of the life cycle of the page without animation * @param base: Pointer to the page being executed * @retval Return true if successful */ bool PageManager::FourceUnload(PageBase* base) { if (base == nullptr) { PM_LOG_ERROR("Page is nullptr, Unload failed"); return false; } PM_LOG_INFO("Page(%s) Fource unloading...", base->_Name); if (base->priv.State == PageBase::PAGE_STATE_ACTIVITY) { PM_LOG_INFO("Page state is ACTIVITY, Disappearing..."); base->onViewWillDisappear(); base->onViewDidDisappear(); } base->priv.State = StateUnloadExecute(base); return true; } /** * @brief Back to the main page (the page at the bottom of the stack) * @param None * @retval Return true if successful */ bool PageManager::BackHome() { /* Check whether the animation of switching pages is being executed */ if (!SwitchAnimStateCheck()) { return false; } SetStackClear(true); _PagePrev = nullptr; PageBase* home = GetStackTop(); SwitchTo(home, false); return true; } /** * @brief Check if the page switching animation is being executed * @param None * @retval Return true if it is executing */ bool PageManager::SwitchAnimStateCheck() { if (_AnimState.IsSwitchReq || _AnimState.IsBusy) { PM_LOG_WARN( "Page switch busy[AnimState.IsSwitchReq = %d," "AnimState.IsBusy = %d]," "request ignored", _AnimState.IsSwitchReq, _AnimState.IsBusy ); return false; } return true; } /** * @brief Page switching request check * @param None * @retval Return true if all pages are executed */ bool PageManager::SwitchReqCheck() { bool ret = false; bool lastNodeBusy = _PagePrev && _PagePrev->priv.Anim.IsBusy; if (!_PageCurrent->priv.Anim.IsBusy && !lastNodeBusy) { PM_LOG_INFO("----Page switch was all finished----"); _AnimState.IsSwitchReq = false; ret = true; _PagePrev = _PageCurrent; } else { if (_PageCurrent->priv.Anim.IsBusy) { PM_LOG_WARN("Page PageCurrent(%s) is busy", _PageCurrent->_Name); } else { PM_LOG_WARN("Page PagePrev(%s) is busy", GetPagePrevName()); } } return ret; } /** * @brief PPage switching animation execution end callback * @param a: Pointer to animation * @retval None */ void PageManager::onSwitchAnimFinish(lv_anim_t* a) { PageBase* base = (PageBase*)lv_anim_get_user_data(a); PageManager* manager = base->_Manager; PM_LOG_INFO("Page(%s) Anim finish", base->_Name); manager->StateUpdate(base); base->priv.Anim.IsBusy = false; bool isFinished = manager->SwitchReqCheck(); if (!manager->_AnimState.IsEntering && isFinished) { manager->SwitchAnimTypeUpdate(manager->_PageCurrent); } } /** * @brief Create page switching animation * @param a: Point to the animated page * @retval None */ void PageManager::SwitchAnimCreate(PageBase* base) { LoadAnimAttr_t animAttr; if (!GetCurrentLoadAnimAttr(&animAttr)) { return; } lv_anim_t a; AnimDefaultInit(&a); lv_anim_set_user_data(&a, base); lv_anim_set_var(&a, base->_root); lv_anim_set_ready_cb(&a, onSwitchAnimFinish); lv_anim_set_exec_cb(&a, animAttr.setter); int32_t start = 0; if (animAttr.getter) { start = animAttr.getter(base->_root); } if (_AnimState.IsEntering) { if (base->priv.Anim.IsEnter) { lv_anim_set_values( &a, animAttr.push.enter.start, animAttr.push.enter.end ); } else /* Exit */ { lv_anim_set_values( &a, start, animAttr.push.exit.end ); } } else /* Pop */ { if (base->priv.Anim.IsEnter) { lv_anim_set_values( &a, animAttr.pop.enter.start, animAttr.pop.enter.end ); } else /* Exit */ { lv_anim_set_values( &a, start, animAttr.pop.exit.end ); } } lv_anim_start(&a); base->priv.Anim.IsBusy = true; } /** * @brief Set global animation properties * @param anim: Animation type * @param time: Animation duration * @param path: Animation curve * @retval None */ void PageManager::SetGlobalLoadAnimType(LoadAnim_t anim, uint16_t time, lv_anim_path_cb_t path) { if (anim > _LOAD_ANIM_LAST) { anim = LOAD_ANIM_NONE; } _AnimState.Global.Type = anim; _AnimState.Global.Time = time; _AnimState.Global.Path = path; PM_LOG_INFO("Set global load anim type = %d", anim); } /** * @brief Update current animation properties, apply page custom animation * @param base: Pointer to page * @retval None */ void PageManager::SwitchAnimTypeUpdate(PageBase* base) { if (base->priv.Anim.Attr.Type == LOAD_ANIM_GLOBAL) { PM_LOG_INFO( "Page(%s) Anim.Type was not set, use AnimState.Global.Type = %d", base->_Name, _AnimState.Global.Type ); _AnimState.Current = _AnimState.Global; } else { if (base->priv.Anim.Attr.Type > _LOAD_ANIM_LAST) { PM_LOG_ERROR( "Page(%s) ERROR custom Anim.Type = %d, use AnimState.Global.Type = %d", base->_Name, base->priv.Anim.Attr.Type, _AnimState.Global.Type ); base->priv.Anim.Attr = _AnimState.Global; } else { PM_LOG_INFO( "Page(%s) custom Anim.Type set = %d", base->_Name, base->priv.Anim.Attr.Type ); } _AnimState.Current = base->priv.Anim.Attr; } } /** * @brief Set animation default parameters * @param a: Pointer to animation * @retval None */ void PageManager::AnimDefaultInit(lv_anim_t* a) { lv_anim_init(a); uint32_t time = (GetCurrentLoadAnimType() == LOAD_ANIM_NONE) ? 0 : _AnimState.Current.Time; lv_anim_set_time(a, time); lv_anim_set_path_cb(a, _AnimState.Current.Path); }