Skip to content

Forairaaaaa/smooth_ui_toolkit

Repository files navigation

Smooth UI Toolkit

  • Spring、Easing 动画插值,RGB 颜色过渡插值
  • Lvgl C++ 封装,NumberFlow 风格控件
  • 动画菜单抽象
  • 颜色类型定义,颜色混合、转换方法
  • Signal、RingBuffer 等常用模板类
  • Vector 类型,clamp、map_range 等数学工具

Jul-26-2025 01-00-39

Jul-26-2025 00-47-13

Jul-26-2025 01-07-33

动画组件

Animate

基础动画插值类,抽象设计参考 Motion

Animate animation;

// 动画参数配置
animation.start = 200;
animation.end = 600;
animation.repeat = -1;
animation.repeatType = AnimateRepeatType::Reverse;

// spring 动画参数配置
// 这里调用 springOptions() ,动画类型自动适应为 spring
animation.springOptions().bounce = 0.4;
animation.springOptions().visualDuration = 0.6;

// 如想要 easing 动画,调用 easingOptions() 即可
// animation.easingOptions().easingFunction = ease::ease_out_quad;
// animation.easingOptions().duration = 0.3;

animation.init();
animation.play();

while (1) {
    // 更新
    animation.update();
    // 取值
    draw_ball(animation.value(), 233);
}

AnimateValue

Animate 的派生类,简化取值赋值操作,使用起来更接近于普通变量

Jul-26-2025 01-24-37

AnimateValue x = 100;
AnimateValue y = 225;

while (1) {
    // 直接赋值即可,动画会自动适应新目标
    x = get_mouse_x();
    y = get_mouse_y();
  
    // 直接取值即可,动画会自动更新
    draw_ball(x, y);
});

配合 spring 参数 可以实现不同的动画效果:

Jul-26-2025 00-54-17

for (int i = 0; i < cursors.size(); i++) {
    // 弹簧刚度逐渐增加
    cursors[i].x.springOptions().stiffness = 55 + i * 25;
  	// 阻尼系数逐渐减小
    cursors[i].x.springOptions().damping = 13 - i;
}

Lvgl Cpp 封装

Lvgl 控件智能指针封装

指针管理参考:https://github.com/vpaeder/lvglpp

简化了控件 API,并添加了信号 Signal 来简化事件回调处理

Jul-26-2025 00-56-19

#include <smooth_lvgl.hpp>
// lvgl cpp 封装为 header only
// 需要工程已满足 #include <lvgl.h> 依赖
// 当前支持 v9.3.0 以上的版本

// Basic lvgl object
auto obj = new Container(screen);
obj->setPos(50, 50);
obj->setSize(200, 100);

// Label
auto label = new Label(screen);
label->setTextFont(&lv_font_montserrat_24);
label->align(LV_ALIGN_CENTER, -180, 0);
label->setText("??");

// Button
int count = 0;
auto btn = new Button(screen);
btn->setPos(50, 200);
btn->label().setText("+1");
btn->onClick().connect([&]() {
    label->setText(fmt::format("{}", count++));
});

// Switch
auto sw = new Switch(screen);
sw->setPos(50, 300);
sw->onValueChanged().connect([&](bool value) {
    label->setText(value ? "ON" : "OFF");
});

// Slider
auto slider = new Slider(screen);
slider->setPos(50, 390);
slider->onValueChanged().connect([&](int value) {
    label->setText(fmt::format("{}", value));
});

// Roller
auto roller = new Roller(screen);
roller->align(LV_ALIGN_CENTER, 0, 0);
roller->setOptions({"nihao", "wohao", "dajiahao"});
roller->onValueChanged().connect([&](uint32_t value) {
    label->setText(fmt::format("{}", roller->getSelectedStr()));
});

// ...

NumberFlow

基于 Lvgl 实现的 NumberFlow 风格数字显示控件,支持正负、小数和前后缀显示

Jul-26-2025 00-50-36

auto number_flow = new NumberFlow(lv_screen_active());

...
btn_random->onClick().connect([&]() {
  	// 设置数值
    number_flow->setValue(randomNum);
});

while (1) {
  	// 更新
    number_flow->update();
}

完整例程

前后缀文本、颜色设置:

Jul-26-2025 21-41-14

完整例程

小数支持:

Jul-26-2025 21-52-02

// 替换对象类型即可
auto number_flow = new NumberFlowFloat(lv_screen_active());
...

控件抽象

把控件的一些共性行为抽离出来作为基类,使用时可以根据实际需求派生

例如一个带动画的选项菜单抽象:

重写 onReadInput() 来读取按键或者是编码器输入,以控制选项的切换

重写 onRender() 来实现实际的画面渲染

非常底层~

SmoothSelectorMenu

基于选择器的菜单抽象,选择器会平滑移动和变形,来匹配选中选项的关键帧

摄像机会自动平滑移动,保持选择器在摄像机范围内

适合常见的横向或纵向列表菜单:

Mix1

SmoothMenuDemo1

简单横向菜单例程

hstack-menu

也可以通过坐标变换实现选择器固定不动,而是选项滚动

选择器固定的横向菜单例程

fixed-selector-hstack-menu

选择器固定的纵向带弧度菜单例程

vstack-curved-menu

可以看这个例程来了解具体的基类设计:

timerrelay

SmoothOptionsMenu

基于选项移动的菜单抽象,每个选项独立动画,循环轮换到关键帧位置

适合圆形旋转菜单、卡片轮播等菜单

SmoothMenuDemo2

可以看这个例程来了解具体的基类设计:

asdasda

颜色

颜色转换、混合函数

// 0xffffff -> rgb(255, 255, 255)
Rgb_t hex_to_rgb(const uint32_t& hex);

// "#ffffff" -> rgb(255, 255, 255)
Rgb_t hex_to_rgb(const std::string& hex);

// rgb(255, 255, 255) -> 0xffffff
uint32_t rgb_to_hex(const Rgb_t& rgb);

// rgb(255, 255, 255) -> "#ffffff"
std::string rgb_to_hex_string(const Rgb_t& rgb);

// 差值混合
Rgb_t blend_in_difference(Rgb_t bg, Rgb_t fg);

// 透明度混合
Rgb_t blend_in_opacity(Rgb_t bg, Rgb_t fg, float opacity);

AnimateRgb_t

RGB 颜色的变换过渡插值

Jul-26-2025 01-03-01

// 颜色列表
std::vector<uint32_t> color_list = {...}

AnimateRgb_t bg_color;
bg_color.duration = 0.3;
bg_color.begin();

// 按钮事件
btn_random.onClick().connect([&]() {
  	// 直接赋值即可
    bg_color = color_list[random];
});

while (1) {
  	// 更新
    bg_color.update();
  	// 取值
    xxx.setBgColor(lv_color_hex(bg_color.toHex()));
}

UI HAL

动画的更新以系统 Tick 为参考基准,所使用的相关函数来自内部定义:

namespace ui_hal {

/**
 * @brief Get the number of milliseconds since running
 *
 * @return uint32_t
 */
uint32_t get_tick();

/**
 * @brief Wait a specified number of milliseconds before returning
 *
 * @param ms
 */
void delay(uint32_t ms);

} // namespace ui_hal

默认实现为 chrono 和 thread

cmake 里 OFF SMOOTH_UI_TOOLKIT_ENABLE_DEFAULT_HAL

或者添加全局宏 SMOOTH_UI_TOOLKIT_ENABLE_DEFAULT_HAL=0

可以关闭这个默认实现

自定义实现方式:

// 比如 Arduino,性能应该比 chrono 好

ui_hal::on_get_tick([]() {
    return millis();
});

ui_hal::on_delay([](uint32_t ms) {
    delay(ms);
});

命名空间

所有类和方法均定义在命名空间 smooth_ui_toolkit

smooth_ui_toolkit::clamp(...);
smooth_ui_toolkit::AnimateValue();
smooth_ui_toolkit::lvgl_cpp::Button(...);
...

可以引用 <uitk/short_namespace.hpp> 来获得一个更简短的重命名 uitk

#include <smooth_ui_toolkit.hpp>
#include <uitk/short_namespace.hpp>

uitk::clamp(...);
uitk::AnimateValue();
uitk::lvgl_cpp::Button(...);
...

编译例程

例程用了 lvglraylib 作为图形库,所以要安装 SDL2

工具链安装:

  • macOS: brew install sdl2 cmake make
  • Ubuntu: sudo apt install build-essential cmake libsdl2-dev

拉取项目:

git clone https://github.com/Forairaaaaa/smooth_ui_toolkit.git
cd smooth_ui_toolkit

拉取依赖:

python example/fetch_repos.py

编译:

mkdir build && cd build
cmake .. && make -j8

运行:

执行 ./build/example/ 下对应例程,如:

./build/example/basic_animations

库引入

CMake工程

工程 CMakeLists.txt 里添加:

# 不编译例程
set(SMOOTH_UI_TOOLKIT_BUILD_EXAMPLE OFF)

# 引入库路径
add_subdirectory(path/to/smooth_ui_toolkit)

# link
target_link_libraries(your_project PUBLIC
    smooth_ui_toolkit
)

IDF 工程

clone 仓库,直接丢到 components 目录里就行

PIO 工程

clone 仓库,直接丢到 libs 目录里就行

Arduino 工程

clone 仓库,直接丢到 libraries 目录里就行

常用图形库模拟器

一些常用图形库的桌面端最简工程

lvgl: https://github.com/Forairaaaaa/lvgl_simulator_cpp

m5gfx: https://github.com/Forairaaaaa/m5gfx_simulator_cpp