Build Your First App / 构建第一个 App

App 是一个页面

PixelUI 里的一个 App 是 IApplication 的派生类。ViewManager 负责构造、切换和销毁 App;App 本身负责画页面、处理没被 Widget 消耗的输入,并在生命周期回调中注册自己的 Widget 和回调。

1. 最小 App Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include "core/app/IApplication.h"
#include "core/app/app_system.h"
#include "widgets/text_button/text_button.h"

class HelloApp final : public IApplication {
public:
HelloApp(PixelUI& ui, void*)
: ui_(ui), closeButton_(ui, 38, 36, 52, 16, "Back") {}

void onEnter(ExitCallback exitCallback) override {
// 这一行会保存 ViewManager 提供的退出回调。
IApplication::onEnter(exitCallback);

closeButton_.setCallback([this]() { requestExit(); });
closeButton_.onLoad();
ui_.addWidgetToFocusManager(&closeButton_);
ui_.markDirty();
}

void draw() override {
U8G2& display = ui_.getU8G2();
display.setFont(u8g2_font_6x10_tf);
display.drawStr(32, 20, "Hello PixelUI");
closeButton_.draw();
}

bool handleInput(InputEvent event) override {
if (event == InputEvent::BACK) {
requestExit();
return true;
}
return false;
}

private:
PixelUI& ui_;
TextButton closeButton_;
};

// 24 x 24 XBM 需要 24 * 3 = 72 字节。这里先用空图标占位。
static const uint8_t hello_icon_bits[72] = {};

AppItem helloApp = AppItem::make<HelloApp>("Hello", hello_icon_bits);

AppItem::make<T>() 默认要求 T(PixelUI&, void*) 这个构造函数。第二个参数是非拥有的启动参数,不需要时可以忽略它。

2. 生命周期

回调 何时发生 适合做什么
构造函数 App 在 arena 中创建 构造成员,不要把 this/成员指针发布给 manager
onEnter() App 成为顶层;如果有 fade,在 fade 完成后 设回调、调 onLoad()、注册 Widget,开始动画
onPause() 另一个 App 压到它上面 暂停 App 自己管理的工作
onResume() 上层 App 退出 恢复持续绘制等 App 级状态
onExit() 离开页面栈,销毁前 撤销 App 自己管理的外部状态
析构函数 onExit() 后 释放 App 内部资源

IApplication::onEnter(exitCallback) 不能漏掉,否则 requestExit() 没有可调用的退出函数。

3. 输入路由

ui.handleInput(event) 的顺序大致是:

1
PixelUI FocusManager -> active Popup -> top App

Widget 没有处理的事件才会落到 HelloApp::handleInput()。一般在这里处理 BACK 和属于整个页面的按键。

4. 注册和启动

1
2
3
4
5
6
7
8
AppManager::getInstance().registerApp(helloApp); // 若此应用打算供 AppLauncher 检索,需要提前在 AppManager 单例中注册

ViewManager& views = *ui.getViewManagerPtr();
const auto result = views.launch(helloApp, nullptr); // 启动

if (result != ViewManager::LaunchResult::Ok) {
show_launch_error(result);
}

不需要出现在 launcher 中的页面可以用 views.push<HelloApp>(ui, nullptr) 直接启动。两种启动方式和失败结果见App 生命周期与 ViewManager。

5. 启动参数

1
2
3
4
5
6
7
8
9
10
11
12
13
struct HelloParameters {
const char* message;
};

class ParameterApp final : public IApplication {
public:
ParameterApp(PixelUI& ui, void* raw)
: ui_(ui), params_(static_cast<HelloParameters*>(raw)) {}

private:
PixelUI& ui_;
HelloParameters* params_; // non-owning
};

parameters 没有类型信息。它和 getCurrentApp() 返回的都是非拥有指针。不要传入函数即将返回的局部变量地址。更通用的规则见回调与对象生命周期。

avatar
Link0327
汪🐱me0w, but furry wolf. 尝试变得毛茸茸
Link's Github