mirror of
https://github.com/m5stack/StackChan.git
synced 2026-04-27 19:12:40 +00:00
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#include "app_launcher.h"
|
||||
#include <hal/hal.h>
|
||||
#include <mooncake.h>
|
||||
#include <mooncake_log.h>
|
||||
#include <stackchan/stackchan.h>
|
||||
#include <cstdint>
|
||||
|
||||
using namespace mooncake;
|
||||
|
||||
void AppLauncher::onLauncherCreate()
|
||||
{
|
||||
mclog::tagInfo(getAppInfo().name, "on create");
|
||||
|
||||
// 打开自己
|
||||
open();
|
||||
}
|
||||
|
||||
void AppLauncher::onLauncherOpen()
|
||||
{
|
||||
mclog::tagInfo(getAppInfo().name, "on open");
|
||||
|
||||
LvglLockGuard lock;
|
||||
|
||||
_view = std::make_unique<view::LauncherView>();
|
||||
_view->init(getAppProps());
|
||||
_view->onAppClicked = [&](int appID) {
|
||||
mclog::tagInfo(getAppInfo().name, "handle open app, app id: {}", appID);
|
||||
openApp(appID);
|
||||
};
|
||||
}
|
||||
|
||||
void AppLauncher::onLauncherRunning()
|
||||
{
|
||||
LvglLockGuard lock;
|
||||
|
||||
_view->update();
|
||||
|
||||
screensaver_update();
|
||||
|
||||
GetStackChan().update();
|
||||
}
|
||||
|
||||
void AppLauncher::onLauncherClose()
|
||||
{
|
||||
mclog::tagInfo(getAppInfo().name, "on close");
|
||||
|
||||
LvglLockGuard lock;
|
||||
|
||||
_view.reset();
|
||||
}
|
||||
|
||||
void AppLauncher::onLauncherDestroy()
|
||||
{
|
||||
mclog::tagInfo(getAppInfo().name, "on close");
|
||||
}
|
||||
|
||||
void AppLauncher::screensaver_update()
|
||||
{
|
||||
const uint32_t SCREENSAVER_TIMEOUT_MS = 30000;
|
||||
|
||||
uint32_t idle_time = lv_display_get_inactive_time(NULL);
|
||||
if (idle_time >= SCREENSAVER_TIMEOUT_MS) {
|
||||
if (!_screensaver) {
|
||||
_screensaver = std::make_unique<view::Screensaver>();
|
||||
_screensaver->init();
|
||||
}
|
||||
} else if (_screensaver) {
|
||||
_screensaver.reset();
|
||||
}
|
||||
|
||||
// Update in 30ms interval
|
||||
if (_screensaver && GetHAL().millis() - _screensaver_timecount > 30) {
|
||||
_screensaver_timecount = GetHAL().millis();
|
||||
_screensaver->update();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#pragma once
|
||||
#include "view/view.h"
|
||||
#include <mooncake.h>
|
||||
#include <mooncake_templates.h>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
class AppLauncher : public mooncake::templates::AppLauncherBase {
|
||||
public:
|
||||
void onLauncherCreate() override;
|
||||
void onLauncherOpen() override;
|
||||
void onLauncherRunning() override;
|
||||
void onLauncherClose() override;
|
||||
void onLauncherDestroy() override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<view::LauncherView> _view;
|
||||
std::unique_ptr<view::Screensaver> _screensaver;
|
||||
|
||||
uint32_t _screensaver_timecount = 0;
|
||||
void screensaver_update();
|
||||
};
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#include "view.h"
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
using namespace view;
|
||||
using namespace uitk;
|
||||
using namespace uitk::lvgl_cpp;
|
||||
using namespace uitk::games;
|
||||
using namespace uitk::games::dvd_screensaver;
|
||||
|
||||
static const Vector2 _screen_size = {320, 240};
|
||||
static const Vector2 _logo_size = {64, 48};
|
||||
static const int _logo_id = 666;
|
||||
static const std::vector<uint32_t> _logo_colors = {
|
||||
0xffffff, 0xfffa01, 0xff8300, 0x00feff, 0xff2600, 0xbe00ff, 0x0026ff, 0xff008b,
|
||||
};
|
||||
static const uint32_t _bg_color = 0x000000;
|
||||
|
||||
Screensaver::~Screensaver()
|
||||
{
|
||||
_prev_screen->load();
|
||||
}
|
||||
|
||||
void Screensaver::onInit()
|
||||
{
|
||||
_prev_screen = std::make_unique<ScreenActive>();
|
||||
|
||||
_screen = std::make_unique<Screen>();
|
||||
_screen->setBgColor(lv_color_hex(_bg_color));
|
||||
_screen->removeFlag(LV_OBJ_FLAG_SCROLLABLE);
|
||||
_screen->setPadding(0, 0, 0, 0);
|
||||
_screen->load();
|
||||
|
||||
_logo = std::make_unique<Container>(_screen->get());
|
||||
_logo->setSize(_logo_size.width, _logo_size.height);
|
||||
_logo->setBgColor(lv_color_hex(_logo_colors[0]));
|
||||
_logo->align(LV_ALIGN_TOP_LEFT, 2333, 2333);
|
||||
_logo->removeFlag(LV_OBJ_FLAG_SCROLLABLE);
|
||||
_logo->setPadding(0, 0, 0, 0);
|
||||
_logo->setBorderWidth(0);
|
||||
_logo->setRadius(0);
|
||||
|
||||
_left_eye = std::make_unique<Container>(_logo->get());
|
||||
_left_eye->align(LV_ALIGN_CENTER, -15, -3);
|
||||
_left_eye->setBgColor(lv_color_hex(_bg_color));
|
||||
_left_eye->removeFlag(LV_OBJ_FLAG_SCROLLABLE);
|
||||
_left_eye->setRadius(LV_RADIUS_CIRCLE);
|
||||
_left_eye->setBorderWidth(0);
|
||||
_left_eye->setSize(6, 6);
|
||||
|
||||
_right_eye = std::make_unique<Container>(_logo->get());
|
||||
_right_eye->align(LV_ALIGN_CENTER, 15, -3);
|
||||
_right_eye->setBgColor(lv_color_hex(_bg_color));
|
||||
_right_eye->removeFlag(LV_OBJ_FLAG_SCROLLABLE);
|
||||
_right_eye->setRadius(LV_RADIUS_CIRCLE);
|
||||
_right_eye->setBorderWidth(0);
|
||||
_right_eye->setSize(6, 6);
|
||||
|
||||
_mouth = std::make_unique<Container>(_logo->get());
|
||||
_mouth->align(LV_ALIGN_CENTER, 0, 5);
|
||||
_mouth->setBgColor(lv_color_hex(_bg_color));
|
||||
_mouth->removeFlag(LV_OBJ_FLAG_SCROLLABLE);
|
||||
_mouth->setBorderWidth(0);
|
||||
_mouth->setSize(18, 2);
|
||||
_mouth->setRadius(0);
|
||||
}
|
||||
|
||||
void Screensaver::onBuildLevel()
|
||||
{
|
||||
addScreenFrameAsWall(_screen_size);
|
||||
|
||||
auto& random = Random::getInstance();
|
||||
Vector2 direction = {random.getFloat(0.3, 0.7), random.getFloat(0.3, 0.7)};
|
||||
direction = direction.normalized();
|
||||
|
||||
addLogo(_logo_id, {_screen_size.width / 2, _screen_size.height / 2}, _logo_size, direction, 110);
|
||||
}
|
||||
|
||||
void Screensaver::onRender(float dt)
|
||||
{
|
||||
getWorld().forEachObject([&](GameObject* obj) {
|
||||
if (obj->groupId == _logo_id) {
|
||||
auto p = obj->get<Transform>()->position;
|
||||
_logo->setPos((int)p.x - _logo_size.width / 2, (int)p.y - _logo_size.height / 2);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void Screensaver::onLogoCollide(int logoGroupId)
|
||||
{
|
||||
_color_index++;
|
||||
if (_color_index >= _logo_colors.size()) {
|
||||
_color_index = 0;
|
||||
}
|
||||
|
||||
_logo->setBgColor(lv_color_hex(_logo_colors[_color_index]));
|
||||
}
|
||||
@@ -0,0 +1,513 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#include "view.h"
|
||||
#include <mooncake_log.h>
|
||||
#include <assets/assets.h>
|
||||
#include <functional>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
using namespace view;
|
||||
using namespace uitk;
|
||||
using namespace uitk::lvgl_cpp;
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Dynamic backgroud color handle class */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
class DynamicBgColor {
|
||||
public:
|
||||
std::function<void(const uint32_t& bgColor)> onBgColorChanged;
|
||||
|
||||
void init(const std::vector<uint32_t>& stepColors, int stepGap)
|
||||
{
|
||||
_step_colors = stepColors;
|
||||
_step_gap = stepGap;
|
||||
|
||||
_bg_color.duration = 0.3;
|
||||
_bg_color.begin();
|
||||
|
||||
jumpTo(0);
|
||||
}
|
||||
|
||||
void jumpTo(int index)
|
||||
{
|
||||
if (index < 0 || index >= _step_colors.size()) {
|
||||
return;
|
||||
}
|
||||
_bg_color.teleport(_step_colors[index]);
|
||||
_current_index = index;
|
||||
|
||||
if (onBgColorChanged) {
|
||||
onBgColorChanged(_step_colors[index]);
|
||||
}
|
||||
}
|
||||
|
||||
void update(int scrollValue)
|
||||
{
|
||||
_last_index = _current_index;
|
||||
|
||||
// Update current index
|
||||
_current_index = (scrollValue + _step_gap / 2) / _step_gap;
|
||||
if (_current_index < 0) {
|
||||
_current_index = 0;
|
||||
}
|
||||
if (_current_index >= _step_colors.size()) {
|
||||
_current_index = _step_colors.size() - 1;
|
||||
}
|
||||
|
||||
// If index changed
|
||||
if (_last_index != _current_index) {
|
||||
// mclog::tagInfo(_tag, "index changed from {} to {}", _last_index, _current_index);
|
||||
_bg_color = _step_colors[_current_index];
|
||||
}
|
||||
|
||||
// Update background color
|
||||
_bg_color.update();
|
||||
if (!_bg_color.done()) {
|
||||
if (onBgColorChanged) {
|
||||
onBgColorChanged(_bg_color.toHex());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<uint32_t> _step_colors;
|
||||
int _current_index = 0;
|
||||
int _last_index = 0;
|
||||
int _step_gap = 0;
|
||||
color::AnimateRgb_t _bg_color;
|
||||
};
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Page indicator */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
class PageIndicator {
|
||||
public:
|
||||
const int dot_size = 8;
|
||||
const int dot_size_big = 14;
|
||||
const int dot_gap = 16;
|
||||
|
||||
void init(int pageNum, int pageGap, lv_obj_t* parent, int posX, int posY)
|
||||
{
|
||||
_page_num = pageNum;
|
||||
_page_gap = pageGap;
|
||||
|
||||
_panel = std::make_unique<Container>(parent);
|
||||
_panel->removeFlag(LV_OBJ_FLAG_SCROLLABLE);
|
||||
_panel->addFlag(LV_OBJ_FLAG_FLOATING);
|
||||
_panel->setAlign(LV_ALIGN_CENTER);
|
||||
_panel->setPadding(0, 0, 24, 24);
|
||||
_panel->setPos(posX, posY);
|
||||
_panel->setBorderWidth(0);
|
||||
_panel->setHeight(24);
|
||||
_panel->setWidth((pageNum * dot_size) + (pageNum - 1) * (dot_gap - dot_size) + 24 * 2);
|
||||
_panel->setBgOpa(0);
|
||||
|
||||
for (int i = 0; i < pageNum; i++) {
|
||||
_dots.push_back(std::make_unique<Container>(_panel->get()));
|
||||
_dots.back()->setAlign(LV_ALIGN_CENTER);
|
||||
_dots.back()->setPos(i * dot_gap - (pageNum - 1) * dot_gap / 2, 0);
|
||||
_dots.back()->setBgColor(lv_color_hex(0xFFFFFF));
|
||||
_dots.back()->removeFlag(LV_OBJ_FLAG_SCROLLABLE);
|
||||
_dots.back()->setRadius(LV_RADIUS_CIRCLE);
|
||||
_dots.back()->setSize(dot_size, dot_size);
|
||||
_dots.back()->setBorderWidth(0);
|
||||
}
|
||||
|
||||
jumpTo(0);
|
||||
}
|
||||
|
||||
void jumpTo(int index)
|
||||
{
|
||||
if (index < 0 || index >= _page_num) {
|
||||
return;
|
||||
}
|
||||
_current_index = index;
|
||||
_last_index = index;
|
||||
update_dots();
|
||||
}
|
||||
|
||||
void update(int scrollValue)
|
||||
{
|
||||
_last_index = _current_index;
|
||||
|
||||
_current_index = (scrollValue + _page_gap / 2) / _page_gap;
|
||||
if (_current_index < 0) {
|
||||
_current_index = 0;
|
||||
}
|
||||
if (_current_index >= _page_num) {
|
||||
_current_index = _page_num - 1;
|
||||
}
|
||||
|
||||
if (_last_index != _current_index) {
|
||||
update_dots();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
int _page_num = 0;
|
||||
int _page_gap = 0;
|
||||
|
||||
int _current_index = 0;
|
||||
int _last_index = 0;
|
||||
|
||||
std::unique_ptr<Container> _panel;
|
||||
std::vector<std::unique_ptr<Container>> _dots;
|
||||
|
||||
void update_dots()
|
||||
{
|
||||
for (int i = 0; i < _page_num; i++) {
|
||||
if (i == _current_index) {
|
||||
_dots[i]->setSize(dot_size_big, dot_size_big);
|
||||
_dots[i]->setOpa(255);
|
||||
} else {
|
||||
_dots[i]->setSize(dot_size, dot_size);
|
||||
_dots[i]->setOpa(128);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Dynamic icon label */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
class DynamicIconLabel {
|
||||
public:
|
||||
const int show_range = 50;
|
||||
const int hidden_pos_y = -150;
|
||||
const int visible_pos_y = -99;
|
||||
const int transition_zone = 30;
|
||||
|
||||
void init(const std::vector<std::string>& iconLabelTexts, int iconGap, lv_obj_t* parent)
|
||||
{
|
||||
_icon_label_texts = iconLabelTexts;
|
||||
_icon_gap = iconGap;
|
||||
|
||||
// Create floating label
|
||||
_label = std::make_unique<Label>(parent);
|
||||
_label->setTextColor(lv_color_hex(0x000000));
|
||||
_label->setTextFont(&MontserratSemiBold26);
|
||||
_label->setAlign(LV_ALIGN_CENTER);
|
||||
_label->addFlag(LV_OBJ_FLAG_FLOATING);
|
||||
_label->setOpa(233);
|
||||
|
||||
// Setup animation
|
||||
_pos_y_anim = std::make_unique<AnimateValue>();
|
||||
_pos_y_anim->springOptions().visualDuration = 0.3;
|
||||
_pos_y_anim->springOptions().bounce = 0.1;
|
||||
_pos_y_anim->begin();
|
||||
|
||||
jumpTo(0);
|
||||
}
|
||||
|
||||
void jumpTo(int index)
|
||||
{
|
||||
if (index < 0 || index >= _icon_label_texts.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
_current_index = index;
|
||||
_last_index = index;
|
||||
_is_visible = true;
|
||||
|
||||
// Update label
|
||||
_label->setText(_icon_label_texts[index]);
|
||||
_label->setPos(0, visible_pos_y);
|
||||
_pos_y_anim->teleport(visible_pos_y);
|
||||
}
|
||||
|
||||
void update(int scrollValue)
|
||||
{
|
||||
_last_index = _current_index;
|
||||
|
||||
// Calculate current icon index and distance to icon center
|
||||
_current_index = (scrollValue + _icon_gap / 2) / _icon_gap;
|
||||
int icon_center_pos_x = _current_index * _icon_gap;
|
||||
int distance_to_icon = std::abs(scrollValue - icon_center_pos_x);
|
||||
|
||||
// Clamp index
|
||||
if (_current_index < 0) {
|
||||
_current_index = 0;
|
||||
}
|
||||
if (_current_index >= _icon_label_texts.size()) {
|
||||
_current_index = _icon_label_texts.size() - 1;
|
||||
}
|
||||
|
||||
// Check if label should be visible
|
||||
bool should_be_visible = (distance_to_icon <= show_range);
|
||||
|
||||
// If index changed, update label text
|
||||
if (_last_index != _current_index) {
|
||||
_label->setText(_icon_label_texts[_current_index]);
|
||||
}
|
||||
|
||||
// Handle visibility state change
|
||||
if (should_be_visible && !_is_visible) {
|
||||
// Show label
|
||||
_pos_y_anim->move(visible_pos_y);
|
||||
_is_visible = true;
|
||||
} else if (!should_be_visible && _is_visible) {
|
||||
// Hide label
|
||||
_pos_y_anim->move(hidden_pos_y);
|
||||
_is_visible = false;
|
||||
}
|
||||
|
||||
// Update animation and apply position
|
||||
_pos_y_anim->update();
|
||||
_label->setY(_pos_y_anim->directValue());
|
||||
|
||||
// Update opacity based on distance when in transition zone
|
||||
if (should_be_visible && distance_to_icon > (show_range - transition_zone)) {
|
||||
// Fade out as approaching edge
|
||||
float fade_ratio = 1.0f - (float)(distance_to_icon - (show_range - transition_zone)) / transition_zone;
|
||||
_label->setOpa(233 * fade_ratio);
|
||||
} else if (should_be_visible) {
|
||||
_label->setOpa(233);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::string> _icon_label_texts;
|
||||
int _icon_gap = 0;
|
||||
int _current_index = 0;
|
||||
int _last_index = 0;
|
||||
bool _is_visible = false;
|
||||
|
||||
std::unique_ptr<Label> _label;
|
||||
std::unique_ptr<AnimateValue> _pos_y_anim;
|
||||
};
|
||||
|
||||
static std::string _tag = "LauncherView";
|
||||
static constexpr int _icon_gap = 320;
|
||||
|
||||
static int _last_clicked_icon_pos_x = -1;
|
||||
static std::unique_ptr<DynamicBgColor> _dynamic_bg_color;
|
||||
static std::unique_ptr<PageIndicator> _page_indicator;
|
||||
static std::unique_ptr<DynamicIconLabel> _dynamic_icon_label;
|
||||
|
||||
LauncherView::~LauncherView()
|
||||
{
|
||||
_icon_images.clear();
|
||||
_icon_panels.clear();
|
||||
_lr_indicators_images.clear();
|
||||
_lr_indicator_panels.clear();
|
||||
_panel.reset();
|
||||
_dynamic_bg_color.reset();
|
||||
_page_indicator.reset();
|
||||
_dynamic_icon_label.reset();
|
||||
}
|
||||
|
||||
void LauncherView::init(std::vector<mooncake::AppProps_t> appPorps)
|
||||
{
|
||||
mclog::tagInfo(_tag, "init");
|
||||
|
||||
/* ------------------------------ Screen setup ------------------------------ */
|
||||
ScreenActive screen;
|
||||
screen.removeFlag(LV_OBJ_FLAG_SCROLLABLE);
|
||||
|
||||
/* ---------------------------------- Panel --------------------------------- */
|
||||
_panel = std::make_unique<Container>(lv_screen_active());
|
||||
_panel->setAlign(LV_ALIGN_CENTER);
|
||||
_panel->setSize(320, 240);
|
||||
_panel->setRadius(0);
|
||||
_panel->setBorderWidth(0);
|
||||
_panel->setScrollbarMode(LV_SCROLLBAR_MODE_OFF);
|
||||
_panel->setBgColor(lv_color_hex(0x33CC99));
|
||||
_panel->addFlag(LV_OBJ_FLAG_SCROLL_ONE);
|
||||
lv_obj_set_scroll_snap_x(_panel->get(), LV_SCROLL_SNAP_CENTER);
|
||||
|
||||
/* ---------------------------------- Icons --------------------------------- */
|
||||
int icon_x = 0;
|
||||
int icon_y = 0;
|
||||
std::vector<std::string> icon_label_texts;
|
||||
for (const auto& props : appPorps) {
|
||||
// mclog::tagInfo(_tag, "name: {}, id: {}", props.info.name, props.appID);
|
||||
|
||||
// Icon panel
|
||||
_icon_panels.push_back(std::make_unique<Container>(_panel->get()));
|
||||
_icon_panels.back()->setAlign(LV_ALIGN_CENTER);
|
||||
_icon_panels.back()->setSize(190, 160);
|
||||
_icon_panels.back()->setPos(icon_x, icon_y);
|
||||
_icon_panels.back()->setBorderWidth(0);
|
||||
_icon_panels.back()->removeFlag(LV_OBJ_FLAG_SCROLLABLE);
|
||||
_icon_panels.back()->setBgOpa(0);
|
||||
|
||||
// Icon click callback
|
||||
auto app_id = props.appID;
|
||||
auto pos_x = icon_x;
|
||||
_icon_panels.back()->onClick().connect([&, app_id, pos_x]() {
|
||||
_clicked_app_id = app_id;
|
||||
_last_clicked_icon_pos_x = pos_x;
|
||||
});
|
||||
|
||||
// Collect icon label texts
|
||||
icon_label_texts.push_back(props.info.name);
|
||||
|
||||
// Icon image
|
||||
if (props.info.icon != nullptr) {
|
||||
_icon_images.push_back(std::make_unique<Image>(_icon_panels.back()->get()));
|
||||
_icon_images.back()->setSrc(props.info.icon);
|
||||
_icon_images.back()->setAlign(LV_ALIGN_CENTER);
|
||||
}
|
||||
|
||||
icon_x += _icon_gap;
|
||||
}
|
||||
|
||||
/* ------------------------------ LR indicators ----------------------------- */
|
||||
// Scroll to nearby icon handler with wrap-around
|
||||
int total_icons = appPorps.size();
|
||||
auto scroll_to_nearby_icon = [&, total_icons](int direction) {
|
||||
auto current_scroll_x = _panel->getScrollX();
|
||||
int current_index = (current_scroll_x + _icon_gap / 2) / _icon_gap;
|
||||
int target_index = current_index + direction;
|
||||
|
||||
// Wrap around at boundaries
|
||||
if (target_index < 0) {
|
||||
target_index = total_icons - 1;
|
||||
} else if (target_index >= total_icons) {
|
||||
target_index = 0;
|
||||
}
|
||||
|
||||
int target_x = target_index * _icon_gap;
|
||||
int scroll_distance = target_x - current_scroll_x;
|
||||
_panel->scrollBy(-scroll_distance, 0, LV_ANIM_ON);
|
||||
};
|
||||
|
||||
// Go left indicator
|
||||
_lr_indicator_panels.push_back(std::make_unique<Container>(_panel->get()));
|
||||
_lr_indicator_panels.back()->setAlign(LV_ALIGN_CENTER);
|
||||
_lr_indicator_panels.back()->setSize(52, 160);
|
||||
_lr_indicator_panels.back()->setPos(-134, 0);
|
||||
_lr_indicator_panels.back()->setBorderWidth(0);
|
||||
_lr_indicator_panels.back()->addFlag(LV_OBJ_FLAG_FLOATING);
|
||||
_lr_indicator_panels.back()->removeFlag(LV_OBJ_FLAG_SCROLLABLE);
|
||||
_lr_indicator_panels.back()->setBgOpa(0);
|
||||
_lr_indicator_panels.back()->onClick().connect([scroll_to_nearby_icon]() { scroll_to_nearby_icon(-1); });
|
||||
|
||||
_lr_indicators_images.push_back(std::make_unique<Image>(_lr_indicator_panels.back()->get()));
|
||||
_lr_indicators_images.back()->setSrc(&icon_indicator_left);
|
||||
_lr_indicators_images.back()->align(LV_ALIGN_CENTER, 0, 0);
|
||||
|
||||
// Go right indicator
|
||||
_lr_indicator_panels.push_back(std::make_unique<Container>(_panel->get()));
|
||||
_lr_indicator_panels.back()->setAlign(LV_ALIGN_CENTER);
|
||||
_lr_indicator_panels.back()->setSize(52, 160);
|
||||
_lr_indicator_panels.back()->setPos(134, 0);
|
||||
_lr_indicator_panels.back()->setBorderWidth(0);
|
||||
_lr_indicator_panels.back()->addFlag(LV_OBJ_FLAG_FLOATING);
|
||||
_lr_indicator_panels.back()->removeFlag(LV_OBJ_FLAG_SCROLLABLE);
|
||||
_lr_indicator_panels.back()->setBgOpa(0);
|
||||
_lr_indicator_panels.back()->onClick().connect([scroll_to_nearby_icon]() { scroll_to_nearby_icon(1); });
|
||||
|
||||
_lr_indicators_images.push_back(std::make_unique<Image>(_lr_indicator_panels.back()->get()));
|
||||
_lr_indicators_images.back()->setSrc(&icon_indicator_right);
|
||||
_lr_indicators_images.back()->align(LV_ALIGN_CENTER, 0, 0);
|
||||
|
||||
/* ---------------------------- Dynamic bg color ---------------------------- */
|
||||
_dynamic_bg_color = std::make_unique<DynamicBgColor>();
|
||||
|
||||
std::vector<uint32_t> step_colors;
|
||||
step_colors.resize(appPorps.size());
|
||||
for (size_t i = 0; i < appPorps.size(); i++) {
|
||||
uint32_t color = 0xDADADA;
|
||||
if (appPorps[i].info.userData != nullptr) {
|
||||
color = *(uint32_t*)appPorps[i].info.userData;
|
||||
}
|
||||
step_colors[i] = color;
|
||||
}
|
||||
|
||||
_dynamic_bg_color->onBgColorChanged = [&](const uint32_t& bgColor) {
|
||||
// mclog::tagInfo(_tag, "bg color changed to {:06X}", bgColor);
|
||||
_panel->setBgColor(lv_color_hex(bgColor));
|
||||
};
|
||||
|
||||
_dynamic_bg_color->init(step_colors, _icon_gap);
|
||||
|
||||
/* ------------------------------ Page indicator ---------------------------- */
|
||||
_page_indicator = std::make_unique<PageIndicator>();
|
||||
_page_indicator->init(appPorps.size(), _icon_gap, _panel->get(), 0, 103);
|
||||
|
||||
/* --------------------------- Dynamic icon label --------------------------- */
|
||||
_dynamic_icon_label = std::make_unique<DynamicIconLabel>();
|
||||
_dynamic_icon_label->init(icon_label_texts, _icon_gap, _panel->get());
|
||||
|
||||
/* ----------------------------- History restore ---------------------------- */
|
||||
if (_last_clicked_icon_pos_x != -1) {
|
||||
// mclog::tagInfo(_tag, "navigate to last clicked icon, pos x: {}", _last_clicked_icon_pos_x);
|
||||
_panel->scrollBy(-_last_clicked_icon_pos_x, 0, LV_ANIM_OFF);
|
||||
|
||||
_dynamic_bg_color->jumpTo(_last_clicked_icon_pos_x / _icon_gap);
|
||||
_page_indicator->jumpTo(_last_clicked_icon_pos_x / _icon_gap);
|
||||
_dynamic_icon_label->jumpTo(_last_clicked_icon_pos_x / _icon_gap);
|
||||
|
||||
_last_clicked_icon_pos_x = -1;
|
||||
_state = STATE_NORMAL;
|
||||
}
|
||||
|
||||
// If first create
|
||||
else {
|
||||
// Setup startup animation
|
||||
// x for pos_y, y for radius
|
||||
_startup_anim = std::make_unique<AnimateVector2>();
|
||||
|
||||
_startup_anim->x.springOptions().damping = 12.0;
|
||||
_startup_anim->y.delay = 0.15;
|
||||
_startup_anim->y.springOptions().visualDuration = 0.4;
|
||||
_startup_anim->y.springOptions().bounce = 0.05;
|
||||
|
||||
_startup_anim->teleport(240, 120);
|
||||
_panel->setY(_startup_anim->directValue().x);
|
||||
_panel->setRadius(_startup_anim->directValue().y);
|
||||
|
||||
_startup_anim->move(0, 0);
|
||||
|
||||
_state = STATE_STARTUP;
|
||||
}
|
||||
}
|
||||
|
||||
void LauncherView::update()
|
||||
{
|
||||
switch (_state) {
|
||||
case STATE_STARTUP:
|
||||
handle_state_startup();
|
||||
break;
|
||||
case STATE_NORMAL:
|
||||
handle_state_normal();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void LauncherView::handle_state_startup()
|
||||
{
|
||||
_startup_anim->update();
|
||||
|
||||
_panel->setY(_startup_anim->directValue().x);
|
||||
_panel->setRadius(_startup_anim->directValue().y);
|
||||
|
||||
if (_startup_anim->done()) {
|
||||
_startup_anim.reset();
|
||||
_state = STATE_NORMAL;
|
||||
}
|
||||
}
|
||||
|
||||
void LauncherView::handle_state_normal()
|
||||
{
|
||||
if (_clicked_app_id != -1) {
|
||||
if (onAppClicked) {
|
||||
onAppClicked(_clicked_app_id);
|
||||
}
|
||||
_clicked_app_id = -1;
|
||||
}
|
||||
|
||||
int scroll_x = _panel->getScrollX();
|
||||
// mclog::tagInfo(_tag, "scroll x: {}", scroll_x);
|
||||
|
||||
_dynamic_bg_color->update(scroll_x);
|
||||
_page_indicator->update(scroll_x);
|
||||
_dynamic_icon_label->update(scroll_x);
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#pragma once
|
||||
#include <mooncake.h>
|
||||
#include <smooth_ui_toolkit.hpp>
|
||||
#include <uitk/short_namespace.hpp>
|
||||
#include <games/dvd_screensaver/dvd_screensaver.hpp>
|
||||
#include <smooth_lvgl.hpp>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
namespace view {
|
||||
|
||||
/**
|
||||
* @brief
|
||||
*
|
||||
*/
|
||||
class LauncherView {
|
||||
public:
|
||||
~LauncherView();
|
||||
|
||||
enum State_t {
|
||||
STATE_STARTUP,
|
||||
STATE_NORMAL,
|
||||
};
|
||||
|
||||
std::function<void(int appID)> onAppClicked;
|
||||
|
||||
void init(std::vector<mooncake::AppProps_t> appPorps);
|
||||
void update();
|
||||
|
||||
private:
|
||||
std::unique_ptr<uitk::lvgl_cpp::Container> _panel;
|
||||
std::vector<std::unique_ptr<uitk::lvgl_cpp::Container>> _icon_panels;
|
||||
std::vector<std::unique_ptr<uitk::lvgl_cpp::Image>> _icon_images;
|
||||
std::vector<std::unique_ptr<uitk::lvgl_cpp::Container>> _lr_indicator_panels;
|
||||
std::vector<std::unique_ptr<uitk::lvgl_cpp::Image>> _lr_indicators_images;
|
||||
|
||||
std::unique_ptr<uitk::AnimateVector2> _startup_anim;
|
||||
|
||||
int _clicked_app_id = -1;
|
||||
State_t _state = STATE_STARTUP;
|
||||
|
||||
void handle_state_startup();
|
||||
void handle_state_normal();
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief
|
||||
*
|
||||
*/
|
||||
class Screensaver : public uitk::games::dvd_screensaver::DvdScreensaver {
|
||||
public:
|
||||
~Screensaver();
|
||||
|
||||
void onInit() override;
|
||||
void onBuildLevel() override;
|
||||
void onRender(float dt) override;
|
||||
void onLogoCollide(int logoGroupId) override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<uitk::lvgl_cpp::ScreenActive> _prev_screen;
|
||||
std::unique_ptr<uitk::lvgl_cpp::Screen> _screen;
|
||||
std::unique_ptr<uitk::lvgl_cpp::Container> _logo;
|
||||
std::unique_ptr<uitk::lvgl_cpp::Container> _left_eye;
|
||||
std::unique_ptr<uitk::lvgl_cpp::Container> _right_eye;
|
||||
std::unique_ptr<uitk::lvgl_cpp::Container> _mouth;
|
||||
int _color_index = 0;
|
||||
};
|
||||
|
||||
} // namespace view
|
||||
Reference in New Issue
Block a user