This is an automated email from the ASF dual-hosted git repository. xiaoxiang pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/nuttx-apps.git
The following commit(s) were added to refs/heads/master by this push: new 18838d23b examples/rust: Add basic Slint example 18838d23b is described below commit 18838d23bff52e9e15627abcb54e7a4085489673 Author: Huang Qi <huang...@xiaomi.com> AuthorDate: Wed Jan 22 16:06:47 2025 +0800 examples/rust: Add basic Slint example Summary: - Added a basic Slint example for NuttX, demonstrating how to integrate Slint UI framework with Rust on NuttX - Includes a simple UI with a counter and touchscreen input handling - Provides CMake, Kconfig, and Makefile configurations for building the example Impact: - Introduces a new example showcasing Slint UI framework usage on NuttX - Enables developers to explore Rust-based UI development on embedded systems - Demonstrates integration with NuttX framebuffer and touchscreen drivers Signed-off-by: Huang Qi <huang...@xiaomi.com> --- examples/rust/slint/.gitignore | 1 + examples/rust/slint/CMakeLists.txt | 31 +++++++ examples/rust/slint/Cargo.toml | 24 ++++++ examples/rust/slint/Kconfig | 29 +++++++ examples/rust/slint/Make.defs | 26 ++++++ examples/rust/slint/Makefile | 34 ++++++++ examples/rust/slint/build.rs | 28 ++++++ examples/rust/slint/src/lib.rs | 171 +++++++++++++++++++++++++++++++++++++ examples/rust/slint/ui/app.slint | 25 ++++++ 9 files changed, 369 insertions(+) diff --git a/examples/rust/slint/.gitignore b/examples/rust/slint/.gitignore new file mode 100644 index 000000000..eb5a316cb --- /dev/null +++ b/examples/rust/slint/.gitignore @@ -0,0 +1 @@ +target diff --git a/examples/rust/slint/CMakeLists.txt b/examples/rust/slint/CMakeLists.txt new file mode 100644 index 000000000..6ed088aea --- /dev/null +++ b/examples/rust/slint/CMakeLists.txt @@ -0,0 +1,31 @@ +# ############################################################################## +# apps/examples/rust/slint/CMakeLists.txt +# +# Licensed to the Apache Software Foundation (ASF) under one or more contributor +# license agreements. See the NOTICE file distributed with this work for +# additional information regarding copyright ownership. The ASF licenses this +# file to you under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. +# +# ############################################################################## + +if(CONFIG_EXAMPLES_RUST_SLINT) + + # Build the Rust crate using nuttx_add_rust + nuttx_add_rust(CRATE_NAME slint CRATE_PATH ${CMAKE_CURRENT_SOURCE_DIR}) + + nuttx_add_application( + NAME ${CONFIG_EXAMPLES_RUST_SLINT_PROGNAME} STACKSIZE + ${CONFIG_EXAMPLES_RUST_SLINT_STACKSIZE} PRIORITY + ${CONFIG_EXAMPLES_RUST_SLINT_PRIORITY}) + +endif() # CONFIG_EXAMPLES_RUST_SLINT diff --git a/examples/rust/slint/Cargo.toml b/examples/rust/slint/Cargo.toml new file mode 100644 index 000000000..4600f2220 --- /dev/null +++ b/examples/rust/slint/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "slint" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["staticlib"] + +[profile.dev] +panic = "abort" + +[profile.release] +panic = "abort" +lto = true +codegen-units = 1 +opt-level = 'z' + +[dependencies] +libc = "0.2" +slint = { version = "1.9", default-features = false, features = ["compat-1-2", "renderer-software", "libm", "unsafe-single-threaded"] } +nuttx = { git = "https://github.com/no1wudi/nuttx-rs.git", branch = "master" } + +[build-dependencies] +slint-build = { version = "1.9" } diff --git a/examples/rust/slint/Kconfig b/examples/rust/slint/Kconfig new file mode 100644 index 000000000..babc19d04 --- /dev/null +++ b/examples/rust/slint/Kconfig @@ -0,0 +1,29 @@ +# +# For a description of the syntax of this configuration file, +# see the file kconfig-language.txt in the NuttX tools repository. +# + +config EXAMPLES_RUST_SLINT + tristate "Slint Basic Example" + default n + ---help--- + Enable the basic Slint example. + +if EXAMPLES_RUST_SLINT + +config EXAMPLES_RUST_SLINT_PROGNAME + string "Program name" + default "slint" + ---help--- + This is the name of the program that will be used when the + program is installed. + +config EXAMPLES_RUST_SLINT_PRIORITY + int "Task priority" + default 100 + +config EXAMPLES_RUST_SLINT_STACKSIZE + int "Stack size" + default DEFAULT_TASK_STACKSIZE + +endif diff --git a/examples/rust/slint/Make.defs b/examples/rust/slint/Make.defs new file mode 100644 index 000000000..9259913fc --- /dev/null +++ b/examples/rust/slint/Make.defs @@ -0,0 +1,26 @@ +############################################################################ +# apps/examples/rust/slint/Make.defs +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +############################################################################ + +include $(APPDIR)/tools/Rust.mk + +ifneq ($(CONFIG_EXAMPLES_RUST_SLINT),) +CONFIGURED_APPS += $(APPDIR)/examples/rust/slint +EXTRA_LIBS += $(call RUST_GET_BINDIR,slint,$(APPDIR)/examples/rust) +endif diff --git a/examples/rust/slint/Makefile b/examples/rust/slint/Makefile new file mode 100644 index 000000000..34b444e9d --- /dev/null +++ b/examples/rust/slint/Makefile @@ -0,0 +1,34 @@ +############################################################################ +# apps/examples/rust/slint/Makefile +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +############################################################################ + +include $(APPDIR)/Make.defs + +PROGNAME = $(CONFIG_EXAMPLES_RUST_SLINT_PROGNAME) +PRIORITY = $(CONFIG_EXAMPLES_RUST_SLINT_PRIORITY) +STACKSIZE = $(CONFIG_EXAMPLES_RUST_SLINT_STACKSIZE) +MODULE = $(CONFIG_EXAMPLES_RUST_SLINT) + +context:: + $(call RUST_CARGO_BUILD,slint,$(APPDIR)/examples/rust) + +clean:: + $(call RUST_CARGO_CLEAN,slint,$(APPDIR)/examples/rust) + +include $(APPDIR)/Application.mk diff --git a/examples/rust/slint/build.rs b/examples/rust/slint/build.rs new file mode 100644 index 000000000..6142de92a --- /dev/null +++ b/examples/rust/slint/build.rs @@ -0,0 +1,28 @@ +// ############################################################################## +// examples/rust/slint/build.rs +// +// Licensed to the Apache Software Foundation (ASF) under one or more contributor +// license agreements. See the NOTICE file distributed with this work for +// additional information regarding copyright ownership. The ASF licenses this +// file to you under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +// +// ############################################################################## + +fn main() { + slint_build::compile_with_config( + "ui/app.slint", + slint_build::CompilerConfiguration::new() + .embed_resources(slint_build::EmbedResourcesKind::EmbedForSoftwareRenderer), + ) + .unwrap(); +} diff --git a/examples/rust/slint/src/lib.rs b/examples/rust/slint/src/lib.rs new file mode 100644 index 000000000..043274cbc --- /dev/null +++ b/examples/rust/slint/src/lib.rs @@ -0,0 +1,171 @@ +// ############################################################################## +// examples/rust/slint/src/lib.rs +// +// Licensed to the Apache Software Foundation (ASF) under one or more contributor +// license agreements. See the NOTICE file distributed with this work for +// additional information regarding copyright ownership. The ASF licenses this +// file to you under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +// +// ############################################################################## + +use std::rc::Rc; +use std::{cell::RefCell, ffi::CStr}; + +use slint::platform::software_renderer::{LineBufferProvider, Rgb565Pixel}; + +use nuttx::input::touchscreen::*; +use nuttx::video::fb::*; +use slint::platform::WindowEvent; + +slint::include_modules!(); + +struct NuttXPlatform { + window: Rc<slint::platform::software_renderer::MinimalSoftwareWindow>, +} + +impl slint::platform::Platform for NuttXPlatform { + fn create_window_adapter( + &self, + ) -> Result<Rc<dyn slint::platform::WindowAdapter>, slint::PlatformError> { + Ok(self.window.clone()) + } + fn duration_since_start(&self) -> std::time::Duration { + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + } +} + +fn create_slint_app() -> AppWindow { + AppWindow::new().expect("Failed to load UI") +} + +#[derive(Debug, Clone)] +struct NuttXRenderer { + frame_buffer: *mut u8, + line_buffer: Rc<RefCell<Vec<Rgb565Pixel>>>, + stride: usize, + lines: usize, +} + +impl LineBufferProvider for NuttXRenderer { + type TargetPixel = Rgb565Pixel; + fn process_line( + &mut self, + line: usize, + range: std::ops::Range<usize>, + render_fn: impl FnOnce(&mut [Self::TargetPixel]), + ) { + let fb_size = self.stride * self.lines; + let frame = unsafe { + std::slice::from_raw_parts_mut(self.frame_buffer as *mut Rgb565Pixel, fb_size) + }; + let mut buffer = self.line_buffer.borrow_mut(); + render_fn(&mut buffer[range.clone()]); + let offset = line * self.stride; + frame[offset..offset + range.end - range.start].copy_from_slice(&buffer[range]); + } +} + +#[no_mangle] +pub extern "C" fn slint_main() { + // Open the framebuffer device and get its information + let fbdev = match FrameBuffer::new(CStr::from_bytes_with_nul(b"/dev/fb0\0").unwrap()) { + Ok(fb) => fb, + Err(_) => { + println!("Failed to open framebuffer device"); + return; + } + }; + + let planeinfo = fbdev.get_plane_info().unwrap(); + let videoinfo = fbdev.get_video_info().unwrap(); + + println!("{:?}", planeinfo); + println!("{:?}", videoinfo); + + if videoinfo.fmt != Format::RGB565 as u8 { + println!("Unsupported pixel format, only RGB565 is supported for now"); + return; + } + + // Open the touchscreen device + let mut tsdev = match TouchScreen::open(CStr::from_bytes_with_nul(b"/dev/input0\0").unwrap()) { + Ok(ts) => ts, + Err(_) => { + println!("Failed to open touchscreen device"); + return; + } + }; + + // Setup the Slint backend + let window = slint::platform::software_renderer::MinimalSoftwareWindow::new(Default::default()); + window.set_size(slint::PhysicalSize::new( + videoinfo.xres.into(), + videoinfo.yres.into(), + )); + + // Set the platform + slint::platform::set_platform(Box::new(NuttXPlatform { + window: window.clone(), + })) + .unwrap(); + + // Configure the UI + let _ui = create_slint_app(); + + // Create the renderer for NuttX + let nxrender = NuttXRenderer { + frame_buffer: planeinfo.fbmem as *mut u8, + line_buffer: Rc::new(RefCell::new(vec![ + Rgb565Pixel::default(); + videoinfo.xres as usize + ])), + stride: videoinfo.xres.into(), + lines: videoinfo.yres.into(), + }; + + let button = slint::platform::PointerEventButton::Left; + let mut last_touch_down = false; + let mut last_position = Default::default(); + + loop { + slint::platform::update_timers_and_animations(); + window.draw_if_needed(|renderer| { + renderer.render_by_line(nxrender.clone()); + }); + + if let Ok(sample) = tsdev.read_sample() { + if sample.npoints > 0 { + let point = sample.point[0]; + if point.is_pos_valid() { + let position = slint::PhysicalPosition::new(point.x.into(), point.y.into()) + .to_logical(window.scale_factor()); + last_position = position; + if point.is_touch_down() || point.is_touch_move() { + last_touch_down = true; + window.dispatch_event(WindowEvent::PointerPressed { position, button }); + } + } + } + } else { + if last_touch_down { + window.dispatch_event(WindowEvent::PointerReleased { + position: last_position, + button, + }); + last_touch_down = false; + } + } + } +} diff --git a/examples/rust/slint/ui/app.slint b/examples/rust/slint/ui/app.slint new file mode 100644 index 000000000..29c14af06 --- /dev/null +++ b/examples/rust/slint/ui/app.slint @@ -0,0 +1,25 @@ +import { Button, VerticalBox, HorizontalBox , AboutSlint } from "std-widgets.slint"; + +export component AppWindow inherits Window { + property <int> counter: 0; + + timer := Timer { + interval: 1s; + running: true; + triggered() => { + counter += 1; + } + } + + VerticalBox { + Text { + horizontal-alignment: center; + vertical-alignment: center; + text: "Times since start on NuttX: \n\{root.counter}"; + font-size: 24px; + } + + AboutSlint { } + + } +}