您尚未登录。

楼主 # 2024-01-18 16:34:25

海石生风
会员
所在地: 深圳
注册时间: 2019-07-02
已发帖子: 522
积分: 643
个人网站

基于LVGL的嵌入式声明式UI框架zdec略有小成!

借鉴Flutter,不使用编程语言之外的标记语言而是使用编程语言本身来实现UI描述。这就要求编程语言支持泛型和反射特性。目前支持这两个特性又可以用于MCU平台的语言就只有Rust跟Zig了,但Rust太复杂了用在MCU平台大材小用,故选择Zig。Zig惟一的问题是目前还远没达到1.0版本,但当前v0.11版本的实用性也不错了。
项目命名为"zdec",“z”取自Zig,“dec”取自英文“声明”的前三个字母;而在汇编语言上一般用“dec”指令表示减法,所以"dec"也有在UI实现上做减法从而方便使用的意思。
目前已初步实现大体框架,实现了控件的创建/属性初始化和命令/属性的绑定。

UI构建方式如下:

    const main_ui = .{
        .{
            d.Id.Button,
            d.Size{ .width = 160, .height = 48 },
            d.Align{ .lv_align = .Center, .y_ofs = -100 },
            d.Text{ .text = "button" },
            struct {
                user_data: *Model,
                pub fn onClicked(event: anytype) void {
                    const the_model = event.userData();
                    const step = 10;
                    std.debug.print("{s}: add Model.count by {d}\n", .{ @typeName(@TypeOf(event.target())), step });
                    the_model.add(step);
                }
            }{ .user_data = &_model },
        },
        .{
            d.Id.Slider,
            d.Size{ .width = 240, .height = 16 },
            d.Align{ .lv_align = .Center, .y_ofs = 100 },
            d.Range{ .min = 0, .max = 200 },
            d.Bind(d.BindType.Value, @TypeOf(_model.count)){ .property = &_model.count },
        },
    };

    var widget = try d.buildUI(lv.Screen.active(), main_ui);

效果是这样的:点击button,Slider的游标就会变化
Screenshot_20240118_162552.png

上述例子的完整代码在这里:https://gitee.com/ufbycd/zdec/blob/dev/zdec/example.zig
项目主页在这里:https://gitee.com/ufbycd/zdec
目前只支持Linux,并使用SDL2作为后端来显示窗口。

最近编辑记录 海石生风 (2024-01-18 16:43:36)

离线

楼主 #1 2024-01-18 19:31:06

海石生风
会员
所在地: 深圳
注册时间: 2019-07-02
已发帖子: 522
积分: 643
个人网站

Re: 基于LVGL的嵌入式声明式UI框架zdec略有小成!

上面main_ui的定义是不是有点QML的意思,这是一个zig元组(tuple),实际上是一个类型名和字段(元素)都是匿名的结构体(struct)。
由于字段是匿名的,就可以包含任意类型的常量甚至可以包含一个类型,不过更时候是包含另一个元组。
而元组是在编译时确定的,就可以利用zig强大的编译时(comptime)特性去解析并构建出UI组件树。

最近编辑记录 海石生风 (2024-01-18 19:32:25)

离线

#2 2024-01-18 22:10:41

jlau
会员
注册时间: 2018-06-11
已发帖子: 198
积分: 190.5

Re: 基于LVGL的嵌入式声明式UI框架zdec略有小成!

6啊,push到github和zig社区,很快就有人会参与

离线

楼主 #3 2024-01-19 09:14:03

海石生风
会员
所在地: 深圳
注册时间: 2019-07-02
已发帖子: 522
积分: 643
个人网站

Re: 基于LVGL的嵌入式声明式UI框架zdec略有小成!

jlau 说:

6啊,push到github和zig社区,很快就有人会参与

这里其实有三个项目:LVGL、zlvgl、zdec,目前工程还是实验性的,这三个项目的文件夹组织得不太分明。等整理好再传到github或推到zig社区。

离线

#5 2024-01-20 02:21:48

jlau
会员
注册时间: 2018-06-11
已发帖子: 198
积分: 190.5

Re: 基于LVGL的嵌入式声明式UI框架zdec略有小成!

compose是靠kotlin编译器插件实现声明式UI的,slint是靠rust的宏实现的,zig的comptime可以看做是编译器插件,也是可以实现声明式UI的

离线

楼主 #6 2024-01-28 15:22:44

海石生风
会员
所在地: 深圳
注册时间: 2019-07-02
已发帖子: 522
积分: 643
个人网站

Re: 基于LVGL的嵌入式声明式UI框架zdec略有小成!

这三个项目整理好了:将lvgl的C源码直接放入到zlvgl项目内,zlvgl和zdec两个项目分开管理而不使用git submodule。工程已经分别上传到gitee和github:
https://gitee.com/ufbycd/zlvgl
https://gitee.com/ufbycd/zdec

https://github.com/ufbycd/zlvgl
https://github.com/ufbycd/zdec

离线

#7 2024-01-29 12:28:32

达克罗德
会员
注册时间: 2018-04-10
已发帖子: 1,134
积分: 1086.5

Re: 基于LVGL的嵌入式声明式UI框架zdec略有小成!

@海石生风
元组是在编译时确定的,那怎么实现的状态diff和更新呢?还有UI元素的增减怎么实现的?比如根据条件显示一个label与否

离线

楼主 #8 2024-01-29 13:53:27

海石生风
会员
所在地: 深圳
注册时间: 2019-07-02
已发帖子: 522
积分: 643
个人网站

Re: 基于LVGL的嵌入式声明式UI框架zdec略有小成!

达克罗德 说:

@海石生风
元组是在编译时确定的,那怎么实现的状态diff和更新呢?还有UI元素的增减怎么实现的?比如根据条件显示一个label与否

zig只在编译时支持泛型和反射,所以只能在编译时确定声明。控件一般来说对用户是隔离的,用户通常只操作模型;控件状态的更新来源于与其绑定的模型。

需要UI元素增减的场景是List和TableView控件吧, 这种控件会绑定一个模型数组,用户对表内的UI元素进行声明,UI框架会在编译时依据声明来生成一个用于构建UI元素的函数,模型有变化时就调用这个构建函数。
目前这还是一个构思,这种操作能否实现,还有待研究;毕竟zig这个编译时特性在众多编程言语中是绝无仅有的。

离线

#9 2024-01-29 17:46:31

达克罗德
会员
注册时间: 2018-04-10
已发帖子: 1,134
积分: 1086.5

Re: 基于LVGL的嵌入式声明式UI框架zdec略有小成!

@海石生风
“zig这个编译时特性在众多编程言语中是绝无仅有的” 这个特性确实是非常适合搞声明UI的,你这个选择非常正确。

看来你走的路线是利用binding来更新属性而不是对vdom进行diff再更新

我个人更喜欢reactjs这种vdom diff的方式,不用显式声明属性绑定,每次model变化,把整个UI 的builder重新运行一遍,再根据vdom实例化组件
坏处是开销大了不少;好处是可以任意方式写UI,每一帧的UI都可以是完全和上一帧不一样,是动态决定的。比如
if (fail_happen)
  Text(‘error is {erro_code}')
else
  Button(text=stat?'Yes':'No')
end

binding除了写起来有点繁琐以外,如果model值不能直接用于UI显示的话,还需要写转化函数。比如:
erro_code -> ‘error is {erro_code}'
stat -> stat?'Yes':'No'
以前玩微软XAML写UI就把我烦死了,写个binding还得写个converter

离线

楼主 #10 2024-01-29 18:47:38

海石生风
会员
所在地: 深圳
注册时间: 2019-07-02
已发帖子: 522
积分: 643
个人网站

Re: 基于LVGL的嵌入式声明式UI框架zdec略有小成!

vdom diff方式我之前也有考虑过,但觉得可能需要运行时反射,zig实现不了,就没有深究了。不过这种方式写起来确实要方便很多,后面有空再研究研究。

converter我目前的构思是在binding时像print函数那样指定格式化,如:

Bind(.Text, ErrorCode){ .property = &_model.error_code, .fmt = "error is {d}" }

“text=stat?'Yes':'No'”这种确实有点麻烦,我还在构思。

最近编辑记录 海石生风 (2024-01-29 18:49:21)

离线

#11 2024-01-30 09:25:45

xboot
会员
注册时间: 2019-10-15
已发帖子: 675
积分: 421

Re: 基于LVGL的嵌入式声明式UI框架zdec略有小成!

很赞,声明式UI运动的又一种探索

离线

楼主 #12 2024-02-05 15:59:35

海石生风
会员
所在地: 深圳
注册时间: 2019-07-02
已发帖子: 522
积分: 643
个人网站

Re: 基于LVGL的嵌入式声明式UI框架zdec略有小成!

最近几天在思考热加载时突然醒悟,@达克罗德所提的“vdom diff”方式非常有助于实现热加载功能。
因为这种模式下的UI声明跟UI状态是分开的,像flutter分成了三颗树,分别对应UI声明、UI状态、UI渲染。热加载时不能影响UI状态,即要将UI状态独立出来,去加载没有状态的部分,这个部分其实就是UI声明。
所以反过来,flutter为什么要这样做,很程度是因为它要实现热加载功能。

最近编辑记录 海石生风 (2024-02-05 16:20:02)

离线

页脚

工信部备案:粤ICP备20025096号 Powered by FluxBB

感谢为中文互联网持续输出优质内容的各位老铁们。 QQ: 516333132, 微信(wechat): whycan_cn (哇酷网/挖坑网/填坑网) service@whycan.cn