前言
本文仅为笔者记忆,个人经验写着玩,目前1.3.2版本。
注:本文尚未完成。
注:本文尚未完成。
注:本文尚未完成。
本人目前想要移植一种贴近前端技术的GUI到裸机上,但是裸机不支持UNIX环境,所以绝大部分框架都用不了(如Flutter/skia等),最后发现Slint最合适。
Slint有三种渲染器{femtovg/OpenGL ,skia ,software },由于本文移植目标是裸机,所以采用CPU/MCU完成这个过程,选用软件渲染software
。
Slint官方文档
- Slint模板语法,类似于 HTML
https://slint.dev/releases/1.3.2/docs/slint/ - MCU移植指南(重要):https://slint.dev/releases/1.3.2/docs/rust/slint/docs/mcu/
- MCU示例工程(SPI/IO等),主要侧重于使用Slint:
https://github.com/slint-ui/slint-mcu-rust-template - MCU移植示例,侧重于移植Slint
https://github.com/slint-ui/slint/tree/master/examples/printerdemo_mcu
本文研究的目标
我们就研究
https://github.com/slint-ui/slint/tree/master/examples/printerdemo_mcu
及其依赖的 mcu-board-support = { path = "../mcu-board-support" }
首先我们去它的目录 ../mcu-board-support
看看,选择我们熟悉的芯片平台来看即可,我选择stm32
这里传递了 StmBackend
,由于Rust没有c那种先声明才能使用的规定,所以继续看下面。看看StmBackend
的声明和定义。
PS:源码里的RNG我们先不管,RNG硬件随机数发生器,是STM32的片上外设硬件。
可以看到,StmBackend
实现了两个行为(Rust里面叫Trait:定义共同行为; Cpp和Java叫做继承,Golang叫接口实现)
-
impl Default
- 这个行为很简单,作用类似于cpp/Java/Golang里面的构造函数,但是一般用来填充非零的特定值。
- 参见Rust官方文档: https://doc.rust-lang.org/std/default/trait.Default.html#
-
impl slint::platform::Platform
- 这个就是slint对接的底层调用,我们一般称之为HAL层。实现这一层,才能让Slint跑在我们想要的平台上 (包括裸机)。
- 参见Slint官方文档:https://slint.dev/releases/1.3.2/docs/rust/slint/platform/trait.Platform
我们只需要实现这些行为,即可完成移植slint的大部分工作(即完成HAL层),剩下的底层就是纯裸机Bringup开发了。
impl slint::platform::Platform for StmBackend
实现如下:
impl slint::platform::Platform for StmBackend {
fn create_window_adapter(
&self,
) -> Result<Rc<dyn slint::platform::WindowAdapter>, slint::PlatformError> {
let window = slint::platform::software_renderer::MinimalSoftwareWindow::new(
slint::platform::software_renderer::RepaintBufferType::SwappedBuffers,
);
self.window.replace(Some(window.clone()));
Ok(window)
}
fn run_event_loop(&self) -> Result<(), slint::PlatformError> {
let inner = &mut *self.inner.borrow_mut();
let mut ft5336 =
ft5336::Ft5336::new(&mut inner.touch_i2c, 0x70 >> 1, &mut inner.delay).unwrap();
ft5336.init(&mut inner.touch_i2c);
// Safety: The Refcell at the beginning of `run_event_loop` prevents re-entrancy and thus multiple mutable references to FB1/FB2.
let (fb1, fb2) = unsafe { (&mut FB1, &mut FB2) };
let mut displayed_fb: &mut [TargetPixel] = fb1;
let mut work_fb: &mut [TargetPixel] = fb2;
let mut last_touch = None;
self.window
.borrow()
.as_ref()
.unwrap()
.set_size(slint::PhysicalSize::new(DISPLAY_WIDTH as u32, DISPLAY_HEIGHT as u32));
loop {
slint::platform::update_timers_and_animations();
if let Some(window) = self.window.borrow().clone() {
window.draw_if_needed(|renderer| {
while inner.layer.is_swap_pending() {}
renderer.render(work_fb, DISPLAY_WIDTH);
inner.scb.clean_dcache_by_slice(work_fb);
// Safety: the frame buffer has the right size
unsafe { inner.layer.swap_framebuffer(work_fb.as_ptr() as *const u8) };
// Swap the buffer pointer so we will work now on the second buffer
core::mem::swap::<&mut [_]>(&mut work_fb, &mut displayed_fb);
});
// handle touch event
let touch = ft5336.detect_touch(&mut inner.touch_i2c).unwrap();
let button = slint::platform::PointerEventButton::Left;
let event = if touch > 0 {
let state = ft5336.get_touch(&mut inner.touch_i2c, 1).unwrap();
let position = slint::PhysicalPosition::new(state.y as i32, state.x as i32)
.to_logical(window.scale_factor());
Some(match last_touch.replace(position) {
Some(_) => slint::platform::WindowEvent::PointerMoved { position },
None => slint::platform::WindowEvent::PointerPressed { position, button },
})
} else {
last_touch.take().map(|position| {
slint::platform::WindowEvent::PointerReleased { position, button }
})
};
if let Some(event) = event {
let is_pointer_release_event =
matches!(event, slint::platform::WindowEvent::PointerReleased { .. });
window.dispatch_event(event);
// removes hover state on widgets
if is_pointer_release_event {
window.dispatch_event(slint::platform::WindowEvent::PointerExited);
}
}
}
// FIXME: cortex_m::asm::wfe();
}
}
fn duration_since_start(&self) -> core::time::Duration {
// FIXME! the timer can overflow
let val = self.timer.counter() / 10;
core::time::Duration::from_millis(val.into())
}
fn debug_log(&self, arguments: core::fmt::Arguments) {
use alloc::string::ToString;
defmt::println!("{=str}", arguments.to_string());
}
}
HAL层的过程是这样的:
create_window_adapter
完成run_event_loop
完成每一次事件处理(包括屏幕刷新/触摸输入等事件)
- Slint上层software renderer渲染器完成渲染,通过FB1和FB2双缓冲机制将图像传递给 可触摸屏幕主控FT5336(I2C通讯),完成显示。也接收触摸屏事件,完成输入响应。
debug_log
完成日志输出接口,其实就是printduration_since_start
是运行时间,通过定时器实现即可。
Slint的官方宣言!
译文:
本文未完成。
看完这个STM32的,再去看其他芯片(例如ESP32)的Slint移植代码,会发现其实都差不多。
标签:mut,platform,window,let,Slint,UI,event,Rust,slint From: https://www.cnblogs.com/yucloud/p/17993104/rust_slint_embed_baremetal