mirror of
https://github.com/m5stack/StackChan.git
synced 2026-04-28 03:22:39 +00:00
update firmware source code to v0.18 (#9)
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#include "neon_light.h"
|
||||
#include <hal/hal.h>
|
||||
|
||||
using namespace stackchan::addon;
|
||||
|
||||
void NeonLight::init()
|
||||
{
|
||||
// Setup color animation
|
||||
_color_anim.duration = 0.3f;
|
||||
_color_anim.begin();
|
||||
|
||||
_is_inited = true;
|
||||
}
|
||||
|
||||
void NeonLight::update()
|
||||
{
|
||||
if (!_is_inited) {
|
||||
init();
|
||||
}
|
||||
|
||||
// Keep update in at most 50Hz
|
||||
if (GetHAL().millis() - _last_tick < 20) {
|
||||
return;
|
||||
}
|
||||
_last_tick = GetHAL().millis();
|
||||
|
||||
// Apply color animation
|
||||
if (!_color_anim.done()) {
|
||||
_color_anim.updateWithDelta(0.02f); // Fixed delta time for consistency
|
||||
for (int i = 0; i < _led_count; i++) {
|
||||
set_rgb_color_impl(i, _color_anim.r, _color_anim.g, _color_anim.b);
|
||||
}
|
||||
refresh_rgb_impl();
|
||||
}
|
||||
|
||||
// Snap to target angle when animation ends
|
||||
else if (_snap_to_target_on_rest) {
|
||||
_snap_to_target_on_rest = false;
|
||||
for (int i = 0; i < _led_count; i++) {
|
||||
set_rgb_color_impl(i, _color_anim.r, _color_anim.g, _color_anim.b);
|
||||
}
|
||||
refresh_rgb_impl();
|
||||
}
|
||||
}
|
||||
|
||||
void NeonLight::setColor(uint8_t r, uint8_t g, uint8_t b)
|
||||
{
|
||||
_color_anim.move(r, g, b);
|
||||
_snap_to_target_on_rest = true;
|
||||
}
|
||||
|
||||
void NeonLight::setColor(const uitk::color::Rgb_t& rgb)
|
||||
{
|
||||
_color_anim.move(rgb);
|
||||
_snap_to_target_on_rest = true;
|
||||
}
|
||||
|
||||
void NeonLight::setColor(uint32_t hex)
|
||||
{
|
||||
_color_anim.move(hex);
|
||||
_snap_to_target_on_rest = true;
|
||||
}
|
||||
|
||||
void NeonLight::setColor(std::string_view hex)
|
||||
{
|
||||
_color_anim.move(hex);
|
||||
_snap_to_target_on_rest = true;
|
||||
}
|
||||
|
||||
void NeonLight::setDuration(float durationSec)
|
||||
{
|
||||
_color_anim.duration = durationSec;
|
||||
_color_anim.begin();
|
||||
}
|
||||
|
||||
void LeftNeonLight::set_rgb_color_impl(uint8_t index, uint8_t r, uint8_t g, uint8_t b)
|
||||
{
|
||||
GetHAL().setRgbColor(index, r, g, b);
|
||||
}
|
||||
|
||||
void LeftNeonLight::refresh_rgb_impl()
|
||||
{
|
||||
GetHAL().refreshRgb();
|
||||
}
|
||||
|
||||
void RightNeonLight::set_rgb_color_impl(uint8_t index, uint8_t r, uint8_t g, uint8_t b)
|
||||
{
|
||||
GetHAL().setRgbColor(index + 6, r, g, b);
|
||||
}
|
||||
|
||||
void RightNeonLight::refresh_rgb_impl()
|
||||
{
|
||||
GetHAL().refreshRgb();
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <smooth_ui_toolkit.hpp>
|
||||
#include <uitk/short_namespace.hpp>
|
||||
#include "core/color/color.hpp"
|
||||
#include <string_view>
|
||||
|
||||
namespace stackchan::addon {
|
||||
|
||||
/**
|
||||
* @brief
|
||||
*
|
||||
*/
|
||||
class NeonLight {
|
||||
public:
|
||||
NeonLight(int ledCount) : _led_count(ledCount)
|
||||
{
|
||||
}
|
||||
|
||||
void init();
|
||||
void update();
|
||||
|
||||
void setColor(uint8_t r, uint8_t g, uint8_t b);
|
||||
void setColor(const uitk::color::Rgb_t& rgb);
|
||||
void setColor(uint32_t hex);
|
||||
void setColor(std::string_view hex);
|
||||
void setDuration(float durationSec);
|
||||
int getLedCount() const
|
||||
{
|
||||
return _led_count;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void set_rgb_color_impl(uint8_t index, uint8_t r, uint8_t g, uint8_t b) = 0;
|
||||
virtual void refresh_rgb_impl() = 0;
|
||||
|
||||
private:
|
||||
int _led_count;
|
||||
bool _is_inited = false;
|
||||
bool _snap_to_target_on_rest = false;
|
||||
uint32_t _last_tick = 0;
|
||||
uitk::color::AnimateRgb_t _color_anim;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief
|
||||
*
|
||||
*/
|
||||
class LeftNeonLight : public NeonLight {
|
||||
public:
|
||||
LeftNeonLight() : NeonLight(6)
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
void set_rgb_color_impl(uint8_t index, uint8_t r, uint8_t g, uint8_t b) override;
|
||||
void refresh_rgb_impl() override;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief
|
||||
*
|
||||
*/
|
||||
class RightNeonLight : public NeonLight {
|
||||
public:
|
||||
RightNeonLight() : NeonLight(6)
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
void set_rgb_color_impl(uint8_t index, uint8_t r, uint8_t g, uint8_t b) override;
|
||||
void refresh_rgb_impl() override;
|
||||
};
|
||||
|
||||
} // namespace stackchan::addon
|
||||
@@ -19,6 +19,7 @@ void Keyframe::apply()
|
||||
feat.setPosition(kf.position);
|
||||
feat.setRotation(kf.rotation);
|
||||
feat.setWeight(kf.weight);
|
||||
feat.setSize(kf.size);
|
||||
};
|
||||
apply_feature(leftEye, avatar.leftEye());
|
||||
apply_feature(rightEye, avatar.rightEye());
|
||||
@@ -27,6 +28,9 @@ void Keyframe::apply()
|
||||
auto apply_servo = [&](const ServoKeyframe& kf, motion::Servo& servo) { servo.moveWithSpeed(kf.angle, kf.speed); };
|
||||
apply_servo(yawServo, motion.yawServo());
|
||||
apply_servo(pitchServo, motion.pitchServo());
|
||||
|
||||
stackchan.leftNeonLight().setColor(leftRgbColor);
|
||||
stackchan.rightNeonLight().setColor(rightRgbColor);
|
||||
}
|
||||
|
||||
void Timeline::start()
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <uitk/short_namespace.hpp>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
namespace stackchan::animation {
|
||||
|
||||
@@ -19,6 +20,7 @@ struct FeatureKeyframe {
|
||||
uitk::Vector2i position;
|
||||
int rotation;
|
||||
int weight;
|
||||
int size;
|
||||
|
||||
FeatureKeyframe(int x = 0, int y = 0, int rotation = 0, int weight = 0)
|
||||
: position(x, y), rotation(rotation), weight(weight)
|
||||
@@ -49,6 +51,8 @@ struct Keyframe {
|
||||
FeatureKeyframe mouth;
|
||||
ServoKeyframe yawServo;
|
||||
ServoKeyframe pitchServo;
|
||||
std::string leftRgbColor;
|
||||
std::string rightRgbColor;
|
||||
uint32_t durationMs = 0;
|
||||
|
||||
Keyframe()
|
||||
|
||||
@@ -97,6 +97,16 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void setModifyLock(bool locked)
|
||||
{
|
||||
_is_modify_locked = locked;
|
||||
}
|
||||
|
||||
bool isModifyLocked()
|
||||
{
|
||||
return _is_modify_locked;
|
||||
}
|
||||
|
||||
/* ---------------------------- Decorator helpers --------------------------- */
|
||||
|
||||
int addDecorator(std::unique_ptr<Decorator> decorator)
|
||||
@@ -120,6 +130,8 @@ protected:
|
||||
Emotion _emotion = Emotion::Neutral;
|
||||
KeyElements_t _key_elements;
|
||||
ObjectPool<Decorator> _decorator_pool;
|
||||
|
||||
bool _is_modify_locked = false;
|
||||
};
|
||||
|
||||
} // namespace stackchan::avatar
|
||||
|
||||
@@ -19,7 +19,7 @@ public:
|
||||
virtual ~Element() = default;
|
||||
|
||||
/**
|
||||
* @brief (-100~100, -100~100)
|
||||
* @brief (-100 ~ 100, -100 ~ 100)
|
||||
*
|
||||
* @param position
|
||||
*/
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#include "decorators.h"
|
||||
#include <hal/hal.h>
|
||||
#include <vector>
|
||||
|
||||
using namespace uitk;
|
||||
@@ -17,47 +18,77 @@ static const std::vector<int> _angry_rotation_frames = {150, 200};
|
||||
LV_IMAGE_DECLARE(decorator_angry);
|
||||
|
||||
AngryDecorator::AngryDecorator(lv_obj_t* parent, uint32_t destroyAfterMs, uint32_t animationIntervalMs)
|
||||
: _animation_interval_ms(animationIntervalMs)
|
||||
{
|
||||
// 初始化 UI 组件
|
||||
_angry = std::make_unique<Image>(parent);
|
||||
_angry->setSrc(&decorator_angry);
|
||||
_angry->setAlign(LV_ALIGN_CENTER);
|
||||
_angry->setPos(_angry_default_position.x, _angry_default_position.y);
|
||||
|
||||
// 设置旋转中心和初始角度
|
||||
_angry->setTransformPivot(_angry->getWidth() / 2, _angry->getHeight() / 2);
|
||||
_angry->setRotation(_angry_rotation_frames[1]);
|
||||
_angry->setRotation(_angry_rotation_frames[0]);
|
||||
|
||||
// 设置颜色偏置
|
||||
_angry->setImageRecolorOpa(LV_OPA_COVER);
|
||||
_angry->setImageRecolor(_angry_default_color);
|
||||
|
||||
if (destroyAfterMs != 0) {
|
||||
scheduleDestroy(destroyAfterMs);
|
||||
uint32_t now = GetHAL().millis();
|
||||
|
||||
// 初始化销毁倒计时
|
||||
if (destroyAfterMs > 0) {
|
||||
_destroy_at = now + destroyAfterMs;
|
||||
_has_lifetime = true;
|
||||
}
|
||||
|
||||
if (animationIntervalMs != 0) {
|
||||
getTimer().addTask(animationIntervalMs, -1, 0, [this]() {
|
||||
setRotation(_angry_rotation_frames[_animation_index]);
|
||||
_animation_index++;
|
||||
if (_animation_index >= _angry_rotation_frames.size()) {
|
||||
_animation_index = 0;
|
||||
}
|
||||
});
|
||||
// 初始化动画倒计时
|
||||
if (_animation_interval_ms > 0) {
|
||||
_next_animation_tick = now + _animation_interval_ms;
|
||||
}
|
||||
}
|
||||
|
||||
AngryDecorator::~AngryDecorator()
|
||||
{
|
||||
_angry.reset();
|
||||
}
|
||||
|
||||
void AngryDecorator::_update()
|
||||
{
|
||||
uint32_t now = GetHAL().millis();
|
||||
|
||||
// 检查自动销毁
|
||||
if (_has_lifetime && now >= _destroy_at) {
|
||||
requestDestroy();
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查动画跳变
|
||||
if (_animation_interval_ms > 0 && now >= _next_animation_tick) {
|
||||
_next_animation_tick = now + _animation_interval_ms;
|
||||
|
||||
// 切换帧
|
||||
_animation_index = (_animation_index + 1) % _angry_rotation_frames.size();
|
||||
_angry->setRotation(_angry_rotation_frames[_animation_index]);
|
||||
}
|
||||
}
|
||||
|
||||
void AngryDecorator::setPosition(int x, int y)
|
||||
{
|
||||
_angry->setPos(x, y);
|
||||
if (_angry) {
|
||||
_angry->setPos(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
void AngryDecorator::setRotation(int rotation)
|
||||
{
|
||||
_angry->setRotation(rotation);
|
||||
if (_angry) {
|
||||
_angry->setRotation(rotation);
|
||||
}
|
||||
}
|
||||
|
||||
void AngryDecorator::setColor(lv_color_t color)
|
||||
{
|
||||
_angry->setImageRecolor(color);
|
||||
if (_angry) {
|
||||
_angry->setImageRecolor(color);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,233 @@
|
||||
#ifdef __has_include
|
||||
#if __has_include("lvgl.h")
|
||||
#ifndef LV_LVGL_H_INCLUDE_SIMPLE
|
||||
#define LV_LVGL_H_INCLUDE_SIMPLE
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(LV_LVGL_H_INCLUDE_SIMPLE)
|
||||
#include "lvgl.h"
|
||||
#else
|
||||
#include "lvgl/lvgl.h"
|
||||
#endif
|
||||
|
||||
#ifndef LV_ATTRIBUTE_MEM_ALIGN
|
||||
#define LV_ATTRIBUTE_MEM_ALIGN
|
||||
#endif
|
||||
|
||||
#ifndef LV_ATTRIBUTE_IMAGE_DECORATOR_DIZZY
|
||||
#define LV_ATTRIBUTE_IMAGE_DECORATOR_DIZZY
|
||||
#endif
|
||||
|
||||
const LV_ATTRIBUTE_MEM_ALIGN LV_ATTRIBUTE_LARGE_CONST LV_ATTRIBUTE_IMAGE_DECORATOR_DIZZY uint8_t
|
||||
decorator_dizzy_map[] = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40,
|
||||
0x80, 0xaf, 0xdf, 0xff, 0xff, 0xcf, 0xbf, 0x60, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x6f, 0xbf, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x9f, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0xdf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xdf, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x20, 0xbf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xbf, 0x8f, 0x5f, 0x40, 0x5f, 0x80, 0xbf, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0xef,
|
||||
0xff, 0xff, 0xff, 0xff, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0xaf, 0xff, 0xff, 0xff,
|
||||
0xff, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8f, 0xff, 0xff, 0xff, 0xff, 0xbf,
|
||||
0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xff, 0xff, 0xff, 0xff, 0x40,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0xbf, 0xff, 0xff, 0xff, 0xef, 0x70, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xff, 0xff, 0xff, 0xcf, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x20, 0xdf, 0xff, 0xff, 0xff, 0xcf, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8f, 0xff, 0xff, 0xef, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20,
|
||||
0xdf, 0xff, 0xff, 0xff, 0x8f, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x5f, 0x80, 0x80, 0x8f, 0x80, 0x80,
|
||||
0x5f, 0x40, 0x20, 0x00, 0x00, 0x10, 0xaf, 0xef, 0x70, 0x00, 0x00, 0x00, 0x00, 0x20, 0xdf, 0xff, 0xff, 0xff,
|
||||
0x80, 0x00, 0x00, 0x00, 0x00, 0x40, 0x8f, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0x9f, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0xdf, 0xff, 0xff, 0xff, 0x60, 0x00, 0x00, 0x00,
|
||||
0x20, 0x9f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xcf, 0x10,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9f, 0xff, 0xff, 0xff, 0x60, 0x00, 0x00, 0x00, 0x30, 0xdf, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xef, 0xdf, 0xdf, 0xdf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x60, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x60, 0xff, 0xff, 0xff, 0xff, 0xef, 0x9f, 0x40,
|
||||
0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x30, 0xdf, 0xff, 0xff, 0xff, 0x10, 0x00, 0x00, 0x00, 0x20, 0xdf,
|
||||
0xff, 0xff, 0x9f, 0x00, 0x00, 0x00, 0x20, 0xef, 0xff, 0xff, 0xff, 0x8f, 0x10, 0x00, 0x00, 0x00, 0x30, 0x40,
|
||||
0x20, 0x00, 0x00, 0x00, 0x00, 0x40, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x80, 0xff, 0xff, 0xdf, 0x20,
|
||||
0x00, 0x00, 0x20, 0xdf, 0xff, 0xff, 0xef, 0x70, 0x00, 0x00, 0x00, 0x30, 0xbf, 0xff, 0xff, 0xff, 0x50, 0x00,
|
||||
0x00, 0x00, 0x00, 0xbf, 0xff, 0xff, 0xcf, 0x00, 0x00, 0x00, 0xdf, 0xff, 0xff, 0x60, 0x00, 0x00, 0x00, 0x9f,
|
||||
0xff, 0xff, 0xff, 0x50, 0x00, 0x00, 0x00, 0x70, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0x10, 0x00, 0x00, 0x00,
|
||||
0x80, 0xff, 0xff, 0xff, 0x10, 0x00, 0x50, 0xff, 0xff, 0xdf, 0x00, 0x00, 0x00, 0x30, 0xff, 0xff, 0xff, 0x80,
|
||||
0x00, 0x00, 0x10, 0xaf, 0xff, 0xff, 0xff, 0xef, 0xff, 0xff, 0xff, 0x5f, 0x00, 0x00, 0x00, 0x40, 0xff, 0xff,
|
||||
0xff, 0x40, 0x00, 0x9f, 0xff, 0xff, 0x9f, 0x00, 0x00, 0x00, 0x9f, 0xff, 0xff, 0xdf, 0x00, 0x00, 0x10, 0xcf,
|
||||
0xff, 0xff, 0xff, 0xdf, 0x30, 0xdf, 0xff, 0xff, 0x8f, 0x00, 0x00, 0x00, 0x20, 0xff, 0xff, 0xff, 0x5f, 0x00,
|
||||
0xbf, 0xff, 0xff, 0x50, 0x00, 0x00, 0x00, 0xcf, 0xff, 0xff, 0x6f, 0x00, 0x00, 0xaf, 0xff, 0xff, 0xff, 0xaf,
|
||||
0x10, 0x00, 0xbf, 0xff, 0xff, 0x9f, 0x00, 0x00, 0x00, 0x10, 0xff, 0xff, 0xff, 0x5f, 0x00, 0xef, 0xff, 0xff,
|
||||
0x30, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x40, 0x00, 0x70, 0xff, 0xff, 0xff, 0x9f, 0x00, 0x00, 0x00, 0xbf,
|
||||
0xff, 0xff, 0x9f, 0x00, 0x00, 0x00, 0x10, 0xff, 0xff, 0xff, 0x5f, 0x00, 0xff, 0xff, 0xff, 0x20, 0x00, 0x00,
|
||||
0x00, 0xff, 0xff, 0xff, 0x40, 0x00, 0xbf, 0xff, 0xff, 0xaf, 0x00, 0x00, 0x00, 0x00, 0xbf, 0xff, 0xff, 0x9f,
|
||||
0x00, 0x00, 0x00, 0x30, 0xff, 0xff, 0xff, 0x40, 0x00, 0xdf, 0xff, 0xff, 0x30, 0x00, 0x00, 0x00, 0xef, 0xff,
|
||||
0xff, 0x6f, 0x00, 0x70, 0xff, 0xdf, 0x20, 0x00, 0x00, 0x00, 0x00, 0xbf, 0xff, 0xff, 0x9f, 0x00, 0x00, 0x00,
|
||||
0x60, 0xff, 0xff, 0xff, 0x20, 0x00, 0xcf, 0xff, 0xff, 0x5f, 0x00, 0x00, 0x00, 0x9f, 0xff, 0xff, 0xdf, 0x10,
|
||||
0x00, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbf, 0xff, 0xff, 0x9f, 0x00, 0x00, 0x00, 0xbf, 0xff, 0xff,
|
||||
0xdf, 0x00, 0x00, 0x8f, 0xff, 0xff, 0xaf, 0x00, 0x00, 0x00, 0x40, 0xff, 0xff, 0xff, 0x9f, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x10, 0xef, 0xff, 0xff, 0x80, 0x00, 0x00, 0x20, 0xff, 0xff, 0xff, 0x9f, 0x00, 0x00,
|
||||
0x40, 0xff, 0xff, 0xef, 0x10, 0x00, 0x00, 0x00, 0xaf, 0xff, 0xff, 0xff, 0xbf, 0x20, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x80, 0xff, 0xff, 0xff, 0x50, 0x00, 0x00, 0xdf, 0xff, 0xff, 0xff, 0x20, 0x00, 0x00, 0x00, 0xef, 0xff,
|
||||
0xff, 0x9f, 0x00, 0x00, 0x00, 0x10, 0xcf, 0xff, 0xff, 0xff, 0xef, 0x9f, 0x40, 0x20, 0x40, 0xaf, 0xff, 0xff,
|
||||
0xff, 0xdf, 0x00, 0x00, 0x8f, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x60, 0xff, 0xff, 0xff, 0x50,
|
||||
0x00, 0x00, 0x00, 0x20, 0xbf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x50, 0x00,
|
||||
0x80, 0xff, 0xff, 0xff, 0xdf, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbf, 0xff, 0xff, 0xef, 0x20, 0x00, 0x00,
|
||||
0x00, 0x00, 0x80, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0x70, 0x00, 0x80, 0xff, 0xff, 0xff,
|
||||
0xdf, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0xef, 0xff, 0xff, 0xdf, 0x20, 0x00, 0x00, 0x00, 0x00,
|
||||
0x20, 0x8f, 0xdf, 0xff, 0xff, 0xff, 0xef, 0x8f, 0x20, 0x00, 0x80, 0xff, 0xff, 0xff, 0xff, 0x30, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0xff, 0xff, 0xff, 0xdf, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x10, 0x30, 0x20, 0x00, 0x00, 0x10, 0x9f, 0xff, 0xff, 0xff, 0xff, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x80, 0xff, 0xff, 0xff, 0xdf, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x40, 0xdf, 0xff, 0xff, 0xff, 0xef, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x80, 0xff, 0xff, 0xff, 0xff, 0x9f, 0x20, 0x00, 0x00, 0x00, 0x00, 0x20, 0x80, 0xdf, 0xff, 0xff,
|
||||
0xff, 0xff, 0xdf, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x80, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0xbf, 0xcf, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xbf, 0x20,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0xdf,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x8f, 0xdf, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xbf, 0x80, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x80, 0x9f, 0x9f,
|
||||
0x8f, 0x5f, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
const lv_image_dsc_t decorator_dizzy = {
|
||||
.header.cf = LV_COLOR_FORMAT_RGB565A8,
|
||||
.header.magic = LV_IMAGE_HEADER_MAGIC,
|
||||
.header.w = 33,
|
||||
.header.h = 36,
|
||||
.data_size = 1188 * 3,
|
||||
.data = decorator_dizzy_map,
|
||||
};
|
||||
@@ -0,0 +1,145 @@
|
||||
#ifdef __has_include
|
||||
#if __has_include("lvgl.h")
|
||||
#ifndef LV_LVGL_H_INCLUDE_SIMPLE
|
||||
#define LV_LVGL_H_INCLUDE_SIMPLE
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(LV_LVGL_H_INCLUDE_SIMPLE)
|
||||
#include "lvgl.h"
|
||||
#else
|
||||
#include "lvgl/lvgl.h"
|
||||
#endif
|
||||
|
||||
#ifndef LV_ATTRIBUTE_MEM_ALIGN
|
||||
#define LV_ATTRIBUTE_MEM_ALIGN
|
||||
#endif
|
||||
|
||||
#ifndef LV_ATTRIBUTE_IMAGE_DECORATOR_SHY
|
||||
#define LV_ATTRIBUTE_IMAGE_DECORATOR_SHY
|
||||
#endif
|
||||
|
||||
const LV_ATTRIBUTE_MEM_ALIGN LV_ATTRIBUTE_LARGE_CONST LV_ATTRIBUTE_IMAGE_DECORATOR_SHY uint8_t decorator_shy_map[] = {
|
||||
0x54, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x54, 0xfd, 0x14, 0xf5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x54, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x54, 0xfd, 0x54, 0xfd,
|
||||
0x34, 0xfd, 0x34, 0xfd, 0x14, 0xf5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x54, 0xfd, 0x54, 0xfd, 0x34, 0xfd, 0x54, 0xfd, 0x14, 0xf5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x54, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34,
|
||||
0xfd, 0x14, 0xf5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x54, 0xfd,
|
||||
0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x54, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x54, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34,
|
||||
0xfd, 0x34, 0xfd, 0x33, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0xfd, 0x34, 0xfd, 0x34,
|
||||
0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x54, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x54, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x33, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd,
|
||||
0x34, 0xfd, 0x34, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x54, 0xfd, 0x34,
|
||||
0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x54, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd,
|
||||
0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x54, 0xfd, 0x14, 0xf5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x33, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0xf5, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x54, 0xfd, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x54, 0xfd, 0x34, 0xfd,
|
||||
0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x14, 0xf5, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x54, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34,
|
||||
0xfd, 0x14, 0xf5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x33, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34,
|
||||
0xfd, 0x00, 0x00, 0x00, 0x00, 0x54, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34,
|
||||
0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x54, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34,
|
||||
0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd,
|
||||
0x34, 0xfd, 0x14, 0xf5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0xf5, 0x34, 0xfd, 0x34,
|
||||
0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0xf5, 0x54, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34,
|
||||
0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x54, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd,
|
||||
0x34, 0xfd, 0x34, 0xfd, 0x54, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x54,
|
||||
0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0xfd, 0x34, 0xfd, 0x34,
|
||||
0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0xfd, 0x34, 0xfd,
|
||||
0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x33, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14,
|
||||
0xf5, 0x54, 0xfd, 0x54, 0xfd, 0x34, 0xfd, 0x54, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x54, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x14, 0xf5, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x54, 0xfd, 0x54, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x54, 0xfd, 0x34, 0xfd, 0x34, 0xfd, 0x54, 0xfd, 0x14, 0xf5, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
|
||||
0x20, 0xcf, 0xef, 0xbf, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0xbf, 0xff, 0xff, 0xff, 0x9f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xcf, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xdf, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xdf, 0xff, 0xff, 0xff, 0xef, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0xbf,
|
||||
0xff, 0xaf, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0xbf, 0xff, 0xbf, 0x10, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0xbf, 0xff, 0xff, 0xff, 0xff, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbf,
|
||||
0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbf, 0xff, 0xff, 0xff, 0xaf, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8f, 0xff, 0xff, 0xff, 0xff, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xef, 0xff, 0xff, 0xff, 0xef, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0xff, 0xff, 0xff, 0xff, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0xbf, 0xff, 0xff, 0xff, 0xff, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9f, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0xff, 0xff, 0xff, 0xff, 0xdf, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x6f, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0xdf, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xbf, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcf, 0xff, 0xff, 0xff, 0xff, 0x40, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x10, 0xff, 0xff, 0xff, 0xff, 0xbf, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0xef,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0x80, 0x10, 0x00, 0x00, 0x00, 0x60, 0xff, 0xff, 0xff, 0xff, 0x9f, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0xbf, 0xff, 0xff, 0xff, 0xff, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x30, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0x80, 0x00, 0x00, 0x20, 0xff, 0xff, 0xff, 0xff, 0xff, 0x30,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6f, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x20, 0xdf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x9f, 0xff, 0xff, 0xff, 0xff,
|
||||
0xdf, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0xff, 0xff, 0xff, 0xff, 0xdf, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x10, 0xbf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xaf, 0x00, 0x00, 0x20, 0xef, 0xff, 0xff,
|
||||
0xff, 0xff, 0xbf, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbf, 0xff, 0xff, 0xff, 0xff, 0x60, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0xdf, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x80, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0xff, 0xff, 0xff, 0xff, 0x9f, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x50, 0xbf, 0xcf, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xaf, 0xff, 0xff, 0xff, 0xff, 0xbf, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcf, 0xff, 0xff, 0xff, 0x60, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x10, 0xdf, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0xbf, 0xdf, 0x70, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x20, 0xaf, 0xcf, 0x8f, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
const lv_image_dsc_t decorator_shy = {
|
||||
.header.cf = LV_COLOR_FORMAT_RGB565A8,
|
||||
.header.magic = LV_IMAGE_HEADER_MAGIC,
|
||||
.header.w = 39,
|
||||
.header.h = 18,
|
||||
.data_size = 702 * 3,
|
||||
.data = decorator_shy_map,
|
||||
};
|
||||
@@ -4,7 +4,6 @@
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#pragma once
|
||||
#include "../../utils/timer.h"
|
||||
#include "../avatar/decorator.h"
|
||||
#include <lvgl.h>
|
||||
#include <smooth_lvgl.hpp>
|
||||
@@ -13,36 +12,11 @@
|
||||
|
||||
namespace stackchan::avatar {
|
||||
|
||||
/**
|
||||
* @brief Decorator with a timer
|
||||
*
|
||||
*/
|
||||
class TimerDecorator : public Decorator {
|
||||
public:
|
||||
void scheduleDestroy(uint32_t ms)
|
||||
{
|
||||
_timer.addTask(ms, 1, 0, [this]() { requestDestroy(); });
|
||||
}
|
||||
|
||||
Timer& getTimer()
|
||||
{
|
||||
return _timer;
|
||||
}
|
||||
|
||||
virtual void _update() override
|
||||
{
|
||||
_timer.update();
|
||||
}
|
||||
|
||||
private:
|
||||
Timer _timer;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief
|
||||
*
|
||||
*/
|
||||
class HeartDecorator : public TimerDecorator {
|
||||
class HeartDecorator : public Decorator {
|
||||
public:
|
||||
/**
|
||||
* @brief
|
||||
@@ -54,14 +28,22 @@ public:
|
||||
HeartDecorator(lv_obj_t* parent, uint32_t destroyAfterMs = 0, uint32_t animationIntervalMs = 500);
|
||||
~HeartDecorator();
|
||||
|
||||
void _update() override;
|
||||
|
||||
using Element::setPosition;
|
||||
|
||||
void setPosition(int x, int y);
|
||||
void setRotation(int rotation);
|
||||
void setRotation(int rotation) override;
|
||||
void setColor(lv_color_t color);
|
||||
|
||||
private:
|
||||
std::unique_ptr<uitk::lvgl_cpp::Image> _heart;
|
||||
|
||||
uint32_t _destroy_at = 0;
|
||||
uint32_t _next_animation_tick = 0;
|
||||
uint32_t _animation_interval_ms = 0;
|
||||
bool _has_lifetime = false;
|
||||
|
||||
int _animation_index = 0;
|
||||
};
|
||||
|
||||
@@ -69,7 +51,7 @@ private:
|
||||
* @brief
|
||||
*
|
||||
*/
|
||||
class AngryDecorator : public TimerDecorator {
|
||||
class AngryDecorator : public Decorator {
|
||||
public:
|
||||
/**
|
||||
* @brief
|
||||
@@ -81,14 +63,22 @@ public:
|
||||
AngryDecorator(lv_obj_t* parent, uint32_t destroyAfterMs = 0, uint32_t animationIntervalMs = 500);
|
||||
~AngryDecorator();
|
||||
|
||||
void _update() override;
|
||||
|
||||
using Element::setPosition;
|
||||
|
||||
void setPosition(int x, int y);
|
||||
void setRotation(int rotation);
|
||||
void setRotation(int rotation) override;
|
||||
void setColor(lv_color_t color);
|
||||
|
||||
private:
|
||||
std::unique_ptr<uitk::lvgl_cpp::Image> _angry;
|
||||
|
||||
uint32_t _destroy_at = 0;
|
||||
uint32_t _next_animation_tick = 0;
|
||||
uint32_t _animation_interval_ms = 0;
|
||||
bool _has_lifetime = false;
|
||||
|
||||
int _animation_index = 0;
|
||||
};
|
||||
|
||||
@@ -96,7 +86,7 @@ private:
|
||||
* @brief
|
||||
*
|
||||
*/
|
||||
class SweatDecorator : public TimerDecorator {
|
||||
class SweatDecorator : public Decorator {
|
||||
public:
|
||||
/**
|
||||
* @brief
|
||||
@@ -108,6 +98,8 @@ public:
|
||||
SweatDecorator(lv_obj_t* parent, uint32_t destroyAfterMs = 0, uint32_t animationIntervalMs = 700);
|
||||
~SweatDecorator();
|
||||
|
||||
void _update() override;
|
||||
|
||||
using Element::setPosition;
|
||||
|
||||
void setPosition(int x, int y);
|
||||
@@ -117,6 +109,81 @@ public:
|
||||
|
||||
private:
|
||||
std::unique_ptr<uitk::lvgl_cpp::Image> _sweat;
|
||||
|
||||
uint32_t _destroy_at = 0;
|
||||
uint32_t _next_animation_tick = 0;
|
||||
uint32_t _animation_interval_ms = 0;
|
||||
bool _has_lifetime = false;
|
||||
|
||||
int _animation_index = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief
|
||||
*
|
||||
*/
|
||||
class ShyDecorator : public Decorator {
|
||||
public:
|
||||
/**
|
||||
* @brief
|
||||
*
|
||||
* @param parent
|
||||
* @param destroyAfterMs Destroy after milliseconds, 0 for infinite
|
||||
*/
|
||||
ShyDecorator(lv_obj_t* parent, uint32_t destroyAfterMs = 0);
|
||||
~ShyDecorator();
|
||||
|
||||
void _update() override;
|
||||
|
||||
using Element::setPosition;
|
||||
|
||||
void setPosition(int x, int y);
|
||||
void setRotation(int rotation) override;
|
||||
void setColor(lv_color_t color);
|
||||
void setVisible(bool visible) override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<uitk::lvgl_cpp::Image> _left;
|
||||
std::unique_ptr<uitk::lvgl_cpp::Image> _right;
|
||||
|
||||
uint32_t _destroy_at = 0;
|
||||
bool _has_lifetime = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief
|
||||
*
|
||||
*/
|
||||
class DizzyDecorator : public Decorator {
|
||||
public:
|
||||
/**
|
||||
* @brief
|
||||
*
|
||||
* @param parent
|
||||
* @param destroyAfterMs Destroy after milliseconds, 0 for infinite
|
||||
* @param animationIntervalMs Animation update interval in milliseconds, 0 for none
|
||||
*/
|
||||
DizzyDecorator(lv_obj_t* parent, uint32_t destroyAfterMs = 0, uint32_t animationIntervalMs = 500);
|
||||
~DizzyDecorator();
|
||||
|
||||
void _update() override;
|
||||
|
||||
using Element::setPosition;
|
||||
|
||||
void setPosition(int x, int y);
|
||||
void setRotation(int rotation) override;
|
||||
void setColor(lv_color_t color);
|
||||
void setVisible(bool visible) override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<uitk::lvgl_cpp::Image> _left;
|
||||
std::unique_ptr<uitk::lvgl_cpp::Image> _right;
|
||||
|
||||
uint32_t _destroy_at = 0;
|
||||
uint32_t _next_animation_tick = 0;
|
||||
uint32_t _animation_interval_ms = 0;
|
||||
bool _has_lifetime = false;
|
||||
|
||||
int _animation_index = 0;
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#include "decorators.h"
|
||||
#include <hal/hal.h>
|
||||
#include <vector>
|
||||
|
||||
using namespace uitk;
|
||||
using namespace uitk::lvgl_cpp;
|
||||
using namespace stackchan::avatar;
|
||||
|
||||
static const Vector2i _dizzy_left_default_position = Vector2i(-70, -16);
|
||||
static const Vector2i _dizzy_right_default_position = Vector2i(70, -16);
|
||||
|
||||
static const lv_color_t _dizzy_default_color = lv_color_hex(0xFFFFFF);
|
||||
|
||||
LV_IMAGE_DECLARE(decorator_dizzy);
|
||||
|
||||
DizzyDecorator::DizzyDecorator(lv_obj_t* parent, uint32_t destroyAfterMs, uint32_t animationIntervalMs)
|
||||
: _animation_interval_ms(animationIntervalMs)
|
||||
{
|
||||
// Initialize Left Image
|
||||
_left = std::make_unique<Image>(parent);
|
||||
_left->setSrc(&decorator_dizzy);
|
||||
_left->setAlign(LV_ALIGN_CENTER);
|
||||
_left->setPos(_dizzy_left_default_position.x, _dizzy_left_default_position.y);
|
||||
_left->setTransformPivot(_left->getWidth() / 2, _left->getHeight() / 2);
|
||||
_left->setRotation(0);
|
||||
_left->setImageRecolorOpa(LV_OPA_COVER);
|
||||
_left->setImageRecolor(_dizzy_default_color);
|
||||
|
||||
// Initialize Right Image
|
||||
_right = std::make_unique<Image>(parent);
|
||||
_right->setSrc(&decorator_dizzy);
|
||||
_right->setAlign(LV_ALIGN_CENTER);
|
||||
_right->setPos(_dizzy_right_default_position.x, _dizzy_right_default_position.y);
|
||||
_right->setTransformPivot(_right->getWidth() / 2, _right->getHeight() / 2);
|
||||
_right->setRotation(450);
|
||||
_right->setImageRecolorOpa(LV_OPA_COVER);
|
||||
_right->setImageRecolor(_dizzy_default_color);
|
||||
|
||||
uint32_t now = GetHAL().millis();
|
||||
|
||||
if (destroyAfterMs > 0) {
|
||||
_destroy_at = now + destroyAfterMs;
|
||||
_has_lifetime = true;
|
||||
}
|
||||
|
||||
if (_animation_interval_ms > 0) {
|
||||
_next_animation_tick = now + _animation_interval_ms;
|
||||
}
|
||||
|
||||
_animation_index = 0; // 用作当前旋转角度
|
||||
}
|
||||
|
||||
DizzyDecorator::~DizzyDecorator()
|
||||
{
|
||||
}
|
||||
|
||||
void DizzyDecorator::_update()
|
||||
{
|
||||
uint32_t now = GetHAL().millis();
|
||||
|
||||
if (_has_lifetime && now >= _destroy_at) {
|
||||
requestDestroy();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_animation_interval_ms > 0 && now >= _next_animation_tick) {
|
||||
_next_animation_tick = now + _animation_interval_ms;
|
||||
|
||||
// 自加30度 (300 unit)
|
||||
_animation_index = (_animation_index + 300) % 3600;
|
||||
int rotation = -_animation_index;
|
||||
|
||||
if (_left) {
|
||||
_left->setRotation(rotation);
|
||||
}
|
||||
if (_right) {
|
||||
_right->setRotation((rotation + 450) % 3600);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DizzyDecorator::setPosition(int x, int y)
|
||||
{
|
||||
if (_left) {
|
||||
_left->setPos(x + _dizzy_left_default_position.x, y + _dizzy_left_default_position.y);
|
||||
}
|
||||
if (_right) {
|
||||
_right->setPos(x + _dizzy_right_default_position.x, y + _dizzy_right_default_position.y);
|
||||
}
|
||||
}
|
||||
|
||||
void DizzyDecorator::setRotation(int rotation)
|
||||
{
|
||||
// 这里要注意,手动设置的 rotation 可能会被 update 中的动画覆盖
|
||||
// 但按照 HeartDecorator 的逻辑,setRotation 也是直接设置的
|
||||
if (_left) {
|
||||
_left->setRotation(rotation);
|
||||
}
|
||||
if (_right) {
|
||||
_right->setRotation(rotation);
|
||||
}
|
||||
}
|
||||
|
||||
void DizzyDecorator::setColor(lv_color_t color)
|
||||
{
|
||||
if (_left) {
|
||||
_left->setImageRecolor(color);
|
||||
}
|
||||
if (_right) {
|
||||
_right->setImageRecolor(color);
|
||||
}
|
||||
}
|
||||
|
||||
void DizzyDecorator::setVisible(bool visible)
|
||||
{
|
||||
if (_left) {
|
||||
_left->setHidden(!visible);
|
||||
}
|
||||
if (_right) {
|
||||
_right->setHidden(!visible);
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#include "decorators.h"
|
||||
#include <hal/hal.h>
|
||||
#include <vector>
|
||||
|
||||
using namespace uitk;
|
||||
@@ -17,47 +18,77 @@ static const std::vector<int> _heart_rotation_frames = {150, 200};
|
||||
LV_IMAGE_DECLARE(decorator_heart);
|
||||
|
||||
HeartDecorator::HeartDecorator(lv_obj_t* parent, uint32_t destroyAfterMs, uint32_t animationIntervalMs)
|
||||
: _animation_interval_ms(animationIntervalMs)
|
||||
{
|
||||
// 初始化图像
|
||||
_heart = std::make_unique<Image>(parent);
|
||||
_heart->setSrc(&decorator_heart);
|
||||
_heart->setAlign(LV_ALIGN_CENTER);
|
||||
_heart->setPos(_heart_default_position.x, _heart_default_position.y);
|
||||
|
||||
// 设置旋转中心为中心点
|
||||
_heart->setTransformPivot(_heart->getWidth() / 2, _heart->getHeight() / 2);
|
||||
_heart->setRotation(_heart_rotation_frames[1]);
|
||||
_heart->setRotation(_heart_rotation_frames[0]);
|
||||
|
||||
// 设置颜色偏向
|
||||
_heart->setImageRecolorOpa(LV_OPA_COVER);
|
||||
_heart->setImageRecolor(_heart_default_color);
|
||||
|
||||
if (destroyAfterMs != 0) {
|
||||
scheduleDestroy(destroyAfterMs);
|
||||
uint32_t now = GetHAL().millis();
|
||||
|
||||
// 设置销毁计时
|
||||
if (destroyAfterMs > 0) {
|
||||
_destroy_at = now + destroyAfterMs;
|
||||
_has_lifetime = true;
|
||||
}
|
||||
|
||||
if (animationIntervalMs != 0) {
|
||||
getTimer().addTask(animationIntervalMs, -1, 0, [this]() {
|
||||
setRotation(_heart_rotation_frames[_animation_index]);
|
||||
_animation_index++;
|
||||
if (_animation_index >= _heart_rotation_frames.size()) {
|
||||
_animation_index = 0;
|
||||
}
|
||||
});
|
||||
// 设置动画计时
|
||||
if (_animation_interval_ms > 0) {
|
||||
_next_animation_tick = now + _animation_interval_ms;
|
||||
}
|
||||
}
|
||||
|
||||
HeartDecorator::~HeartDecorator()
|
||||
{
|
||||
_heart.reset();
|
||||
}
|
||||
|
||||
void HeartDecorator::_update()
|
||||
{
|
||||
uint32_t now = GetHAL().millis();
|
||||
|
||||
// 1. 处理销毁
|
||||
if (_has_lifetime && now >= _destroy_at) {
|
||||
requestDestroy();
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 处理动画跳变(心跳效果)
|
||||
if (_animation_interval_ms > 0 && now >= _next_animation_tick) {
|
||||
_next_animation_tick = now + _animation_interval_ms;
|
||||
|
||||
// 切换旋转角度
|
||||
_animation_index = (_animation_index + 1) % _heart_rotation_frames.size();
|
||||
_heart->setRotation(_heart_rotation_frames[_animation_index]);
|
||||
}
|
||||
}
|
||||
|
||||
void HeartDecorator::setPosition(int x, int y)
|
||||
{
|
||||
_heart->setPos(x, y);
|
||||
if (_heart) {
|
||||
_heart->setPos(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
void HeartDecorator::setRotation(int rotation)
|
||||
{
|
||||
_heart->setRotation(rotation);
|
||||
if (_heart) {
|
||||
_heart->setRotation(rotation);
|
||||
}
|
||||
}
|
||||
|
||||
void HeartDecorator::setColor(lv_color_t color)
|
||||
{
|
||||
_heart->setImageRecolor(color);
|
||||
if (_heart) {
|
||||
_heart->setImageRecolor(color);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#include "decorators.h"
|
||||
#include <hal/hal.h>
|
||||
#include <vector>
|
||||
|
||||
using namespace uitk;
|
||||
using namespace uitk::lvgl_cpp;
|
||||
using namespace stackchan::avatar;
|
||||
|
||||
static const Vector2i _shy_left_default_position = Vector2i(-108, 28);
|
||||
static const Vector2i _shy_right_default_position = Vector2i(108, 28);
|
||||
static const lv_color_t _shy_default_color = lv_color_hex(0xF7A59E);
|
||||
|
||||
LV_IMAGE_DECLARE(decorator_shy);
|
||||
|
||||
ShyDecorator::ShyDecorator(lv_obj_t* parent, uint32_t destroyAfterMs)
|
||||
{
|
||||
// Initialize Left Image
|
||||
_left = std::make_unique<Image>(parent);
|
||||
_left->setSrc(&decorator_shy);
|
||||
_left->setAlign(LV_ALIGN_CENTER);
|
||||
_left->setPos(_shy_left_default_position.x, _shy_left_default_position.y);
|
||||
_left->setTransformPivot(_left->getWidth() / 2, _left->getHeight() / 2);
|
||||
_left->setImageRecolorOpa(LV_OPA_COVER);
|
||||
_left->setImageRecolor(_shy_default_color);
|
||||
|
||||
// Initialize Right Image
|
||||
_right = std::make_unique<Image>(parent);
|
||||
_right->setSrc(&decorator_shy);
|
||||
_right->setAlign(LV_ALIGN_CENTER);
|
||||
_right->setPos(_shy_right_default_position.x, _shy_right_default_position.y);
|
||||
_right->setTransformPivot(_right->getWidth() / 2, _right->getHeight() / 2);
|
||||
_right->setImageRecolorOpa(LV_OPA_COVER);
|
||||
_right->setImageRecolor(_shy_default_color);
|
||||
|
||||
uint32_t now = GetHAL().millis();
|
||||
|
||||
if (destroyAfterMs > 0) {
|
||||
_destroy_at = now + destroyAfterMs;
|
||||
_has_lifetime = true;
|
||||
}
|
||||
}
|
||||
|
||||
ShyDecorator::~ShyDecorator()
|
||||
{
|
||||
}
|
||||
|
||||
void ShyDecorator::_update()
|
||||
{
|
||||
uint32_t now = GetHAL().millis();
|
||||
|
||||
if (_has_lifetime && now >= _destroy_at) {
|
||||
requestDestroy();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void ShyDecorator::setPosition(int x, int y)
|
||||
{
|
||||
if (_left) {
|
||||
_left->setPos(x + _shy_left_default_position.x, y + _shy_left_default_position.y);
|
||||
}
|
||||
if (_right) {
|
||||
_right->setPos(x + _shy_right_default_position.x, y + _shy_right_default_position.y);
|
||||
}
|
||||
}
|
||||
|
||||
void ShyDecorator::setRotation(int rotation)
|
||||
{
|
||||
if (_left) {
|
||||
_left->setRotation(rotation);
|
||||
}
|
||||
if (_right) {
|
||||
_right->setRotation(rotation);
|
||||
}
|
||||
}
|
||||
|
||||
void ShyDecorator::setColor(lv_color_t color)
|
||||
{
|
||||
if (_left) {
|
||||
_left->setImageRecolor(color);
|
||||
}
|
||||
if (_right) {
|
||||
_right->setImageRecolor(color);
|
||||
}
|
||||
}
|
||||
|
||||
void ShyDecorator::setVisible(bool visible)
|
||||
{
|
||||
if (_left) {
|
||||
_left->setHidden(!visible);
|
||||
}
|
||||
if (_right) {
|
||||
_right->setHidden(!visible);
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#include "decorators.h"
|
||||
#include <hal/hal.h>
|
||||
#include <vector>
|
||||
|
||||
using namespace uitk;
|
||||
@@ -17,59 +18,89 @@ static const std::vector<int> _sweat_pos_y_frames = {-72, -68, -62, -58, 0};
|
||||
LV_IMAGE_DECLARE(decorator_sweat);
|
||||
|
||||
SweatDecorator::SweatDecorator(lv_obj_t* parent, uint32_t destroyAfterMs, uint32_t animationIntervalMs)
|
||||
: _animation_interval_ms(animationIntervalMs)
|
||||
{
|
||||
// 初始化图像
|
||||
_sweat = std::make_unique<Image>(parent);
|
||||
_sweat->setSrc(&decorator_sweat);
|
||||
_sweat->setAlign(LV_ALIGN_CENTER);
|
||||
_sweat->setPos(_sweat_default_position.x, _sweat_default_position.y);
|
||||
|
||||
_sweat->setTransformPivot(_sweat->getWidth() / 2, _sweat->getHeight() / 2);
|
||||
_sweat->setImageRecolorOpa(LV_OPA_COVER);
|
||||
_sweat->setImageRecolor(_sweat_default_color);
|
||||
|
||||
if (destroyAfterMs != 0) {
|
||||
scheduleDestroy(destroyAfterMs);
|
||||
uint32_t now = GetHAL().millis();
|
||||
|
||||
if (destroyAfterMs > 0) {
|
||||
_destroy_at = now + destroyAfterMs;
|
||||
_has_lifetime = true;
|
||||
}
|
||||
|
||||
if (animationIntervalMs != 0) {
|
||||
getTimer().addTask(animationIntervalMs, -1, 0, [this]() {
|
||||
if (_sweat_pos_y_frames[_animation_index] == 0) {
|
||||
setVisible(false);
|
||||
} else {
|
||||
setVisible(true);
|
||||
setPosition(_sweat_default_position.x, _sweat_pos_y_frames[_animation_index]);
|
||||
}
|
||||
_animation_index++;
|
||||
if (_animation_index >= _sweat_pos_y_frames.size()) {
|
||||
_animation_index = 0;
|
||||
}
|
||||
});
|
||||
if (_animation_interval_ms > 0) {
|
||||
_next_animation_tick = now + _animation_interval_ms;
|
||||
}
|
||||
}
|
||||
|
||||
SweatDecorator::~SweatDecorator()
|
||||
{
|
||||
_sweat.reset();
|
||||
}
|
||||
|
||||
void SweatDecorator::_update()
|
||||
{
|
||||
uint32_t now = GetHAL().millis();
|
||||
|
||||
// 检查自动销毁
|
||||
if (_has_lifetime && now >= _destroy_at) {
|
||||
requestDestroy();
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查动画帧更新
|
||||
if (_animation_interval_ms > 0 && now >= _next_animation_tick) {
|
||||
_next_animation_tick = now + _animation_interval_ms;
|
||||
|
||||
int current_y_frame = _sweat_pos_y_frames[_animation_index];
|
||||
|
||||
if (current_y_frame == 0) {
|
||||
// 特殊帧:隐藏图像
|
||||
setVisible(false);
|
||||
} else {
|
||||
// 普通帧:移动位置并显示
|
||||
setVisible(true);
|
||||
_sweat->setPos(_sweat_default_position.x, current_y_frame);
|
||||
}
|
||||
|
||||
// 步进索引
|
||||
_animation_index = (_animation_index + 1) % _sweat_pos_y_frames.size();
|
||||
}
|
||||
}
|
||||
|
||||
void SweatDecorator::setPosition(int x, int y)
|
||||
{
|
||||
Element::setPosition({x, y});
|
||||
_sweat->setPos(x, y);
|
||||
// 注意:这里的 setPosition 会覆盖动画中的 x 坐标
|
||||
if (_sweat) {
|
||||
_sweat->setPos(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
void SweatDecorator::setRotation(int rotation)
|
||||
{
|
||||
Element::setRotation(rotation);
|
||||
_sweat->setRotation(rotation);
|
||||
if (_sweat) {
|
||||
_sweat->setRotation(rotation);
|
||||
}
|
||||
}
|
||||
|
||||
void SweatDecorator::setColor(lv_color_t color)
|
||||
{
|
||||
_sweat->setImageRecolor(color);
|
||||
if (_sweat) {
|
||||
_sweat->setImageRecolor(color);
|
||||
}
|
||||
}
|
||||
|
||||
void SweatDecorator::setVisible(bool visible)
|
||||
{
|
||||
Element::setVisible(visible);
|
||||
_sweat->setHidden(!visible);
|
||||
if (_sweat) {
|
||||
_sweat->setHidden(!visible);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,6 +157,9 @@ static FeatureKeyframe parse_feature(ArduinoJson::JsonObject jsonObject)
|
||||
if (jsonObject["weight"].is<int>()) {
|
||||
kf.weight = jsonObject["weight"];
|
||||
}
|
||||
if (jsonObject["size"].is<int>()) {
|
||||
kf.size = jsonObject["size"];
|
||||
}
|
||||
return kf;
|
||||
}
|
||||
|
||||
@@ -210,6 +213,12 @@ KeyframeSequence parse_sequence_from_json(const char* jsonContent)
|
||||
if (kfObj["pitchServo"].is<ArduinoJson::JsonObject>()) {
|
||||
kf.pitchServo = parse_servo(kfObj["pitchServo"]);
|
||||
}
|
||||
if (kfObj["leftRgbColor"].is<const char*>()) {
|
||||
kf.leftRgbColor = kfObj["leftRgbColor"].as<std::string>();
|
||||
}
|
||||
if (kfObj["rightRgbColor"].is<const char*>()) {
|
||||
kf.rightRgbColor = kfObj["rightRgbColor"].as<std::string>();
|
||||
}
|
||||
if (kfObj["durationMs"].is<int>()) {
|
||||
kf.durationMs = kfObj["durationMs"];
|
||||
}
|
||||
@@ -221,3 +230,35 @@ KeyframeSequence parse_sequence_from_json(const char* jsonContent)
|
||||
}
|
||||
|
||||
} // namespace stackchan::animation
|
||||
|
||||
namespace stackchan::addon {
|
||||
|
||||
void update_neon_light_from_json(NeonLight* left, NeonLight* right, const char* jsonContent)
|
||||
{
|
||||
if (!left || !right || !jsonContent) {
|
||||
return;
|
||||
}
|
||||
|
||||
ArduinoJson::JsonDocument doc;
|
||||
auto error = ArduinoJson::deserializeJson(doc, jsonContent);
|
||||
if (error) {
|
||||
mclog::tagError(_tag, "deserializeJson failed: {}", error.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
if (doc["leftRgbDuration"].is<float>()) {
|
||||
left->setDuration(doc["leftRgbDuration"].as<float>());
|
||||
}
|
||||
if (doc["leftRgbColor"].is<std::string>()) {
|
||||
left->setColor(doc["leftRgbColor"].as<std::string>());
|
||||
}
|
||||
|
||||
if (doc["rightRgbDuration"].is<float>()) {
|
||||
right->setDuration(doc["rightRgbDuration"].as<float>());
|
||||
}
|
||||
if (doc["rightRgbColor"].is<std::string>()) {
|
||||
right->setColor(doc["rightRgbColor"].as<std::string>());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace stackchan::addon
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "../avatar/avatar.h"
|
||||
#include "../motion/motion.h"
|
||||
#include "../animation/animation.h"
|
||||
#include "../addons/neon_light/neon_light.h"
|
||||
|
||||
namespace stackchan {
|
||||
|
||||
@@ -22,4 +23,8 @@ namespace animation {
|
||||
KeyframeSequence parse_sequence_from_json(const char* jsonContent);
|
||||
}
|
||||
|
||||
namespace addon {
|
||||
void update_neon_light_from_json(NeonLight* left, NeonLight* right, const char* jsonContent);
|
||||
}
|
||||
|
||||
} // namespace stackchan
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#pragma once
|
||||
#include "avatar/avatar.h"
|
||||
#include "motion/motion.h"
|
||||
#include "addons/neon_light/neon_light.h"
|
||||
#include "utils/object_pool.h"
|
||||
|
||||
namespace stackchan {
|
||||
@@ -23,6 +24,12 @@ public:
|
||||
virtual avatar::Avatar& avatar() = 0;
|
||||
|
||||
virtual bool hasAvatar() = 0;
|
||||
|
||||
// virtual bool hasMotion() = 0; // Motion must be always present
|
||||
|
||||
virtual addon::NeonLight& leftNeonLight() = 0;
|
||||
|
||||
virtual addon::NeonLight& rightNeonLight() = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#pragma once
|
||||
#include "timed.h"
|
||||
#include "../modifiable.h"
|
||||
#include "../utils/random.h"
|
||||
#include <hal/hal.h>
|
||||
#include <cstdint>
|
||||
|
||||
namespace stackchan {
|
||||
@@ -13,16 +15,27 @@ namespace stackchan {
|
||||
* @brief
|
||||
*
|
||||
*/
|
||||
class BlinkModifier : public TimerModifier {
|
||||
class BlinkModifier : public Modifier {
|
||||
public:
|
||||
/**
|
||||
* @param destroyAfterMs 持续多久后停止眨眼并销毁(0 为永久)
|
||||
* @param openIntervalMs 睁眼持续时间
|
||||
* @param closeIntervalMs 闭眼持续时间(瞬间)
|
||||
*/
|
||||
BlinkModifier(uint32_t destroyAfterMs = 0, uint32_t openIntervalMs = 5200, uint32_t closeIntervalMs = 200)
|
||||
: _open_interval_ms(openIntervalMs), _close_interval_ms(closeIntervalMs)
|
||||
{
|
||||
if (destroyAfterMs != 0) {
|
||||
scheduleDestroy(destroyAfterMs);
|
||||
uint32_t now = GetHAL().millis();
|
||||
|
||||
// 处理销毁计时
|
||||
if (destroyAfterMs > 0) {
|
||||
_destroy_at = now + destroyAfterMs;
|
||||
_has_lifetime = true;
|
||||
}
|
||||
|
||||
_should_close = true;
|
||||
// 初始化:从睁眼状态开始,立即准备闭眼
|
||||
_state = State::OPEN;
|
||||
_next_state_tick = now + _open_interval_ms;
|
||||
}
|
||||
|
||||
void resyncEyeWeights()
|
||||
@@ -32,49 +45,73 @@ public:
|
||||
|
||||
void _update(Modifiable& stackchan) override
|
||||
{
|
||||
TimerModifier::_update(stackchan);
|
||||
|
||||
if (!stackchan.hasAvatar()) {
|
||||
if (!stackchan.hasAvatar() || stackchan.avatar().isModifyLocked()) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t now = GetHAL().millis();
|
||||
|
||||
// 1. 处理销毁逻辑
|
||||
if (_has_lifetime && now >= _destroy_at) {
|
||||
// 销毁前确保眼睛是睁开的
|
||||
if (_state == State::CLOSED) {
|
||||
apply_eye_weights(stackchan, _left_eye_weight, _right_eye_weight);
|
||||
}
|
||||
requestDestroy();
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 处理权重同步请求
|
||||
// 如果眼睛正闭着,我们只记录权重,等睁眼时再应用
|
||||
if (_needs_resync) {
|
||||
_needs_resync = false;
|
||||
_left_eye_weight = stackchan.avatar().leftEye().getWeight();
|
||||
_right_eye_weight = stackchan.avatar().rightEye().getWeight();
|
||||
}
|
||||
|
||||
if (_should_close) {
|
||||
_should_close = false;
|
||||
// 3. 状态机切换逻辑
|
||||
if (now >= _next_state_tick) {
|
||||
if (_state == State::OPEN) {
|
||||
// 睁眼 -> 闭眼
|
||||
_state = State::CLOSED;
|
||||
_next_state_tick = now + _close_interval_ms;
|
||||
|
||||
_left_eye_weight = stackchan.avatar().leftEye().getWeight();
|
||||
_right_eye_weight = stackchan.avatar().rightEye().getWeight();
|
||||
// 闭眼瞬间,先备份当前权重(以防外部中途修改了权重)
|
||||
_left_eye_weight = stackchan.avatar().leftEye().getWeight();
|
||||
_right_eye_weight = stackchan.avatar().rightEye().getWeight();
|
||||
|
||||
stackchan.avatar().leftEye().setWeight(25);
|
||||
stackchan.avatar().rightEye().setWeight(25);
|
||||
apply_eye_weights(stackchan, 25, 25);
|
||||
} else {
|
||||
// 闭眼 -> 睁眼
|
||||
_state = State::OPEN;
|
||||
// 睁眼时间可以加一点随机抖动,看起来更自然
|
||||
uint32_t jitter = Random::getInstance().getInt(0, 500);
|
||||
_next_state_tick = now + _open_interval_ms + jitter;
|
||||
|
||||
getTimer().addTask(_close_interval_ms, 1, 0, [this]() { _should_open = true; });
|
||||
}
|
||||
|
||||
if (_should_open) {
|
||||
_should_open = false;
|
||||
|
||||
stackchan.avatar().leftEye().setWeight(_left_eye_weight);
|
||||
stackchan.avatar().rightEye().setWeight(_right_eye_weight);
|
||||
|
||||
getTimer().addTask(_open_interval_ms, 1, 0, [this]() { _should_close = true; });
|
||||
apply_eye_weights(stackchan, _left_eye_weight, _right_eye_weight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
enum class State { OPEN, CLOSED };
|
||||
|
||||
void apply_eye_weights(Modifiable& stackchan, int left, int right)
|
||||
{
|
||||
stackchan.avatar().leftEye().setWeight(left);
|
||||
stackchan.avatar().rightEye().setWeight(right);
|
||||
}
|
||||
|
||||
State _state;
|
||||
uint32_t _next_state_tick = 0;
|
||||
uint32_t _open_interval_ms;
|
||||
uint32_t _close_interval_ms;
|
||||
|
||||
bool _should_close = false;
|
||||
bool _should_open = false;
|
||||
uint32_t _destroy_at = 0;
|
||||
bool _has_lifetime = false;
|
||||
bool _needs_resync = false;
|
||||
int _left_eye_weight = 0;
|
||||
int _right_eye_weight = 0;
|
||||
int _left_eye_weight = 100;
|
||||
int _right_eye_weight = 100;
|
||||
};
|
||||
|
||||
} // namespace stackchan
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#pragma once
|
||||
#include "timed.h"
|
||||
#include "../modifiable.h"
|
||||
#include <hal/hal.h>
|
||||
#include <cstdint>
|
||||
#include <cmath>
|
||||
|
||||
namespace stackchan {
|
||||
|
||||
@@ -13,83 +15,92 @@ namespace stackchan {
|
||||
* @brief
|
||||
*
|
||||
*/
|
||||
class BreathModifier : public TimerModifier {
|
||||
class BreathModifier : public Modifier {
|
||||
public:
|
||||
BreathModifier(uint32_t destroyAfterMs = 0, uint32_t updateIntervalMs = 600)
|
||||
/**
|
||||
* @param destroyAfterMs 持续时间(0 为永久)
|
||||
* @param amplitude 呼吸幅度,单位像素
|
||||
* @param breathCycleMs 呼吸一次的周期(吸+呼)
|
||||
* @param updateIntervalMs 更新间隔
|
||||
*/
|
||||
BreathModifier(uint32_t destroyAfterMs = 0, int amplitude = 16, uint32_t breathCycleMs = 6600,
|
||||
uint32_t updateIntervalMs = 600)
|
||||
: _amplitude(amplitude), _breath_cycle_ms(breathCycleMs), _update_interval_ms(updateIntervalMs)
|
||||
{
|
||||
if (destroyAfterMs != 0) {
|
||||
scheduleDestroy(destroyAfterMs);
|
||||
_start_tick = GetHAL().millis();
|
||||
if (destroyAfterMs > 0) {
|
||||
_destroy_at = _start_tick + destroyAfterMs;
|
||||
_has_lifetime = true;
|
||||
}
|
||||
|
||||
if (updateIntervalMs == 0) {
|
||||
requestDestroy();
|
||||
}
|
||||
|
||||
getTimer().addTask(updateIntervalMs, -1, 0, [this]() { _update_flag = true; });
|
||||
}
|
||||
|
||||
void _update(Modifiable& stackchan) override
|
||||
{
|
||||
TimerModifier::_update(stackchan);
|
||||
if (!stackchan.hasAvatar()) return;
|
||||
|
||||
if (!stackchan.hasAvatar()) {
|
||||
uint32_t now = GetHAL().millis();
|
||||
|
||||
// 销毁逻辑
|
||||
if (_has_lifetime && now >= _destroy_at) {
|
||||
reset_position(stackchan.avatar()); // 销毁前复位
|
||||
requestDestroy();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_update_flag) {
|
||||
if (now - _last_update_tick < _update_interval_ms) {
|
||||
return;
|
||||
}
|
||||
_update_flag = false;
|
||||
_last_update_tick = now;
|
||||
|
||||
// Check state switch
|
||||
_breathe_count++;
|
||||
if (_is_breathing_in) {
|
||||
if (_breathe_count >= _breathe_in_count) {
|
||||
_is_breathing_in = false;
|
||||
_breathe_count = 0;
|
||||
}
|
||||
} else {
|
||||
if (_breathe_count >= _breathe_out_count) {
|
||||
_is_breathing_in = true;
|
||||
_breathe_count = 0;
|
||||
}
|
||||
}
|
||||
// 使用正弦波计算偏移量
|
||||
// (now - _start_tick) / cycle 得到进度,乘以 2PI 传给 sin
|
||||
float phase = (float)((now - _start_tick) % _breath_cycle_ms) / _breath_cycle_ms;
|
||||
float sin_val = sinf(phase * 2.0f * M_PI);
|
||||
|
||||
// Update breath
|
||||
auto& avatar = stackchan.avatar();
|
||||
_left_eye_position = avatar.leftEye().getPosition();
|
||||
_right_eye_position = avatar.rightEye().getPosition();
|
||||
_mouth_position = avatar.mouth().getPosition();
|
||||
// 计算当前偏移
|
||||
int current_offset = static_cast<int>(sin_val * _amplitude);
|
||||
|
||||
if (_is_breathing_in) {
|
||||
_left_eye_position.y += _breathe_in_offset;
|
||||
_right_eye_position.y += _breathe_in_offset;
|
||||
_mouth_position.y += _breathe_in_offset;
|
||||
} else {
|
||||
_left_eye_position.y += _breathe_out_offset;
|
||||
_right_eye_position.y += _breathe_out_offset;
|
||||
_mouth_position.y += _breathe_out_offset;
|
||||
}
|
||||
|
||||
avatar.leftEye().setPosition(_left_eye_position);
|
||||
avatar.rightEye().setPosition(_right_eye_position);
|
||||
avatar.mouth().setPosition(_mouth_position);
|
||||
// 应用增量偏移
|
||||
apply_relative_offset(stackchan.avatar(), current_offset);
|
||||
}
|
||||
|
||||
private:
|
||||
// Keep in and out total offset added up to 0
|
||||
const int _breathe_in_count = 7;
|
||||
const int _breathe_in_offset = -4;
|
||||
const int _breathe_out_count = 7;
|
||||
const int _breathe_out_offset = 4;
|
||||
void apply_relative_offset(avatar::Avatar& avatar, int new_offset)
|
||||
{
|
||||
// 计算本次需要移动的差值
|
||||
int delta = new_offset - _last_applied_offset;
|
||||
if (delta == 0) return;
|
||||
|
||||
bool _update_flag = false;
|
||||
bool _is_breathing_in = true;
|
||||
int _breathe_count = 0;
|
||||
// 批量移动五官
|
||||
move_component(avatar.leftEye(), delta);
|
||||
move_component(avatar.rightEye(), delta);
|
||||
move_component(avatar.mouth(), delta);
|
||||
|
||||
uitk::Vector2i _left_eye_position;
|
||||
uitk::Vector2i _right_eye_position;
|
||||
uitk::Vector2i _mouth_position;
|
||||
_last_applied_offset = new_offset;
|
||||
}
|
||||
|
||||
void move_component(avatar::Feature& comp, int delta_y)
|
||||
{
|
||||
auto pos = comp.getPosition();
|
||||
pos.y += delta_y;
|
||||
comp.setPosition(pos);
|
||||
}
|
||||
|
||||
void reset_position(avatar::Avatar& avatar)
|
||||
{
|
||||
// 将偏移归零
|
||||
apply_relative_offset(avatar, 0);
|
||||
}
|
||||
|
||||
int _amplitude;
|
||||
uint32_t _breath_cycle_ms;
|
||||
uint32_t _update_interval_ms;
|
||||
uint32_t _start_tick = 0;
|
||||
uint32_t _last_update_tick = 0;
|
||||
uint32_t _destroy_at = 0;
|
||||
bool _has_lifetime = false;
|
||||
|
||||
int _last_applied_offset = 0; // 记录上一次应用的偏移量,用于增量更新
|
||||
};
|
||||
|
||||
} // namespace stackchan
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#pragma once
|
||||
#include "timed.h"
|
||||
#include "../modifiable.h"
|
||||
#include "../avatar/decorators/decorators.h"
|
||||
#include "../utils/random.h"
|
||||
#include <smooth_ui_toolkit.hpp>
|
||||
@@ -14,22 +14,20 @@
|
||||
|
||||
namespace stackchan {
|
||||
|
||||
class HeadPetModifier : public TimerModifier {
|
||||
/**
|
||||
* @brief
|
||||
*
|
||||
*/
|
||||
class HeadPetModifier : public Modifier {
|
||||
public:
|
||||
HeadPetModifier(uint32_t restoreDelayMs = 3000) : _restore_delay_ms(restoreDelayMs)
|
||||
{
|
||||
// 绑定信号
|
||||
_signal_connection = GetHAL().onHeadPetGesture.connect([this](HeadPetGesture gesture) {
|
||||
LvglLockGuard lock;
|
||||
|
||||
if (gesture == HeadPetGesture::SwipeForward || gesture == HeadPetGesture::SwipeBackward) {
|
||||
_trigger_happy = true;
|
||||
_trigger_release = false;
|
||||
|
||||
// 每次抚摸都打断恢复倒计时
|
||||
_should_restore = false;
|
||||
_restore_timer_active = false;
|
||||
_event_swipe = true;
|
||||
} else if (gesture == HeadPetGesture::Release) {
|
||||
_trigger_release = true;
|
||||
_event_release = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -41,105 +39,124 @@ public:
|
||||
|
||||
void _update(Modifiable& stackchan) override
|
||||
{
|
||||
TimerModifier::_update(stackchan);
|
||||
uint32_t now = GetHAL().millis();
|
||||
|
||||
if (_trigger_happy) {
|
||||
_trigger_happy = false;
|
||||
|
||||
// 1. 首次进入记录当前位置
|
||||
if (!_in_happy_state) {
|
||||
_prev_emotion = stackchan.avatar().getEmotion();
|
||||
_prev_pitch = stackchan.motion().getCurrentPitchAngle();
|
||||
_prev_yaw = stackchan.motion().getCurrentYawAngle();
|
||||
_in_happy_state = true;
|
||||
}
|
||||
|
||||
// 2. 表情反馈
|
||||
stackchan.avatar().setEmotion(avatar::Emotion::Happy);
|
||||
int duration = Random::getInstance().getInt(1500, 2500);
|
||||
stackchan.avatar().addDecorator(
|
||||
std::make_unique<avatar::HeartDecorator>(lv_screen_active(), duration, 500));
|
||||
|
||||
// 3. 执行随机动作
|
||||
perform_random_motion(stackchan);
|
||||
// 处理“被抚摸中”事件
|
||||
if (_event_swipe) {
|
||||
_event_swipe = false;
|
||||
handle_swipe(stackchan);
|
||||
// 只要在摸,就推迟恢复时间
|
||||
_is_waiting_restore = false;
|
||||
}
|
||||
|
||||
if (_trigger_release) {
|
||||
_trigger_release = false;
|
||||
// 处理“手松开”事件
|
||||
if (_event_release) {
|
||||
_event_release = false;
|
||||
if (_in_happy_state) {
|
||||
// 启动恢复计时
|
||||
_restore_timer_active = true;
|
||||
getTimer().addTask(_restore_delay_ms, 1, 0, [this]() { _should_restore = true; });
|
||||
_is_waiting_restore = true;
|
||||
_restore_tick = now + _restore_delay_ms;
|
||||
}
|
||||
}
|
||||
|
||||
if (_should_restore) {
|
||||
_should_restore = false;
|
||||
if (_restore_timer_active && _in_happy_state) {
|
||||
// 恢复原状
|
||||
stackchan.avatar().setEmotion(_prev_emotion);
|
||||
|
||||
auto& motion = stackchan.motion();
|
||||
|
||||
motion.moveWithSpeed(_prev_yaw, _prev_pitch, 200);
|
||||
|
||||
_in_happy_state = false;
|
||||
_restore_timer_active = false;
|
||||
}
|
||||
// 处理恢复逻辑
|
||||
if (_is_waiting_restore && now >= _restore_tick) {
|
||||
_is_waiting_restore = false;
|
||||
restore_original_state(stackchan);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void perform_random_motion(Modifiable& stackchan)
|
||||
void handle_swipe(Modifiable& stackchan)
|
||||
{
|
||||
auto& avatar = stackchan.avatar();
|
||||
|
||||
// 首次进入开心状态,记录原始信息
|
||||
if (!_in_happy_state) {
|
||||
_in_happy_state = true;
|
||||
_prev_emotion = avatar.getEmotion();
|
||||
auto angles = stackchan.motion().getCurrentAngles();
|
||||
_prev_yaw = angles.x;
|
||||
_prev_pitch = angles.y;
|
||||
}
|
||||
|
||||
// 视觉反馈
|
||||
avatar.setEmotion(avatar::Emotion::Happy);
|
||||
|
||||
// 添加爱心装饰
|
||||
int duration = Random::getInstance().getInt(1500, 2500);
|
||||
avatar.removeDecorator(_heart_decorator_id);
|
||||
avatar.removeDecorator(_shy_decorator_id);
|
||||
_heart_decorator_id =
|
||||
avatar.addDecorator(std::make_unique<avatar::HeartDecorator>(lv_screen_active(), duration, 500));
|
||||
_shy_decorator_id = avatar.addDecorator(std::make_unique<avatar::ShyDecorator>(lv_screen_active(), duration));
|
||||
|
||||
// 动作反馈
|
||||
perform_pet_motion(stackchan);
|
||||
}
|
||||
|
||||
void restore_original_state(Modifiable& stackchan)
|
||||
{
|
||||
if (!_in_happy_state) {
|
||||
return;
|
||||
}
|
||||
|
||||
stackchan.avatar().setEmotion(_prev_emotion);
|
||||
stackchan.motion().moveWithSpeed(_prev_yaw, _prev_pitch, 200);
|
||||
|
||||
_in_happy_state = false;
|
||||
}
|
||||
|
||||
void perform_pet_motion(Modifiable& stackchan)
|
||||
{
|
||||
int action = Random::getInstance().getInt(0, 2);
|
||||
auto& motion = stackchan.motion();
|
||||
if (motion.isModifyLocked() || motion.isMoving()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int speed = Random::getInstance().getInt(400, 800);
|
||||
int action = Random::getInstance().getInt(0, 2);
|
||||
int speed = Random::getInstance().getInt(300, 500);
|
||||
|
||||
int32_t target_pitch = _prev_pitch;
|
||||
int32_t target_yaw = _prev_yaw;
|
||||
int32_t target_pitch = _prev_pitch;
|
||||
|
||||
// 角度限制: 10 units = 1度
|
||||
switch (action) {
|
||||
case 0: // 舒服地蹭手/抬头
|
||||
case 0: // 抬头
|
||||
target_pitch += Random::getInstance().getInt(150, 250);
|
||||
target_yaw += Random::getInstance().getInt(-50, 50); // 微调 Yaw
|
||||
target_yaw += Random::getInstance().getInt(-50, 50);
|
||||
break;
|
||||
|
||||
case 1: // 开心歪头/摇晃
|
||||
target_pitch -= Random::getInstance().getInt(0, 50); // 略微低头
|
||||
if (Random::getInstance().getInt(0, 1) == 0) {
|
||||
target_yaw += Random::getInstance().getInt(100, 200);
|
||||
} else {
|
||||
target_yaw -= Random::getInstance().getInt(100, 200);
|
||||
}
|
||||
case 1: // 歪头
|
||||
target_pitch -= Random::getInstance().getInt(0, 50);
|
||||
target_yaw += (Random::getInstance().getInt(0, 1) == 0 ? 150 : -150);
|
||||
break;
|
||||
|
||||
case 2: // 兴奋大幅度抬头
|
||||
case 2: // 大幅度开心
|
||||
target_pitch += Random::getInstance().getInt(250, 400);
|
||||
break;
|
||||
}
|
||||
|
||||
target_pitch = uitk::clamp(target_pitch, 0, 900);
|
||||
target_yaw = uitk::clamp(target_yaw, -1280, 1280);
|
||||
// 自然范围限制
|
||||
target_pitch = uitk::clamp(target_pitch, 0, 540);
|
||||
target_yaw = uitk::clamp(target_yaw, -512, 512);
|
||||
|
||||
motion.moveWithSpeed(target_yaw, target_pitch, speed);
|
||||
}
|
||||
|
||||
// 信号相关
|
||||
int _signal_connection;
|
||||
volatile bool _event_swipe = false;
|
||||
volatile bool _event_release = false;
|
||||
|
||||
// 状态机相关
|
||||
bool _in_happy_state = false;
|
||||
bool _is_waiting_restore = false;
|
||||
uint32_t _restore_tick = 0;
|
||||
uint32_t _restore_delay_ms;
|
||||
int _heart_decorator_id = -1;
|
||||
int _shy_decorator_id = -1;
|
||||
|
||||
bool _trigger_happy = false;
|
||||
bool _trigger_release = false;
|
||||
|
||||
bool _in_happy_state = false;
|
||||
bool _restore_timer_active = false;
|
||||
bool _should_restore = false;
|
||||
|
||||
// 记忆相关
|
||||
avatar::Emotion _prev_emotion = avatar::Emotion::Neutral;
|
||||
int32_t _prev_pitch = 0;
|
||||
int32_t _prev_yaw = 0;
|
||||
int32_t _prev_pitch = 0;
|
||||
};
|
||||
|
||||
} // namespace stackchan
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#pragma once
|
||||
#include "../modifiable.h"
|
||||
#include "../utils/random.h"
|
||||
#include <smooth_ui_toolkit.hpp>
|
||||
#include <hal/hal.h>
|
||||
#include <cstdint>
|
||||
|
||||
namespace stackchan {
|
||||
|
||||
/**
|
||||
* @brief
|
||||
*
|
||||
*/
|
||||
class IdleExpressionModifier : public Modifier {
|
||||
public:
|
||||
IdleExpressionModifier(uint32_t interval_min = 2000, uint32_t interval_max = 6000)
|
||||
: _interval_min(interval_min), _interval_max(interval_max)
|
||||
{
|
||||
_next_tick = GetHAL().millis() + 500;
|
||||
}
|
||||
|
||||
void _update(Modifiable& stackchan) override
|
||||
{
|
||||
if (!stackchan.hasAvatar() || stackchan.avatar().isModifyLocked()) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t now = GetHAL().millis();
|
||||
if (now < _next_tick) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 执行随机微表情
|
||||
perform_idle_emotion(stackchan.avatar());
|
||||
|
||||
// 计算下一次触发时间
|
||||
uint32_t delay = Random::getInstance().getInt(_interval_min, _interval_max);
|
||||
_next_tick = now + delay;
|
||||
}
|
||||
|
||||
private:
|
||||
void perform_idle_emotion(avatar::Avatar& avatar)
|
||||
{
|
||||
int action = Random::getInstance().getInt(0, 100);
|
||||
|
||||
if (action < 70) {
|
||||
// 【动作 1:眼神游离】双眼协同移动一个小范围
|
||||
int offsetX = Random::getInstance().getInt(-20, 20);
|
||||
int offsetY = Random::getInstance().getInt(-15, 15);
|
||||
avatar.leftEye().setPosition({offsetX, offsetY});
|
||||
avatar.rightEye().setPosition({offsetX, offsetY});
|
||||
|
||||
// 嘴巴也配合动一下
|
||||
avatar.mouth().setPosition({0, Random::getInstance().getInt(0, 10)});
|
||||
} else if (action < 80) {
|
||||
// 【动作 3:嘴巴歪一下】旋转角度
|
||||
// Rotation: 0~3600
|
||||
int rotation = Random::getInstance().getInt(-30, 30);
|
||||
// 加上基准值
|
||||
avatar.mouth().setRotation(rotation < 0 ? 3600 + rotation : rotation);
|
||||
} else {
|
||||
// 【动作 4:回归中性】
|
||||
reset_to_neutral(avatar);
|
||||
}
|
||||
}
|
||||
|
||||
void reset_to_neutral(avatar::Avatar& avatar)
|
||||
{
|
||||
// 位置回归中心
|
||||
avatar.leftEye().setPosition({0, 0});
|
||||
avatar.rightEye().setPosition({0, 0});
|
||||
avatar.mouth().setPosition({0, 0});
|
||||
|
||||
// 缩放回归正常
|
||||
avatar.leftEye().setSize(0);
|
||||
avatar.rightEye().setSize(0);
|
||||
|
||||
// 旋转和权重回归
|
||||
avatar.mouth().setRotation(0);
|
||||
avatar.mouth().setWeight(0);
|
||||
}
|
||||
|
||||
uint32_t _interval_min;
|
||||
uint32_t _interval_max;
|
||||
uint32_t _next_tick = 0;
|
||||
};
|
||||
|
||||
} // namespace stackchan
|
||||
@@ -4,108 +4,118 @@
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#pragma once
|
||||
#include "timed.h"
|
||||
#include "../modifiable.h"
|
||||
#include "../utils/random.h"
|
||||
#include <smooth_ui_toolkit.hpp>
|
||||
// #include <mooncake_log.h>
|
||||
#include <hal/hal.h>
|
||||
#include <cstdint>
|
||||
|
||||
namespace stackchan {
|
||||
|
||||
class IdleMotionModifier : public TimerModifier {
|
||||
/**
|
||||
* @brief
|
||||
*
|
||||
*/
|
||||
class IdleMotionModifier : public Modifier {
|
||||
public:
|
||||
IdleMotionModifier(uint32_t interval_min = 2000, uint32_t interval_max = 8000)
|
||||
IdleMotionModifier(uint32_t interval_min = 4000, uint32_t interval_max = 8000)
|
||||
: _interval_min(interval_min), _interval_max(interval_max)
|
||||
{
|
||||
schedule_next_move();
|
||||
_next_tick = GetHAL().millis() + 1000; // 启动 1 秒后开始第一次动作
|
||||
}
|
||||
|
||||
void pause()
|
||||
{
|
||||
_paused = true;
|
||||
}
|
||||
|
||||
void resume()
|
||||
{
|
||||
if (_paused) {
|
||||
_paused = false;
|
||||
if (!_timer_active) {
|
||||
schedule_next_move();
|
||||
}
|
||||
_paused = false;
|
||||
_next_tick = GetHAL().millis() + 500;
|
||||
}
|
||||
}
|
||||
|
||||
void _update(Modifiable& stackchan) override
|
||||
{
|
||||
TimerModifier::_update(stackchan);
|
||||
if (_paused || !stackchan.hasAvatar()) return;
|
||||
|
||||
if (_trigger_move) {
|
||||
_trigger_move = false;
|
||||
_timer_active = false;
|
||||
if (!_paused) {
|
||||
perform_idle_motion(stackchan);
|
||||
schedule_next_move();
|
||||
}
|
||||
uint32_t now = GetHAL().millis();
|
||||
|
||||
// 如果时间没到,直接跳过
|
||||
if (now < _next_tick) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果上次动作还没做完,就把下一次尝试推迟 500ms,避免指令堆积
|
||||
if (stackchan.motion().isMoving()) {
|
||||
_next_tick = now + 500;
|
||||
return;
|
||||
}
|
||||
|
||||
// 执行动作
|
||||
perform_idle_motion(stackchan);
|
||||
|
||||
// 算下一次的时间间隔
|
||||
uint32_t delay = Random::getInstance().getInt(_interval_min, _interval_max);
|
||||
_next_tick = now + delay;
|
||||
// mclog::info("next idle motion in {} ms", delay);
|
||||
}
|
||||
|
||||
private:
|
||||
void schedule_next_move()
|
||||
{
|
||||
if (_paused) {
|
||||
_timer_active = false;
|
||||
return;
|
||||
}
|
||||
_timer_active = true;
|
||||
uint32_t delay = Random::getInstance().getInt(_interval_min, _interval_max);
|
||||
getTimer().addTask(delay, 1, 0, [this]() { _trigger_move = true; });
|
||||
}
|
||||
|
||||
void perform_idle_motion(Modifiable& stackchan)
|
||||
{
|
||||
auto& motion = stackchan.motion();
|
||||
int action = Random::getInstance().getInt(0, 10); // Probabilistic choice
|
||||
|
||||
int32_t current_yaw = motion.getCurrentYawAngle();
|
||||
int32_t current_pitch = motion.getCurrentPitchAngle();
|
||||
|
||||
int32_t target_yaw = current_yaw;
|
||||
int32_t target_pitch = current_pitch;
|
||||
int speed = 400;
|
||||
|
||||
if (action < 5) {
|
||||
// 50% chance: Casual look around (Look at a random point in front)
|
||||
target_yaw = Random::getInstance().getInt(-300, 300);
|
||||
target_pitch = Random::getInstance().getInt(100, 250);
|
||||
speed = Random::getInstance().getInt(200, 500); // Slow to medium speed
|
||||
} else if (action < 7) {
|
||||
// 20% chance: Small adjustment / "Breathing" (Very small movement from current)
|
||||
target_yaw += Random::getInstance().getInt(-100, 100);
|
||||
target_pitch += Random::getInstance().getInt(-50, 50);
|
||||
speed = Random::getInstance().getInt(100, 300); // Very slow
|
||||
} else if (action < 9) {
|
||||
// 20% chance: Quick glance (Faster, smaller range)
|
||||
target_yaw += Random::getInstance().getInt(-100, 100);
|
||||
target_pitch += Random::getInstance().getInt(-30, 30);
|
||||
speed = Random::getInstance().getInt(300, 600); // Fast
|
||||
} else {
|
||||
// 10% chance: Return to center / Neutral
|
||||
target_yaw = 0;
|
||||
target_pitch = 200; // Slightly looking up/forward
|
||||
speed = 100;
|
||||
if (motion.isModifyLocked()) {
|
||||
return;
|
||||
}
|
||||
|
||||
target_yaw = uitk::clamp(target_yaw, -1280, 1280);
|
||||
target_pitch = uitk::clamp(target_pitch, 0, 900);
|
||||
speed = uitk::clamp(speed, 0, 1000);
|
||||
int action = Random::getInstance().getInt(0, 100);
|
||||
|
||||
motion.moveWithSpeed(target_yaw, target_pitch, speed);
|
||||
if (action < 50) {
|
||||
// 【动作 1:随意环视】使用归一化坐标 (-1.0 ~ 1.0)
|
||||
float target_x = Random::getInstance().getFloat(-0.4f, 0.4f); // 左右看
|
||||
float target_y = Random::getInstance().getFloat(-0.95f, 0.2f); // 上下看
|
||||
int speed = Random::getInstance().getInt(150, 300);
|
||||
|
||||
// mclog::info("action 1: look at normalized ({}, {}) in speed {}", target_x, target_y, speed);
|
||||
motion.lookAtNormalized(target_x, target_y, speed);
|
||||
} else if (action < 80) {
|
||||
// 【动作 2:微小的观察动作】基于当前位置的小偏移
|
||||
auto current = motion.getCurrentAngles(); // Vector2i(yaw, pitch)
|
||||
|
||||
int diff_yaw = Random::getInstance().getInt(-150, 150);
|
||||
int diff_pitch = Random::getInstance().getInt(-80, 80);
|
||||
|
||||
int target_yaw = uitk::clamp(current.x + diff_yaw, -800, 800);
|
||||
int target_pitch = uitk::clamp(current.y + diff_pitch, 0, 600);
|
||||
int speed = Random::getInstance().getInt(100, 250);
|
||||
|
||||
// mclog::info("action 2: small move to ({}, {}) in speed {}", target_yaw, target_pitch, speed);
|
||||
motion.moveWithSpeed(target_yaw, target_pitch, speed);
|
||||
} else if (action < 90) {
|
||||
// 【动作 3:快速撇一眼】速度快,跨度中等
|
||||
int target_yaw = Random::getInstance().getInt(-500, 500);
|
||||
int target_pitch = Random::getInstance().getInt(100, 400);
|
||||
int speed = Random::getInstance().getInt(250, 400);
|
||||
|
||||
// mclog::info("action 3: quick glance to ({}, {}) in speed {}", target_yaw, target_pitch, speed);
|
||||
motion.moveWithSpeed(target_yaw, target_pitch, speed);
|
||||
} else {
|
||||
// 【动作 4:yaw 回正】
|
||||
int target_pitch = Random::getInstance().getInt(50, 400);
|
||||
int speed = Random::getInstance().getInt(100, 300);
|
||||
|
||||
// mclog::info("action 4: go home to (0, {}) in speed {}", target_pitch, speed);
|
||||
motion.moveWithSpeed(0, target_pitch, speed);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t _interval_min;
|
||||
uint32_t _interval_max;
|
||||
bool _trigger_move = false;
|
||||
bool _paused = false;
|
||||
bool _timer_active = false;
|
||||
uint32_t _next_tick = 0;
|
||||
bool _paused = false;
|
||||
};
|
||||
|
||||
} // namespace stackchan
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#pragma once
|
||||
#include "timed.h"
|
||||
#
|
||||
#include "../modifiable.h"
|
||||
#include <hal/hal.h>
|
||||
#include <cstdint>
|
||||
|
||||
namespace stackchan {
|
||||
@@ -14,61 +14,124 @@ namespace stackchan {
|
||||
* @brief
|
||||
*
|
||||
*/
|
||||
class IMUMotionModifier : public TimerModifier {
|
||||
class ImuEventModifier : public Modifier {
|
||||
public:
|
||||
IMUMotionModifier()
|
||||
ImuEventModifier(uint32_t reactionDurationMs = 4000) : _reaction_duration_ms(reactionDurationMs)
|
||||
{
|
||||
GetHAL().onImuMotionEvent.connect([&](ImuMotionEvent event) {
|
||||
if (_is_applied) {
|
||||
return;
|
||||
_signal_connection = GetHAL().onImuMotionEvent.connect([this](ImuMotionEvent event) {
|
||||
if (event == ImuMotionEvent::Shake) {
|
||||
_event_shake = true;
|
||||
}
|
||||
_new_event = event;
|
||||
});
|
||||
}
|
||||
|
||||
~ImuEventModifier()
|
||||
{
|
||||
GetHAL().onImuMotionEvent.disconnect(_signal_connection);
|
||||
}
|
||||
|
||||
void _update(Modifiable& stackchan) override
|
||||
{
|
||||
TimerModifier::_update(stackchan);
|
||||
uint32_t now = GetHAL().millis();
|
||||
|
||||
if (_new_event != ImuMotionEvent::None) {
|
||||
_new_event = ImuMotionEvent::None;
|
||||
apply(stackchan, false);
|
||||
_is_applied = true;
|
||||
_restore_flag = false;
|
||||
getTimer().addTask(2000, 0, 0, [&]() { _restore_flag = true; });
|
||||
// 收到晃动事件
|
||||
if (_event_shake) {
|
||||
_event_shake = false;
|
||||
handle_shake_start(stackchan, now);
|
||||
}
|
||||
|
||||
if (_restore_flag) {
|
||||
apply(stackchan, true);
|
||||
_restore_flag = false;
|
||||
_is_applied = false;
|
||||
}
|
||||
}
|
||||
// 如果处于晃动反应状态
|
||||
if (_is_reacting) {
|
||||
if (now >= _next_toggle_tick) {
|
||||
_next_toggle_tick = now + 600;
|
||||
|
||||
void apply(Modifiable& stackchan, bool isRestore)
|
||||
{
|
||||
auto& avatar = stackchan.avatar();
|
||||
if (isRestore) {
|
||||
avatar.leftEye().setPosition({0, 0});
|
||||
avatar.rightEye().setPosition({0, 0});
|
||||
avatar.leftEye().setSize(0);
|
||||
avatar.rightEye().setSize(0);
|
||||
avatar.mouth().setPosition({0, 0});
|
||||
avatar.mouth().setWeight(0);
|
||||
} else {
|
||||
avatar.leftEye().setPosition({-20, 0});
|
||||
avatar.rightEye().setPosition({20, 0});
|
||||
avatar.leftEye().setSize(60);
|
||||
avatar.rightEye().setSize(60);
|
||||
avatar.mouth().setPosition({0, -40});
|
||||
avatar.mouth().setWeight(58);
|
||||
_toggle_phase = !_toggle_phase;
|
||||
int mouth_rotation = _toggle_phase ? -25 : 25;
|
||||
|
||||
auto& avatar = stackchan.avatar();
|
||||
avatar.mouth().setRotation(mouth_rotation);
|
||||
avatar.mouth().setWeight(65);
|
||||
}
|
||||
|
||||
// Lock motion modify and move home
|
||||
auto& motion = stackchan.motion();
|
||||
if (!motion.isModifyLocked()) {
|
||||
motion.setModifyLock(true);
|
||||
motion.goHome(300);
|
||||
}
|
||||
|
||||
// 检查是否结束反应
|
||||
if (now >= _restore_at) {
|
||||
restore_state(stackchan);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
ImuMotionEvent _new_event = ImuMotionEvent::None;
|
||||
bool _is_applied = false;
|
||||
bool _restore_flag = false;
|
||||
void handle_shake_start(Modifiable& stackchan, uint32_t now)
|
||||
{
|
||||
if (!_is_reacting) {
|
||||
// 首次触发时,记录状态以便恢复
|
||||
_is_reacting = true;
|
||||
|
||||
auto& avatar = stackchan.avatar();
|
||||
|
||||
avatar.setModifyLock(true);
|
||||
avatar.leftEye().setVisible(false);
|
||||
avatar.rightEye().setVisible(false);
|
||||
|
||||
stackchan.avatar().removeDecorator(_dizzy_decorator_id);
|
||||
stackchan.avatar().removeDecorator(_shy_decorator_id);
|
||||
_dizzy_decorator_id =
|
||||
stackchan.avatar().addDecorator(std::make_unique<avatar::DizzyDecorator>(lv_screen_active(), 0, 300));
|
||||
_shy_decorator_id =
|
||||
stackchan.avatar().addDecorator(std::make_unique<avatar::ShyDecorator>(lv_screen_active(), 0));
|
||||
}
|
||||
|
||||
// 刷新恢复时间和切换时间
|
||||
_restore_at = now + _reaction_duration_ms;
|
||||
if (_next_toggle_tick <= now) {
|
||||
_next_toggle_tick = now; // 立即触发第一次嘴巴动作
|
||||
}
|
||||
}
|
||||
|
||||
void restore_state(Modifiable& stackchan)
|
||||
{
|
||||
if (!_is_reacting) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& avatar = stackchan.avatar();
|
||||
avatar.setModifyLock(false);
|
||||
avatar.leftEye().setVisible(true);
|
||||
avatar.rightEye().setVisible(true);
|
||||
avatar.mouth().setWeight(0);
|
||||
avatar.mouth().setRotation(0);
|
||||
|
||||
stackchan.avatar().removeDecorator(_dizzy_decorator_id);
|
||||
stackchan.avatar().removeDecorator(_shy_decorator_id);
|
||||
_dizzy_decorator_id = -1;
|
||||
_shy_decorator_id = -1;
|
||||
|
||||
auto& motion = stackchan.motion();
|
||||
motion.setModifyLock(false);
|
||||
|
||||
_is_reacting = false;
|
||||
}
|
||||
|
||||
// 信号相关
|
||||
int _signal_connection;
|
||||
volatile bool _event_shake = false;
|
||||
|
||||
// 状态控制
|
||||
bool _is_reacting = false;
|
||||
bool _toggle_phase = false;
|
||||
uint32_t _restore_at = 0;
|
||||
uint32_t _next_toggle_tick = 0;
|
||||
uint32_t _reaction_duration_ms;
|
||||
|
||||
int _dizzy_decorator_id = -1;
|
||||
int _shy_decorator_id = -1;
|
||||
};
|
||||
|
||||
} // namespace stackchan
|
||||
|
||||
@@ -10,5 +10,6 @@
|
||||
#include "speaking.h"
|
||||
#include "head_pet.h"
|
||||
#include "idle_motion.h"
|
||||
#include "idle_expression.h"
|
||||
#include "dance.h"
|
||||
#include "imu.h"
|
||||
|
||||
@@ -4,123 +4,144 @@
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#pragma once
|
||||
#include "timed.h"
|
||||
#include "../modifiable.h"
|
||||
#include "../utils/random.h"
|
||||
#include <smooth_ui_toolkit.hpp>
|
||||
#include <hal/hal.h>
|
||||
#include <cstdint>
|
||||
|
||||
namespace stackchan {
|
||||
|
||||
/**
|
||||
* @brief
|
||||
*
|
||||
*/
|
||||
class SpeakingModifier : public TimerModifier {
|
||||
class SpeakingModifier : public Modifier {
|
||||
public:
|
||||
SpeakingModifier(uint32_t destroyAfterMs = 0, uint32_t updateIntervalMs = 180, bool enableMotion = true)
|
||||
: _enable_motion(enableMotion)
|
||||
/**
|
||||
* @param destroyAfterMs 持续说话时间(0 为永久,直到手动移除)
|
||||
* @param mouthIntervalMs 嘴巴开合频率(默认 180ms)
|
||||
* @param enableMotion 是否在说话时伴随头部微动
|
||||
*/
|
||||
SpeakingModifier(uint32_t destroyAfterMs = 0, uint32_t mouthIntervalMs = 180, bool enableMotion = true)
|
||||
: _mouth_interval_ms(mouthIntervalMs), _enable_motion(enableMotion)
|
||||
{
|
||||
if (destroyAfterMs != 0) {
|
||||
getTimer().addTask(destroyAfterMs, -1, 0, [this]() { _destroy_flag = true; });
|
||||
uint32_t now = GetHAL().millis();
|
||||
|
||||
// 销毁计时
|
||||
if (destroyAfterMs > 0) {
|
||||
_destroy_at = now + destroyAfterMs;
|
||||
_has_lifetime = true;
|
||||
}
|
||||
|
||||
if (updateIntervalMs == 0) {
|
||||
requestDestroy();
|
||||
}
|
||||
|
||||
getTimer().addTask(updateIntervalMs, -1, 0, [this]() { _update_flag = true; });
|
||||
// 嘴巴计时
|
||||
_next_mouth_tick = now + _mouth_interval_ms;
|
||||
|
||||
// 动作计时
|
||||
if (_enable_motion) {
|
||||
schedule_next_motion();
|
||||
_next_motion_tick = now + Random::getInstance().getInt(1000, 2000);
|
||||
}
|
||||
|
||||
_need_get_prev_angles = true;
|
||||
}
|
||||
|
||||
void _update(Modifiable& stackchan) override
|
||||
{
|
||||
TimerModifier::_update(stackchan);
|
||||
|
||||
if (!stackchan.hasAvatar()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_motion_flag) {
|
||||
_motion_flag = false;
|
||||
perform_speaking_motion(stackchan);
|
||||
schedule_next_motion();
|
||||
}
|
||||
uint32_t now = GetHAL().millis();
|
||||
|
||||
if (!_update_flag) {
|
||||
return;
|
||||
}
|
||||
_update_flag = false;
|
||||
|
||||
if (_destroy_flag) {
|
||||
stackchan.avatar().mouth().setWeight(0);
|
||||
// 检查销毁逻辑
|
||||
if (_has_lifetime && now >= _destroy_at) {
|
||||
stackchan.avatar().mouth().setWeight(0); // 闭嘴
|
||||
requestDestroy();
|
||||
return;
|
||||
}
|
||||
|
||||
auto& random = Random::getInstance();
|
||||
// 嘴巴开合动画
|
||||
if (now >= _next_mouth_tick) {
|
||||
_next_mouth_tick = now + _mouth_interval_ms;
|
||||
animate_mouth(stackchan.avatar());
|
||||
}
|
||||
|
||||
_is_opened = !_is_opened;
|
||||
if (_is_opened) {
|
||||
stackchan.avatar().mouth().setWeight(random.getInt(_open_min_weight, _open_max_weight));
|
||||
} else {
|
||||
stackchan.avatar().mouth().setWeight(random.getInt(_close_min_weight, _close_max_weight));
|
||||
// 身体微动动作
|
||||
if (_enable_motion && now >= _next_motion_tick) {
|
||||
// 随机下一个动作的时间 (1.5s ~ 2.5s)
|
||||
_next_motion_tick = now + Random::getInstance().getInt(1500, 2500);
|
||||
perform_subtle_speaking_motion(stackchan);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void schedule_next_motion()
|
||||
void animate_mouth(avatar::Avatar& avatar)
|
||||
{
|
||||
uint32_t delay = Random::getInstance().getInt(1600, 2200);
|
||||
getTimer().addTask(delay, 1, 0, [this]() { _motion_flag = true; });
|
||||
_is_mouth_open = !_is_mouth_open;
|
||||
auto& random = Random::getInstance();
|
||||
|
||||
int weight = _is_mouth_open ? random.getInt(_open_min_weight, _open_max_weight)
|
||||
: random.getInt(_close_min_weight, _close_max_weight);
|
||||
|
||||
avatar.mouth().setWeight(weight);
|
||||
}
|
||||
|
||||
void perform_speaking_motion(Modifiable& stackchan)
|
||||
void perform_subtle_speaking_motion(Modifiable& stackchan)
|
||||
{
|
||||
auto& motion = stackchan.motion();
|
||||
int action = Random::getInstance().getInt(0, 10);
|
||||
|
||||
int32_t current_yaw = motion.getCurrentYawAngle();
|
||||
int32_t current_pitch = motion.getCurrentPitchAngle();
|
||||
|
||||
int32_t target_yaw = current_yaw;
|
||||
int32_t target_pitch = current_pitch;
|
||||
int speed = 120;
|
||||
|
||||
if (action < 4) {
|
||||
// Emphasize: Nod / Head bob
|
||||
target_pitch += Random::getInstance().getInt(-30, 60);
|
||||
speed = Random::getInstance().getInt(100, 160);
|
||||
} else if (action < 7) {
|
||||
// Look at audience: Small yaw changes
|
||||
target_yaw += Random::getInstance().getInt(-60, 60);
|
||||
target_pitch += Random::getInstance().getInt(-30, 30);
|
||||
speed = Random::getInstance().getInt(160, 200);
|
||||
} else if (action < 9) {
|
||||
// Reset / Center attention
|
||||
target_yaw = Random::getInstance().getInt(-60, 60);
|
||||
target_pitch = 200 + Random::getInstance().getInt(-50, 50);
|
||||
speed = 120;
|
||||
if (motion.isMoving()) {
|
||||
return;
|
||||
}
|
||||
|
||||
target_yaw = uitk::clamp(target_yaw, -1280, 1280);
|
||||
target_pitch = uitk::clamp(target_pitch, 0, 900);
|
||||
uitk::Vector2i current_actual_angles = motion.getCurrentAngles();
|
||||
|
||||
if (_need_get_prev_angles) {
|
||||
_prev_angles = current_actual_angles;
|
||||
_need_get_prev_angles = false;
|
||||
} else {
|
||||
// If there is a large external movement
|
||||
// sync the baseline angles to preventsnapping back to old position
|
||||
const int32_t threshold = 300;
|
||||
int32_t diff_x = std::abs(current_actual_angles.x - _prev_angles.x);
|
||||
int32_t diff_y = std::abs(current_actual_angles.y - _prev_angles.y);
|
||||
|
||||
if (diff_x > threshold || diff_y > threshold) {
|
||||
_prev_angles = current_actual_angles;
|
||||
}
|
||||
}
|
||||
|
||||
int32_t target_yaw = _prev_angles.x;
|
||||
int32_t target_pitch = _prev_angles.y;
|
||||
|
||||
int action = Random::getInstance().getInt(0, 10);
|
||||
int speed = Random::getInstance().getInt(100, 200); // 说话时的动作都很慢
|
||||
|
||||
if (action < 5) {
|
||||
// 动作 A:轻微点头 (Nod)
|
||||
target_pitch += Random::getInstance().getInt(-20, 50);
|
||||
} else {
|
||||
// 动作 B:轻微摆头 (Yaw drift)
|
||||
target_yaw += Random::getInstance().getInt(-40, 40);
|
||||
target_pitch += Random::getInstance().getInt(-20, 20);
|
||||
}
|
||||
|
||||
motion.moveWithSpeed(target_yaw, target_pitch, speed);
|
||||
}
|
||||
|
||||
// 配置常量
|
||||
const int _open_min_weight = 40;
|
||||
const int _open_max_weight = 70;
|
||||
const int _open_max_weight = 80;
|
||||
const int _close_min_weight = 0;
|
||||
const int _close_max_weight = 30;
|
||||
const int _close_max_weight = 20;
|
||||
|
||||
bool _enable_motion = false;
|
||||
bool _motion_flag = false;
|
||||
bool _update_flag = false;
|
||||
bool _destroy_flag = false;
|
||||
bool _is_opened = false;
|
||||
// 计时状态
|
||||
uint32_t _destroy_at = 0;
|
||||
uint32_t _next_mouth_tick = 0;
|
||||
uint32_t _next_motion_tick = 0;
|
||||
uint32_t _mouth_interval_ms;
|
||||
|
||||
bool _has_lifetime = false;
|
||||
bool _enable_motion = false;
|
||||
bool _is_mouth_open = false;
|
||||
bool _need_get_prev_angles = true;
|
||||
|
||||
uitk::Vector2i _prev_angles;
|
||||
};
|
||||
|
||||
} // namespace stackchan
|
||||
|
||||
@@ -4,73 +4,42 @@
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#pragma once
|
||||
#include "../utils/timer.h"
|
||||
#include "../modifiable.h"
|
||||
#include <string_view>
|
||||
#include <functional>
|
||||
#include <hal/hal.h>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
namespace stackchan {
|
||||
|
||||
/**
|
||||
* @brief
|
||||
*
|
||||
*/
|
||||
class TimerModifier : public Modifier {
|
||||
public:
|
||||
void scheduleDestroy(uint32_t ms)
|
||||
{
|
||||
getTimer().addTask(ms, 1, 0, [this]() { requestDestroy(); });
|
||||
}
|
||||
|
||||
Timer& getTimer()
|
||||
{
|
||||
if (!_timer) {
|
||||
_timer = std::make_unique<Timer>();
|
||||
}
|
||||
return *_timer;
|
||||
}
|
||||
|
||||
virtual void _update(Modifiable& stackchan) override
|
||||
{
|
||||
if (_timer) {
|
||||
_timer->update();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<Timer> _timer;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A timed event modifier base, which will be destroyed after the given duration
|
||||
*
|
||||
*/
|
||||
class TimedEventModifier : public TimerModifier {
|
||||
class TimedEventModifier : public Modifier {
|
||||
public:
|
||||
TimedEventModifier(uint32_t durationMs)
|
||||
TimedEventModifier(uint32_t durationMs) : _duration_ms(durationMs), _start_time(0), _is_started(false)
|
||||
{
|
||||
if (durationMs == 0) {
|
||||
requestDestroy();
|
||||
}
|
||||
|
||||
getTimer().addTask(durationMs, -1, 0, [this]() { _destroy_flag = true; });
|
||||
|
||||
_is_first_in = true;
|
||||
}
|
||||
|
||||
void _update(Modifiable& stackchan) override
|
||||
{
|
||||
TimerModifier::_update(stackchan);
|
||||
uint32_t now = GetHAL().millis();
|
||||
|
||||
if (_is_first_in) {
|
||||
_is_first_in = false;
|
||||
if (!_is_started) {
|
||||
_is_started = true;
|
||||
_start_time = now;
|
||||
_on_start(stackchan);
|
||||
|
||||
if (_duration_ms == 0) {
|
||||
_on_end(stackchan);
|
||||
requestDestroy();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (_destroy_flag) {
|
||||
if (now - _start_time >= _duration_ms) {
|
||||
_on_end(stackchan);
|
||||
requestDestroy();
|
||||
}
|
||||
@@ -85,8 +54,9 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
bool _is_first_in = true;
|
||||
bool _destroy_flag = false;
|
||||
uint32_t _duration_ms;
|
||||
uint32_t _start_time;
|
||||
bool _is_started;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -137,3 +137,13 @@ void Motion::setAutoAngleSyncEnabled(bool enabled)
|
||||
_yaw_servo->setAutoAngleSyncEnabled(enabled);
|
||||
_pitch_servo->setAutoAngleSyncEnabled(enabled);
|
||||
}
|
||||
|
||||
void Motion::setModifyLock(bool locked)
|
||||
{
|
||||
_is_modify_locked = locked;
|
||||
}
|
||||
|
||||
bool Motion::isModifyLocked()
|
||||
{
|
||||
return _is_modify_locked;
|
||||
}
|
||||
|
||||
@@ -156,9 +156,13 @@ public:
|
||||
void setAutoTorqueReleaseEnabled(bool enabled);
|
||||
void setAutoAngleSyncEnabled(bool enabled);
|
||||
|
||||
void setModifyLock(bool locked);
|
||||
bool isModifyLocked();
|
||||
|
||||
private:
|
||||
std::unique_ptr<Servo> _yaw_servo;
|
||||
std::unique_ptr<Servo> _pitch_servo;
|
||||
bool _is_modify_locked = false;
|
||||
|
||||
static constexpr float RAD_TO_DEG = 180.0f / M_PI;
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ void Servo::init()
|
||||
|
||||
void Servo::update()
|
||||
{
|
||||
// Update at 50Hz
|
||||
// Keep update in at most 50Hz
|
||||
if (GetHAL().millis() - _last_tick < 20) {
|
||||
return;
|
||||
}
|
||||
@@ -45,7 +45,7 @@ void Servo::update()
|
||||
|
||||
// Apply animation
|
||||
if (!_angle_anim.done()) {
|
||||
_angle_anim.update();
|
||||
_angle_anim.updateWithDelta(0.02f); // Fixed delta time for consistency
|
||||
set_angle_impl(static_cast<int>(_angle_anim.directValue()));
|
||||
}
|
||||
|
||||
@@ -92,6 +92,11 @@ int Servo::getCurrentAngle()
|
||||
return _angle_anim.directValue();
|
||||
}
|
||||
|
||||
bool Servo::isMoving()
|
||||
{
|
||||
return _angle_anim.done() == false || is_moving_impl();
|
||||
}
|
||||
|
||||
void Servo::apply_default_spring_options()
|
||||
{
|
||||
auto& options = _angle_anim.springOptions();
|
||||
@@ -102,6 +107,8 @@ void Servo::apply_default_spring_options()
|
||||
|
||||
void Servo::update_angle_anim_target(int angle)
|
||||
{
|
||||
angle = uitk::clamp(angle, _angle_limit.x, _angle_limit.y);
|
||||
|
||||
if (_auto_angle_sync_enabled) {
|
||||
_angle_anim.teleport(getCurrentAngle()); // Use current angle as start
|
||||
}
|
||||
|
||||
@@ -86,10 +86,7 @@ public:
|
||||
* @return true
|
||||
* @return false
|
||||
*/
|
||||
virtual bool isMoving()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
bool isMoving();
|
||||
|
||||
/**
|
||||
* @brief
|
||||
@@ -139,6 +136,14 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief
|
||||
*
|
||||
*/
|
||||
virtual void resetZeroCalibration()
|
||||
{
|
||||
}
|
||||
|
||||
protected:
|
||||
Servo()
|
||||
{
|
||||
@@ -155,8 +160,12 @@ protected:
|
||||
* @param angle
|
||||
*/
|
||||
virtual void set_angle_impl(int angle) = 0;
|
||||
virtual bool is_moving_impl()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
protected:
|
||||
uitk::Vector2i _angle_limit;
|
||||
uitk::AnimateValue _angle_anim;
|
||||
|
||||
|
||||
@@ -90,18 +90,14 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if motion is attached
|
||||
*
|
||||
* @return true
|
||||
* @return false
|
||||
*/
|
||||
bool hasMotion()
|
||||
addon::NeonLight& leftNeonLight() override
|
||||
{
|
||||
if (_motion) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return _left_neon_light;
|
||||
}
|
||||
|
||||
addon::NeonLight& rightNeonLight() override
|
||||
{
|
||||
return _right_neon_light;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -143,6 +139,9 @@ public:
|
||||
if (_motion) {
|
||||
_motion->update();
|
||||
}
|
||||
|
||||
_left_neon_light.update();
|
||||
_right_neon_light.update();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -169,9 +168,21 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief
|
||||
*
|
||||
* @param jsonContent
|
||||
*/
|
||||
void updateNeonLightFromJson(const char* jsonContent)
|
||||
{
|
||||
addon::update_neon_light_from_json(&_left_neon_light, &_right_neon_light, jsonContent);
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<avatar::Avatar> _avatar;
|
||||
std::unique_ptr<motion::Motion> _motion;
|
||||
addon::LeftNeonLight _left_neon_light;
|
||||
addon::RightNeonLight _right_neon_light;
|
||||
ObjectPool<Modifier> _modifier_pool;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,165 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#pragma once
|
||||
#include <hal/hal.h>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
namespace stackchan {
|
||||
|
||||
class Timer {
|
||||
public:
|
||||
using Callback = std::function<void(void)>;
|
||||
|
||||
struct Task {
|
||||
uint32_t intervalMs; // Time interval between executions (ms)
|
||||
uint32_t lastTick; // Last tick recorded
|
||||
uint32_t delayMs; // Delay before first execution (ms)
|
||||
int repeatCount; // Remaining repeat count (-1 = infinite)
|
||||
Callback callback; // Task callback function
|
||||
bool enabled; // Enabled or disabled
|
||||
bool started; // Has delay finished and started interval execution?
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Register a new task
|
||||
* @param intervalMs Execution interval in milliseconds
|
||||
* @param repeatCount Times to execute (-1 = infinite loop)
|
||||
* @param delayMs Delay before the first execution (ms)
|
||||
* @param cb Callback function
|
||||
* @param start Whether to start immediately
|
||||
* @return Task ID
|
||||
*/
|
||||
int addTask(uint32_t intervalMs, int repeatCount, uint32_t delayMs, Callback cb, bool start = true)
|
||||
{
|
||||
int id = -1;
|
||||
|
||||
if (!_free_indices.empty()) {
|
||||
id = _free_indices.back();
|
||||
_free_indices.pop_back();
|
||||
} else {
|
||||
id = (int)_tasks.size();
|
||||
_tasks.push_back(Task());
|
||||
}
|
||||
|
||||
Task& t = _tasks[id];
|
||||
t.intervalMs = intervalMs;
|
||||
t.repeatCount = repeatCount;
|
||||
t.delayMs = delayMs;
|
||||
t.callback = cb;
|
||||
t.enabled = start;
|
||||
t.started = false;
|
||||
t.lastTick = GetHAL().millis();
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
void setInterval(int id, uint32_t intervalMs)
|
||||
{
|
||||
if (is_valid(id)) {
|
||||
_tasks[id].intervalMs = intervalMs;
|
||||
}
|
||||
}
|
||||
|
||||
void setRepeat(int id, int repeatCount)
|
||||
{
|
||||
if (is_valid(id)) {
|
||||
_tasks[id].repeatCount = repeatCount;
|
||||
}
|
||||
}
|
||||
|
||||
void setDelay(int id, uint32_t delayMs)
|
||||
{
|
||||
if (is_valid(id)) {
|
||||
_tasks[id].delayMs = delayMs;
|
||||
}
|
||||
}
|
||||
|
||||
void enable(int id)
|
||||
{
|
||||
if (is_valid(id)) {
|
||||
_tasks[id].enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
void disable(int id)
|
||||
{
|
||||
if (is_valid(id)) {
|
||||
_tasks[id].enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Remove task and recycle its ID
|
||||
*/
|
||||
void remove(int id)
|
||||
{
|
||||
if (is_valid(id)) {
|
||||
_tasks[id] = Task(); // Reset content
|
||||
_tasks[id].enabled = false;
|
||||
_tasks[id].callback = nullptr;
|
||||
_free_indices.push_back(id); // Recycle index
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief MUST be called periodically in your main loop
|
||||
*/
|
||||
void update()
|
||||
{
|
||||
uint32_t now = GetHAL().millis();
|
||||
|
||||
for (int i = 0; i < (int)_tasks.size(); i++) {
|
||||
Task& t = _tasks[i];
|
||||
if (!t.enabled) {
|
||||
continue;
|
||||
}
|
||||
if (!t.callback) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle delay start
|
||||
if (!t.started) {
|
||||
if ((now - t.lastTick) >= t.delayMs) {
|
||||
t.started = true;
|
||||
t.lastTick = now;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle interval execution
|
||||
if ((now - t.lastTick) >= t.intervalMs) {
|
||||
t.lastTick = now;
|
||||
t.callback();
|
||||
|
||||
if (t.repeatCount > 0) {
|
||||
t.repeatCount--;
|
||||
if (t.repeatCount == 0) {
|
||||
t.enabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
bool is_valid(int id)
|
||||
{
|
||||
if (id < 0) {
|
||||
return false;
|
||||
}
|
||||
if (id >= (int)_tasks.size()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<Task> _tasks;
|
||||
std::vector<int> _free_indices;
|
||||
};
|
||||
|
||||
} // namespace stackchan
|
||||
Reference in New Issue
Block a user