mirror of
https://github.com/slint-ui/slint.git
synced 2025-12-23 09:19:32 +00:00
WIP: Add Safety Critical UI Demo
This commit is contained in:
parent
8a95bc557a
commit
5df82f052d
11 changed files with 731 additions and 1 deletions
|
|
@ -38,8 +38,13 @@ description = "Run cargo fmt --all"
|
|||
run = "cargo fmt --all"
|
||||
dir = "examples/servo"
|
||||
|
||||
["fix:rust:format:safeui"]
|
||||
description = "Run cargo fmt --all"
|
||||
run = "cargo fmt --all"
|
||||
dir = "examples/safe-ui"
|
||||
|
||||
["fix:rust:format:all"]
|
||||
depends = ["fix:rust:format:root", "fix:rust:format:bevy", "fix:rust:format:servo"]
|
||||
depends = ["fix:rust:format:root", "fix:rust:format:bevy", "fix:rust:format:servo", "fix:rust:format:safeui"]
|
||||
|
||||
["fix:toml:format"]
|
||||
description = "Run taplo format"
|
||||
|
|
|
|||
1
examples/safe-ui/.gitignore
vendored
Normal file
1
examples/safe-ui/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
target/
|
||||
32
examples/safe-ui/CMakeLists.txt
Normal file
32
examples/safe-ui/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
# Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
cmake_minimum_required(VERSION 3.21)
|
||||
project(SlintSafeUI LANGUAGES C CXX VERSION 1.0)
|
||||
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(
|
||||
Corrosion
|
||||
GIT_REPOSITORY https://github.com/corrosion-rs/corrosion.git
|
||||
GIT_TAG v0.6.0
|
||||
)
|
||||
FetchContent_MakeAvailable(Corrosion)
|
||||
|
||||
set(Rust_CARGO_TARGET_LINK_NATIVE_LIBS "" CACHE INTERNAL "")
|
||||
|
||||
corrosion_import_crate(MANIFEST_PATH "${CMAKE_CURRENT_SOURCE_DIR}/Cargo.toml"
|
||||
CRATES slint-safeui CRATE_TYPES staticlib)
|
||||
|
||||
set_property(
|
||||
TARGET slint_safeui_lib
|
||||
PROPERTY CORROSION_NO_DEFAULT_FEATURES
|
||||
ON
|
||||
)
|
||||
|
||||
add_library(SlintSafeUi INTERFACE)
|
||||
target_link_libraries(SlintSafeUi INTERFACE slint_safeui_lib)
|
||||
|
||||
target_include_directories(SlintSafeUi INTERFACE
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/src>
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src>
|
||||
)
|
||||
39
examples/safe-ui/Cargo.toml
Normal file
39
examples/safe-ui/Cargo.toml
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
# Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
[workspace]
|
||||
|
||||
[package]
|
||||
name = "slint-safeui"
|
||||
version = "1.15.0"
|
||||
edition = "2024"
|
||||
build = "build.rs"
|
||||
|
||||
[lib]
|
||||
path = "src/lib.rs"
|
||||
crate-type = ["rlib", "staticlib"]
|
||||
name = "slint_safeui_lib"
|
||||
|
||||
[[bin]]
|
||||
name = "slint_safeui"
|
||||
path = "src/simulator.rs"
|
||||
|
||||
[features]
|
||||
simulator = ["slint/backend-winit", "dep:smol"]
|
||||
default = ["simulator"]
|
||||
|
||||
[dependencies]
|
||||
bytemuck = "1.24.0"
|
||||
slint = { version = "1.14.1", default-features = false, features = ["compat-1-2", "renderer-software", "libm", "unsafe-single-threaded"] }
|
||||
smol = { version = "2.0.0", optional = true }
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
opt-level = "s"
|
||||
|
||||
[profile.dev]
|
||||
panic = "abort"
|
||||
|
||||
[build-dependencies]
|
||||
bindgen = "0.72.1"
|
||||
slint-build = { version = "1.14.1", features = ["sdf-fonts"] }
|
||||
77
examples/safe-ui/README.md
Normal file
77
examples/safe-ui/README.md
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
# Slint Safety Critical UI Demo
|
||||
|
||||
We aim to make Slint suitable in environments that require reliable display of safety-critical UI, such as vehicles of any kind, medical devices, or industrial tools and machines.
|
||||
|
||||
This example serves as a starting point for a setup where strict separation of domains into a safety domain and an application domain is implemented either by hardware or system software:
|
||||
|
||||
- The application domain is for example a Slint based application running on Linux, rendering into some kind of surface that only indirectly makes it to the physical output screen.
|
||||
- The safety domain could be implemented by means of hardware or software. This domain is restricted and would be subject to a device specific safety certification. We aim to demonstrate
|
||||
that Slint is suitable for this use-case.
|
||||
|
||||
The safety domain is assumed to be split into two parts again:
|
||||
|
||||
- A system or hardware specific layer.
|
||||
- The Rust-based Slint and application safety layer.
|
||||
|
||||
This directory contains the Slint safety layer scaffolding and interface. The interface to the system layer is based on a few low-level C functions. The application specific
|
||||
safety critical UI is implemented in Slint and Rust.
|
||||
|
||||
The reference device used for developing the example is the Toradex NXP i.MX 95 Verdin https://www.toradex.com/computer-on-modules/verdin-arm-family/nxp-imx95-evaluation-kit#explore
|
||||
with NXP's SafeAssure framework.
|
||||
|
||||
The following video shows this demo in action, with Linux booting underneath a Slint based rectangular overlay.
|
||||
|
||||
The Linux based underlay starts the gallery demo, rendering with OpenGL on a Mali GPU with Skia and Slint's LinuxKMS backend.
|
||||
|
||||
https://github.com/user-attachments/assets/077790db-b325-49d2-9d10-1e1be7c5a660
|
||||
|
||||
The overlay is rendered on the Cortex-M7 running FreeRTOS and NXP's SafeAssure framework, to handle driving the Display Processing Unit (DPU) for blending, and to run Slint's event loop.
|
||||
The Slint scene rendered can be found in [appwindow.slint](./ui/app-window.slint).
|
||||
The application entry point is [./src/lib.rs](./src/lib.rs);
|
||||
|
||||
## Build System Integration
|
||||
|
||||
Integration of this example into an existing safety domain build system works by means of CMake. In your existing `CMakeLists.txt` for your target
|
||||
that produces the final binary, use `FetchContent` to pull in the `SlintSafeUi` target:
|
||||
|
||||
```cmake
|
||||
set(Rust_CARGO_TARGET "thumbv7em-none-eabihf" CACHE STRING "")
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(
|
||||
SlintSafeUi
|
||||
GIT_REPOSITORY https://github.com/slint-ui/slint.git
|
||||
GIT_TAG master
|
||||
SOURCE_SUBDIR examples/safe-ui
|
||||
)
|
||||
FetchContent_MakeAvailable(SlintSafeUi)
|
||||
```
|
||||
|
||||
Link against it in your firmware target, to ensure linkage and access to the C system interface headers:
|
||||
|
||||
```cmake
|
||||
target_link_libraries(my_firmware PRIVATE SlintSafeUi)
|
||||
```
|
||||
|
||||
## C System Interface
|
||||
|
||||
The basic C system interface is documented in [./src/slint-safeui-platform-interface.h](./src/slint-safeui-platform-interface.h). This header file is also part of the `INTERFACE`
|
||||
of the `SlintSafeUi` CMake target. Implement these functions in your firmware.
|
||||
|
||||
Once you've started your UI task, invoke `slint_app_main()` to start the Slint event loop and the UI safety layer.
|
||||
|
||||
## Simulation
|
||||
|
||||
For convenience, this example provides a "simulator" feature in [./src/simulator.rs](./src/simulator.rs), so that you can just run this on a desktop system with
|
||||
|
||||
```
|
||||
cargo run
|
||||
```
|
||||
|
||||
The "simulator" implements the same C system interface and runs the Slint UI safety layer example in a secondary thread.
|
||||
|
||||
## Known Limitations
|
||||
|
||||
- Partial rendering is not implemented. While this is technically possible, we aim to exclude the partial renderer from the safety certification process for now.
|
||||
- The pixel format is hard-coded to BGRA8888. This is relatively easy to change, if necessary.
|
||||
- `slint::invoke_from_event_loop()` (and `slint_safeui_platform_wake` in the interface) isn't fully implemented yet. This is partly due to missing abstractions
|
||||
(mutexes) as well as missing support to distinguish between waking up from an interrupt handler vs. being invoked from another task (`vTaskNotifyGiveFromISR()` vs `xTaskNotifyGive()`)
|
||||
25
examples/safe-ui/build.rs
Normal file
25
examples/safe-ui/build.rs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn main() {
|
||||
let bindings = bindgen::Builder::default()
|
||||
.header("src/slint-safeui-platform-interface.h")
|
||||
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
|
||||
.use_core()
|
||||
.generate()
|
||||
.expect("Unable to generate bindings");
|
||||
|
||||
// Write the bindings to the $OUT_DIR/bindings.rs file.
|
||||
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
|
||||
bindings.write_to_file(out_path.join("bindings.rs")).expect("Couldn't write bindings!");
|
||||
|
||||
let config = slint_build::CompilerConfiguration::new()
|
||||
.with_style("fluent-light".into())
|
||||
.with_sdf_fonts(true)
|
||||
.embed_resources(slint_build::EmbedResourcesKind::EmbedForSoftwareRenderer)
|
||||
.with_scale_factor(2.);
|
||||
slint_build::compile_with_config("ui/app-window.slint", config).unwrap();
|
||||
}
|
||||
21
examples/safe-ui/src/lib.rs
Normal file
21
examples/safe-ui/src/lib.rs
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#![no_std]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
pub mod platform;
|
||||
|
||||
slint::include_modules!();
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn slint_app_main() {
|
||||
platform::slint_init_safeui_platform();
|
||||
|
||||
let app = MainWindow::new().unwrap();
|
||||
|
||||
app.show().unwrap();
|
||||
|
||||
app.run().unwrap();
|
||||
}
|
||||
293
examples/safe-ui/src/platform.rs
Normal file
293
examples/safe-ui/src/platform.rs
Normal file
|
|
@ -0,0 +1,293 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
extern crate alloc;
|
||||
use alloc::boxed::Box;
|
||||
use alloc::rc::Rc;
|
||||
//use alloc::vec::Vec;
|
||||
//use core::cell::RefCell;
|
||||
|
||||
use slint::platform::software_renderer::MinimalSoftwareWindow;
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
|
||||
|
||||
struct Platform {
|
||||
window: Rc<MinimalSoftwareWindow>,
|
||||
//event_queue: Queue,
|
||||
}
|
||||
|
||||
impl slint::platform::Platform for Platform {
|
||||
fn create_window_adapter(
|
||||
&self,
|
||||
) -> Result<alloc::rc::Rc<dyn slint::platform::WindowAdapter>, slint::PlatformError> {
|
||||
Ok(self.window.clone())
|
||||
}
|
||||
|
||||
fn run_event_loop(&self) -> Result<(), slint::PlatformError> {
|
||||
self.window
|
||||
.dispatch_event(slint::platform::WindowEvent::ScaleFactorChanged { scale_factor: 2.0 });
|
||||
|
||||
let mut width: u32 = 0;
|
||||
let mut height: u32 = 0;
|
||||
unsafe {
|
||||
slint_safeui_platform_get_screen_size(&mut width as *mut _, &mut height as *mut _);
|
||||
}
|
||||
|
||||
self.window.set_size(slint::WindowSize::Physical(slint::PhysicalSize::new(width, height)));
|
||||
self.window.request_redraw();
|
||||
|
||||
loop {
|
||||
slint::platform::update_timers_and_animations();
|
||||
|
||||
// let events_to_process =
|
||||
// critical_section::with(|cs| self.event_queue.0.borrow(cs).take());
|
||||
// for event in events_to_process.into_iter() {
|
||||
// match event {
|
||||
// Event::Quit => return Ok(()),
|
||||
// Event::Event(f) => f(),
|
||||
// }
|
||||
// }
|
||||
|
||||
self.window.draw_if_needed(|renderer| {
|
||||
render_wrapper(&|buffer, pixel_stride| {
|
||||
renderer.render(buffer, pixel_stride);
|
||||
})
|
||||
});
|
||||
|
||||
let mut next_timeout = slint::platform::duration_until_next_timer_update();
|
||||
|
||||
if self.window.has_active_animations() {
|
||||
let frame_duration = core::time::Duration::from_millis(16);
|
||||
next_timeout = Some(match next_timeout {
|
||||
Some(x) => x.min(frame_duration),
|
||||
None => frame_duration,
|
||||
})
|
||||
}
|
||||
|
||||
unsafe {
|
||||
slint_safeui_platform_wait_for_events(
|
||||
next_timeout.map_or(-1, |dur| dur.as_millis() as i32),
|
||||
)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
//#[cfg(not(feature = "simulator"))]
|
||||
//fn new_event_loop_proxy(&self) -> Option<Box<dyn slint::platform::EventLoopProxy>> {
|
||||
// Some(Box::new(self.event_queue.clone()) as Box<dyn slint::platform::EventLoopProxy>)
|
||||
//}
|
||||
|
||||
#[cfg(not(feature = "simulator"))]
|
||||
fn duration_since_start(&self) -> core::time::Duration {
|
||||
core::time::Duration::from_millis(unsafe {
|
||||
slint_safeui_platform_duration_since_start() as u64
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn render_wrapper<F: Fn(&mut [Bgra8888Pixel], usize)>(f: &F) {
|
||||
let user_data = f as *const _ as *const core::ffi::c_void;
|
||||
|
||||
unsafe extern "C" fn c_render_wrap<F: Fn(&mut [Bgra8888Pixel], usize)>(
|
||||
user_data: *const core::ffi::c_void,
|
||||
buffer: *mut core::ffi::c_char,
|
||||
byte_size: core::ffi::c_uint,
|
||||
pixel_stride: core::ffi::c_uint,
|
||||
) {
|
||||
let buffer = unsafe {
|
||||
core::slice::from_raw_parts_mut(
|
||||
buffer as *mut Bgra8888Pixel,
|
||||
byte_size as usize / core::mem::size_of::<Bgra8888Pixel>(),
|
||||
)
|
||||
};
|
||||
let f = unsafe { &*(user_data as *const F) };
|
||||
f(buffer, pixel_stride as usize)
|
||||
}
|
||||
|
||||
unsafe { slint_safeui_platform_render(user_data, Some(c_render_wrap::<F>)) }
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
pub struct Bgra8888Pixel(pub u32);
|
||||
|
||||
impl From<Bgra8888Pixel> for slint::platform::software_renderer::PremultipliedRgbaColor {
|
||||
#[inline]
|
||||
fn from(pixel: Bgra8888Pixel) -> Self {
|
||||
let v = pixel.0;
|
||||
slint::platform::software_renderer::PremultipliedRgbaColor {
|
||||
blue: (v >> 0) as u8,
|
||||
green: (v >> 8) as u8,
|
||||
red: (v >> 16) as u8,
|
||||
alpha: (v >> 24) as u8,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<slint::platform::software_renderer::PremultipliedRgbaColor> for Bgra8888Pixel {
|
||||
#[inline]
|
||||
fn from(pixel: slint::platform::software_renderer::PremultipliedRgbaColor) -> Self {
|
||||
Self(
|
||||
(pixel.alpha as u32) << 24
|
||||
| ((pixel.red as u32) << 16)
|
||||
| ((pixel.green as u32) << 8)
|
||||
| (pixel.blue as u32),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl slint::platform::software_renderer::TargetPixel for Bgra8888Pixel {
|
||||
fn blend(&mut self, color: slint::platform::software_renderer::PremultipliedRgbaColor) {
|
||||
let mut x = slint::platform::software_renderer::PremultipliedRgbaColor::from(*self);
|
||||
x.blend(color);
|
||||
*self = x.into();
|
||||
}
|
||||
fn from_rgb(r: u8, g: u8, b: u8) -> Self {
|
||||
Self(0xff000000 | ((r as u32) << 16) | ((g as u32) << 8) | (b as u32))
|
||||
}
|
||||
fn background() -> Self {
|
||||
Self(0)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn slint_init_safeui_platform() {
|
||||
let platform = Platform {
|
||||
window: slint::platform::software_renderer::MinimalSoftwareWindow::new(
|
||||
slint::platform::software_renderer::RepaintBufferType::NewBuffer,
|
||||
),
|
||||
//event_queue: Queue(critical_section::Mutex::new(RefCell::new(Vec::new())).into()),
|
||||
};
|
||||
|
||||
slint::platform::set_platform(Box::new(platform)).unwrap();
|
||||
}
|
||||
|
||||
//enum Event {
|
||||
// Quit,
|
||||
// Event(Box<dyn FnOnce() + Send>),
|
||||
//}
|
||||
//
|
||||
//#[derive(Clone)]
|
||||
//struct Queue(alloc::sync::Arc<critical_section::Mutex<RefCell<Vec<Event>>>>);
|
||||
//
|
||||
//impl slint::platform::EventLoopProxy for Queue {
|
||||
// fn quit_event_loop(&self) -> Result<(), slint::EventLoopError> {
|
||||
// critical_section::with(|cs| {
|
||||
// self.0.borrow_ref_mut(cs).push(Event::Quit);
|
||||
// });
|
||||
//
|
||||
// unsafe { slint_safeui_platform_wake() };
|
||||
// Ok(())
|
||||
// }
|
||||
//
|
||||
// fn invoke_from_event_loop(
|
||||
// &self,
|
||||
// event: Box<dyn FnOnce() + Send>,
|
||||
// ) -> Result<(), slint::EventLoopError> {
|
||||
// critical_section::with(|cs| {
|
||||
// self.0.borrow_ref_mut(cs).push(Event::Event(event));
|
||||
// });
|
||||
// unsafe { slint_safeui_platform_wake() };
|
||||
// Ok(())
|
||||
// }
|
||||
//}
|
||||
|
||||
#[cfg_attr(not(feature = "simulator"), panic_handler)]
|
||||
#[cfg(not(feature = "simulator"))]
|
||||
fn panic(info: &core::panic::PanicInfo) -> ! {
|
||||
use core::ffi::CStr;
|
||||
use core::fmt::{self, Write};
|
||||
|
||||
pub struct FixedBuf<'a> {
|
||||
buf: &'a mut [u8],
|
||||
pos: usize,
|
||||
}
|
||||
|
||||
impl<'a> FixedBuf<'a> {
|
||||
pub fn new(storage: &'a mut [u8]) -> Self {
|
||||
Self { buf: storage, pos: 0 }
|
||||
}
|
||||
|
||||
pub fn as_cstr(&mut self) -> &CStr {
|
||||
let cap = self.buf.len();
|
||||
let end = core::cmp::min(self.pos, cap.saturating_sub(1));
|
||||
self.buf[end] = 0;
|
||||
unsafe { CStr::from_bytes_with_nul_unchecked(&self.buf[..=end]) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for FixedBuf<'_> {
|
||||
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||
let bytes = s.as_bytes();
|
||||
let cap = self.buf.len();
|
||||
|
||||
if self.pos >= cap {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Leave room for terminating null
|
||||
let remaining = cap - self.pos - 1;
|
||||
let to_copy = remaining.min(bytes.len());
|
||||
|
||||
let dst = &mut self.buf[self.pos..self.pos + to_copy];
|
||||
dst.copy_from_slice(&bytes[..to_copy]);
|
||||
|
||||
self.pos += to_copy;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" {
|
||||
pub fn slint_log_error(msg: *const core::ffi::c_char);
|
||||
}
|
||||
|
||||
let mut STORAGE: [u8; 256] = [0; 256];
|
||||
|
||||
unsafe {
|
||||
let mut w = FixedBuf::new(&mut STORAGE);
|
||||
write!(&mut w, "Rust PANIC: {:?}", info).ok();
|
||||
slint_log_error(w.as_cstr().as_ptr());
|
||||
};
|
||||
|
||||
loop {}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "simulator"))]
|
||||
mod allocator {
|
||||
use core::alloc::Layout;
|
||||
use core::ffi::c_void;
|
||||
unsafe extern "C" {
|
||||
pub fn free(p: *mut c_void);
|
||||
pub fn malloc(size: usize) -> *mut c_void;
|
||||
}
|
||||
|
||||
struct CAlloc;
|
||||
unsafe impl core::alloc::GlobalAlloc for CAlloc {
|
||||
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
|
||||
let align = layout.align();
|
||||
if align <= core::mem::size_of::<usize>() {
|
||||
unsafe { malloc(layout.size()) as *mut u8 }
|
||||
} else {
|
||||
// Ideally we'd use aligned_alloc, but that function caused heap corruption with esp-idf
|
||||
let ptr = unsafe { malloc(layout.size() + align) as *mut u8 };
|
||||
let shift = align - (ptr as usize % align);
|
||||
let ptr = ptr.add(shift);
|
||||
core::ptr::write(ptr.sub(1), shift as u8);
|
||||
ptr
|
||||
}
|
||||
}
|
||||
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
|
||||
unsafe {
|
||||
let align = layout.align();
|
||||
if align <= core::mem::size_of::<usize>() {
|
||||
free(ptr as *mut c_void);
|
||||
} else {
|
||||
let shift = core::ptr::read(ptr.sub(1)) as usize;
|
||||
free(ptr.sub(shift) as *mut c_void);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[global_allocator]
|
||||
static ALLOCATOR: CAlloc = CAlloc;
|
||||
}
|
||||
105
examples/safe-ui/src/simulator.rs
Normal file
105
examples/safe-ui/src/simulator.rs
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use slint_safeui_lib::platform::Bgra8888Pixel;
|
||||
|
||||
const WIDTH_PIXELS: u32 = 640;
|
||||
const HEIGHT_PIXELS: u32 = 480;
|
||||
const PIXEL_STRIDE: u32 = WIDTH_PIXELS;
|
||||
|
||||
static SIM_THREAD: OnceLock<std::thread::Thread> = OnceLock::new();
|
||||
static PIXEL_CHANNEL: OnceLock<smol::channel::Sender<Vec<Bgra8888Pixel>>> = OnceLock::new();
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
extern "C" fn slint_safeui_platform_wait_for_events(max_wait_milliseconds: i32) {
|
||||
if max_wait_milliseconds > 0 {
|
||||
std::thread::park_timeout(std::time::Duration::from_millis(max_wait_milliseconds as u64))
|
||||
} else {
|
||||
std::thread::park();
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
extern "C" fn slint_safeui_platform_wake() {
|
||||
if let Some(thread) = SIM_THREAD.get() {
|
||||
thread.unpark();
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
extern "C" fn slint_safeui_platform_render(
|
||||
user_data: *mut (),
|
||||
render_fn: extern "C" fn(
|
||||
*mut (),
|
||||
*mut core::ffi::c_char,
|
||||
buffer_size_bytes: u32,
|
||||
pixel_stride: u32,
|
||||
),
|
||||
) {
|
||||
let mut pixels = Vec::new();
|
||||
pixels.resize(PIXEL_STRIDE as usize * HEIGHT_PIXELS as usize, Bgra8888Pixel(0));
|
||||
let pixel_bytes: &mut [u8] = bytemuck::cast_slice_mut(&mut pixels);
|
||||
render_fn(
|
||||
user_data,
|
||||
pixel_bytes.as_mut_ptr() as *mut core::ffi::c_char,
|
||||
pixel_bytes.len() as u32,
|
||||
PIXEL_STRIDE,
|
||||
);
|
||||
|
||||
PIXEL_CHANNEL.get().unwrap().send_blocking(pixels).unwrap();
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
extern "C" fn slint_safeui_platform_get_screen_size(width: *mut u32, height: *mut u32) {
|
||||
unsafe {
|
||||
*width = WIDTH_PIXELS;
|
||||
*height = HEIGHT_PIXELS;
|
||||
}
|
||||
}
|
||||
|
||||
slint::slint! {import { AboutSlint, VerticalBox } from "std-widgets.slint";
|
||||
|
||||
export component MainWindow inherits Window {
|
||||
in property <image> image <=> screen.source;
|
||||
screen := Image { }
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let (pixel_sender, pixel_receiver) = smol::channel::unbounded();
|
||||
|
||||
PIXEL_CHANNEL.set(pixel_sender).unwrap();
|
||||
|
||||
let _thr = std::thread::spawn(|| {
|
||||
SIM_THREAD.set(std::thread::current()).unwrap();
|
||||
slint_safeui_lib::slint_app_main()
|
||||
});
|
||||
|
||||
let window = MainWindow::new().unwrap();
|
||||
|
||||
let window_weak = window.as_weak();
|
||||
|
||||
slint::spawn_local(async move {
|
||||
loop {
|
||||
if let Ok(source_pixels) = pixel_receiver.recv().await
|
||||
&& let Some(window) = window_weak.upgrade()
|
||||
{
|
||||
let mut pixel_buf: slint::SharedPixelBuffer<slint::Rgb8Pixel> =
|
||||
slint::SharedPixelBuffer::new(WIDTH_PIXELS, HEIGHT_PIXELS);
|
||||
let pixel_dest = pixel_buf.make_mut_slice();
|
||||
for i in 0..(WIDTH_PIXELS * HEIGHT_PIXELS) as usize {
|
||||
let src = slint::platform::software_renderer::PremultipliedRgbaColor::from(
|
||||
source_pixels[i],
|
||||
);
|
||||
pixel_dest[i] = slint::Rgb8Pixel { r: src.red, g: src.green, b: src.blue };
|
||||
}
|
||||
window.set_image(slint::Image::from_rgb8(pixel_buf));
|
||||
}
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
window.run().unwrap();
|
||||
}
|
||||
79
examples/safe-ui/src/slint-safeui-platform-interface.h
Normal file
79
examples/safe-ui/src/slint-safeui-platform-interface.h
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef SLINT_SAFEUI_PLATFORM_INTERFACE
|
||||
#define SLINT_SAFEUI_PLATFORM_INTERFACE
|
||||
|
||||
/**
|
||||
* Implement this function to suspend the current task. The function should return if one of the two
|
||||
* conditions are met:
|
||||
*
|
||||
* 1. If `max_wait_milliseconds` is positive and `max_wait_milliseconds` have elapsed since the
|
||||
* invocation, wake up and return.
|
||||
* 2. If `slint_safeui_platform_wake()` was invoked.
|
||||
*
|
||||
* In practice, this function marks to FreeRTOS' TaskNotifyTake function(s), like this:
|
||||
*
|
||||
* ```cpp
|
||||
* TickType_t ticks_to_wait = portMAX_DELAY;
|
||||
* if (max_wait_milliseconds >= 0) {
|
||||
* ticks_to_wait = pdMS_TO_TICKS(max_wait_milliseconds);
|
||||
* }
|
||||
* ulTaskNotifyTake(pdTRUE, ticks_to_wait);
|
||||
* ```
|
||||
*/
|
||||
void slint_safeui_platform_wait_for_events(int max_wait_milliseconds);
|
||||
|
||||
/**
|
||||
* Implement this function to wake up the suspend slint task.
|
||||
*
|
||||
* With FreeRTOS, this typically maps to `vTaskNotifyGiveFromISR()`.
|
||||
*/
|
||||
void slint_safeui_platform_wake(void);
|
||||
|
||||
/**
|
||||
* Implement this function to provide Slint with temporary access to the framebuffer, for rendering.
|
||||
*
|
||||
* The framebuffer is expected to be in BGRA8888 format (blue in the lower 8 bit, alpha in the upper
|
||||
* most, etc.)
|
||||
*
|
||||
* The implementation is typically three-fold:
|
||||
*
|
||||
* 1. Obtain a pointer to the framebuffer to render into.
|
||||
* 2. Invoke `render_fn()` with the provided `user_data()`, as well as a pointer to the frame
|
||||
* buffer, the size of the buffer in bytes, as well as the number of pixels per line. Slint is
|
||||
* expected to write to all bytes of the buffer.
|
||||
* 3. Flush the framebuffer to the display.
|
||||
*/
|
||||
void slint_safeui_platform_render(const void *user_data,
|
||||
void (*render_fn)(const void *user_data, char *frame_buffer,
|
||||
unsigned int buffer_size_bytes,
|
||||
unsigned int pixel_stride));
|
||||
|
||||
/**
|
||||
* Implement this function to provide Slint with a "sense of time". This is used to driver
|
||||
* animations as well as timers.
|
||||
*
|
||||
* A FreeRTOS-based implementation is typically a two-liner:
|
||||
*
|
||||
* ```cpp
|
||||
* TickType_t ticks = xTaskGetTickCount();
|
||||
* return ticks * portTICK_PERIOD_MS;
|
||||
* ```
|
||||
*/
|
||||
int slint_safeui_platform_duration_since_start(void);
|
||||
|
||||
/**
|
||||
* Implement this function to provide Slint with the dimensions of the frame buffer in pixels.
|
||||
*
|
||||
* This function is called only once. Resizing of the frame buffer is not implemented right now.
|
||||
*/
|
||||
void slint_safeui_platform_get_screen_size(unsigned int *width, unsigned int *height);
|
||||
|
||||
/**
|
||||
* This function is provided by the `SlintSafeUi` CMake target. It's implemented in
|
||||
* [./lib.rs](./lib.rs); Invoke this from your UI task to spin the Slint event loop.
|
||||
*/
|
||||
void slint_app_main(void);
|
||||
|
||||
#endif
|
||||
53
examples/safe-ui/ui/app-window.slint
Normal file
53
examples/safe-ui/ui/app-window.slint
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { AboutSlint, VerticalBox } from "std-widgets.slint";
|
||||
|
||||
export component MainWindow inherits Window {
|
||||
width: 320px;
|
||||
height: 240px;
|
||||
background: #ff000047;
|
||||
VerticalBox {
|
||||
AboutSlint {
|
||||
preferred-height: 150px;
|
||||
}
|
||||
}
|
||||
|
||||
property <[color]> colors: [Colors.green, Colors.orange, Colors.red];
|
||||
property <int> idx: 0;
|
||||
|
||||
Timer {
|
||||
running: true;
|
||||
interval: 1s;
|
||||
triggered => {
|
||||
idx = (idx + 1).mod(colors.length);
|
||||
}
|
||||
}
|
||||
|
||||
first := Rectangle {
|
||||
x: 0px;
|
||||
y: 0px;
|
||||
width: 40px;
|
||||
height: self.width;
|
||||
border-radius: self.width / 2;
|
||||
background: colors[idx.mod(colors.length)];
|
||||
}
|
||||
|
||||
second := Rectangle {
|
||||
x: 50px;
|
||||
y: 0px;
|
||||
width: 40px;
|
||||
height: self.width;
|
||||
border-radius: self.width / 2;
|
||||
background: colors[(idx + 1).mod(colors.length)];
|
||||
}
|
||||
|
||||
third := Rectangle {
|
||||
x: 100px;
|
||||
y: 0px;
|
||||
width: 40px;
|
||||
height: self.width;
|
||||
border-radius: self.width / 2;
|
||||
background: colors[(idx + 2).mod(colors.length)];
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue