From c4f990b05bb8249d71fc44e798a3882290d26ffd Mon Sep 17 00:00:00 2001 From: Petr Portnov Date: Sat, 31 Dec 2022 18:46:01 +0300 Subject: [PATCH 01/49] feat: implement wrapper for `ViewPort` API --- crates/.cargo/config.toml | 2 +- crates/Cargo.toml | 1 + crates/gui/Cargo.toml | 25 +++ crates/gui/src/lib.rs | 5 + crates/gui/src/view_port.rs | 389 ++++++++++++++++++++++++++++++++++++ examples/gui/Cargo.lock | 8 + examples/gui/Cargo.toml | 1 + examples/gui/src/main.rs | 20 +- 8 files changed, 443 insertions(+), 8 deletions(-) create mode 100644 crates/gui/Cargo.toml create mode 100644 crates/gui/src/lib.rs create mode 100644 crates/gui/src/view_port.rs diff --git a/crates/.cargo/config.toml b/crates/.cargo/config.toml index cd5be99a..c44af23b 100644 --- a/crates/.cargo/config.toml +++ b/crates/.cargo/config.toml @@ -20,4 +20,4 @@ rustflags = [ ] [build] -target = ["thumbv7em-none-eabihf"] +target = "thumbv7em-none-eabihf" diff --git a/crates/Cargo.toml b/crates/Cargo.toml index decb5f38..0a2a491c 100644 --- a/crates/Cargo.toml +++ b/crates/Cargo.toml @@ -4,6 +4,7 @@ members = [ "flipperzero", "sys", "rt", + "gui", ] resolver = "2" diff --git a/crates/gui/Cargo.toml b/crates/gui/Cargo.toml new file mode 100644 index 00000000..0359d4ee --- /dev/null +++ b/crates/gui/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "flipperzero-gui" +version = "0.6.0-alpha" +description = "Rust GUI bindings for Flipper Zero" +repository = "https://github.com/dcoles/flipperzero-rs" +readme = "../../README.md" +license = "MIT" +edition = "2021" +rust-version = "1.64.0" +autobins = false +autoexamples = false +autotests = false +autobenches = false + +[package.metadata.docs.rs] +default-target = "thumbv7em-none-eabihf" +targets = [] +all-features = true + +[lib] +bench = false +test = false + +[dependencies] +flipperzero-sys = { path = "../sys", version = "0.6.0-alpha" } diff --git a/crates/gui/src/lib.rs b/crates/gui/src/lib.rs new file mode 100644 index 00000000..87d97477 --- /dev/null +++ b/crates/gui/src/lib.rs @@ -0,0 +1,5 @@ +//! Safe wrappers for Flipper GUI APIs. + +#![no_std] + +pub mod view_port; diff --git a/crates/gui/src/view_port.rs b/crates/gui/src/view_port.rs new file mode 100644 index 00000000..7bee701a --- /dev/null +++ b/crates/gui/src/view_port.rs @@ -0,0 +1,389 @@ +//! ViewPort APIs + +use core::{ + num::NonZeroU8, + ptr::{null_mut, NonNull}, +}; +use flipperzero_sys::{ + self as sys, ViewPort as SysViewPort, ViewPortOrientation as SysViewPortOrientation, +}; + +/// System ViewPort. +pub struct ViewPort { + view_port: *mut SysViewPort, +} + +impl ViewPort { + /// Creates a new `ViewPort`. + /// + /// # Example + /// + /// Basic usage: + /// + /// ``` + /// use flipperzero_gui::view_port::ViewPort; + /// + /// let view_port = ViewPort::new(); + /// ``` + pub fn new() -> ViewPort { + // SAFETY: allocation either succeeds producing the valid pointer + // or stops the system on OOM + let view_port = unsafe { sys::view_port_alloc() }; + Self { view_port } + } + + /// Sets the width of this `ViewPort`. + /// Empty `width` means automatic. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// use std::num::NonZeroU8; + /// use flipperzero_gui::view_port::ViewPort; + /// + /// let mut view_port = ViewPort::new(); + /// view_port.set_width(NonZeroU8::new(128u8)); + /// ``` + /// + /// Resize `ViewPort` to automatically selected width: + /// + /// ``` + /// use flipperzero_gui::view_port::ViewPort; + /// + /// let mut view_port = ViewPort::new(); + /// view_port.set_width(None); + /// ``` + pub fn set_width(&mut self, width: Option) { + let width = width.map_or(0u8, NonZeroU8::into); + // SAFETY: `self.view_port` is always valid + // and there are no `width` constraints + unsafe { sys::view_port_set_width(self.view_port, width) } + } + + /// Gets the width of this `ViewPort`. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// use flipperzero_gui::view_port::ViewPort; + /// + /// let view_port = ViewPort::new(); + /// let width = view_port.get_width(); + /// ``` + pub fn get_width(&self) -> NonZeroU8 { + // SAFETY: `self.view_port` is always valid + unsafe { sys::view_port_get_width(self.view_port) } + .try_into() + .expect("`view_port_get_width` should produce a positive value") + } + + /// Sets the height of this `ViewPort`. + /// Empty `height` means automatic. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// use std::num::NonZeroU8; + /// use flipperzero_gui::view_port::ViewPort; + /// + /// let mut view_port = ViewPort::new(); + /// view_port.set_height(NonZeroU8::new(128u8)); + /// ``` + /// + /// Resize `ViewPort` to automatically selected height: + /// + /// ``` + /// use flipperzero_gui::view_port::ViewPort; + /// + /// let mut view_port = ViewPort::new(); + /// view_port.set_height(None); + /// ``` + pub fn set_height(&mut self, height: Option) { + let height = height.map_or(0u8, NonZeroU8::into); + // SAFETY: `self.view_port` is always valid + // and there are no `height` constraints + unsafe { sys::view_port_set_height(self.view_port, height) } + } + + /// Gets the height of this `ViewPort`. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// use flipperzero_gui::view_port::ViewPort; + /// + /// let view_port = ViewPort::new(); + /// let height = view_port.get_height(); + /// ``` + pub fn get_height(&self) -> NonZeroU8 { + // SAFETY: `self.view_port` is always valid + unsafe { sys::view_port_get_height(self.view_port) } + .try_into() + .expect("`view_port_get_height` should produce a positive value") + } + + /// Sets the dimensions of this `ViewPort`. + /// Empty `dimensions` means automatic. + /// + /// # Example + /// + /// Basic usage: + /// + /// ``` + /// use std::num::NonZeroU8; + /// use flipperzero_gui::view_port::ViewPort; + /// + /// let mut view_port = ViewPort::new(); + /// view_port.set_dimensions(Some((NonZeroU8::new(120).unwrap(), NonZeroU8::new(80).unwrap()))); + /// ``` + /// + /// Resize `ViewPort` to automatically selected dimensions: + /// + /// ``` + /// use flipperzero_gui::view_port::ViewPort; + /// + /// let mut view_port = ViewPort::new(); + /// view_port.set_dimensions(None); + /// ``` + pub fn set_dimensions(&mut self, dimensions: Option<(NonZeroU8, NonZeroU8)>) { + match dimensions { + Some((width, height)) => { + self.set_width(Some(width)); + self.set_height(Some(height)); + } + None => { + self.set_width(None); + self.set_height(None); + } + } + } + + /// Gets the dimensions of this `ViewPort`. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// use flipperzero_gui::view_port::ViewPort; + /// + /// let view_port = ViewPort::new(); + /// let (width, height) = view_port.get_dimensions(); + /// ``` + pub fn get_dimensions(&self) -> (NonZeroU8, NonZeroU8) { + (self.get_width(), self.get_height()) + } + + /// Sets the orientation of this `ViewPort`. + /// + /// # Example + /// + /// Basic usage: + /// + /// ``` + /// use flipperzero_gui::view_port::{ViewPort, ViewPortOrientation}; + /// let mut view_port = ViewPort::new(); + /// view_port.set_orientation(ViewPortOrientation::Vertical); + /// ``` + pub fn set_orientation(&mut self, orientation: ViewPortOrientation) { + let orientation = SysViewPortOrientation::from(orientation); + + // SAFETY: `self.view_port` is always valid + // and `orientation` is guaranteed to be valid by `From` implementation + unsafe { sys::view_port_set_orientation(self.view_port, orientation) } + } + + /// Gets the orientation of this `ViewPort`. + /// + /// # Example + /// + /// Basic usage: + /// + /// ``` + /// use std::num::NonZeroU8; + /// use flipperzero_gui::view_port::{ViewPort, ViewPortOrientation}; + /// + /// let mut view_port = ViewPort::new(); + /// let orientation = view_port.get_orientation(); + /// ``` + pub fn get_orientation(&self) -> ViewPortOrientation { + // SAFETY: `self.view_port` is always valid + unsafe { sys::view_port_get_orientation(self.view_port) } + .try_into() + .expect("`view_port_get_orientation` should produce a valid `ViewPort`") + } + + /// Enables or disables this `ViewPort` rendering. + /// + /// `ViewPort` is enabled by default. + /// + /// # Example + /// + /// Basic usage: + /// + /// ``` + /// use flipperzero_gui::view_port::ViewPort; + /// + /// let mut view_port = ViewPort::new(); + /// view_port.set_enabled(false); + /// ``` + pub fn set_enabled(&mut self, enabled: bool) { + // SAFETY: `self.view_port` is always valid + unsafe { sys::view_port_enabled_set(self.view_port, enabled) } + } + + /// Checks if this `ViewPort` is enabled. + /// + /// # Example + /// + /// Basic usage: + /// + /// ``` + /// use flipperzero_gui::view_port::ViewPort; + /// + /// let mut view_port = ViewPort::new(); + /// let enabled = view_port.is_enabled(); + /// ``` + pub fn is_enabled(&self) -> bool { + // SAFETY: `self.view_port` is always valid + unsafe { sys::view_port_is_enabled(self.view_port) } + } + + /// Construct a `ViewPort` from a raw non-null pointer. + /// + /// After calling this function, the raw pointer is owned by the resulting `ViewPort`. + /// Specifically, the `ViewPort` destructor wil free the allocated memory. + /// + /// # Safety + /// + /// `raw` should be a valid pointer to [`SysViewPort`]. + /// + /// # Examples + /// + /// Recreate a `ViewPort` + /// which was preciously converted to a raw pointer using [`ViewPort::into_raw`]. + /// + /// ``` + /// use flipperzero_gui::view_port::ViewPort; + /// + /// let view_port = ViewPort::new(); + /// let ptr = view_port.into_raw(); + /// let view_port = unsafe { ViewPort::from_raw(ptr) }; + /// ``` + pub unsafe fn from_raw(raw: NonNull) -> Self { + Self { + view_port: raw.as_ptr(), + } + } + + /// Consumes this wrapper, returning a non-null raw pointer. + /// + /// After calling this function, the caller is responsible + /// for the memory previously managed by the `ViewPort`. + /// In particular, the caller should properly destroy `SysViewPort` and release the memory + /// such as by calling [`sys::view_port_free`]. + /// The easiest way to do this is to convert the raw pointer + /// back into a `ViewPort` with the [ViewPort::from_raw] function, + /// allowing the `ViewPort`` destructor to perform the cleanup. + /// + /// # Example + /// + /// Converting the raw pointer back into a `ViewPort` + /// with `ViewPort::from_raw` for automatic cleanup: + /// + /// ``` + /// use flipperzero_gui::view_port::ViewPort; + /// + /// let view_port = ViewPort::new(); + /// let ptr = view_port.into_raw(); + /// let view_port = unsafe { ViewPort::from_raw(ptr) }; + /// ``` + pub fn into_raw(mut self) -> NonNull { + let raw_pointer = core::mem::replace(&mut self.view_port, null_mut()); + // SAFETY: `self.view_port` is guaranteed to be non-null + // since it only becomes null after call to this function + // which consumes the wrapper + unsafe { NonNull::new_unchecked(raw_pointer) } + } +} + +impl Default for ViewPort { + fn default() -> Self { + Self::new() + } +} + +impl Drop for ViewPort { + fn drop(&mut self) { + // `self.view_port` is `null` iff it has been taken by call to `into_raw()` + if !self.view_port.is_null() { + // FIXME: unregister from system + // SAFETY: `self.view_port` is always valid + // and it should have been unregistered from the system by now + unsafe { sys::view_port_free(self.view_port) } + } + } +} + +#[derive(Clone, Copy, Debug)] +pub enum ViewPortOrientation { + Horizontal, + HorizontalFlip, + Vertical, + VerticalFlip, +} + +#[derive(Clone, Copy, Debug)] +pub enum FromSysViewPortOrientationError { + Max, + Invalid(SysViewPortOrientation), +} + +impl TryFrom for ViewPortOrientation { + type Error = FromSysViewPortOrientationError; + + fn try_from(value: SysViewPortOrientation) -> Result { + use sys::{ + ViewPortOrientation_ViewPortOrientationHorizontal as SYS_VIEW_PORT_ORIENTATION_HORIZONTAL, + ViewPortOrientation_ViewPortOrientationHorizontalFlip as SYS_VIEW_PORT_ORIENTATION_HORIZONTAL_FLIP, + ViewPortOrientation_ViewPortOrientationMAX as SYS_VIEW_PORT_ORIENTATION_MAX, + ViewPortOrientation_ViewPortOrientationVertical as SYS_VIEW_PORT_ORIENTATION_VERTICAL, + ViewPortOrientation_ViewPortOrientationVerticalFlip as SYS_VIEW_PORT_ORIENTATION_VERTICAL_FLIP, + }; + + match value { + SYS_VIEW_PORT_ORIENTATION_HORIZONTAL => Ok(Self::Horizontal), + SYS_VIEW_PORT_ORIENTATION_HORIZONTAL_FLIP => Ok(Self::HorizontalFlip), + SYS_VIEW_PORT_ORIENTATION_VERTICAL => Ok(Self::Vertical), + SYS_VIEW_PORT_ORIENTATION_VERTICAL_FLIP => Ok(Self::VerticalFlip), + SYS_VIEW_PORT_ORIENTATION_MAX => Err(Self::Error::Max), + invalid => Err(Self::Error::Invalid(invalid)), + } + } +} + +impl From for SysViewPortOrientation { + fn from(value: ViewPortOrientation) -> Self { + use sys::{ + ViewPortOrientation_ViewPortOrientationHorizontal as SYS_VIEW_PORT_ORIENTATION_HORIZONTAL, + ViewPortOrientation_ViewPortOrientationHorizontalFlip as SYS_VIEW_PORT_ORIENTATION_HORIZONTAL_FLIP, + ViewPortOrientation_ViewPortOrientationMAX as SYS_VIEW_PORT_ORIENTATION_MAX, + ViewPortOrientation_ViewPortOrientationVertical as SYS_VIEW_PORT_ORIENTATION_VERTICAL, + }; + + match value { + ViewPortOrientation::Horizontal => SYS_VIEW_PORT_ORIENTATION_HORIZONTAL, + ViewPortOrientation::HorizontalFlip => SYS_VIEW_PORT_ORIENTATION_HORIZONTAL_FLIP, + ViewPortOrientation::Vertical => SYS_VIEW_PORT_ORIENTATION_MAX, + ViewPortOrientation::VerticalFlip => SYS_VIEW_PORT_ORIENTATION_VERTICAL, + } + } +} diff --git a/examples/gui/Cargo.lock b/examples/gui/Cargo.lock index 0d93b962..2e6a1b3c 100644 --- a/examples/gui/Cargo.lock +++ b/examples/gui/Cargo.lock @@ -9,6 +9,13 @@ dependencies = [ "flipperzero-sys", ] +[[package]] +name = "flipperzero-gui" +version = "0.6.0-alpha" +dependencies = [ + "flipperzero-sys", +] + [[package]] name = "flipperzero-rt" version = "0.6.0-alpha" @@ -25,6 +32,7 @@ name = "gui" version = "0.1.0" dependencies = [ "flipperzero", + "flipperzero-gui", "flipperzero-rt", "flipperzero-sys", ] diff --git a/examples/gui/Cargo.toml b/examples/gui/Cargo.toml index 3a92c8cb..2a004b36 100644 --- a/examples/gui/Cargo.toml +++ b/examples/gui/Cargo.toml @@ -17,3 +17,4 @@ test = false flipperzero = { version = "0.6.0-alpha", path = "../../crates/flipperzero" } flipperzero-sys = { version = "0.6.0-alpha", path = "../../crates/sys" } flipperzero-rt = { version = "0.6.0-alpha", path = "../../crates/rt" } +flipperzero-gui = { version = "0.6.0-alpha", path = "../../crates/gui" } diff --git a/examples/gui/src/main.rs b/examples/gui/src/main.rs index 2760f9d8..925f8f36 100644 --- a/examples/gui/src/main.rs +++ b/examples/gui/src/main.rs @@ -12,6 +12,7 @@ use core::ptr; use core::time::Duration; use flipperzero::furi::thread::sleep; +use flipperzero_gui::view_port::ViewPort; use flipperzero_rt::{entry, manifest}; use flipperzero_sys as sys; @@ -23,7 +24,11 @@ manifest!(name = "Rust GUI example"); entry!(main); /// View draw handler. -pub extern "C" fn draw_callback(canvas: *mut sys::Canvas, _context: *mut c_void) { +/// +/// # Safety +/// +/// `canvas` should be a valid pointer to [`sys::Canvas`] +pub unsafe extern "C" fn draw_callback(canvas: *mut sys::Canvas, _context: *mut c_void) { unsafe { sys::canvas_draw_str(canvas, 39, 31, sys::c_string!("Hello, Rust!")); } @@ -32,19 +37,20 @@ pub extern "C" fn draw_callback(canvas: *mut sys::Canvas, _context: *mut c_void) fn main(_args: *mut u8) -> i32 { // Currently there is no high level GUI bindings, // so this all has to be done using the `sys` bindings. + let view_port = ViewPort::new().into_raw(); unsafe { - let view_port = sys::view_port_alloc(); - sys::view_port_draw_callback_set(view_port, Some(draw_callback), ptr::null_mut()); + sys::view_port_draw_callback_set(view_port.as_ptr(), Some(draw_callback), ptr::null_mut()); let gui = sys::furi_record_open(RECORD_GUI) as *mut sys::Gui; - sys::gui_add_view_port(gui, view_port, FULLSCREEN); + sys::gui_add_view_port(gui, view_port.as_ptr(), FULLSCREEN); sleep(Duration::from_secs(1)); - sys::view_port_enabled_set(view_port, false); - sys::gui_remove_view_port(gui, view_port); + sys::view_port_enabled_set(view_port.as_ptr(), false); + sys::gui_remove_view_port(gui, view_port.as_ptr()); sys::furi_record_close(RECORD_GUI); - sys::view_port_free(view_port); + + let _ = ViewPort::from_raw(view_port); } 0 From ebf0cb184ca11f5864d4820ab4396ed92cf23930 Mon Sep 17 00:00:00 2001 From: Petr Portnov Date: Sun, 1 Jan 2023 04:35:10 +0300 Subject: [PATCH 02/49] feat: implement basic APIs of `canvas` and `gui` --- crates/flipperzero/src/furi/canvas.rs | 52 ++- crates/flipperzero/src/furi/dialog.rs | 17 +- crates/flipperzero/src/furi/thread.rs | 8 +- crates/gui/Cargo.toml | 2 + crates/gui/src/canvas.rs | 576 ++++++++++++++++++++++++++ crates/gui/src/gui.rs | 182 ++++++++ crates/gui/src/lib.rs | 2 + crates/gui/src/view_port.rs | 43 +- crates/sys/src/bindings.rs | 2 +- crates/sys/src/furi.rs | 25 +- examples/gui/Cargo.lock | 1 + examples/gui/src/main.rs | 24 +- 12 files changed, 878 insertions(+), 56 deletions(-) create mode 100644 crates/gui/src/canvas.rs create mode 100644 crates/gui/src/gui.rs diff --git a/crates/flipperzero/src/furi/canvas.rs b/crates/flipperzero/src/furi/canvas.rs index 355c446d..760f8500 100644 --- a/crates/flipperzero/src/furi/canvas.rs +++ b/crates/flipperzero/src/furi/canvas.rs @@ -1,8 +1,8 @@ //! Canvases. -use flipperzero_sys as sys; +use flipperzero_sys::{self as sys, Align as SysAlign}; -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum Align { Left, Right, @@ -11,14 +11,46 @@ pub enum Align { Center, } -impl Align { - pub fn to_sys(&self) -> sys::Align { - match self { - Self::Left => sys::Align_AlignLeft, - Self::Right => sys::Align_AlignRight, - Self::Top => sys::Align_AlignTop, - Self::Bottom => sys::Align_AlignBottom, - Self::Center => sys::Align_AlignCenter, +#[derive(Clone, Copy, Debug)] +pub enum FromSysAlign { + Invalid(SysAlign), +} + +impl TryFrom for Align { + type Error = FromSysAlign; + + fn try_from(value: SysAlign) -> Result { + use sys::{ + Align_AlignBottom as SYS_ALIGN_BOTTOM, Align_AlignCenter as SYS_ALIGN_CENTER, + Align_AlignLeft as SYS_ALIGN_LEFT, Align_AlignRight as SYS_ALIGN_RIGHT, + Align_AlignTop as SYS_ALIGN_TOP, + }; + + Ok(match value { + SYS_ALIGN_LEFT => Self::Left, + SYS_ALIGN_RIGHT => Self::Right, + SYS_ALIGN_TOP => Self::Top, + SYS_ALIGN_BOTTOM => Self::Bottom, + SYS_ALIGN_CENTER => Self::Center, + invalid => Err(Self::Error::Invalid(invalid))?, + }) + } +} + +impl From for SysAlign { + fn from(value: Align) -> Self { + use sys::{ + Align_AlignBottom as SYS_ALIGN_BOTTOM, Align_AlignCenter as SYS_ALIGN_CENTER, + Align_AlignLeft as SYS_ALIGN_LEFT, Align_AlignRight as SYS_ALIGN_RIGHT, + Align_AlignTop as SYS_ALIGN_TOP, + }; + + match value { + Align::Left => SYS_ALIGN_LEFT, + Align::Right => SYS_ALIGN_RIGHT, + Align::Top => SYS_ALIGN_TOP, + Align::Bottom => SYS_ALIGN_BOTTOM, + Align::Center => SYS_ALIGN_CENTER, } } } diff --git a/crates/flipperzero/src/furi/dialog.rs b/crates/flipperzero/src/furi/dialog.rs index 753264cb..7acbb13a 100644 --- a/crates/flipperzero/src/furi/dialog.rs +++ b/crates/flipperzero/src/furi/dialog.rs @@ -46,7 +46,8 @@ impl DialogsApp { /// Displays a message. pub fn show(&mut self, message: &DialogMessage) -> DialogMessageButton { - let button_sys = unsafe { sys::dialog_message_show(self.data.as_ptr(), message.data) }; + let button_sys = + unsafe { sys::dialog_message_show(self.data.as_raw().as_ptr(), message.data) }; DialogMessageButton::from_sys(button_sys).expect("Invalid button") } @@ -95,8 +96,8 @@ impl<'a> DialogMessage<'a> { header.as_ptr(), x, y, - horizontal.to_sys(), - vertical.to_sys(), + horizontal.into(), + vertical.into(), ); } } @@ -109,8 +110,8 @@ impl<'a> DialogMessage<'a> { text.as_ptr(), x, y, - horizontal.to_sys(), - vertical.to_sys(), + horizontal.into(), + vertical.into(), ); } } @@ -164,6 +165,12 @@ impl DialogMessageButton { } } +impl Default for DialogMessage<'_> { + fn default() -> Self { + Self::new() + } +} + /// Displays a simple dialog. #[cfg(feature = "alloc")] pub fn alert(text: &str) { diff --git a/crates/flipperzero/src/furi/thread.rs b/crates/flipperzero/src/furi/thread.rs index 4581fcc9..70dfac09 100644 --- a/crates/flipperzero/src/furi/thread.rs +++ b/crates/flipperzero/src/furi/thread.rs @@ -1,15 +1,19 @@ //! Furi Thread API. +use core::time::Duration; use flipperzero_sys as sys; /// Puts the current thread to sleep for at least the specified amount of time. -pub fn sleep(duration: core::time::Duration) { +pub fn sleep(duration: Duration) { + const MAX_US_DURATION: Duration = Duration::from_secs(3600); + unsafe { // For durations of 1h+, use delay_ms so uint32_t doesn't overflow - if duration < core::time::Duration::from_secs(3600) { + if duration < MAX_US_DURATION { sys::furi_delay_us(duration.as_micros() as u32); } else { sys::furi_delay_ms(duration.as_millis() as u32); + // TODO: add reamining us-part } } } diff --git a/crates/gui/Cargo.toml b/crates/gui/Cargo.toml index 0359d4ee..8ce62b50 100644 --- a/crates/gui/Cargo.toml +++ b/crates/gui/Cargo.toml @@ -23,3 +23,5 @@ test = false [dependencies] flipperzero-sys = { path = "../sys", version = "0.6.0-alpha" } +# FIXME: this is only required for access to `Align` enum which may be mvoved to this crate +flipperzero = { path = "../flipperzero", version = "0.6.0-alpha" } diff --git a/crates/gui/src/canvas.rs b/crates/gui/src/canvas.rs new file mode 100644 index 00000000..350c84f4 --- /dev/null +++ b/crates/gui/src/canvas.rs @@ -0,0 +1,576 @@ +//! ViewPort APIs + +use crate::gui::Gui; +use core::{ffi::CStr, num::NonZeroU8}; +use flipperzero::furi::canvas::Align; +use flipperzero_sys::{ + self as sys, Canvas as SysCanvas, CanvasFontParameters as SysCanvasFontParameters, + Color as SysColor, Font as SysFont, +}; + +/// System Canvas. +pub struct Canvas<'a> { + _parent: &'a Gui, + canvas: *mut SysCanvas, +} + +impl<'a> Canvas<'a> { + /// Construct a `Canvas` from a raw non-null pointer. + /// + /// After calling this function, the raw pointer is owned by the resulting `Canvas`. + /// Specifically, the `Canvas` destructor will free the allocated memory. + /// + /// # Safety + /// + /// - `parent` should be the `Gui` which owns this canvas; + /// + /// - `raw` should be a valid pointer to [`Canvas`]. + /// + /// # Examples + /// + /// Recreate a `Canvas` + /// which vas previously converted to a raw pointer using [`Canvas::into_raw`]. + /// + /// ``` + /// use flipperzero_gui::{canvas::Canvas, gui::Gui}; + /// + /// let mut gui = Gui::new(); + /// let canvas = gui.direct_draw_acquire(); + /// let ptr = canvas.into_raw(); + /// let canvas = unsafe { Canvas::from_raw(gui, ptr) }; + /// ``` + pub unsafe fn from_raw(parent: &mut Gui, raw: NonNull) -> Self { + Self { + _parent: parent, + canvas: raw.as_ptr(), + } + } + + /// Consumes this wrapper, returning a non-null raw pointer. + /// + /// After calling this function, the caller is responsible + /// for the memory previously managed by the `Canvas`. + /// In particular, the caller should properly destroy `SysCanvas` and release the memory. + /// The easiest way to do this is to convert the raw pointer + /// back into a `Canvas` with the [ViewPort::from_raw] function, + /// allowing the `Canvas` destructor to perform the cleanup. + /// + /// # Example + /// + /// Converting the raw pointer back into a `Canvas` + /// with `Canvas::from_raw` for automatic cleanup: + /// + /// ``` + /// use flipperzero_gui::{canvas::Canvas, gui::Gui}; + /// + /// let mut gui = Gui::new(); + /// let canvas = gui.direct_draw_acquire(); + /// let ptr = canvas.into_raw(); + /// let canvas = unsafe { Canvas::from_raw(gui, ptr) }; + /// ``` + pub fn into_raw(mut self) -> NonNull { + let raw_pointer = core::mem::replace(&mut self.canvas, null_mut()); + // SAFETY: `self.canvas` is guaranteed to be non-null + // since it only becomes null after call to this function + // which consumes the wrapper + unsafe { NonNull::new_unchecked(raw_pointer) } + } + + // FIXME: + // - canvas_reset + // - canvas_commit + + pub fn width(&self) -> NonZeroU8 { + // SAFETY: `self.canvas` is always a valid pointer + unsafe { sys::canvas_width(self.canvas) } + .try_into() + .expect("`canvas_width` should produce a positive value") + } + + pub fn height(&self) -> NonZeroU8 { + // SAFETY: `self.canvas` is always a valid pointer + unsafe { sys::canvas_height(self.canvas) } + .try_into() + .expect("`canvas_height` should produce a positive value") + } + + pub fn current_font_height(&self) -> NonZeroU8 { + // SAFETY: `self.canvas` is always a valid pointer + unsafe { sys::canvas_current_font_height(self.canvas) } + .try_into() + .expect("`canvas_current_font_height` should produce a positive value") + } + + pub fn get_font_params(&self, font: Font) -> CanvasFontParameters<'_> { + let font = font.into(); + // SAFETY: `self.canvas` is always a valid pointer + // and `font` is guaranteed to be a valid value by `From` implementation + let raw = unsafe { sys::canvas_get_font_params(self.canvas, font) }; + CanvasFontParameters { _parent: self, raw } + } + + pub fn clear(&mut self) { + // SAFETY: `self.canvas` is always a valid pointer + unsafe { sys::canvas_clear(self.canvas) }; + } + + pub fn set_color(&mut self, color: Color) { + let color = color.into(); + // SAFETY: `self.canvas` is always a valid pointer + // and `font` is guaranteed to be a valid value by `From` implementation + unsafe { sys::canvas_set_color(self.canvas, color) }; + } + + pub fn set_font_direction(&mut self, font_direction: CanvasDirection) { + let font_direction = font_direction.into(); + // SAFETY: `self.canvas` is always a valid pointer + // and `font_direction` is guaranteed to be a valid value by `From` implementation + unsafe { sys::canvas_set_font_direction(self.canvas, font_direction) }; + } + + pub fn invert_color(&mut self) { + // SAFETY: `self.canvas` is always a valid pointer + unsafe { sys::canvas_invert_color(self.canvas) }; + } + + pub fn set_font(&mut self, font: Font) { + let font = font.into(); + // SAFETY: `self.canvas` is always a valid pointer + // and `font` is guaranteed to be a valid value by `From` implementation + unsafe { sys::canvas_set_font(self.canvas, font) }; + } + + pub fn draw_str(&mut self, x: u8, y: u8, str: impl AsRef>) { + let font = font.into(); + let str = str.as_ref().as_ptr(); + // SAFETY: `self.canvas` is always a valid pointer + // and `text` is guaranteed to be a valid pointer since it was created from `CStr` + unsafe { sys::canvas_draw_str(self.canvas, x, y, str) }; + } + + pub fn draw_str_aligned( + &mut self, + x: u8, + y: u8, + horizontal: Align, + vertical: Align, + str: impl AsRef>, + ) { + let font = font.into(); + let horizontal = horizontal.into(); + let vertical = vertical.into(); + let str = str.as_ref().as_ptr(); + // SAFETY: `self.canvas` is always a valid pointer, + // `horixontal` and `vertival` are guaranteed to be valid by `From` implementation + // and `text` is guaranteed to be a valid pointer since it was created from `CStr` + unsafe { sys::canvas_draw_str_aligned(self.canvas, x, y, horizontal, vertical, str) }; + } + + // TODO: + // - `canvas_string_width` this API looks quite strange yet + // - `canvas_flyph_width` this API looks quite strange yet + // - `canvas_draw_bitmap` bitmap constraints + // - `canvas_draw_icon_animation` animation lifetimes + // - `canvas_draw_icon` icon lifetimes + // - `canvas_draw_xbm` bitmap constraints + + // TODO: decide if we want to pack x-y pairs into tuples + + pub fn draw_dot(&mut self, x: u8, y: u8) { + // SAFETY: `self.canvas` is always a valid pointer, + unsafe { sys::canvas_draw_dot(self.canvas, x, y) } + } + + // TODO: do we need range checks? + // TODO: do `width` and `height` have to be non-zero + pub fn draw_box(&mut self, x: u8, y: u8, width: u8, height: u8) { + // SAFETY: `self.canvas` is always a valid pointer, + unsafe { sys::canvas_draw_box(self.canvas, x, y, width, height) } + } + + // TODO: do we need range checks? + // TODO: do `width` and `height` have to be non-zero + pub fn draw_frame(&mut self, x: u8, y: u8, width: u8, height: u8) { + // SAFETY: `self.canvas` is always a valid pointer, + unsafe { sys::canvas_draw_frame(self.canvas, x, y, width, height) } + } + + // TODO: do we need range checks? + // TODO: do `x2` and `y2` have to be non-zero + pub fn draw_line(&mut self, x1: u8, y1: u8, x2: u8, y2: u8) { + // SAFETY: `self.canvas` is always a valid pointer, + unsafe { sys::canvas_draw_line(self.canvas, x1, y1, x2, y2) } + } + + // TODO: do we need range checks? + // TODO: does `radius` have to be non-zero + pub fn draw_circle(&mut self, x: u8, y: u8, radius: u8) { + // SAFETY: `self.canvas` is always a valid pointer, + unsafe { sys::canvas_draw_circle(self.canvas, x, y, radius) } + } + + // TODO: do we need range checks? + // TODO: does `radius` have to be non-zero + pub fn draw_disc(&mut self, x: u8, y: u8, radius: u8) { + // SAFETY: `self.canvas` is always a valid pointer, + unsafe { sys::canvas_draw_disc(self.canvas, x, y, radius) } + } + + // TODO: do we need range checks? + // TODO: do `base` and `height` have to be non-zero + pub fn draw_triangle( + &mut self, + x: u8, + y: u8, + base: u8, + height: u8, + direction: CanvasDirection, + ) { + let direction = direction.into(); + // SAFETY: `self.canvas` is always a valid pointer, + // and `direction` is guaranteed to be valid by `From` implementation + unsafe { sys::canvas_draw_triangle(self.canvas, x, y, base, height, direction) } + } + + // TODO: do we need range checks? + // TODO: does `character` have to be of a wrapper type + pub fn draw_glyph(&mut self, x: u8, y: u8, character: u16) { + // SAFETY: `self.canvas` is always a valid pointer, + unsafe { sys::canvas_draw_glyph(self.canvas, x, y, character) } + } + + pub fn set_bitmap_mode(&mut self, alpha: bool) { + // SAFETY: `self.canvas` is always a valid pointer, + unsafe { sys::canvas_set_bitmap_mode(self.canvas, alpha) } + } + + // TODO: do we need range checks? + // TODO: do `width`, `height` and `radius` have to be non-zero + pub fn draw_rframe(&mut self, x: u8, y: u8, width: u8, height: u8, radius: u8) { + // SAFETY: `self.canvas` is always a valid pointer, + unsafe { sys::canvas_draw_rframe(self.canvas, x, y, width, height, radius) } + } + + // TODO: do we need range checks? + // TODO: do `width`, `height` and `radius` have to be non-zero + pub fn draw_rbox(&mut self, x: u8, y: u8, width: u8, height: u8, radius: u8) { + // SAFETY: `self.canvas` is always a valid pointer, + unsafe { sys::canvas_draw_rbox(self.canvas, x, y, width, height, radius) } + } +} + +impl Drop for Canvas<'_> { + fn drop(&mut self) { + // unsafe { sys::gui_direct_draw_release(self.parent...) } + } +} + +pub struct CanvasFontParameters<'a> { + _parent: &'a Canvas, + raw: *mut SysCanvasFontParameters, +} + +impl<'a> CanvasFontParameters<'a> { + fn leading_default(&self) -> NonZeroU8 { + // SAFETY: this allways outlives its parent + unsafe { *self.raw } + .leading_default + .try_into() + .expect("`leading_default` should always be positive") + } + + fn set_leading_default(&mut self, leading_default: NonZeroU8) { + // SAFETY: this allways outlives its parent + unsafe { *self.raw }.leading_default = leading_default.into() + } + + fn leading_min(&self) -> NonZeroU8 { + // SAFETY: this allways outlives its parent + unsafe { *self.raw } + .leading_min + .try_into() + .expect("`leading_min` should always be positive") + } + + fn set_leading_min(&mut self, leading_min: NonZeroU8) { + // SAFETY: this allways outlives its parent + unsafe { *self.raw }.leading_min = leading_min.into() + } + + fn height(&self) -> NonZeroU8 { + // SAFETY: this allways outlives its parent + unsafe { *self.raw } + .height + .try_into() + .expect("`height` should always be positive") + } + + fn set_height(&mut self, height: NonZeroU8) { + // SAFETY: this allways outlives its parent + unsafe { *self.raw }.height = height.into() + } + + fn descender(&self) -> u8 { + // SAFETY: this allways outlives its parent + unsafe { *self.raw }.descender + } + + fn set_descender(&mut self, descender: u8) { + // SAFETY: this allways outlives its parent + unsafe { *self.raw }.descender = descender + } + + fn snapshot(&self) -> CanvasFontParametersSnapshot { + unsafe { *self.raw } + .try_into() + .expect("raw `CanvasFontParameters` should be valid") + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct CanvasFontParametersSnapshot { + leading_default: NonZeroU8, + leading_min: NonZeroU8, + height: NonZeroU8, + descender: u8, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum FromSysGuiLayerError { + ZeroLeadingDefault, + ZeroLeadingMin, + ZeroHeight, +} + +impl TryFrom for CanvasFontParametersSnapshot { + type Error = FromSysGuiLayerError; + + fn try_from(value: SysCanvasFontParameters) -> Result { + Ok(Self { + leading_default: value + .leading_default + .try_into() + .or(Err(Self::Error::ZeroLeadingDefault))?, + leading_min: value + .leading_min + .try_into() + .or(Err(Self::Error::ZeroLeadingMin))?, + height: value.height.try_into().or(Err(Self::Error::ZeroHeight))?, + descender: value.descender, + }) + } +} + +impl From for SysCanvasFontParameters { + fn from(value: CanvasFontParametersSnapshot) -> Self { + Self { + leading_default: value.leading_default.into(), + leading_min: value.leading_min.into(), + height: value.height.into(), + descender: value.descender.into(), + } + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum Color { + White, + Black, + // TDOO: add this color + // Xor, +} + +#[derive(Clone, Copy, Debug)] +pub enum FromSysColor { + Invalid(SysColor), +} + +impl TryFrom for Color { + type Error = FromSysColor; + + fn try_from(value: SysColor) -> Result { + use sys::{ + Color_ColorBlack as SYS_COLOR_BLACK, + Color_ColorWhite as SYS_COLOR_WHITE, + // Color_ColorXOR as SYS_COLOR_XOR, + }; + + Ok(match value { + SYS_COLOR_WHITE => Self::White, + SYS_COLOR_BLACK => Self::Black, + // SYS_COLOR_XOR => Ok(Self::Xor), + invalid => Err(Self::Error::Invalid(invalid))?, + }) + } +} + +impl From for SysColor { + fn from(value: Color) -> Self { + use sys::{ + Color_ColorBlack as SYS_COLOR_BLACK, + Color_ColorWhite as SYS_COLOR_WHITE, + // Color_ColorXOR as SYS_COLOR_XOR, + }; + + match value { + Color::White => SYS_COLOR_WHITE, + Color::Black => SYS_COLOR_BLACK, + // Color::Xor => SYS_COLOR_XOR, + } + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum Font { + Primary, + Secondary, + Keyboard, + BigNumbers, +} + +#[derive(Clone, Copy, Debug)] +pub enum FromSysFont { + TotalNumber, + Invalid(SysFont), +} + +impl TryFrom for Font { + type Error = FromSysFont; + + fn try_from(value: SysFont) -> Result { + use sys::{ + Font_FontBigNumbers as SYS_FONT_BIG_NUMBERS, Font_FontKeyboard as SYS_FONT_KEYBOARD, + Font_FontPrimary as SYS_FONT_PRIMARY, Font_FontSecondary as SYS_FONT_SECONDARY, + Font_FontTotalNumber as SYS_FONT_TOTAL_NUMBER, + }; + + Ok(match value { + SYS_FONT_PRIMARY => Self::Primary, + SYS_FONT_SECONDARY => Self::Secondary, + SYS_FONT_KEYBOARD => Self::Keyboard, + SYS_FONT_BIG_NUMBERS => Self::BigNumbers, + SYS_FONT_TOTAL_NUMBER => Err(Self::Error::TotalNumber)?, + invalid => Err(Self::Error::Invalid(invalid))?, + }) + } +} + +impl From for SysFont { + fn from(value: Font) -> Self { + use sys::{ + Font_FontBigNumbers as SYS_FONT_BIG_NUMBERS, Font_FontKeyboard as SYS_FONT_KEYBOARD, + Font_FontPrimary as SYS_FONT_PRIMARY, Font_FontSecondary as SYS_FONT_SECONDARY, + }; + + match value { + Font::Primary => SYS_FONT_PRIMARY, + Font::Secondary => SYS_FONT_SECONDARY, + Font::Keyboard => SYS_FONT_KEYBOARD, + Font::BigNumbers => SYS_FONT_BIG_NUMBERS, + } + } +} + +#[derive(Clone, Copy, Debug)] +pub enum CanvasOrientation { + Horizontal, + HorizontalFlip, + Vertical, + VerticalFlip, +} + +#[derive(Clone, Copy, Debug)] +pub enum FromSysCanvasOrientationError { + Invalid(SysCanvasOrientation), +} + +impl TryFrom for CanvasOrientation { + type Error = FromSysCanvasOrientationError; + + fn try_from(value: SysCanvasOrientation) -> Result { + use sys::{ + CanvasOrientation_CanvasOrientationHorizontal as SYS_CANVAS_ORIENTATION_HORIZONTAL, + CanvasOrientation_CanvasOrientationHorizontalFlip as SYS_CANVAS_ORIENTATION_HORIZONTAL_FLIP, + CanvasOrientation_CanvasOrientationVertical as SYS_CANVAS_ORIENTATION_VERTICAL, + CanvasOrientation_CanvasOrientationVerticalFlip as SYS_CANVAS_ORIENTATION_VERTICAL_FLIP, + }; + + Ok(match value { + SYS_CANVAS_ORIENTATION_HORIZONTAL => Self::Horizontal, + SYS_CANVAS_ORIENTATION_HORIZONTAL_FLIP => Self::HorizontalFlip, + SYS_CANVAS_ORIENTATION_VERTICAL => Self::Vertical, + SYS_CANVAS_ORIENTATION_VERTICAL_FLIP => Self::VerticalFlip, + invalid => Err(Self::Error::Invalid(invalid))?, + }) + } +} + +impl From for SysCanvasOrientation { + fn from(value: CanvasOrientation) -> Self { + use sys::{ + CanvasOrientation_CanvasOrientationHorizontal as SYS_CANVAS_ORIENTATION_HORIZONTAL, + CanvasOrientation_CanvasOrientationHorizontalFlip as SYS_CANVAS_ORIENTATION_HORIZONTAL_FLIP, + CanvasOrientation_CanvasOrientationVertical as SYS_CANVAS_ORIENTATION_VERTICAL, + CanvasOrientation_CanvasOrientationVerticalFlip as SYS_CANVAS_ORIENTATION_VERTICAL_FLIP, + }; + + match value { + CanvasOrientation::Horizontal => SYS_CANVAS_ORIENTATION_HORIZONTAL, + CanvasOrientation::HorizontalFlip => SYS_CANVAS_ORIENTATION_HORIZONTAL_FLIP, + CanvasOrientation::Vertical => SYS_CANVAS_ORIENTATION_VERTICAL, + CanvasOrientation::VerticalFlip => SYS_CANVAS_ORIENTATION_VERTICAL_FLIP, + } + } +} + +#[derive(Clone, Copy, Debug)] +pub enum CanvasDirection { + LeftToRight, + TopToBottom, + RightToLeft, + BottomToTop, +} + +#[derive(Clone, Copy, Debug)] +pub enum FromSysCanvasDirectionError { + Invalid(SysCanvasDirection), +} + +impl TryFrom for CanvasDirection { + type Error = FromSysCanvasDirectionError; + + fn try_from(value: SysCanvasDirection) -> Result { + use sys::{ + CanvasDirection_BottomToTop as SYS_CANVAS_DIRECTION_BOTTOM_TO_TOP, + CanvasDirection_LeftToRight as SYS_CANVAS_DIRECTION_LEFT_TO_RIGHT, + CanvasDirection_RightToLeft as SYS_CANVAS_DIRECTION_RIGHT_TO_LEFT, + CanvasDirection_TopToBottom as SYS_CANVAS_DIRECTION_TOP_TO_BOTTOM, + }; + + Ok(match value { + SYS_CANVAS_LEFT_TO_RIGHT => Self::LeftToRight, + SYS_CANVAS_TOP_TO_BOTTOM => Self::TopToBottom, + SYS_CANVAS_RIGHT_TO_LEFT => Self::RightToLeft, + SYS_CANVAS_BOTTOM_TO_TOP => Self::BottomToTop, + invalid => Err(Self::Error::Invalid(invalid))?, + }) + } +} + +impl From for SysCanvasDirection { + fn from(value: CanvasDirection) -> Self { + use sys::{ + CanvasDirection_BottomToTop as SYS_CANVAS_DIRECTION_BOTTOM_TO_TOP, + CanvasDirection_LeftToRight as SYS_CANVAS_DIRECTION_LEFT_TO_RIGHT, + CanvasDirection_RightToLeft as SYS_CANVAS_DIRECTION_RIGHT_TO_LEFT, + CanvasDirection_TopToBottom as SYS_CANVAS_DIRECTION_TOP_TO_BOTTOM, + }; + + match value { + CanvasDirection::BottomToTop => SYS_CANVAS_DIRECTION_BOTTOM_TO_TOP, + CanvasDirection::LeftToRight => SYS_CANVAS_DIRECTION_LEFT_TO_RIGHT, + CanvasDirection::RightToLeft => SYS_CANVAS_DIRECTION_RIGHT_TO_LEFT, + CanvasDirection::TopToBottom => SYS_CANVAS_DIRECTION_TOP_TO_BOTTOM, + } + } +} diff --git a/crates/gui/src/gui.rs b/crates/gui/src/gui.rs new file mode 100644 index 00000000..9b681a87 --- /dev/null +++ b/crates/gui/src/gui.rs @@ -0,0 +1,182 @@ +//! GUI APIs + +use crate::canvas::Canvas; +use crate::view_port::ViewPort; +use core::ffi::c_char; +use core::fmt::Debug; +use flipperzero_sys::{self as sys, furi::UnsafeRecord, Gui as SysGui, GuiLayer as SysGuiLayer}; + +/// System ViewPort. +pub struct Gui { + gui: UnsafeRecord, +} + +impl Gui { + /// Furi record corresponding to GUI. + pub const RECORD: *const c_char = sys::c_string!("gui"); + + pub fn new() -> Self { + // SAFETY: `RECORD` is a constant + let gui = unsafe { UnsafeRecord::open(Self::RECORD) }; + + Self { gui } + } + + pub fn add_view_port(&mut self, view_port: ViewPort, layer: GuiLayer) -> GuiViewPort<'_> { + // SAFETY: `self.gui` is owned by this `Gui` + let gui = unsafe { self.gui.as_raw() }.as_ptr(); + // SAFETY: `view_port` should outlive this `Gui` + let view_port_ptr = unsafe { view_port.as_raw() }.as_ptr(); + let layer = layer.into(); + + // SAFETY: all pointers are valid and `view_port` outlives this `Gui` + unsafe { sys::gui_add_view_port(gui, view_port_ptr, layer) }; + + GuiViewPort { + parent: self, + view_port, + } + } + + pub fn get_frame_buffer_size(&self) -> usize { + // SAFETY: `self.gui` is owned by this `Gui` + let gui = unsafe { self.gui.as_raw() }.as_ptr(); + // SAFETY: `gui` is always a valid pointer + unsafe { sys::gui_get_framebuffer_size(gui) } + } + + pub fn set_lockdown(&self, lockdown: bool) { + // SAFETY: `self.gui` is owned by this `Gui` + let gui = unsafe { self.gui.as_raw() }.as_ptr(); + // SAFETY: `gui` is always a valid pointer + unsafe { sys::gui_set_lockdown(gui, lockdown) } + } + + pub fn direct_draw_acquire(&self) -> Canvas<'_> { + // SAFETY: `self.gui` is owned by this `Gui` + let gui = unsafe { self.gui.as_raw() }.as_ptr(); + + // SAFETY: `gui` is always a valid pointer + // let canvas = unsafe { sys::gui_direct_draw_acquire(gui) } + let canvas = unimplemented!(""); + + // SAFETY: `self` os the parent of `canvas` + // and `canvas` is a freshly created valid pointer + unsafe { Canvas::from_raw(self, canvas) } + } + + // TODO: canvas method + // TODO: callback methods +} + +impl Default for Gui { + fn default() -> Self { + Self::new() + } +} + +/// `ViewPort` bound to a `Gui`. +pub struct GuiViewPort<'a> { + parent: &'a Gui, + view_port: ViewPort, +} + +impl<'a> GuiViewPort<'a> { + pub fn view_port(&self) -> &ViewPort { + &self.view_port + } + + pub fn view_port_mut(&mut self) -> &mut ViewPort { + &mut self.view_port + } + + pub fn send_to_front(&mut self) { + // # SAFETY: `self.parent` outlives this `GuiVewPort` + let gui = unsafe { self.parent.gui.as_raw() }.as_ptr(); + // # SAFETY: `self.view_port` is owned + let view_port = unsafe { self.view_port.as_raw() }.as_ptr(); + + // # SAFETY: `self.parent` outlives this `GuiVewPort` + unsafe { sys::gui_view_port_send_to_front(gui, view_port) }; + } + + // FIXME(Coles): `gui_view_port_send_to_back` is not present in bindings + // pub fn send_to_back(&mut self) { + // // # SAFETY: `self.parent` outlives this `GuiVewPort` + // let gui = unsafe { self.parent.gui.as_raw() }.as_ptr(); + // let view_port = unsafe { self.view_port.as_raw() }.as_ptr(); + // + // unsafe { sys::gui_view_port_send_to_back(gui, view_port) }; + // } +} + +impl Drop for GuiViewPort<'_> { + fn drop(&mut self) { + // # SAFETY: `self.parent` outlives this `GuiVewPort` + let gui = unsafe { self.parent.gui.as_raw() }.as_ptr(); + // # SAFETY: `self.view_port` is owned + let view_port = unsafe { self.view_port.as_raw() }.as_ptr(); + + unsafe { sys::gui_remove_view_port(gui, view_port) } + } +} + +#[derive(Clone, Copy, Debug)] +pub enum GuiLayer { + Desktop, + Window, + StatusBarLeft, + StatusBarRight, + Fullscreen, +} + +#[derive(Clone, Copy, Debug)] +pub enum FromSysGuiLayerError { + Max, + Invalid(SysGuiLayer), +} + +impl TryFrom for GuiLayer { + type Error = FromSysGuiLayerError; + + fn try_from(value: SysGuiLayer) -> Result { + use sys::{ + GuiLayer_GuiLayerDesktop as SYS_GUI_LAYER_DESKTOP, + GuiLayer_GuiLayerFullscreen as SYS_GUI_LAYER_FULLSCREN, + GuiLayer_GuiLayerMAX as SYS_GUI_LAYER_MAX, + GuiLayer_GuiLayerStatusBarLeft as SYS_GUI_LAYER_BAR_LEFT, + GuiLayer_GuiLayerStatusBarRight as SYS_GUI_LAYER_BAR_RIGHT, + GuiLayer_GuiLayerWindow as SYS_GUI_LAYER_WINDOW, + }; + + Ok(match value { + SYS_GUI_LAYER_DESKTOP => Self::Desktop, + SYS_GUI_LAYER_WINDOW => Self::Window, + SYS_GUI_LAYER_BAR_LEFT => Self::StatusBarLeft, + SYS_GUI_LAYER_BAR_RIGHT => Self::StatusBarRight, + SYS_GUI_LAYER_FULLSCREN => Self::Fullscreen, + SYS_GUI_LAYER_MAX => Err(Self::Error::Max)?, + invalid => Err(Self::Error::Invalid(invalid))?, + }) + } +} + +impl From for SysGuiLayer { + fn from(value: GuiLayer) -> Self { + use sys::{ + GuiLayer_GuiLayerDesktop as SYS_GUI_LAYER_DESKTOP, + GuiLayer_GuiLayerFullscreen as SYS_GUI_LAYER_FULLSCREN, + GuiLayer_GuiLayerStatusBarLeft as SYS_GUI_LAYER_BAR_LEFT, + GuiLayer_GuiLayerStatusBarRight as SYS_GUI_LAYER_BAR_RIGHT, + GuiLayer_GuiLayerWindow as SYS_GUI_LAYER_WINDOW, + }; + + match value { + GuiLayer::Desktop => SYS_GUI_LAYER_DESKTOP, + GuiLayer::Window => SYS_GUI_LAYER_WINDOW, + GuiLayer::StatusBarLeft => SYS_GUI_LAYER_BAR_LEFT, + GuiLayer::StatusBarRight => SYS_GUI_LAYER_BAR_RIGHT, + GuiLayer::Fullscreen => SYS_GUI_LAYER_FULLSCREN, + } + } +} diff --git a/crates/gui/src/lib.rs b/crates/gui/src/lib.rs index 87d97477..8e82141b 100644 --- a/crates/gui/src/lib.rs +++ b/crates/gui/src/lib.rs @@ -2,4 +2,6 @@ #![no_std] +pub mod canvas; +pub mod gui; pub mod view_port; diff --git a/crates/gui/src/view_port.rs b/crates/gui/src/view_port.rs index 7bee701a..ce5af83f 100644 --- a/crates/gui/src/view_port.rs +++ b/crates/gui/src/view_port.rs @@ -216,7 +216,7 @@ impl ViewPort { /// ``` pub fn get_orientation(&self) -> ViewPortOrientation { // SAFETY: `self.view_port` is always valid - unsafe { sys::view_port_get_orientation(self.view_port) } + unsafe { sys::view_port_get_orientation(self.view_port as *const _) } .try_into() .expect("`view_port_get_orientation` should produce a valid `ViewPort`") } @@ -246,6 +246,7 @@ impl ViewPort { /// /// Basic usage: /// + /// /// ``` /// use flipperzero_gui::view_port::ViewPort; /// @@ -260,7 +261,7 @@ impl ViewPort { /// Construct a `ViewPort` from a raw non-null pointer. /// /// After calling this function, the raw pointer is owned by the resulting `ViewPort`. - /// Specifically, the `ViewPort` destructor wil free the allocated memory. + /// Specifically, the `ViewPort` destructor will free the allocated memory. /// /// # Safety /// @@ -269,7 +270,7 @@ impl ViewPort { /// # Examples /// /// Recreate a `ViewPort` - /// which was preciously converted to a raw pointer using [`ViewPort::into_raw`]. + /// which vas previously converted to a raw pointer using [`ViewPort::into_raw`]. /// /// ``` /// use flipperzero_gui::view_port::ViewPort; @@ -292,7 +293,7 @@ impl ViewPort { /// such as by calling [`sys::view_port_free`]. /// The easiest way to do this is to convert the raw pointer /// back into a `ViewPort` with the [ViewPort::from_raw] function, - /// allowing the `ViewPort`` destructor to perform the cleanup. + /// allowing the `ViewPort` destructor to perform the cleanup. /// /// # Example /// @@ -313,6 +314,16 @@ impl ViewPort { // which consumes the wrapper unsafe { NonNull::new_unchecked(raw_pointer) } } + + /// Creates a copy of the non-null raw pointer to the [`SysViewPort`]. + /// + /// # Safety + /// + /// Caller must ensure that the provided pointer does not outlive this wrapper. + pub unsafe fn as_raw(&self) -> NonNull { + // SAFETY: the pointer is guaranteed to be non-null + unsafe { NonNull::new_unchecked(self.view_port) } + } } impl Default for ViewPort { @@ -350,7 +361,7 @@ pub enum FromSysViewPortOrientationError { impl TryFrom for ViewPortOrientation { type Error = FromSysViewPortOrientationError; - fn try_from(value: SysViewPortOrientation) -> Result { + fn try_from(value: SysViewPortOrientation) -> Result { use sys::{ ViewPortOrientation_ViewPortOrientationHorizontal as SYS_VIEW_PORT_ORIENTATION_HORIZONTAL, ViewPortOrientation_ViewPortOrientationHorizontalFlip as SYS_VIEW_PORT_ORIENTATION_HORIZONTAL_FLIP, @@ -359,14 +370,14 @@ impl TryFrom for ViewPortOrientation { ViewPortOrientation_ViewPortOrientationVerticalFlip as SYS_VIEW_PORT_ORIENTATION_VERTICAL_FLIP, }; - match value { - SYS_VIEW_PORT_ORIENTATION_HORIZONTAL => Ok(Self::Horizontal), - SYS_VIEW_PORT_ORIENTATION_HORIZONTAL_FLIP => Ok(Self::HorizontalFlip), - SYS_VIEW_PORT_ORIENTATION_VERTICAL => Ok(Self::Vertical), - SYS_VIEW_PORT_ORIENTATION_VERTICAL_FLIP => Ok(Self::VerticalFlip), - SYS_VIEW_PORT_ORIENTATION_MAX => Err(Self::Error::Max), - invalid => Err(Self::Error::Invalid(invalid)), - } + Ok(match value { + SYS_VIEW_PORT_ORIENTATION_HORIZONTAL => Self::Horizontal, + SYS_VIEW_PORT_ORIENTATION_HORIZONTAL_FLIP => Self::HorizontalFlip, + SYS_VIEW_PORT_ORIENTATION_VERTICAL => Self::Vertical, + SYS_VIEW_PORT_ORIENTATION_VERTICAL_FLIP => Self::VerticalFlip, + SYS_VIEW_PORT_ORIENTATION_MAX => Err(Self::Error::Max)?, + invalid => Err(Self::Error::Invalid(invalid))?, + }) } } @@ -375,15 +386,15 @@ impl From for SysViewPortOrientation { use sys::{ ViewPortOrientation_ViewPortOrientationHorizontal as SYS_VIEW_PORT_ORIENTATION_HORIZONTAL, ViewPortOrientation_ViewPortOrientationHorizontalFlip as SYS_VIEW_PORT_ORIENTATION_HORIZONTAL_FLIP, - ViewPortOrientation_ViewPortOrientationMAX as SYS_VIEW_PORT_ORIENTATION_MAX, ViewPortOrientation_ViewPortOrientationVertical as SYS_VIEW_PORT_ORIENTATION_VERTICAL, + ViewPortOrientation_ViewPortOrientationVerticalFlip as SYS_VIEW_PORT_ORIENTATION_VERTICAL_FLIP, }; match value { ViewPortOrientation::Horizontal => SYS_VIEW_PORT_ORIENTATION_HORIZONTAL, ViewPortOrientation::HorizontalFlip => SYS_VIEW_PORT_ORIENTATION_HORIZONTAL_FLIP, - ViewPortOrientation::Vertical => SYS_VIEW_PORT_ORIENTATION_MAX, - ViewPortOrientation::VerticalFlip => SYS_VIEW_PORT_ORIENTATION_VERTICAL, + ViewPortOrientation::Vertical => SYS_VIEW_PORT_ORIENTATION_VERTICAL, + ViewPortOrientation::VerticalFlip => SYS_VIEW_PORT_ORIENTATION_VERTICAL_FLIP, } } } diff --git a/crates/sys/src/bindings.rs b/crates/sys/src/bindings.rs index 01ef623a..77b19f8a 100644 --- a/crates/sys/src/bindings.rs +++ b/crates/sys/src/bindings.rs @@ -7243,7 +7243,7 @@ pub const ViewPortOrientation_ViewPortOrientationVertical: ViewPortOrientation = pub const ViewPortOrientation_ViewPortOrientationVerticalFlip: ViewPortOrientation = 3; #[doc = "< Special value, don't use it"] pub const ViewPortOrientation_ViewPortOrientationMAX: ViewPortOrientation = 4; -pub type ViewPortOrientation = core::ffi::c_uchar; +pub type CanvasOrientationViewPortOrientation = core::ffi::c_uchar; #[doc = " ViewPort Draw callback"] #[doc = " @warning called from GUI thread"] pub type ViewPortDrawCallback = ::core::option::Option< diff --git a/crates/sys/src/furi.rs b/crates/sys/src/furi.rs index b1354365..bbf30e41 100644 --- a/crates/sys/src/furi.rs +++ b/crates/sys/src/furi.rs @@ -2,13 +2,14 @@ use core::ffi::c_char; use core::fmt::Display; +use core::ptr::NonNull; use core::time::Duration; /// Operation status. /// The Furi API switches between using `enum FuriStatus`, `int32_t` and `uint32_t`. /// Since these all use the same bit representation, we can just "cast" the returns to this type. #[repr(transparent)] -#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Status(pub i32); impl Status { @@ -91,7 +92,9 @@ pub struct UnsafeRecord { impl UnsafeRecord { /// Opens a record. /// - /// Safety: The caller must ensure that `record_name` lives for the + /// # Safety + /// + /// The caller must ensure that `record_name` lives for the /// duration of the object lifetime. pub unsafe fn open(name: *const c_char) -> Self { Self { @@ -101,16 +104,24 @@ impl UnsafeRecord { } /// Returns the record data as a raw pointer. - pub fn as_ptr(&self) -> *mut T { - self.data + /// + /// # Safety + /// + /// Caller must ensure that the provided pointer does not outlive this wrapper. + pub unsafe fn as_raw(&self) -> NonNull { + // SAFETY: the pointer is guaranteed to be non-null + unsafe { NonNull::new_unchecked(self.data) } } } impl Drop for UnsafeRecord { fn drop(&mut self) { - unsafe { - // decrement the holders count - crate::furi_record_close(self.name); + if !self.data.is_null() { + unsafe { + // SAFETY: `self.name` is valid since it was used to construct this istance + // and ownership has not been taken + crate::furi_record_close(self.name); + } } } } diff --git a/examples/gui/Cargo.lock b/examples/gui/Cargo.lock index 2e6a1b3c..cd8b832c 100644 --- a/examples/gui/Cargo.lock +++ b/examples/gui/Cargo.lock @@ -13,6 +13,7 @@ dependencies = [ name = "flipperzero-gui" version = "0.6.0-alpha" dependencies = [ + "flipperzero", "flipperzero-sys", ] diff --git a/examples/gui/src/main.rs b/examples/gui/src/main.rs index 925f8f36..beb84830 100644 --- a/examples/gui/src/main.rs +++ b/examples/gui/src/main.rs @@ -7,19 +7,16 @@ // Required for panic handler extern crate flipperzero_rt; -use core::ffi::{c_char, c_void}; +use core::ffi::c_void; use core::ptr; use core::time::Duration; use flipperzero::furi::thread::sleep; +use flipperzero_gui::gui::{Gui, GuiLayer}; use flipperzero_gui::view_port::ViewPort; use flipperzero_rt::{entry, manifest}; use flipperzero_sys as sys; -// GUI record -const RECORD_GUI: *const c_char = sys::c_string!("gui"); -const FULLSCREEN: sys::GuiLayer = sys::GuiLayer_GuiLayerFullscreen; - manifest!(name = "Rust GUI example"); entry!(main); @@ -38,20 +35,17 @@ fn main(_args: *mut u8) -> i32 { // Currently there is no high level GUI bindings, // so this all has to be done using the `sys` bindings. let view_port = ViewPort::new().into_raw(); - unsafe { + let view_port = unsafe { sys::view_port_draw_callback_set(view_port.as_ptr(), Some(draw_callback), ptr::null_mut()); + ViewPort::from_raw(view_port) + }; - let gui = sys::furi_record_open(RECORD_GUI) as *mut sys::Gui; - sys::gui_add_view_port(gui, view_port.as_ptr(), FULLSCREEN); + let mut gui = Gui::new(); + let mut view_port = gui.add_view_port(view_port, GuiLayer::Fullscreen); - sleep(Duration::from_secs(1)); + sleep(Duration::from_secs(1)); - sys::view_port_enabled_set(view_port.as_ptr(), false); - sys::gui_remove_view_port(gui, view_port.as_ptr()); - sys::furi_record_close(RECORD_GUI); - - let _ = ViewPort::from_raw(view_port); - } + view_port.view_port_mut().set_enabled(false); 0 } From 1d087b36d8b9887313ebd962c7f794a1f853f2db Mon Sep 17 00:00:00 2001 From: Petr Portnov Date: Sun, 1 Jan 2023 04:40:28 +0300 Subject: [PATCH 03/49] fix: revert changes to `bindings.rs` --- crates/sys/src/bindings.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/sys/src/bindings.rs b/crates/sys/src/bindings.rs index 77b19f8a..01ef623a 100644 --- a/crates/sys/src/bindings.rs +++ b/crates/sys/src/bindings.rs @@ -7243,7 +7243,7 @@ pub const ViewPortOrientation_ViewPortOrientationVertical: ViewPortOrientation = pub const ViewPortOrientation_ViewPortOrientationVerticalFlip: ViewPortOrientation = 3; #[doc = "< Special value, don't use it"] pub const ViewPortOrientation_ViewPortOrientationMAX: ViewPortOrientation = 4; -pub type CanvasOrientationViewPortOrientation = core::ffi::c_uchar; +pub type ViewPortOrientation = core::ffi::c_uchar; #[doc = " ViewPort Draw callback"] #[doc = " @warning called from GUI thread"] pub type ViewPortDrawCallback = ::core::option::Option< From 38a04c0dd50af19cf7e890ad804f461bb205bc99 Mon Sep 17 00:00:00 2001 From: Petr Portnov Date: Sun, 1 Jan 2023 04:52:53 +0300 Subject: [PATCH 04/49] fix: resolve issues with `canvas.rs` --- crates/gui/src/canvas.rs | 141 +++++++++++++++++++-------------------- crates/gui/src/gui.rs | 4 +- 2 files changed, 73 insertions(+), 72 deletions(-) diff --git a/crates/gui/src/canvas.rs b/crates/gui/src/canvas.rs index 350c84f4..add4c237 100644 --- a/crates/gui/src/canvas.rs +++ b/crates/gui/src/canvas.rs @@ -1,11 +1,12 @@ //! ViewPort APIs use crate::gui::Gui; +use core::ptr::{null_mut, NonNull}; use core::{ffi::CStr, num::NonZeroU8}; use flipperzero::furi::canvas::Align; use flipperzero_sys::{ - self as sys, Canvas as SysCanvas, CanvasFontParameters as SysCanvasFontParameters, - Color as SysColor, Font as SysFont, + self as sys, Canvas as SysCanvas, CanvasDirection as SysCanvasDirection, + CanvasFontParameters as SysCanvasFontParameters, Color as SysColor, Font as SysFont, }; /// System Canvas. @@ -39,7 +40,7 @@ impl<'a> Canvas<'a> { /// let ptr = canvas.into_raw(); /// let canvas = unsafe { Canvas::from_raw(gui, ptr) }; /// ``` - pub unsafe fn from_raw(parent: &mut Gui, raw: NonNull) -> Self { + pub unsafe fn from_raw(parent: &'a mut Gui, raw: NonNull) -> Self { Self { _parent: parent, canvas: raw.as_ptr(), @@ -140,8 +141,7 @@ impl<'a> Canvas<'a> { unsafe { sys::canvas_set_font(self.canvas, font) }; } - pub fn draw_str(&mut self, x: u8, y: u8, str: impl AsRef>) { - let font = font.into(); + pub fn draw_str(&mut self, x: u8, y: u8, str: impl AsRef) { let str = str.as_ref().as_ptr(); // SAFETY: `self.canvas` is always a valid pointer // and `text` is guaranteed to be a valid pointer since it was created from `CStr` @@ -154,9 +154,8 @@ impl<'a> Canvas<'a> { y: u8, horizontal: Align, vertical: Align, - str: impl AsRef>, + str: impl AsRef, ) { - let font = font.into(); let horizontal = horizontal.into(); let vertical = vertical.into(); let str = str.as_ref().as_ptr(); @@ -266,7 +265,7 @@ impl Drop for Canvas<'_> { } pub struct CanvasFontParameters<'a> { - _parent: &'a Canvas, + _parent: &'a Canvas<'a>, raw: *mut SysCanvasFontParameters, } @@ -471,57 +470,57 @@ impl From for SysFont { } } -#[derive(Clone, Copy, Debug)] -pub enum CanvasOrientation { - Horizontal, - HorizontalFlip, - Vertical, - VerticalFlip, -} - -#[derive(Clone, Copy, Debug)] -pub enum FromSysCanvasOrientationError { - Invalid(SysCanvasOrientation), -} - -impl TryFrom for CanvasOrientation { - type Error = FromSysCanvasOrientationError; - - fn try_from(value: SysCanvasOrientation) -> Result { - use sys::{ - CanvasOrientation_CanvasOrientationHorizontal as SYS_CANVAS_ORIENTATION_HORIZONTAL, - CanvasOrientation_CanvasOrientationHorizontalFlip as SYS_CANVAS_ORIENTATION_HORIZONTAL_FLIP, - CanvasOrientation_CanvasOrientationVertical as SYS_CANVAS_ORIENTATION_VERTICAL, - CanvasOrientation_CanvasOrientationVerticalFlip as SYS_CANVAS_ORIENTATION_VERTICAL_FLIP, - }; - - Ok(match value { - SYS_CANVAS_ORIENTATION_HORIZONTAL => Self::Horizontal, - SYS_CANVAS_ORIENTATION_HORIZONTAL_FLIP => Self::HorizontalFlip, - SYS_CANVAS_ORIENTATION_VERTICAL => Self::Vertical, - SYS_CANVAS_ORIENTATION_VERTICAL_FLIP => Self::VerticalFlip, - invalid => Err(Self::Error::Invalid(invalid))?, - }) - } -} - -impl From for SysCanvasOrientation { - fn from(value: CanvasOrientation) -> Self { - use sys::{ - CanvasOrientation_CanvasOrientationHorizontal as SYS_CANVAS_ORIENTATION_HORIZONTAL, - CanvasOrientation_CanvasOrientationHorizontalFlip as SYS_CANVAS_ORIENTATION_HORIZONTAL_FLIP, - CanvasOrientation_CanvasOrientationVertical as SYS_CANVAS_ORIENTATION_VERTICAL, - CanvasOrientation_CanvasOrientationVerticalFlip as SYS_CANVAS_ORIENTATION_VERTICAL_FLIP, - }; - - match value { - CanvasOrientation::Horizontal => SYS_CANVAS_ORIENTATION_HORIZONTAL, - CanvasOrientation::HorizontalFlip => SYS_CANVAS_ORIENTATION_HORIZONTAL_FLIP, - CanvasOrientation::Vertical => SYS_CANVAS_ORIENTATION_VERTICAL, - CanvasOrientation::VerticalFlip => SYS_CANVAS_ORIENTATION_VERTICAL_FLIP, - } - } -} +// #[derive(Clone, Copy, Debug)] +// pub enum CanvasOrientation { +// Horizontal, +// HorizontalFlip, +// Vertical, +// VerticalFlip, +// } +// +// #[derive(Clone, Copy, Debug)] +// pub enum FromSysCanvasOrientationError { +// Invalid(SysCanvasOrientation), +// } +// +// impl TryFrom for CanvasOrientation { +// type Error = FromSysCanvasOrientationError; +// +// fn try_from(value: SysCanvasOrientation) -> Result { +// use sys::{ +// CanvasOrientation_CanvasOrientationHorizontal as SYS_CANVAS_ORIENTATION_HORIZONTAL, +// CanvasOrientation_CanvasOrientationHorizontalFlip as SYS_CANVAS_ORIENTATION_HORIZONTAL_FLIP, +// CanvasOrientation_CanvasOrientationVertical as SYS_CANVAS_ORIENTATION_VERTICAL, +// CanvasOrientation_CanvasOrientationVerticalFlip as SYS_CANVAS_ORIENTATION_VERTICAL_FLIP, +// }; +// +// Ok(match value { +// SYS_CANVAS_ORIENTATION_HORIZONTAL => Self::Horizontal, +// SYS_CANVAS_ORIENTATION_HORIZONTAL_FLIP => Self::HorizontalFlip, +// SYS_CANVAS_ORIENTATION_VERTICAL => Self::Vertical, +// SYS_CANVAS_ORIENTATION_VERTICAL_FLIP => Self::VerticalFlip, +// invalid => Err(Self::Error::Invalid(invalid))?, +// }) +// } +// } +// +// impl From for SysCanvasOrientation { +// fn from(value: CanvasOrientation) -> Self { +// use sys::{ +// CanvasOrientation_CanvasOrientationHorizontal as SYS_CANVAS_ORIENTATION_HORIZONTAL, +// CanvasOrientation_CanvasOrientationHorizontalFlip as SYS_CANVAS_ORIENTATION_HORIZONTAL_FLIP, +// CanvasOrientation_CanvasOrientationVertical as SYS_CANVAS_ORIENTATION_VERTICAL, +// CanvasOrientation_CanvasOrientationVerticalFlip as SYS_CANVAS_ORIENTATION_VERTICAL_FLIP, +// }; +// +// match value { +// CanvasOrientation::Horizontal => SYS_CANVAS_ORIENTATION_HORIZONTAL, +// CanvasOrientation::HorizontalFlip => SYS_CANVAS_ORIENTATION_HORIZONTAL_FLIP, +// CanvasOrientation::Vertical => SYS_CANVAS_ORIENTATION_VERTICAL, +// CanvasOrientation::VerticalFlip => SYS_CANVAS_ORIENTATION_VERTICAL_FLIP, +// } +// } +// } #[derive(Clone, Copy, Debug)] pub enum CanvasDirection { @@ -541,17 +540,17 @@ impl TryFrom for CanvasDirection { fn try_from(value: SysCanvasDirection) -> Result { use sys::{ - CanvasDirection_BottomToTop as SYS_CANVAS_DIRECTION_BOTTOM_TO_TOP, - CanvasDirection_LeftToRight as SYS_CANVAS_DIRECTION_LEFT_TO_RIGHT, - CanvasDirection_RightToLeft as SYS_CANVAS_DIRECTION_RIGHT_TO_LEFT, - CanvasDirection_TopToBottom as SYS_CANVAS_DIRECTION_TOP_TO_BOTTOM, + CanvasDirection_CanvasDirectionBottomToTop as SYS_CANVAS_DIRECTION_BOTTOM_TO_TOP, + CanvasDirection_CanvasDirectionLeftToRight as SYS_CANVAS_DIRECTION_LEFT_TO_RIGHT, + CanvasDirection_CanvasDirectionRightToLeft as SYS_CANVAS_DIRECTION_RIGHT_TO_LEFT, + CanvasDirection_CanvasDirectionTopToBottom as SYS_CANVAS_DIRECTION_TOP_TO_BOTTOM, }; Ok(match value { - SYS_CANVAS_LEFT_TO_RIGHT => Self::LeftToRight, - SYS_CANVAS_TOP_TO_BOTTOM => Self::TopToBottom, - SYS_CANVAS_RIGHT_TO_LEFT => Self::RightToLeft, - SYS_CANVAS_BOTTOM_TO_TOP => Self::BottomToTop, + SYS_CANVAS_DIRECTION_LEFT_TO_RIGHT => Self::LeftToRight, + SYS_CANVAS_DIRECTION_TOP_TO_BOTTOM => Self::TopToBottom, + SYS_CANVAS_DIRECTION_RIGHT_TO_LEFT => Self::RightToLeft, + SYS_CANVAS_DIRECTION_BOTTOM_TO_TOP => Self::BottomToTop, invalid => Err(Self::Error::Invalid(invalid))?, }) } @@ -560,10 +559,10 @@ impl TryFrom for CanvasDirection { impl From for SysCanvasDirection { fn from(value: CanvasDirection) -> Self { use sys::{ - CanvasDirection_BottomToTop as SYS_CANVAS_DIRECTION_BOTTOM_TO_TOP, - CanvasDirection_LeftToRight as SYS_CANVAS_DIRECTION_LEFT_TO_RIGHT, - CanvasDirection_RightToLeft as SYS_CANVAS_DIRECTION_RIGHT_TO_LEFT, - CanvasDirection_TopToBottom as SYS_CANVAS_DIRECTION_TOP_TO_BOTTOM, + CanvasDirection_CanvasDirectionBottomToTop as SYS_CANVAS_DIRECTION_BOTTOM_TO_TOP, + CanvasDirection_CanvasDirectionLeftToRight as SYS_CANVAS_DIRECTION_LEFT_TO_RIGHT, + CanvasDirection_CanvasDirectionRightToLeft as SYS_CANVAS_DIRECTION_RIGHT_TO_LEFT, + CanvasDirection_CanvasDirectionTopToBottom as SYS_CANVAS_DIRECTION_TOP_TO_BOTTOM, }; match value { diff --git a/crates/gui/src/gui.rs b/crates/gui/src/gui.rs index 9b681a87..d29823fb 100644 --- a/crates/gui/src/gui.rs +++ b/crates/gui/src/gui.rs @@ -52,6 +52,8 @@ impl Gui { unsafe { sys::gui_set_lockdown(gui, lockdown) } } + // TODO: separate `GuiCanvas` (locking the parent) + // and `Canvas` (independent of the parent) pub fn direct_draw_acquire(&self) -> Canvas<'_> { // SAFETY: `self.gui` is owned by this `Gui` let gui = unsafe { self.gui.as_raw() }.as_ptr(); @@ -62,7 +64,7 @@ impl Gui { // SAFETY: `self` os the parent of `canvas` // and `canvas` is a freshly created valid pointer - unsafe { Canvas::from_raw(self, canvas) } + // unsafe { Canvas::from_raw(self, canvas) } } // TODO: canvas method From 164eb8229a6f284bd218745acbde29525c6a46a9 Mon Sep 17 00:00:00 2001 From: Petr Portnov Date: Tue, 3 Jan 2023 00:21:45 +0300 Subject: [PATCH 05/49] feat: implement test callback support --- crates/gui/Cargo.toml | 3 + crates/gui/src/canvas.rs | 2 +- crates/gui/src/lib.rs | 4 + crates/gui/src/view_port.rs | 270 ++++++++++++++++++++++++------------ examples/gui/Cargo.lock | 8 ++ examples/gui/Cargo.toml | 3 +- examples/gui/src/main.rs | 22 ++- 7 files changed, 206 insertions(+), 106 deletions(-) diff --git a/crates/gui/Cargo.toml b/crates/gui/Cargo.toml index 8ce62b50..1cb45f06 100644 --- a/crates/gui/Cargo.toml +++ b/crates/gui/Cargo.toml @@ -25,3 +25,6 @@ test = false flipperzero-sys = { path = "../sys", version = "0.6.0-alpha" } # FIXME: this is only required for access to `Align` enum which may be mvoved to this crate flipperzero = { path = "../flipperzero", version = "0.6.0-alpha" } + +[features] +alloc = ["flipperzero/alloc"] diff --git a/crates/gui/src/canvas.rs b/crates/gui/src/canvas.rs index add4c237..1cd6f0b1 100644 --- a/crates/gui/src/canvas.rs +++ b/crates/gui/src/canvas.rs @@ -59,7 +59,7 @@ impl<'a> Canvas<'a> { /// # Example /// /// Converting the raw pointer back into a `Canvas` - /// with `Canvas::from_raw` for automatic cleanup: + /// with [`Canvas::from_raw`] for automatic cleanup: /// /// ``` /// use flipperzero_gui::{canvas::Canvas, gui::Gui}; diff --git a/crates/gui/src/lib.rs b/crates/gui/src/lib.rs index 8e82141b..212ecfa9 100644 --- a/crates/gui/src/lib.rs +++ b/crates/gui/src/lib.rs @@ -2,6 +2,10 @@ #![no_std] +#[cfg(feature = "alloc")] +extern crate alloc; + pub mod canvas; pub mod gui; +pub mod view; pub mod view_port; diff --git a/crates/gui/src/view_port.rs b/crates/gui/src/view_port.rs index ce5af83f..b6453621 100644 --- a/crates/gui/src/view_port.rs +++ b/crates/gui/src/view_port.rs @@ -1,6 +1,12 @@ //! ViewPort APIs +#[cfg(feature = "alloc")] +pub use self::alloc_features::*; + +use core::mem::size_of_val; use core::{ + ffi::c_void, + mem::size_of, num::NonZeroU8, ptr::{null_mut, NonNull}, }; @@ -10,7 +16,9 @@ use flipperzero_sys::{ /// System ViewPort. pub struct ViewPort { - view_port: *mut SysViewPort, + raw: *mut SysViewPort, + #[cfg(feature = "alloc")] + draw_callback: Option, } impl ViewPort { @@ -25,11 +33,90 @@ impl ViewPort { /// /// let view_port = ViewPort::new(); /// ``` - pub fn new() -> ViewPort { + pub fn new() -> Self { // SAFETY: allocation either succeeds producing the valid pointer // or stops the system on OOM - let view_port = unsafe { sys::view_port_alloc() }; - Self { view_port } + let raw = unsafe { sys::view_port_alloc() }; + + Self { + raw, + draw_callback: None, + } + } + + /// Construct a `ViewPort` from a raw non-null pointer. + /// + /// After calling this function, the raw pointer is owned by the resulting `ViewPort`. + /// Specifically, the `ViewPort` destructor will free the allocated memory. + /// + /// # Safety + /// + /// `raw` should be a valid pointer to [`SysViewPort`]. + /// + /// # Examples + /// + /// Recreate a `ViewPort` + /// which vas previously converted to a raw pointer using [`ViewPort::into_raw`]. + /// + /// ``` + /// use flipperzero_gui::view_port::ViewPort; + /// + /// let view_port = ViewPort::new(); + /// let (raw, draw_callback) = view_port.into_raw(); + /// let view_port = unsafe { ViewPort::from_raw(raw, draw_callback) }; + /// ``` + pub unsafe fn from_raw( + raw: NonNull, + draw_callback: Option, + ) -> Self { + Self { + raw: raw.as_ptr(), + #[cfg(feature = "alloc")] + draw_callback, + } + } + + /// Consumes this wrapper, returning a non-null raw pointer. + /// + /// After calling this function, the caller is responsible + /// for the memory previously managed by the `ViewPort`. + /// In particular, the caller should properly destroy `SysViewPort` and release the memory + /// such as by calling [`sys::view_port_free`]. + /// The easiest way to do this is to convert the raw pointer + /// back into a `ViewPort` with the [ViewPort::from_raw] function, + /// allowing the `ViewPort` destructor to perform the cleanup. + /// + /// # Example + /// + /// Converting the raw pointer back into a `ViewPort` + /// with [`ViewPort::from_raw`] for automatic cleanup: + /// + /// ``` + /// use flipperzero_gui::view_port::ViewPort; + /// + /// let view_port = ViewPort::new(); + /// let (raw, draw_callback) = view_port.into_raw(); + /// let view_port = unsafe { ViewPort::from_raw(raw, draw_callback) }; + /// ``` + pub fn into_raw(mut self) -> (NonNull, Option) { + let raw_pointer = core::mem::replace(&mut self.raw, null_mut()); + ( + // SAFETY: `self.raw` is guaranteed to be non-null + // since it only becomes null after call to this function + // which consumes the wrapper + unsafe { NonNull::new_unchecked(raw_pointer) }, + self.draw_callback.take(), + ) + } + + /// Creates a copy of the non-null raw pointer to the [`SysViewPort`]. + /// + /// # Safety + /// + /// Caller must ensure that the provided pointer does not outlive this wrapper. + pub unsafe fn as_raw(&self) -> NonNull { + // SAFETY: the pointer is guaranteed to be non-null + unsafe { NonNull::new_unchecked(self.raw) } } /// Sets the width of this `ViewPort`. @@ -57,9 +144,9 @@ impl ViewPort { /// ``` pub fn set_width(&mut self, width: Option) { let width = width.map_or(0u8, NonZeroU8::into); - // SAFETY: `self.view_port` is always valid + // SAFETY: `self.raw` is always valid // and there are no `width` constraints - unsafe { sys::view_port_set_width(self.view_port, width) } + unsafe { sys::view_port_set_width(self.raw, width) } } /// Gets the width of this `ViewPort`. @@ -75,8 +162,8 @@ impl ViewPort { /// let width = view_port.get_width(); /// ``` pub fn get_width(&self) -> NonZeroU8 { - // SAFETY: `self.view_port` is always valid - unsafe { sys::view_port_get_width(self.view_port) } + // SAFETY: `self.raw` is always valid + unsafe { sys::view_port_get_width(self.raw) } .try_into() .expect("`view_port_get_width` should produce a positive value") } @@ -106,9 +193,9 @@ impl ViewPort { /// ``` pub fn set_height(&mut self, height: Option) { let height = height.map_or(0u8, NonZeroU8::into); - // SAFETY: `self.view_port` is always valid + // SAFETY: `self.raw` is always valid // and there are no `height` constraints - unsafe { sys::view_port_set_height(self.view_port, height) } + unsafe { sys::view_port_set_height(self.raw, height) } } /// Gets the height of this `ViewPort`. @@ -124,8 +211,8 @@ impl ViewPort { /// let height = view_port.get_height(); /// ``` pub fn get_height(&self) -> NonZeroU8 { - // SAFETY: `self.view_port` is always valid - unsafe { sys::view_port_get_height(self.view_port) } + // SAFETY: `self.raw` is always valid + unsafe { sys::view_port_get_height(self.raw) } .try_into() .expect("`view_port_get_height` should produce a positive value") } @@ -196,9 +283,9 @@ impl ViewPort { pub fn set_orientation(&mut self, orientation: ViewPortOrientation) { let orientation = SysViewPortOrientation::from(orientation); - // SAFETY: `self.view_port` is always valid + // SAFETY: `self.raw` is always valid // and `orientation` is guaranteed to be valid by `From` implementation - unsafe { sys::view_port_set_orientation(self.view_port, orientation) } + unsafe { sys::view_port_set_orientation(self.raw, orientation) } } /// Gets the orientation of this `ViewPort`. @@ -215,8 +302,8 @@ impl ViewPort { /// let orientation = view_port.get_orientation(); /// ``` pub fn get_orientation(&self) -> ViewPortOrientation { - // SAFETY: `self.view_port` is always valid - unsafe { sys::view_port_get_orientation(self.view_port as *const _) } + // SAFETY: `self.raw` is always valid + unsafe { sys::view_port_get_orientation(self.raw as *const _) } .try_into() .expect("`view_port_get_orientation` should produce a valid `ViewPort`") } @@ -236,8 +323,8 @@ impl ViewPort { /// view_port.set_enabled(false); /// ``` pub fn set_enabled(&mut self, enabled: bool) { - // SAFETY: `self.view_port` is always valid - unsafe { sys::view_port_enabled_set(self.view_port, enabled) } + // SAFETY: `self.raw` is always valid + unsafe { sys::view_port_enabled_set(self.raw, enabled) } } /// Checks if this `ViewPort` is enabled. @@ -254,76 +341,28 @@ impl ViewPort { /// let enabled = view_port.is_enabled(); /// ``` pub fn is_enabled(&self) -> bool { - // SAFETY: `self.view_port` is always valid - unsafe { sys::view_port_is_enabled(self.view_port) } - } - - /// Construct a `ViewPort` from a raw non-null pointer. - /// - /// After calling this function, the raw pointer is owned by the resulting `ViewPort`. - /// Specifically, the `ViewPort` destructor will free the allocated memory. - /// - /// # Safety - /// - /// `raw` should be a valid pointer to [`SysViewPort`]. - /// - /// # Examples - /// - /// Recreate a `ViewPort` - /// which vas previously converted to a raw pointer using [`ViewPort::into_raw`]. - /// - /// ``` - /// use flipperzero_gui::view_port::ViewPort; - /// - /// let view_port = ViewPort::new(); - /// let ptr = view_port.into_raw(); - /// let view_port = unsafe { ViewPort::from_raw(ptr) }; - /// ``` - pub unsafe fn from_raw(raw: NonNull) -> Self { - Self { - view_port: raw.as_ptr(), - } - } - - /// Consumes this wrapper, returning a non-null raw pointer. - /// - /// After calling this function, the caller is responsible - /// for the memory previously managed by the `ViewPort`. - /// In particular, the caller should properly destroy `SysViewPort` and release the memory - /// such as by calling [`sys::view_port_free`]. - /// The easiest way to do this is to convert the raw pointer - /// back into a `ViewPort` with the [ViewPort::from_raw] function, - /// allowing the `ViewPort` destructor to perform the cleanup. - /// - /// # Example - /// - /// Converting the raw pointer back into a `ViewPort` - /// with `ViewPort::from_raw` for automatic cleanup: - /// - /// ``` - /// use flipperzero_gui::view_port::ViewPort; - /// - /// let view_port = ViewPort::new(); - /// let ptr = view_port.into_raw(); - /// let view_port = unsafe { ViewPort::from_raw(ptr) }; - /// ``` - pub fn into_raw(mut self) -> NonNull { - let raw_pointer = core::mem::replace(&mut self.view_port, null_mut()); - // SAFETY: `self.view_port` is guaranteed to be non-null - // since it only becomes null after call to this function - // which consumes the wrapper - unsafe { NonNull::new_unchecked(raw_pointer) } - } - - /// Creates a copy of the non-null raw pointer to the [`SysViewPort`]. - /// - /// # Safety - /// - /// Caller must ensure that the provided pointer does not outlive this wrapper. - pub unsafe fn as_raw(&self) -> NonNull { - // SAFETY: the pointer is guaranteed to be non-null - unsafe { NonNull::new_unchecked(self.view_port) } + // SAFETY: `self.raw` is always valid + unsafe { sys::view_port_is_enabled(self.raw) } } + // + // pub fn set_static_draw_callback<'a: 'b, 'b, F: Fn(*mut sys::Canvas)>( + // &'a mut self, + // callback: &'static fn(*mut sys::Canvas), + // ) { + // pub unsafe extern "C" fn dispatch( + // canvas: *mut sys::Canvas, + // context: *mut core::ffi::c_void, + // ) { + // // SAFETY: `context` is always a valid pointer + // let context = NonNull::new_unchecked(context as *const F as *mut F); + // // SAFETY: context is a valid pointer + // (unsafe { context.as_ref() })(canvas); + // } + // // FIXME: flipperzero-firmware: function pointer should be const + // let ptr = callback.as_ref().get_ref() as *const F as *mut c_void; + // + // unsafe { sys::view_port_draw_callback_set(self.raw, Some(dispatch::), ptr) } + // } } impl Default for ViewPort { @@ -334,12 +373,12 @@ impl Default for ViewPort { impl Drop for ViewPort { fn drop(&mut self) { - // `self.view_port` is `null` iff it has been taken by call to `into_raw()` - if !self.view_port.is_null() { + // `self.raw` is `null` iff it has been taken by call to `into_raw()` + if !self.raw.is_null() { // FIXME: unregister from system - // SAFETY: `self.view_port` is always valid + // SAFETY: `self.raw` is always valid // and it should have been unregistered from the system by now - unsafe { sys::view_port_free(self.view_port) } + unsafe { sys::view_port_free(self.raw) } } } } @@ -398,3 +437,52 @@ impl From for SysViewPortOrientation { } } } + +/// Functionality only available when `alloc` feature is enabled. +#[cfg(feature = "alloc")] +mod alloc_features { + use super::*; + use alloc::boxed::Box; + use core::pin::Pin; + + impl ViewPort { + pub fn set_draw_callback(&mut self, mut callback: ViewPortDrawCallback) { + type CallbackPtr = *mut ViewPortDrawCallback; + + pub unsafe extern "C" fn dispatch(canvas: *mut sys::Canvas, context: *mut c_void) { + let context: CallbackPtr = context.cast(); + // SAFETY: `context` is stored in a pinned Box which is a member of `ViewPort` + // and the callback is accessed exclusively by this function + (unsafe { NonNull::new_unchecked(context).as_mut() }).0(canvas); + } + + let mut callback = Box::pin(callback); + let ptr = callback.as_mut().get_mut() as CallbackPtr as *mut c_void; + // keep old cllback alive until the new one is written + let old_callback = self.draw_callback.replace(callback); + + const _: () = assert!( + size_of::<*const ViewPortDrawCallback>() == size_of::<*mut c_void>(), + "`ViewPortDrawCallback` should be a thin pointer" + ); + + unsafe { sys::view_port_draw_callback_set(self.raw, Some(dispatch), ptr) } + + drop(old_callback); + } + } + + pub struct ViewPortDrawCallback(Box); + + impl ViewPortDrawCallback { + pub fn new(callback: Box) -> Self { + Self(callback) + } + + pub fn boxed(callback: F) -> Self { + Self::new(Box::new(callback)) + } + } + + pub(super) type PinnedViewPortDrawCallback = Pin>; +} diff --git a/examples/gui/Cargo.lock b/examples/gui/Cargo.lock index cd8b832c..36e92dc5 100644 --- a/examples/gui/Cargo.lock +++ b/examples/gui/Cargo.lock @@ -9,6 +9,13 @@ dependencies = [ "flipperzero-sys", ] +[[package]] +name = "flipperzero-alloc" +version = "0.6.0-alpha" +dependencies = [ + "flipperzero-sys", +] + [[package]] name = "flipperzero-gui" version = "0.6.0-alpha" @@ -33,6 +40,7 @@ name = "gui" version = "0.1.0" dependencies = [ "flipperzero", + "flipperzero-alloc", "flipperzero-gui", "flipperzero-rt", "flipperzero-sys", diff --git a/examples/gui/Cargo.toml b/examples/gui/Cargo.toml index 2a004b36..23fcc883 100644 --- a/examples/gui/Cargo.toml +++ b/examples/gui/Cargo.toml @@ -17,4 +17,5 @@ test = false flipperzero = { version = "0.6.0-alpha", path = "../../crates/flipperzero" } flipperzero-sys = { version = "0.6.0-alpha", path = "../../crates/sys" } flipperzero-rt = { version = "0.6.0-alpha", path = "../../crates/rt" } -flipperzero-gui = { version = "0.6.0-alpha", path = "../../crates/gui" } +flipperzero-alloc = { version = "0.6.0-alpha", path = "../../crates/alloc" } +flipperzero-gui = { version = "0.6.0-alpha", path = "../../crates/gui", features = ["alloc"] } diff --git a/examples/gui/src/main.rs b/examples/gui/src/main.rs index beb84830..a6a44b25 100644 --- a/examples/gui/src/main.rs +++ b/examples/gui/src/main.rs @@ -6,14 +6,15 @@ // Required for panic handler extern crate flipperzero_rt; +// Alloc +extern crate alloc; +extern crate flipperzero_alloc; -use core::ffi::c_void; -use core::ptr; use core::time::Duration; use flipperzero::furi::thread::sleep; use flipperzero_gui::gui::{Gui, GuiLayer}; -use flipperzero_gui::view_port::ViewPort; +use flipperzero_gui::view_port::{ViewPort, ViewPortDrawCallback}; use flipperzero_rt::{entry, manifest}; use flipperzero_sys as sys; @@ -21,11 +22,8 @@ manifest!(name = "Rust GUI example"); entry!(main); /// View draw handler. -/// -/// # Safety -/// -/// `canvas` should be a valid pointer to [`sys::Canvas`] -pub unsafe extern "C" fn draw_callback(canvas: *mut sys::Canvas, _context: *mut c_void) { +fn draw_callback(canvas: *mut sys::Canvas) { + // # SAFETY: `canvas` should be a valid pointer unsafe { sys::canvas_draw_str(canvas, 39, 31, sys::c_string!("Hello, Rust!")); } @@ -34,11 +32,9 @@ pub unsafe extern "C" fn draw_callback(canvas: *mut sys::Canvas, _context: *mut fn main(_args: *mut u8) -> i32 { // Currently there is no high level GUI bindings, // so this all has to be done using the `sys` bindings. - let view_port = ViewPort::new().into_raw(); - let view_port = unsafe { - sys::view_port_draw_callback_set(view_port.as_ptr(), Some(draw_callback), ptr::null_mut()); - ViewPort::from_raw(view_port) - }; + let mut view_port = ViewPort::new(); + + view_port.set_draw_callback(ViewPortDrawCallback::boxed(draw_callback)); let mut gui = Gui::new(); let mut view_port = gui.add_view_port(view_port, GuiLayer::Fullscreen); From 3f4c26b294e7905b80eed6ffeeb61846e5d96111 Mon Sep 17 00:00:00 2001 From: Petr Portnov Date: Tue, 3 Jan 2023 01:43:12 +0300 Subject: [PATCH 06/49] feat: no vcall callbacks --- crates/gui/Cargo.toml | 5 +- crates/gui/src/gui.rs | 20 ++- crates/gui/src/lib.rs | 2 +- crates/gui/src/view.rs | 108 +++++++++++++ crates/gui/src/view_port.rs | 293 ++++++++++++++++++++++-------------- examples/gui/Cargo.toml | 2 +- examples/gui/src/main.rs | 36 +++-- 7 files changed, 322 insertions(+), 144 deletions(-) create mode 100644 crates/gui/src/view.rs diff --git a/crates/gui/Cargo.toml b/crates/gui/Cargo.toml index 1cb45f06..e5c6c0e7 100644 --- a/crates/gui/Cargo.toml +++ b/crates/gui/Cargo.toml @@ -24,7 +24,4 @@ test = false [dependencies] flipperzero-sys = { path = "../sys", version = "0.6.0-alpha" } # FIXME: this is only required for access to `Align` enum which may be mvoved to this crate -flipperzero = { path = "../flipperzero", version = "0.6.0-alpha" } - -[features] -alloc = ["flipperzero/alloc"] +flipperzero = { path = "../flipperzero", version = "0.6.0-alpha", features = ["alloc"] } diff --git a/crates/gui/src/gui.rs b/crates/gui/src/gui.rs index d29823fb..32f72052 100644 --- a/crates/gui/src/gui.rs +++ b/crates/gui/src/gui.rs @@ -1,7 +1,7 @@ //! GUI APIs use crate::canvas::Canvas; -use crate::view_port::ViewPort; +use crate::view_port::{ViewPort, ViewPortCallbacks}; use core::ffi::c_char; use core::fmt::Debug; use flipperzero_sys::{self as sys, furi::UnsafeRecord, Gui as SysGui, GuiLayer as SysGuiLayer}; @@ -22,7 +22,11 @@ impl Gui { Self { gui } } - pub fn add_view_port(&mut self, view_port: ViewPort, layer: GuiLayer) -> GuiViewPort<'_> { + pub fn add_view_port( + &mut self, + view_port: ViewPort, + layer: GuiLayer, + ) -> GuiViewPort<'_, VPC> { // SAFETY: `self.gui` is owned by this `Gui` let gui = unsafe { self.gui.as_raw() }.as_ptr(); // SAFETY: `view_port` should outlive this `Gui` @@ -78,17 +82,17 @@ impl Default for Gui { } /// `ViewPort` bound to a `Gui`. -pub struct GuiViewPort<'a> { +pub struct GuiViewPort<'a, VPC: ViewPortCallbacks> { parent: &'a Gui, - view_port: ViewPort, + view_port: ViewPort, } -impl<'a> GuiViewPort<'a> { - pub fn view_port(&self) -> &ViewPort { +impl<'a, VPC: ViewPortCallbacks> GuiViewPort<'a, VPC> { + pub fn view_port(&self) -> &ViewPort { &self.view_port } - pub fn view_port_mut(&mut self) -> &mut ViewPort { + pub fn view_port_mut(&mut self) -> &mut ViewPort { &mut self.view_port } @@ -112,7 +116,7 @@ impl<'a> GuiViewPort<'a> { // } } -impl Drop for GuiViewPort<'_> { +impl Drop for GuiViewPort<'_, VPC> { fn drop(&mut self) { // # SAFETY: `self.parent` outlives this `GuiVewPort` let gui = unsafe { self.parent.gui.as_raw() }.as_ptr(); diff --git a/crates/gui/src/lib.rs b/crates/gui/src/lib.rs index 212ecfa9..8837f739 100644 --- a/crates/gui/src/lib.rs +++ b/crates/gui/src/lib.rs @@ -1,8 +1,8 @@ //! Safe wrappers for Flipper GUI APIs. #![no_std] +#![feature(thin_box)] -#[cfg(feature = "alloc")] extern crate alloc; pub mod canvas; diff --git a/crates/gui/src/view.rs b/crates/gui/src/view.rs new file mode 100644 index 00000000..8c6bb29d --- /dev/null +++ b/crates/gui/src/view.rs @@ -0,0 +1,108 @@ +use core::ptr::{null_mut, NonNull}; +use flipperzero_sys::{self as sys, View as SysView}; + +pub struct View { + raw: *mut SysView, +} + +impl View { + /// Creates a new `View`. + /// + /// # Example + /// + /// Basic usage: + /// + /// ``` + /// use flipperzero_gui::view::View; + /// + /// let view = View::new(); + /// ``` + pub fn new() -> View { + // SAFETY: allocation either succeeds producing the valid pointer + // or stops the system on OOM + let view = unsafe { sys::view_alloc() }; + Self { raw: view } + } + + /// Construct a `View` from a raw non-null pointer. + /// + /// After calling this function, the raw pointer is owned by the resulting `View`. + /// Specifically, the `View` destructor will free the allocated memory. + /// + /// # Safety + /// + /// `raw` should be a valid pointer to [`SysView`]. + /// + /// # Examples + /// + /// Recreate a `View` + /// which vas previously converted to a raw pointer using [`View::into_raw`]. + /// + /// ``` + /// use flipperzero_gui::view::View; + /// + /// let view = View::new(); + /// let ptr = view.into_raw(); + /// let view = unsafe { View::from_raw(ptr) }; + /// ``` + pub unsafe fn from_raw(raw: NonNull) -> Self { + Self { raw: raw.as_ptr() } + } + + /// Consumes this wrapper, returning a non-null raw pointer. + /// + /// After calling this function, the caller is responsible + /// for the memory previously managed by the `View`. + /// In particular, the caller should properly destroy `SysView` and release the memory + /// such as by calling [`sys::view_free`]. + /// The easiest way to do this is to convert the raw pointer + /// back into a `View` with the [View::from_raw] function, + /// allowing the `View` destructor to perform the cleanup. + /// + /// # Example + /// + /// Converting the raw pointer back into a `ViewPort` + /// with [`View::from_raw`] for automatic cleanup: + /// + /// ``` + /// use flipperzero_gui::view::View; + /// + /// let view = View::new(); + /// let ptr = view.into_raw(); + /// let view = unsafe { View::from_raw(ptr) }; + /// ``` + pub fn into_raw(mut self) -> NonNull { + let raw_pointer = core::mem::replace(&mut self.raw, null_mut()); + // SAFETY: `self.raw` is guaranteed to be non-null + // since it only becomes null after call to this function + // which consumes the wrapper + unsafe { NonNull::new_unchecked(raw_pointer) } + } + + /// Creates a copy of the non-null raw pointer to the [`SysView`]. + /// + /// # Safety + /// + /// Caller must ensure that the provided pointer does not outlive this wrapper. + pub unsafe fn as_raw(&self) -> NonNull { + // SAFETY: the pointer is guaranteed to be non-null + unsafe { NonNull::new_unchecked(self.raw) } + } +} + +impl Default for View { + fn default() -> Self { + Self::new() + } +} + +impl Drop for View { + fn drop(&mut self) { + // `self.raw` is `null` iff it has been taken by call to `into_raw()` + if !self.raw.is_null() { + // SAFETY: `self.raw` is always valid + // and it should have been unregistered from the system by now + unsafe { sys::view_free(self.raw) } + } + } +} diff --git a/crates/gui/src/view_port.rs b/crates/gui/src/view_port.rs index b6453621..907cdbac 100644 --- a/crates/gui/src/view_port.rs +++ b/crates/gui/src/view_port.rs @@ -3,25 +3,26 @@ #[cfg(feature = "alloc")] pub use self::alloc_features::*; -use core::mem::size_of_val; +use alloc::boxed::{Box, ThinBox}; use core::{ ffi::c_void, - mem::size_of, + mem::{size_of, size_of_val}, num::NonZeroU8, + pin::Pin, ptr::{null_mut, NonNull}, }; use flipperzero_sys::{ - self as sys, ViewPort as SysViewPort, ViewPortOrientation as SysViewPortOrientation, + self as sys, Canvas, InputEvent, ViewPort as SysViewPort, + ViewPortOrientation as SysViewPortOrientation, }; /// System ViewPort. -pub struct ViewPort { +pub struct ViewPort { raw: *mut SysViewPort, - #[cfg(feature = "alloc")] - draw_callback: Option, + callbacks: Option>>, } -impl ViewPort { +impl ViewPort { /// Creates a new `ViewPort`. /// /// # Example @@ -40,81 +41,78 @@ impl ViewPort { Self { raw, - draw_callback: None, + callbacks: None, } } - /// Construct a `ViewPort` from a raw non-null pointer. - /// - /// After calling this function, the raw pointer is owned by the resulting `ViewPort`. - /// Specifically, the `ViewPort` destructor will free the allocated memory. - /// - /// # Safety - /// - /// `raw` should be a valid pointer to [`SysViewPort`]. - /// - /// # Examples - /// - /// Recreate a `ViewPort` - /// which vas previously converted to a raw pointer using [`ViewPort::into_raw`]. - /// - /// ``` - /// use flipperzero_gui::view_port::ViewPort; - /// - /// let view_port = ViewPort::new(); - /// let (raw, draw_callback) = view_port.into_raw(); - /// let view_port = unsafe { ViewPort::from_raw(raw, draw_callback) }; - /// ``` - pub unsafe fn from_raw( - raw: NonNull, - draw_callback: Option, - ) -> Self { - Self { - raw: raw.as_ptr(), - #[cfg(feature = "alloc")] - draw_callback, - } - } - - /// Consumes this wrapper, returning a non-null raw pointer. - /// - /// After calling this function, the caller is responsible - /// for the memory previously managed by the `ViewPort`. - /// In particular, the caller should properly destroy `SysViewPort` and release the memory - /// such as by calling [`sys::view_port_free`]. - /// The easiest way to do this is to convert the raw pointer - /// back into a `ViewPort` with the [ViewPort::from_raw] function, - /// allowing the `ViewPort` destructor to perform the cleanup. - /// - /// # Example - /// - /// Converting the raw pointer back into a `ViewPort` - /// with [`ViewPort::from_raw`] for automatic cleanup: - /// - /// ``` - /// use flipperzero_gui::view_port::ViewPort; - /// - /// let view_port = ViewPort::new(); - /// let (raw, draw_callback) = view_port.into_raw(); - /// let view_port = unsafe { ViewPort::from_raw(raw, draw_callback) }; - /// ``` - pub fn into_raw(mut self) -> (NonNull, Option) { - let raw_pointer = core::mem::replace(&mut self.raw, null_mut()); - ( - // SAFETY: `self.raw` is guaranteed to be non-null - // since it only becomes null after call to this function - // which consumes the wrapper - unsafe { NonNull::new_unchecked(raw_pointer) }, - self.draw_callback.take(), - ) - } + // Unsound (and probably not needed) + // /// Construct a `ViewPort` from a raw non-null pointer. + // /// + // /// After calling this function, the raw pointer is owned by the resulting `ViewPort`. + // /// Specifically, the `ViewPort` destructor will free the allocated memory. + // /// + // /// # Safety + // /// + // /// `raw` should be a valid pointer to [`SysViewPort`]. + // /// + // /// # Examples + // /// + // /// Recreate a `ViewPort` + // /// which vas previously converted to a raw pointer using [`ViewPort::into_raw`]. + // /// + // /// ``` + // /// use flipperzero_gui::view_port::ViewPort; + // /// + // /// let view_port = ViewPort::new(); + // /// let (raw, draw_callback) = view_port.into_raw(); + // /// let view_port = unsafe { ViewPort::from_raw(raw, draw_callback) }; + // /// ``` + // pub unsafe fn from_raw( + // raw: NonNull, + // draw_callback: Option, + // ) -> Self { + // Self { + // raw: raw.as_ptr(), + // #[cfg(feature = "alloc")] + // draw_callback, + // } + // } + // + // /// Consumes this wrapper, returning a non-null raw pointer. + // /// + // /// After calling this function, the caller is responsible + // /// for the memory previously managed by the `ViewPort`. + // /// In particular, the caller should properly destroy `SysViewPort` and release the memory + // /// such as by calling [`sys::view_port_free`]. + // /// The easiest way to do this is to convert the raw pointer + // /// back into a `ViewPort` with the [ViewPort::from_raw] function, + // /// allowing the `ViewPort` destructor to perform the cleanup. + // /// + // /// # Example + // /// + // /// Converting the raw pointer back into a `ViewPort` + // /// with [`ViewPort::from_raw`] for automatic cleanup: + // /// + // /// ``` + // /// use flipperzero_gui::view_port::ViewPort; + // /// + // /// let view_port = ViewPort::new(); + // /// let (raw, draw_callback) = view_port.into_raw(); + // /// let view_port = unsafe { ViewPort::from_raw(raw, draw_callback) }; + // /// ``` + // pub fn into_raw(mut self) -> (NonNull, Option) { + // let raw_pointer = core::mem::replace(&mut self.raw, null_mut()); + // ( + // // SAFETY: `self.raw` is guaranteed to be non-null + // // since it only becomes null after call to this function + // // which consumes the wrapper + // unsafe { NonNull::new_unchecked(raw_pointer) }, + // self.draw_callback.take(), + // ) + // } /// Creates a copy of the non-null raw pointer to the [`SysViewPort`]. - /// - /// # Safety - /// - /// Caller must ensure that the provided pointer does not outlive this wrapper. - pub unsafe fn as_raw(&self) -> NonNull { + pub fn as_raw(&self) -> NonNull { // SAFETY: the pointer is guaranteed to be non-null unsafe { NonNull::new_unchecked(self.raw) } } @@ -365,17 +363,17 @@ impl ViewPort { // } } -impl Default for ViewPort { +impl Default for ViewPort { fn default() -> Self { Self::new() } } -impl Drop for ViewPort { +impl Drop for ViewPort { fn drop(&mut self) { // `self.raw` is `null` iff it has been taken by call to `into_raw()` if !self.raw.is_null() { - // FIXME: unregister from system + // FIXME: unregister from system (whatever this means) // SAFETY: `self.raw` is always valid // and it should have been unregistered from the system by now unsafe { sys::view_port_free(self.raw) } @@ -438,51 +436,116 @@ impl From for SysViewPortOrientation { } } -/// Functionality only available when `alloc` feature is enabled. -#[cfg(feature = "alloc")] -mod alloc_features { - use super::*; - use alloc::boxed::Box; - use core::pin::Pin; - - impl ViewPort { - pub fn set_draw_callback(&mut self, mut callback: ViewPortDrawCallback) { - type CallbackPtr = *mut ViewPortDrawCallback; - - pub unsafe extern "C" fn dispatch(canvas: *mut sys::Canvas, context: *mut c_void) { - let context: CallbackPtr = context.cast(); - // SAFETY: `context` is stored in a pinned Box which is a member of `ViewPort` - // and the callback is accessed exclusively by this function - (unsafe { NonNull::new_unchecked(context).as_mut() }).0(canvas); - } +impl ViewPort { + // pub fn set_draw_callback(&mut self, callback: ViewPortDrawCallback) { + // type CallbackPtr = *mut ViewPortDrawCallback; + // + // pub unsafe extern "C" fn dispatch(canvas: *mut sys::Canvas, context: *mut c_void) { + // let context: CallbackPtr = context.cast(); + // // SAFETY: `context` is stored in a pinned Box which is a member of `ViewPort` + // // and the callback is accessed exclusively by this function + // (unsafe { NonNull::new_unchecked(context).as_mut() }).0(canvas); + // } + // + // let mut callback = Box::pin(callback); + // let ptr = callback.as_mut().get_mut() as CallbackPtr as *mut c_void; + // // keep old cllback alive until the new one is written + // let old_callback = self.callbacks.replace(callback); + // + // const _: () = assert!( + // size_of::<*const ViewPortDrawCallback>() == size_of::<*mut c_void>(), + // "`ViewPortDrawCallback` should be a thin pointer" + // ); + // + // unsafe { sys::view_port_draw_callback_set(self.raw, Some(dispatch), ptr) } + // + // drop(old_callback); + // } + pub fn set_callbacks(&mut self, callbacks: C) + where + C: Unpin, + { + pub unsafe extern "C" fn dispatch_draw( + canvas: *mut sys::Canvas, + context: *mut c_void, + ) { + let context: *mut C = context.cast(); + // SAFETY: `context` is stored in a pinned Box which is a member of `ViewPort` + // and the callback is accessed exclusively by this function + (unsafe { NonNull::new_unchecked(context).as_mut() }).on_draw(canvas); + } + pub unsafe extern "C" fn dispatch_input( + canvas: *mut sys::InputEvent, + context: *mut c_void, + ) { + let context: *mut C = context.cast(); + // SAFETY: `context` is stored in a pinned Box which is a member of `ViewPort` + // and the callback is accessed exclusively by this function + (unsafe { NonNull::new_unchecked(context).as_mut() }).on_input(canvas); + } - let mut callback = Box::pin(callback); - let ptr = callback.as_mut().get_mut() as CallbackPtr as *mut c_void; - // keep old cllback alive until the new one is written - let old_callback = self.draw_callback.replace(callback); + let mut callback = Box::pin(callbacks); + let ptr = callback.as_mut().get_mut() as *mut C as *mut c_void; + // keep old cllback alive until the new one is written + let old_callback = self.callbacks.replace(callback); - const _: () = assert!( - size_of::<*const ViewPortDrawCallback>() == size_of::<*mut c_void>(), - "`ViewPortDrawCallback` should be a thin pointer" - ); + // const _: () = assert!( + // size_of::<*mut C>() == size_of::<*mut c_void>(), + // "`*mut C` should be a thin pointer" + // ); - unsafe { sys::view_port_draw_callback_set(self.raw, Some(dispatch), ptr) } + unsafe { sys::view_port_draw_callback_set(self.raw, Some(dispatch_draw::), ptr) } + unsafe { sys::view_port_input_callback_set(self.raw, Some(dispatch_input::), ptr) } - drop(old_callback); - } + drop(old_callback); } +} - pub struct ViewPortDrawCallback(Box); +pub trait ViewPortCallbacks { + fn on_draw(&mut self, canvas: *mut sys::Canvas) {} + fn on_input(&mut self, canvas: *mut sys::InputEvent) {} +} - impl ViewPortDrawCallback { - pub fn new(callback: Box) -> Self { - Self(callback) +pub struct DynamicViewPortCallbacks { + on_draw: Option>, + on_input: Option>, +} + +impl DynamicViewPortCallbacks { + fn new() -> Self { + Self { + on_draw: None, + on_input: None, } + } - pub fn boxed(callback: F) -> Self { - Self::new(Box::new(callback)) + // pub fn on_draw(&mut self, callback: Box) { + // self.on_draw = Some(callback) + // } + // + // pub fn on_input_none(&mut self, callback: Box) { + // self.on_input = None + // } + // + // pub fn on_input(&mut self, callback: Box) { + // self.on_input = Some(callback) + // } + // + // pub fn on_input_none(&mut self, callback: Box) { + // self.on_input = None + // } +} + +impl ViewPortCallbacks for DynamicViewPortCallbacks { + fn on_draw(&mut self, canvas: *mut Canvas) { + if let Some(callback) = &self.on_draw { + callback(canvas) } } - pub(super) type PinnedViewPortDrawCallback = Pin>; + fn on_input(&mut self, canvas: *mut InputEvent) { + if let Some(callback) = &self.on_input { + callback(canvas) + } + } } diff --git a/examples/gui/Cargo.toml b/examples/gui/Cargo.toml index 23fcc883..b3d02fa4 100644 --- a/examples/gui/Cargo.toml +++ b/examples/gui/Cargo.toml @@ -18,4 +18,4 @@ flipperzero = { version = "0.6.0-alpha", path = "../../crates/flipperzero" } flipperzero-sys = { version = "0.6.0-alpha", path = "../../crates/sys" } flipperzero-rt = { version = "0.6.0-alpha", path = "../../crates/rt" } flipperzero-alloc = { version = "0.6.0-alpha", path = "../../crates/alloc" } -flipperzero-gui = { version = "0.6.0-alpha", path = "../../crates/gui", features = ["alloc"] } +flipperzero-gui = { version = "0.6.0-alpha", path = "../../crates/gui" } diff --git a/examples/gui/src/main.rs b/examples/gui/src/main.rs index a6a44b25..f9e41540 100644 --- a/examples/gui/src/main.rs +++ b/examples/gui/src/main.rs @@ -14,28 +14,16 @@ use core::time::Duration; use flipperzero::furi::thread::sleep; use flipperzero_gui::gui::{Gui, GuiLayer}; -use flipperzero_gui::view_port::{ViewPort, ViewPortDrawCallback}; +use flipperzero_gui::view_port::{ViewPort, ViewPortCallbacks}; use flipperzero_rt::{entry, manifest}; use flipperzero_sys as sys; +use flipperzero_sys::Canvas; manifest!(name = "Rust GUI example"); entry!(main); -/// View draw handler. -fn draw_callback(canvas: *mut sys::Canvas) { - // # SAFETY: `canvas` should be a valid pointer - unsafe { - sys::canvas_draw_str(canvas, 39, 31, sys::c_string!("Hello, Rust!")); - } -} - fn main(_args: *mut u8) -> i32 { - // Currently there is no high level GUI bindings, - // so this all has to be done using the `sys` bindings. - let mut view_port = ViewPort::new(); - - view_port.set_draw_callback(ViewPortDrawCallback::boxed(draw_callback)); - + let view_port = new_view_port(); let mut gui = Gui::new(); let mut view_port = gui.add_view_port(view_port, GuiLayer::Fullscreen); @@ -45,3 +33,21 @@ fn main(_args: *mut u8) -> i32 { 0 } + +fn new_view_port() -> ViewPort { + let mut view_port = ViewPort::new(); + + struct Callbacks; + + impl ViewPortCallbacks for Callbacks { + fn on_draw(&mut self, canvas: *mut Canvas) { + // # SAFETY: `canvas` should be a valid pointer + unsafe { + sys::canvas_draw_str(canvas, 39, 31, sys::c_string!("Hello, Rust!")); + } + } + } + view_port.set_callbacks(Callbacks); + + view_port +} From aaa4eb0f34e118d36a278553baf1de513fabedd0 Mon Sep 17 00:00:00 2001 From: Petr Portnov Date: Tue, 3 Jan 2023 02:28:10 +0300 Subject: [PATCH 07/49] feat: implement effective callback API --- crates/gui/src/view_port.rs | 350 +++++++++--------------------------- examples/gui/src/main.rs | 11 +- 2 files changed, 93 insertions(+), 268 deletions(-) diff --git a/crates/gui/src/view_port.rs b/crates/gui/src/view_port.rs index 907cdbac..20abedde 100644 --- a/crates/gui/src/view_port.rs +++ b/crates/gui/src/view_port.rs @@ -1,25 +1,15 @@ //! ViewPort APIs -#[cfg(feature = "alloc")] -pub use self::alloc_features::*; - -use alloc::boxed::{Box, ThinBox}; -use core::{ - ffi::c_void, - mem::{size_of, size_of_val}, - num::NonZeroU8, - pin::Pin, - ptr::{null_mut, NonNull}, -}; +use alloc::boxed::Box; +use core::{ffi::c_void, mem::size_of, num::NonZeroU8, ptr::NonNull}; use flipperzero_sys::{ - self as sys, Canvas, InputEvent, ViewPort as SysViewPort, - ViewPortOrientation as SysViewPortOrientation, + self as sys, ViewPort as SysViewPort, ViewPortOrientation as SysViewPortOrientation, }; /// System ViewPort. pub struct ViewPort { - raw: *mut SysViewPort, - callbacks: Option>>, + raw: NonNull, + callbacks: NonNull, } impl ViewPort { @@ -32,89 +22,50 @@ impl ViewPort { /// ``` /// use flipperzero_gui::view_port::ViewPort; /// - /// let view_port = ViewPort::new(); + /// let view_port = ViewPort::new(todo!()); /// ``` - pub fn new() -> Self { + pub fn new(callbacks: C) -> Self { // SAFETY: allocation either succeeds producing the valid pointer // or stops the system on OOM - let raw = unsafe { sys::view_port_alloc() }; + let raw = unsafe { NonNull::new_unchecked(sys::view_port_alloc()) }; + let callbacks = NonNull::from(Box::leak(Box::new(callbacks))); + + let mut view_port = Self { raw, callbacks }; - Self { - raw, - callbacks: None, + pub unsafe extern "C" fn dispatch_draw( + canvas: *mut sys::Canvas, + context: *mut c_void, + ) { + let context: *mut C = context.cast(); + // SAFETY: `context` is stored in a `Box` which is a member of `ViewPort` + // and the callback is accessed exclusively by this function + (unsafe { &mut *context }).on_draw(canvas); } - } + pub unsafe extern "C" fn dispatch_input( + canvas: *mut sys::InputEvent, + context: *mut c_void, + ) { + let context: *mut C = context.cast(); + // SAFETY: `context` is stored in a pinned Box which is a member of `ViewPort` + // and the callback is accessed exclusively by this function + (unsafe { &mut *context }).on_input(canvas); + } + + // SAFETY: `callbacks` is a valid pointer and + let context = unsafe { view_port.callbacks.as_ptr() }.cast(); - // Unsound (and probably not needed) - // /// Construct a `ViewPort` from a raw non-null pointer. - // /// - // /// After calling this function, the raw pointer is owned by the resulting `ViewPort`. - // /// Specifically, the `ViewPort` destructor will free the allocated memory. - // /// - // /// # Safety - // /// - // /// `raw` should be a valid pointer to [`SysViewPort`]. - // /// - // /// # Examples - // /// - // /// Recreate a `ViewPort` - // /// which vas previously converted to a raw pointer using [`ViewPort::into_raw`]. - // /// - // /// ``` - // /// use flipperzero_gui::view_port::ViewPort; - // /// - // /// let view_port = ViewPort::new(); - // /// let (raw, draw_callback) = view_port.into_raw(); - // /// let view_port = unsafe { ViewPort::from_raw(raw, draw_callback) }; - // /// ``` - // pub unsafe fn from_raw( - // raw: NonNull, - // draw_callback: Option, - // ) -> Self { - // Self { - // raw: raw.as_ptr(), - // #[cfg(feature = "alloc")] - // draw_callback, - // } - // } - // - // /// Consumes this wrapper, returning a non-null raw pointer. - // /// - // /// After calling this function, the caller is responsible - // /// for the memory previously managed by the `ViewPort`. - // /// In particular, the caller should properly destroy `SysViewPort` and release the memory - // /// such as by calling [`sys::view_port_free`]. - // /// The easiest way to do this is to convert the raw pointer - // /// back into a `ViewPort` with the [ViewPort::from_raw] function, - // /// allowing the `ViewPort` destructor to perform the cleanup. - // /// - // /// # Example - // /// - // /// Converting the raw pointer back into a `ViewPort` - // /// with [`ViewPort::from_raw`] for automatic cleanup: - // /// - // /// ``` - // /// use flipperzero_gui::view_port::ViewPort; - // /// - // /// let view_port = ViewPort::new(); - // /// let (raw, draw_callback) = view_port.into_raw(); - // /// let view_port = unsafe { ViewPort::from_raw(raw, draw_callback) }; - // /// ``` - // pub fn into_raw(mut self) -> (NonNull, Option) { - // let raw_pointer = core::mem::replace(&mut self.raw, null_mut()); - // ( - // // SAFETY: `self.raw` is guaranteed to be non-null - // // since it only becomes null after call to this function - // // which consumes the wrapper - // unsafe { NonNull::new_unchecked(raw_pointer) }, - // self.draw_callback.take(), - // ) - // } + let raw = raw.as_ptr(); + unsafe { + sys::view_port_draw_callback_set(raw, Some(dispatch_draw::), context); + sys::view_port_input_callback_set(raw, Some(dispatch_input::), context); + }; + + view_port + } /// Creates a copy of the non-null raw pointer to the [`SysViewPort`]. pub fn as_raw(&self) -> NonNull { - // SAFETY: the pointer is guaranteed to be non-null - unsafe { NonNull::new_unchecked(self.raw) } + self.raw.clone() } /// Sets the width of this `ViewPort`. @@ -128,7 +79,7 @@ impl ViewPort { /// use std::num::NonZeroU8; /// use flipperzero_gui::view_port::ViewPort; /// - /// let mut view_port = ViewPort::new(); + /// let mut view_port = ViewPort::new(todo!()); /// view_port.set_width(NonZeroU8::new(128u8)); /// ``` /// @@ -137,14 +88,15 @@ impl ViewPort { /// ``` /// use flipperzero_gui::view_port::ViewPort; /// - /// let mut view_port = ViewPort::new(); + /// let mut view_port = ViewPort::new(todo!()); /// view_port.set_width(None); /// ``` pub fn set_width(&mut self, width: Option) { let width = width.map_or(0u8, NonZeroU8::into); - // SAFETY: `self.raw` is always valid + let raw = self.as_raw().as_ptr(); + // SAFETY: `raw` is always valid // and there are no `width` constraints - unsafe { sys::view_port_set_width(self.raw, width) } + unsafe { sys::view_port_set_width(raw, width) } } /// Gets the width of this `ViewPort`. @@ -156,12 +108,13 @@ impl ViewPort { /// ``` /// use flipperzero_gui::view_port::ViewPort; /// - /// let view_port = ViewPort::new(); + /// let view_port = ViewPort::new(todo!()); /// let width = view_port.get_width(); /// ``` pub fn get_width(&self) -> NonZeroU8 { - // SAFETY: `self.raw` is always valid - unsafe { sys::view_port_get_width(self.raw) } + let raw = self.as_raw().as_ptr(); + // SAFETY: `raw` is always valid + unsafe { sys::view_port_get_width(raw) } .try_into() .expect("`view_port_get_width` should produce a positive value") } @@ -177,7 +130,7 @@ impl ViewPort { /// use std::num::NonZeroU8; /// use flipperzero_gui::view_port::ViewPort; /// - /// let mut view_port = ViewPort::new(); + /// let mut view_port = ViewPort::new(todo!()); /// view_port.set_height(NonZeroU8::new(128u8)); /// ``` /// @@ -186,14 +139,15 @@ impl ViewPort { /// ``` /// use flipperzero_gui::view_port::ViewPort; /// - /// let mut view_port = ViewPort::new(); + /// let mut view_port = ViewPort::new(todo!()); /// view_port.set_height(None); /// ``` pub fn set_height(&mut self, height: Option) { let height = height.map_or(0u8, NonZeroU8::into); - // SAFETY: `self.raw` is always valid + let raw = self.as_raw().as_ptr(); + // SAFETY: `raw` is always valid // and there are no `height` constraints - unsafe { sys::view_port_set_height(self.raw, height) } + unsafe { sys::view_port_set_height(raw, height) } } /// Gets the height of this `ViewPort`. @@ -205,12 +159,13 @@ impl ViewPort { /// ``` /// use flipperzero_gui::view_port::ViewPort; /// - /// let view_port = ViewPort::new(); + /// let view_port = ViewPort::new(todo!()); /// let height = view_port.get_height(); /// ``` pub fn get_height(&self) -> NonZeroU8 { - // SAFETY: `self.raw` is always valid - unsafe { sys::view_port_get_height(self.raw) } + let raw = self.as_raw().as_ptr(); + // SAFETY: `raw` is always valid + unsafe { sys::view_port_get_height(raw) } .try_into() .expect("`view_port_get_height` should produce a positive value") } @@ -226,7 +181,7 @@ impl ViewPort { /// use std::num::NonZeroU8; /// use flipperzero_gui::view_port::ViewPort; /// - /// let mut view_port = ViewPort::new(); + /// let mut view_port = ViewPort::new(todo!()); /// view_port.set_dimensions(Some((NonZeroU8::new(120).unwrap(), NonZeroU8::new(80).unwrap()))); /// ``` /// @@ -235,7 +190,7 @@ impl ViewPort { /// ``` /// use flipperzero_gui::view_port::ViewPort; /// - /// let mut view_port = ViewPort::new(); + /// let mut view_port = ViewPort::new(todo!()); /// view_port.set_dimensions(None); /// ``` pub fn set_dimensions(&mut self, dimensions: Option<(NonZeroU8, NonZeroU8)>) { @@ -260,7 +215,7 @@ impl ViewPort { /// ``` /// use flipperzero_gui::view_port::ViewPort; /// - /// let view_port = ViewPort::new(); + /// let view_port = ViewPort::new(todo!()); /// let (width, height) = view_port.get_dimensions(); /// ``` pub fn get_dimensions(&self) -> (NonZeroU8, NonZeroU8) { @@ -275,15 +230,16 @@ impl ViewPort { /// /// ``` /// use flipperzero_gui::view_port::{ViewPort, ViewPortOrientation}; - /// let mut view_port = ViewPort::new(); + /// let mut view_port = ViewPort::new(todo!()); /// view_port.set_orientation(ViewPortOrientation::Vertical); /// ``` pub fn set_orientation(&mut self, orientation: ViewPortOrientation) { let orientation = SysViewPortOrientation::from(orientation); - // SAFETY: `self.raw` is always valid + let raw = self.as_raw().as_ptr(); + // SAFETY: `raw` is always valid // and `orientation` is guaranteed to be valid by `From` implementation - unsafe { sys::view_port_set_orientation(self.raw, orientation) } + unsafe { sys::view_port_set_orientation(raw, orientation) } } /// Gets the orientation of this `ViewPort`. @@ -296,12 +252,13 @@ impl ViewPort { /// use std::num::NonZeroU8; /// use flipperzero_gui::view_port::{ViewPort, ViewPortOrientation}; /// - /// let mut view_port = ViewPort::new(); + /// let mut view_port = ViewPort::new(todo!()); /// let orientation = view_port.get_orientation(); /// ``` pub fn get_orientation(&self) -> ViewPortOrientation { - // SAFETY: `self.raw` is always valid - unsafe { sys::view_port_get_orientation(self.raw as *const _) } + let raw = self.as_raw().as_ptr().cast_const(); + // SAFETY: `raw` is always valid + unsafe { sys::view_port_get_orientation(raw) } .try_into() .expect("`view_port_get_orientation` should produce a valid `ViewPort`") } @@ -317,12 +274,13 @@ impl ViewPort { /// ``` /// use flipperzero_gui::view_port::ViewPort; /// - /// let mut view_port = ViewPort::new(); + /// let mut view_port = ViewPort::new(todo!()); /// view_port.set_enabled(false); /// ``` pub fn set_enabled(&mut self, enabled: bool) { - // SAFETY: `self.raw` is always valid - unsafe { sys::view_port_enabled_set(self.raw, enabled) } + let raw = self.as_raw().as_ptr(); + // SAFETY: `raw` is always valid + unsafe { sys::view_port_enabled_set(raw, enabled) } } /// Checks if this `ViewPort` is enabled. @@ -335,49 +293,28 @@ impl ViewPort { /// ``` /// use flipperzero_gui::view_port::ViewPort; /// - /// let mut view_port = ViewPort::new(); + /// let mut view_port = ViewPort::new(todo!()); /// let enabled = view_port.is_enabled(); /// ``` pub fn is_enabled(&self) -> bool { - // SAFETY: `self.raw` is always valid - unsafe { sys::view_port_is_enabled(self.raw) } - } - // - // pub fn set_static_draw_callback<'a: 'b, 'b, F: Fn(*mut sys::Canvas)>( - // &'a mut self, - // callback: &'static fn(*mut sys::Canvas), - // ) { - // pub unsafe extern "C" fn dispatch( - // canvas: *mut sys::Canvas, - // context: *mut core::ffi::c_void, - // ) { - // // SAFETY: `context` is always a valid pointer - // let context = NonNull::new_unchecked(context as *const F as *mut F); - // // SAFETY: context is a valid pointer - // (unsafe { context.as_ref() })(canvas); - // } - // // FIXME: flipperzero-firmware: function pointer should be const - // let ptr = callback.as_ref().get_ref() as *const F as *mut c_void; - // - // unsafe { sys::view_port_draw_callback_set(self.raw, Some(dispatch::), ptr) } - // } -} - -impl Default for ViewPort { - fn default() -> Self { - Self::new() + let raw = self.as_raw().as_ptr(); + // SAFETY: `raw` is always valid + unsafe { sys::view_port_is_enabled(raw) } } } impl Drop for ViewPort { fn drop(&mut self) { - // `self.raw` is `null` iff it has been taken by call to `into_raw()` - if !self.raw.is_null() { - // FIXME: unregister from system (whatever this means) - // SAFETY: `self.raw` is always valid - // and it should have been unregistered from the system by now - unsafe { sys::view_port_free(self.raw) } - } + // FIXME: unregister from system (whatever this means) + + let raw = self.raw.as_ptr(); + // SAFETY: `self.raw` is always valid + // and it should have been unregistered from the system by now + unsafe { sys::view_port_free(raw) } + + let callbacks = self.callbacks.as_ptr(); + // SAFETY: `callbacks` was created using `Box::into_raw()` on `ViewPort` creation + let _ = unsafe { Box::from_raw(callbacks) }; } } @@ -436,116 +373,7 @@ impl From for SysViewPortOrientation { } } -impl ViewPort { - // pub fn set_draw_callback(&mut self, callback: ViewPortDrawCallback) { - // type CallbackPtr = *mut ViewPortDrawCallback; - // - // pub unsafe extern "C" fn dispatch(canvas: *mut sys::Canvas, context: *mut c_void) { - // let context: CallbackPtr = context.cast(); - // // SAFETY: `context` is stored in a pinned Box which is a member of `ViewPort` - // // and the callback is accessed exclusively by this function - // (unsafe { NonNull::new_unchecked(context).as_mut() }).0(canvas); - // } - // - // let mut callback = Box::pin(callback); - // let ptr = callback.as_mut().get_mut() as CallbackPtr as *mut c_void; - // // keep old cllback alive until the new one is written - // let old_callback = self.callbacks.replace(callback); - // - // const _: () = assert!( - // size_of::<*const ViewPortDrawCallback>() == size_of::<*mut c_void>(), - // "`ViewPortDrawCallback` should be a thin pointer" - // ); - // - // unsafe { sys::view_port_draw_callback_set(self.raw, Some(dispatch), ptr) } - // - // drop(old_callback); - // } - pub fn set_callbacks(&mut self, callbacks: C) - where - C: Unpin, - { - pub unsafe extern "C" fn dispatch_draw( - canvas: *mut sys::Canvas, - context: *mut c_void, - ) { - let context: *mut C = context.cast(); - // SAFETY: `context` is stored in a pinned Box which is a member of `ViewPort` - // and the callback is accessed exclusively by this function - (unsafe { NonNull::new_unchecked(context).as_mut() }).on_draw(canvas); - } - pub unsafe extern "C" fn dispatch_input( - canvas: *mut sys::InputEvent, - context: *mut c_void, - ) { - let context: *mut C = context.cast(); - // SAFETY: `context` is stored in a pinned Box which is a member of `ViewPort` - // and the callback is accessed exclusively by this function - (unsafe { NonNull::new_unchecked(context).as_mut() }).on_input(canvas); - } - - let mut callback = Box::pin(callbacks); - let ptr = callback.as_mut().get_mut() as *mut C as *mut c_void; - // keep old cllback alive until the new one is written - let old_callback = self.callbacks.replace(callback); - - // const _: () = assert!( - // size_of::<*mut C>() == size_of::<*mut c_void>(), - // "`*mut C` should be a thin pointer" - // ); - - unsafe { sys::view_port_draw_callback_set(self.raw, Some(dispatch_draw::), ptr) } - unsafe { sys::view_port_input_callback_set(self.raw, Some(dispatch_input::), ptr) } - - drop(old_callback); - } -} - pub trait ViewPortCallbacks { - fn on_draw(&mut self, canvas: *mut sys::Canvas) {} - fn on_input(&mut self, canvas: *mut sys::InputEvent) {} -} - -pub struct DynamicViewPortCallbacks { - on_draw: Option>, - on_input: Option>, -} - -impl DynamicViewPortCallbacks { - fn new() -> Self { - Self { - on_draw: None, - on_input: None, - } - } - - // pub fn on_draw(&mut self, callback: Box) { - // self.on_draw = Some(callback) - // } - // - // pub fn on_input_none(&mut self, callback: Box) { - // self.on_input = None - // } - // - // pub fn on_input(&mut self, callback: Box) { - // self.on_input = Some(callback) - // } - // - // pub fn on_input_none(&mut self, callback: Box) { - // self.on_input = None - // } -} - -impl ViewPortCallbacks for DynamicViewPortCallbacks { - fn on_draw(&mut self, canvas: *mut Canvas) { - if let Some(callback) = &self.on_draw { - callback(canvas) - } - } - - fn on_input(&mut self, canvas: *mut InputEvent) { - if let Some(callback) = &self.on_input { - callback(canvas) - } - } + fn on_draw(&mut self, _canvas: *mut sys::Canvas) {} + fn on_input(&mut self, _event: *mut sys::InputEvent) {} } diff --git a/examples/gui/src/main.rs b/examples/gui/src/main.rs index f9e41540..ab8428cc 100644 --- a/examples/gui/src/main.rs +++ b/examples/gui/src/main.rs @@ -10,6 +10,7 @@ extern crate flipperzero_rt; extern crate alloc; extern crate flipperzero_alloc; +use core::ffi::c_char; use core::time::Duration; use flipperzero::furi::thread::sleep; @@ -35,19 +36,15 @@ fn main(_args: *mut u8) -> i32 { } fn new_view_port() -> ViewPort { - let mut view_port = ViewPort::new(); - - struct Callbacks; + struct Callbacks(*const c_char); impl ViewPortCallbacks for Callbacks { fn on_draw(&mut self, canvas: *mut Canvas) { // # SAFETY: `canvas` should be a valid pointer unsafe { - sys::canvas_draw_str(canvas, 39, 31, sys::c_string!("Hello, Rust!")); + sys::canvas_draw_str(canvas, 39, 31, self.0); } } } - view_port.set_callbacks(Callbacks); - - view_port + ViewPort::new(Callbacks(sys::c_string!("Hello, Rust!"))) } From 3783da94babde09ce77a38d39eb79b9287134fc7 Mon Sep 17 00:00:00 2001 From: Petr Portnov Date: Tue, 3 Jan 2023 03:01:01 +0300 Subject: [PATCH 08/49] refactor: make `as_raw` safe --- crates/flipperzero/src/furi/dialog.rs | 6 +-- crates/gui/src/gui.rs | 37 +++++++-------- crates/gui/src/view.rs | 66 ++------------------------- crates/gui/src/view_port.rs | 38 +++++++-------- crates/sys/src/furi.rs | 9 +--- 5 files changed, 41 insertions(+), 115 deletions(-) diff --git a/crates/flipperzero/src/furi/dialog.rs b/crates/flipperzero/src/furi/dialog.rs index 7acbb13a..37ef0df5 100644 --- a/crates/flipperzero/src/furi/dialog.rs +++ b/crates/flipperzero/src/furi/dialog.rs @@ -15,7 +15,7 @@ use super::canvas::Align; const RECORD_DIALOGS: *const c_char = sys::c_string!("dialogs"); #[cfg(feature = "alloc")] -const BUTTON_OK: &'static CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"OK\0") }; +const BUTTON_OK: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"OK\0") }; /// A handle to the Dialogs app. pub struct DialogsApp { @@ -46,8 +46,8 @@ impl DialogsApp { /// Displays a message. pub fn show(&mut self, message: &DialogMessage) -> DialogMessageButton { - let button_sys = - unsafe { sys::dialog_message_show(self.data.as_raw().as_ptr(), message.data) }; + let data = self.data.as_raw(); + let button_sys = unsafe { sys::dialog_message_show(data, message.data) }; DialogMessageButton::from_sys(button_sys).expect("Invalid button") } diff --git a/crates/gui/src/gui.rs b/crates/gui/src/gui.rs index 32f72052..f20e683d 100644 --- a/crates/gui/src/gui.rs +++ b/crates/gui/src/gui.rs @@ -27,10 +27,8 @@ impl Gui { view_port: ViewPort, layer: GuiLayer, ) -> GuiViewPort<'_, VPC> { - // SAFETY: `self.gui` is owned by this `Gui` - let gui = unsafe { self.gui.as_raw() }.as_ptr(); - // SAFETY: `view_port` should outlive this `Gui` - let view_port_ptr = unsafe { view_port.as_raw() }.as_ptr(); + let gui = self.gui.as_raw(); + let view_port_ptr = view_port.as_raw(); let layer = layer.into(); // SAFETY: all pointers are valid and `view_port` outlives this `Gui` @@ -43,15 +41,13 @@ impl Gui { } pub fn get_frame_buffer_size(&self) -> usize { - // SAFETY: `self.gui` is owned by this `Gui` - let gui = unsafe { self.gui.as_raw() }.as_ptr(); + let gui = self.gui.as_raw(); // SAFETY: `gui` is always a valid pointer unsafe { sys::gui_get_framebuffer_size(gui) } } pub fn set_lockdown(&self, lockdown: bool) { - // SAFETY: `self.gui` is owned by this `Gui` - let gui = unsafe { self.gui.as_raw() }.as_ptr(); + let gui = self.gui.as_raw(); // SAFETY: `gui` is always a valid pointer unsafe { sys::gui_set_lockdown(gui, lockdown) } } @@ -59,8 +55,7 @@ impl Gui { // TODO: separate `GuiCanvas` (locking the parent) // and `Canvas` (independent of the parent) pub fn direct_draw_acquire(&self) -> Canvas<'_> { - // SAFETY: `self.gui` is owned by this `Gui` - let gui = unsafe { self.gui.as_raw() }.as_ptr(); + let gui = self.gui.as_raw(); // SAFETY: `gui` is always a valid pointer // let canvas = unsafe { sys::gui_direct_draw_acquire(gui) } @@ -97,10 +92,8 @@ impl<'a, VPC: ViewPortCallbacks> GuiViewPort<'a, VPC> { } pub fn send_to_front(&mut self) { - // # SAFETY: `self.parent` outlives this `GuiVewPort` - let gui = unsafe { self.parent.gui.as_raw() }.as_ptr(); - // # SAFETY: `self.view_port` is owned - let view_port = unsafe { self.view_port.as_raw() }.as_ptr(); + let gui = self.parent.gui.as_raw(); + let view_port = self.view_port.as_raw(); // # SAFETY: `self.parent` outlives this `GuiVewPort` unsafe { sys::gui_view_port_send_to_front(gui, view_port) }; @@ -108,9 +101,8 @@ impl<'a, VPC: ViewPortCallbacks> GuiViewPort<'a, VPC> { // FIXME(Coles): `gui_view_port_send_to_back` is not present in bindings // pub fn send_to_back(&mut self) { - // // # SAFETY: `self.parent` outlives this `GuiVewPort` - // let gui = unsafe { self.parent.gui.as_raw() }.as_ptr(); - // let view_port = unsafe { self.view_port.as_raw() }.as_ptr(); + // let gui = self.gui.as_raw(); + // let view_port = self.view_port.as_raw(); // // unsafe { sys::gui_view_port_send_to_back(gui, view_port) }; // } @@ -118,10 +110,8 @@ impl<'a, VPC: ViewPortCallbacks> GuiViewPort<'a, VPC> { impl Drop for GuiViewPort<'_, VPC> { fn drop(&mut self) { - // # SAFETY: `self.parent` outlives this `GuiVewPort` - let gui = unsafe { self.parent.gui.as_raw() }.as_ptr(); - // # SAFETY: `self.view_port` is owned - let view_port = unsafe { self.view_port.as_raw() }.as_ptr(); + let gui = self.parent.gui.as_raw(); + let view_port = self.view_port().as_raw(); unsafe { sys::gui_remove_view_port(gui, view_port) } } @@ -186,3 +176,8 @@ impl From for SysGuiLayer { } } } + +pub trait GuiCallbacks { + fn on_draw(&mut self, _canvas: *mut sys::Canvas) {} + fn on_input(&mut self, _event: *mut sys::InputEvent) {} +} diff --git a/crates/gui/src/view.rs b/crates/gui/src/view.rs index 8c6bb29d..7881164f 100644 --- a/crates/gui/src/view.rs +++ b/crates/gui/src/view.rs @@ -24,69 +24,9 @@ impl View { Self { raw: view } } - /// Construct a `View` from a raw non-null pointer. - /// - /// After calling this function, the raw pointer is owned by the resulting `View`. - /// Specifically, the `View` destructor will free the allocated memory. - /// - /// # Safety - /// - /// `raw` should be a valid pointer to [`SysView`]. - /// - /// # Examples - /// - /// Recreate a `View` - /// which vas previously converted to a raw pointer using [`View::into_raw`]. - /// - /// ``` - /// use flipperzero_gui::view::View; - /// - /// let view = View::new(); - /// let ptr = view.into_raw(); - /// let view = unsafe { View::from_raw(ptr) }; - /// ``` - pub unsafe fn from_raw(raw: NonNull) -> Self { - Self { raw: raw.as_ptr() } - } - - /// Consumes this wrapper, returning a non-null raw pointer. - /// - /// After calling this function, the caller is responsible - /// for the memory previously managed by the `View`. - /// In particular, the caller should properly destroy `SysView` and release the memory - /// such as by calling [`sys::view_free`]. - /// The easiest way to do this is to convert the raw pointer - /// back into a `View` with the [View::from_raw] function, - /// allowing the `View` destructor to perform the cleanup. - /// - /// # Example - /// - /// Converting the raw pointer back into a `ViewPort` - /// with [`View::from_raw`] for automatic cleanup: - /// - /// ``` - /// use flipperzero_gui::view::View; - /// - /// let view = View::new(); - /// let ptr = view.into_raw(); - /// let view = unsafe { View::from_raw(ptr) }; - /// ``` - pub fn into_raw(mut self) -> NonNull { - let raw_pointer = core::mem::replace(&mut self.raw, null_mut()); - // SAFETY: `self.raw` is guaranteed to be non-null - // since it only becomes null after call to this function - // which consumes the wrapper - unsafe { NonNull::new_unchecked(raw_pointer) } - } - - /// Creates a copy of the non-null raw pointer to the [`SysView`]. - /// - /// # Safety - /// - /// Caller must ensure that the provided pointer does not outlive this wrapper. - pub unsafe fn as_raw(&self) -> NonNull { - // SAFETY: the pointer is guaranteed to be non-null - unsafe { NonNull::new_unchecked(self.raw) } + /// Creates a copy of raw pointer to the [`SysView`]. + pub unsafe fn as_raw(&self) -> *mut SysView { + self.raw } } diff --git a/crates/gui/src/view_port.rs b/crates/gui/src/view_port.rs index 20abedde..bcae9585 100644 --- a/crates/gui/src/view_port.rs +++ b/crates/gui/src/view_port.rs @@ -1,7 +1,7 @@ //! ViewPort APIs use alloc::boxed::Box; -use core::{ffi::c_void, mem::size_of, num::NonZeroU8, ptr::NonNull}; +use core::{ffi::c_void, num::NonZeroU8, ptr::NonNull}; use flipperzero_sys::{ self as sys, ViewPort as SysViewPort, ViewPortOrientation as SysViewPortOrientation, }; @@ -63,9 +63,9 @@ impl ViewPort { view_port } - /// Creates a copy of the non-null raw pointer to the [`SysViewPort`]. - pub fn as_raw(&self) -> NonNull { - self.raw.clone() + /// Creates a copy of the raw pointer to the [`SysViewPort`]. + pub fn as_raw(&self) -> *mut SysViewPort { + self.raw.as_ptr() } /// Sets the width of this `ViewPort`. @@ -93,7 +93,7 @@ impl ViewPort { /// ``` pub fn set_width(&mut self, width: Option) { let width = width.map_or(0u8, NonZeroU8::into); - let raw = self.as_raw().as_ptr(); + let raw = self.as_raw(); // SAFETY: `raw` is always valid // and there are no `width` constraints unsafe { sys::view_port_set_width(raw, width) } @@ -111,12 +111,10 @@ impl ViewPort { /// let view_port = ViewPort::new(todo!()); /// let width = view_port.get_width(); /// ``` - pub fn get_width(&self) -> NonZeroU8 { - let raw = self.as_raw().as_ptr(); + pub fn get_width(&self) -> Option { + let raw = self.as_raw(); // SAFETY: `raw` is always valid - unsafe { sys::view_port_get_width(raw) } - .try_into() - .expect("`view_port_get_width` should produce a positive value") + NonZeroU8::new(unsafe { sys::view_port_get_width(raw) }) } /// Sets the height of this `ViewPort`. @@ -144,7 +142,7 @@ impl ViewPort { /// ``` pub fn set_height(&mut self, height: Option) { let height = height.map_or(0u8, NonZeroU8::into); - let raw = self.as_raw().as_ptr(); + let raw = self.as_raw(); // SAFETY: `raw` is always valid // and there are no `height` constraints unsafe { sys::view_port_set_height(raw, height) } @@ -162,12 +160,10 @@ impl ViewPort { /// let view_port = ViewPort::new(todo!()); /// let height = view_port.get_height(); /// ``` - pub fn get_height(&self) -> NonZeroU8 { - let raw = self.as_raw().as_ptr(); + pub fn get_height(&self) -> Option { + let raw = self.as_raw(); // SAFETY: `raw` is always valid - unsafe { sys::view_port_get_height(raw) } - .try_into() - .expect("`view_port_get_height` should produce a positive value") + NonZeroU8::new(unsafe { sys::view_port_get_height(raw) }) } /// Sets the dimensions of this `ViewPort`. @@ -218,7 +214,7 @@ impl ViewPort { /// let view_port = ViewPort::new(todo!()); /// let (width, height) = view_port.get_dimensions(); /// ``` - pub fn get_dimensions(&self) -> (NonZeroU8, NonZeroU8) { + pub fn get_dimensions(&self) -> (Option, Option) { (self.get_width(), self.get_height()) } @@ -236,7 +232,7 @@ impl ViewPort { pub fn set_orientation(&mut self, orientation: ViewPortOrientation) { let orientation = SysViewPortOrientation::from(orientation); - let raw = self.as_raw().as_ptr(); + let raw = self.as_raw(); // SAFETY: `raw` is always valid // and `orientation` is guaranteed to be valid by `From` implementation unsafe { sys::view_port_set_orientation(raw, orientation) } @@ -256,7 +252,7 @@ impl ViewPort { /// let orientation = view_port.get_orientation(); /// ``` pub fn get_orientation(&self) -> ViewPortOrientation { - let raw = self.as_raw().as_ptr().cast_const(); + let raw = self.as_raw().cast_const(); // SAFETY: `raw` is always valid unsafe { sys::view_port_get_orientation(raw) } .try_into() @@ -278,7 +274,7 @@ impl ViewPort { /// view_port.set_enabled(false); /// ``` pub fn set_enabled(&mut self, enabled: bool) { - let raw = self.as_raw().as_ptr(); + let raw = self.as_raw(); // SAFETY: `raw` is always valid unsafe { sys::view_port_enabled_set(raw, enabled) } } @@ -297,7 +293,7 @@ impl ViewPort { /// let enabled = view_port.is_enabled(); /// ``` pub fn is_enabled(&self) -> bool { - let raw = self.as_raw().as_ptr(); + let raw = self.as_raw(); // SAFETY: `raw` is always valid unsafe { sys::view_port_is_enabled(raw) } } diff --git a/crates/sys/src/furi.rs b/crates/sys/src/furi.rs index bbf30e41..8e51ebd0 100644 --- a/crates/sys/src/furi.rs +++ b/crates/sys/src/furi.rs @@ -104,13 +104,8 @@ impl UnsafeRecord { } /// Returns the record data as a raw pointer. - /// - /// # Safety - /// - /// Caller must ensure that the provided pointer does not outlive this wrapper. - pub unsafe fn as_raw(&self) -> NonNull { - // SAFETY: the pointer is guaranteed to be non-null - unsafe { NonNull::new_unchecked(self.data) } + pub fn as_raw(&self) -> *mut T { + self.data } } From a7beba09f5a8bb3035bc051c17d0c65bf8489fa0 Mon Sep 17 00:00:00 2001 From: Petr Portnov Date: Tue, 3 Jan 2023 05:47:59 +0300 Subject: [PATCH 09/49] feat: make `gui` example fully safe --- crates/flipperzero/src/furi/message_queue.rs | 57 ++++-- crates/gui/src/canvas.rs | 105 ++++++----- crates/gui/src/gui.rs | 11 +- crates/gui/src/input.rs | 172 +++++++++++++++++++ crates/gui/src/lib.rs | 1 + crates/gui/src/view.rs | 1 - crates/gui/src/view_port.rs | 29 ++-- crates/sys/src/furi.rs | 1 - crates/sys/src/lib.rs | 31 +++- examples/gui/src/main.rs | 92 +++++++--- 10 files changed, 386 insertions(+), 114 deletions(-) create mode 100644 crates/gui/src/input.rs diff --git a/crates/flipperzero/src/furi/message_queue.rs b/crates/flipperzero/src/furi/message_queue.rs index 3134533e..589ee9ce 100644 --- a/crates/flipperzero/src/furi/message_queue.rs +++ b/crates/flipperzero/src/furi/message_queue.rs @@ -1,44 +1,59 @@ use core::ffi::c_void; +use core::mem::size_of; +use core::ptr::NonNull; use core::time::Duration; use flipperzero_sys as sys; -use flipperzero_sys::furi::{Status, duration_to_ticks}; +use flipperzero_sys::furi::{duration_to_ticks, Status}; use crate::furi; /// MessageQueue provides a safe wrapper around the furi message queue primitive. pub struct MessageQueue { - hnd: *mut sys::FuriMessageQueue, + raw: NonNull, _marker: core::marker::PhantomData, } impl MessageQueue { /// Constructs a message queue with the given capacity. - pub fn new(capacity: usize) -> Self { + pub fn new(capacity: u32) -> Self { + let message_size = size_of::() as u32; + // SAFETY: there are no expplicit size restrictions + // and allocation will either succed or crash the application + let raw = unsafe { + NonNull::new_unchecked(sys::furi_message_queue_alloc(capacity, message_size)) + }; Self { - hnd: unsafe { sys::furi_message_queue_alloc(capacity as u32, core::mem::size_of::() as u32) }, + raw, _marker: core::marker::PhantomData::, } } // Attempts to add the message to the end of the queue, waiting up to timeout ticks. - pub fn put(&self, msg: M, timeout: Duration) -> furi::Result<()> { - let mut msg = core::mem::ManuallyDrop::new(msg); + pub fn put(&self, message: M, timeout: Duration) -> furi::Result<()> { + // the value will be retrieved from the queue either explicitly or on queue drop + // after which it will be dropped + let mut message = core::mem::ManuallyDrop::new(message); + let message = &mut message as *mut _ as *mut c_void; let timeout_ticks = sys::furi::duration_to_ticks(timeout); - let status: Status = unsafe { - sys::furi_message_queue_put(self.hnd, &mut msg as *mut _ as *const c_void, timeout_ticks).into() - }; + let raw = self.raw.as_ptr().cast(); + let status: Status = + unsafe { sys::furi_message_queue_put(raw, message, timeout_ticks) }.into(); status.err_or(()) } // Attempts to read a message from the front of the queue within timeout ticks. pub fn get(&self, timeout: Duration) -> furi::Result { + let raw = self.raw.as_ptr(); let timeout_ticks = duration_to_ticks(timeout); let mut out = core::mem::MaybeUninit::::uninit(); + let out_ptr = out.as_mut_ptr().cast(); + // SAFETY: `raw` is always valid, + // `out_ptr` is only used to write into is never read from (TODO: check correctness) let status: Status = - unsafe { sys::furi_message_queue_get(self.hnd, out.as_mut_ptr() as *mut c_void, timeout_ticks).into() }; + unsafe { sys::furi_message_queue_get(raw, out_ptr, timeout_ticks) }.into(); if status.is_ok() { Ok(unsafe { out.assume_init() }) @@ -48,13 +63,17 @@ impl MessageQueue { } /// Returns the capacity of the queue. - pub fn capacity(&self) -> usize { - unsafe { sys::furi_message_queue_get_capacity(self.hnd) as usize } + pub fn capacity(&self) -> u32 { + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is always valid + unsafe { sys::furi_message_queue_get_capacity(raw) } } /// Returns the number of elements in the queue. - pub fn len(&self) -> usize { - unsafe { sys::furi_message_queue_get_count(self.hnd) as usize } + pub fn len(&self) -> u32 { + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is always valid + unsafe { sys::furi_message_queue_get_count(raw) } } /// Is the message queue empty? @@ -63,8 +82,10 @@ impl MessageQueue { } /// Returns the number of free slots in the queue. - pub fn space(&self) -> usize { - unsafe { sys::furi_message_queue_get_space(self.hnd) as usize } + pub fn space(&self) -> u32 { + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is always valid + unsafe { sys::furi_message_queue_get_space(raw) } } } @@ -79,6 +100,8 @@ impl Drop for MessageQueue { } } - unsafe { sys::furi_message_queue_free(self.hnd) } + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is always valid + unsafe { sys::furi_message_queue_free(raw) } } } diff --git a/crates/gui/src/canvas.rs b/crates/gui/src/canvas.rs index 1cd6f0b1..12cec8e3 100644 --- a/crates/gui/src/canvas.rs +++ b/crates/gui/src/canvas.rs @@ -1,8 +1,11 @@ //! ViewPort APIs use crate::gui::Gui; -use core::ptr::{null_mut, NonNull}; -use core::{ffi::CStr, num::NonZeroU8}; +use core::{ + ffi::CStr, + num::NonZeroU8, + ptr::{null_mut, NonNull}, +}; use flipperzero::furi::canvas::Align; use flipperzero_sys::{ self as sys, Canvas as SysCanvas, CanvasDirection as SysCanvasDirection, @@ -10,12 +13,11 @@ use flipperzero_sys::{ }; /// System Canvas. -pub struct Canvas<'a> { - _parent: &'a Gui, - canvas: *mut SysCanvas, +pub struct CanvasView { + raw: *mut SysCanvas, } -impl<'a> Canvas<'a> { +impl CanvasView { /// Construct a `Canvas` from a raw non-null pointer. /// /// After calling this function, the raw pointer is owned by the resulting `Canvas`. @@ -25,26 +27,21 @@ impl<'a> Canvas<'a> { /// /// - `parent` should be the `Gui` which owns this canvas; /// - /// - `raw` should be a valid pointer to [`Canvas`]. + /// - `raw` should be a valid pointer to [`CanvasView`]. /// /// # Examples /// /// Recreate a `Canvas` - /// which vas previously converted to a raw pointer using [`Canvas::into_raw`]. + /// which vas previously converted to a raw pointer using [`CanvasView::into_raw`]. /// /// ``` - /// use flipperzero_gui::{canvas::Canvas, gui::Gui}; + /// use flipperzero_gui::canvas::CanvasView; /// - /// let mut gui = Gui::new(); - /// let canvas = gui.direct_draw_acquire(); - /// let ptr = canvas.into_raw(); - /// let canvas = unsafe { Canvas::from_raw(gui, ptr) }; + /// let ptr = todo!(); + /// let canvas = unsafe { CanvasView::from_raw(ptr) }; /// ``` - pub unsafe fn from_raw(parent: &'a mut Gui, raw: NonNull) -> Self { - Self { - _parent: parent, - canvas: raw.as_ptr(), - } + pub unsafe fn from_raw(raw: NonNull) -> Self { + Self { raw: raw.as_ptr() } } /// Consumes this wrapper, returning a non-null raw pointer. @@ -59,18 +56,18 @@ impl<'a> Canvas<'a> { /// # Example /// /// Converting the raw pointer back into a `Canvas` - /// with [`Canvas::from_raw`] for automatic cleanup: + /// with [`CanvasView::from_raw`] for automatic cleanup: /// /// ``` - /// use flipperzero_gui::{canvas::Canvas, gui::Gui}; + /// use flipperzero_gui::{canvas::CanvasView, gui::Gui}; /// /// let mut gui = Gui::new(); /// let canvas = gui.direct_draw_acquire(); /// let ptr = canvas.into_raw(); - /// let canvas = unsafe { Canvas::from_raw(gui, ptr) }; + /// let canvas = unsafe { CanvasView::from_raw(gui, ptr) }; /// ``` pub fn into_raw(mut self) -> NonNull { - let raw_pointer = core::mem::replace(&mut self.canvas, null_mut()); + let raw_pointer = core::mem::replace(&mut self.raw, null_mut()); // SAFETY: `self.canvas` is guaranteed to be non-null // since it only becomes null after call to this function // which consumes the wrapper @@ -83,21 +80,21 @@ impl<'a> Canvas<'a> { pub fn width(&self) -> NonZeroU8 { // SAFETY: `self.canvas` is always a valid pointer - unsafe { sys::canvas_width(self.canvas) } + unsafe { sys::canvas_width(self.raw) } .try_into() .expect("`canvas_width` should produce a positive value") } pub fn height(&self) -> NonZeroU8 { // SAFETY: `self.canvas` is always a valid pointer - unsafe { sys::canvas_height(self.canvas) } + unsafe { sys::canvas_height(self.raw) } .try_into() .expect("`canvas_height` should produce a positive value") } pub fn current_font_height(&self) -> NonZeroU8 { // SAFETY: `self.canvas` is always a valid pointer - unsafe { sys::canvas_current_font_height(self.canvas) } + unsafe { sys::canvas_current_font_height(self.raw) } .try_into() .expect("`canvas_current_font_height` should produce a positive value") } @@ -106,46 +103,46 @@ impl<'a> Canvas<'a> { let font = font.into(); // SAFETY: `self.canvas` is always a valid pointer // and `font` is guaranteed to be a valid value by `From` implementation - let raw = unsafe { sys::canvas_get_font_params(self.canvas, font) }; + let raw = unsafe { sys::canvas_get_font_params(self.raw, font) }; CanvasFontParameters { _parent: self, raw } } pub fn clear(&mut self) { // SAFETY: `self.canvas` is always a valid pointer - unsafe { sys::canvas_clear(self.canvas) }; + unsafe { sys::canvas_clear(self.raw) }; } pub fn set_color(&mut self, color: Color) { let color = color.into(); // SAFETY: `self.canvas` is always a valid pointer // and `font` is guaranteed to be a valid value by `From` implementation - unsafe { sys::canvas_set_color(self.canvas, color) }; + unsafe { sys::canvas_set_color(self.raw, color) }; } pub fn set_font_direction(&mut self, font_direction: CanvasDirection) { let font_direction = font_direction.into(); // SAFETY: `self.canvas` is always a valid pointer // and `font_direction` is guaranteed to be a valid value by `From` implementation - unsafe { sys::canvas_set_font_direction(self.canvas, font_direction) }; + unsafe { sys::canvas_set_font_direction(self.raw, font_direction) }; } pub fn invert_color(&mut self) { // SAFETY: `self.canvas` is always a valid pointer - unsafe { sys::canvas_invert_color(self.canvas) }; + unsafe { sys::canvas_invert_color(self.raw) }; } pub fn set_font(&mut self, font: Font) { let font = font.into(); // SAFETY: `self.canvas` is always a valid pointer // and `font` is guaranteed to be a valid value by `From` implementation - unsafe { sys::canvas_set_font(self.canvas, font) }; + unsafe { sys::canvas_set_font(self.raw, font) }; } pub fn draw_str(&mut self, x: u8, y: u8, str: impl AsRef) { let str = str.as_ref().as_ptr(); // SAFETY: `self.canvas` is always a valid pointer // and `text` is guaranteed to be a valid pointer since it was created from `CStr` - unsafe { sys::canvas_draw_str(self.canvas, x, y, str) }; + unsafe { sys::canvas_draw_str(self.raw, x, y, str) }; } pub fn draw_str_aligned( @@ -162,7 +159,7 @@ impl<'a> Canvas<'a> { // SAFETY: `self.canvas` is always a valid pointer, // `horixontal` and `vertival` are guaranteed to be valid by `From` implementation // and `text` is guaranteed to be a valid pointer since it was created from `CStr` - unsafe { sys::canvas_draw_str_aligned(self.canvas, x, y, horizontal, vertical, str) }; + unsafe { sys::canvas_draw_str_aligned(self.raw, x, y, horizontal, vertical, str) }; } // TODO: @@ -177,42 +174,42 @@ impl<'a> Canvas<'a> { pub fn draw_dot(&mut self, x: u8, y: u8) { // SAFETY: `self.canvas` is always a valid pointer, - unsafe { sys::canvas_draw_dot(self.canvas, x, y) } + unsafe { sys::canvas_draw_dot(self.raw, x, y) } } // TODO: do we need range checks? // TODO: do `width` and `height` have to be non-zero pub fn draw_box(&mut self, x: u8, y: u8, width: u8, height: u8) { // SAFETY: `self.canvas` is always a valid pointer, - unsafe { sys::canvas_draw_box(self.canvas, x, y, width, height) } + unsafe { sys::canvas_draw_box(self.raw, x, y, width, height) } } // TODO: do we need range checks? // TODO: do `width` and `height` have to be non-zero pub fn draw_frame(&mut self, x: u8, y: u8, width: u8, height: u8) { // SAFETY: `self.canvas` is always a valid pointer, - unsafe { sys::canvas_draw_frame(self.canvas, x, y, width, height) } + unsafe { sys::canvas_draw_frame(self.raw, x, y, width, height) } } // TODO: do we need range checks? // TODO: do `x2` and `y2` have to be non-zero pub fn draw_line(&mut self, x1: u8, y1: u8, x2: u8, y2: u8) { // SAFETY: `self.canvas` is always a valid pointer, - unsafe { sys::canvas_draw_line(self.canvas, x1, y1, x2, y2) } + unsafe { sys::canvas_draw_line(self.raw, x1, y1, x2, y2) } } // TODO: do we need range checks? // TODO: does `radius` have to be non-zero pub fn draw_circle(&mut self, x: u8, y: u8, radius: u8) { // SAFETY: `self.canvas` is always a valid pointer, - unsafe { sys::canvas_draw_circle(self.canvas, x, y, radius) } + unsafe { sys::canvas_draw_circle(self.raw, x, y, radius) } } // TODO: do we need range checks? // TODO: does `radius` have to be non-zero pub fn draw_disc(&mut self, x: u8, y: u8, radius: u8) { // SAFETY: `self.canvas` is always a valid pointer, - unsafe { sys::canvas_draw_disc(self.canvas, x, y, radius) } + unsafe { sys::canvas_draw_disc(self.raw, x, y, radius) } } // TODO: do we need range checks? @@ -228,44 +225,44 @@ impl<'a> Canvas<'a> { let direction = direction.into(); // SAFETY: `self.canvas` is always a valid pointer, // and `direction` is guaranteed to be valid by `From` implementation - unsafe { sys::canvas_draw_triangle(self.canvas, x, y, base, height, direction) } + unsafe { sys::canvas_draw_triangle(self.raw, x, y, base, height, direction) } } // TODO: do we need range checks? // TODO: does `character` have to be of a wrapper type pub fn draw_glyph(&mut self, x: u8, y: u8, character: u16) { // SAFETY: `self.canvas` is always a valid pointer, - unsafe { sys::canvas_draw_glyph(self.canvas, x, y, character) } + unsafe { sys::canvas_draw_glyph(self.raw, x, y, character) } } pub fn set_bitmap_mode(&mut self, alpha: bool) { // SAFETY: `self.canvas` is always a valid pointer, - unsafe { sys::canvas_set_bitmap_mode(self.canvas, alpha) } + unsafe { sys::canvas_set_bitmap_mode(self.raw, alpha) } } // TODO: do we need range checks? // TODO: do `width`, `height` and `radius` have to be non-zero pub fn draw_rframe(&mut self, x: u8, y: u8, width: u8, height: u8, radius: u8) { // SAFETY: `self.canvas` is always a valid pointer, - unsafe { sys::canvas_draw_rframe(self.canvas, x, y, width, height, radius) } + unsafe { sys::canvas_draw_rframe(self.raw, x, y, width, height, radius) } } // TODO: do we need range checks? // TODO: do `width`, `height` and `radius` have to be non-zero pub fn draw_rbox(&mut self, x: u8, y: u8, width: u8, height: u8, radius: u8) { // SAFETY: `self.canvas` is always a valid pointer, - unsafe { sys::canvas_draw_rbox(self.canvas, x, y, width, height, radius) } + unsafe { sys::canvas_draw_rbox(self.raw, x, y, width, height, radius) } } } -impl Drop for Canvas<'_> { +impl Drop for CanvasView { fn drop(&mut self) { // unsafe { sys::gui_direct_draw_release(self.parent...) } } } pub struct CanvasFontParameters<'a> { - _parent: &'a Canvas<'a>, + _parent: &'a CanvasView, raw: *mut SysCanvasFontParameters, } @@ -326,7 +323,7 @@ impl<'a> CanvasFontParameters<'a> { } } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] pub struct CanvasFontParametersSnapshot { leading_default: NonZeroU8, leading_min: NonZeroU8, @@ -334,7 +331,7 @@ pub struct CanvasFontParametersSnapshot { descender: u8, } -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] pub enum FromSysGuiLayerError { ZeroLeadingDefault, ZeroLeadingMin, @@ -371,7 +368,7 @@ impl From for SysCanvasFontParameters { } } -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] pub enum Color { White, Black, @@ -379,7 +376,7 @@ pub enum Color { // Xor, } -#[derive(Clone, Copy, Debug)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] pub enum FromSysColor { Invalid(SysColor), } @@ -419,7 +416,7 @@ impl From for SysColor { } } -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] pub enum Font { Primary, Secondary, @@ -427,7 +424,7 @@ pub enum Font { BigNumbers, } -#[derive(Clone, Copy, Debug)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] pub enum FromSysFont { TotalNumber, Invalid(SysFont), @@ -522,7 +519,7 @@ impl From for SysFont { // } // } -#[derive(Clone, Copy, Debug)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] pub enum CanvasDirection { LeftToRight, TopToBottom, @@ -530,7 +527,7 @@ pub enum CanvasDirection { BottomToTop, } -#[derive(Clone, Copy, Debug)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] pub enum FromSysCanvasDirectionError { Invalid(SysCanvasDirection), } diff --git a/crates/gui/src/gui.rs b/crates/gui/src/gui.rs index f20e683d..3152fe75 100644 --- a/crates/gui/src/gui.rs +++ b/crates/gui/src/gui.rs @@ -1,6 +1,7 @@ //! GUI APIs -use crate::canvas::Canvas; +use crate::canvas::CanvasView; +use crate::input::InputEvent; use crate::view_port::{ViewPort, ViewPortCallbacks}; use core::ffi::c_char; use core::fmt::Debug; @@ -54,7 +55,7 @@ impl Gui { // TODO: separate `GuiCanvas` (locking the parent) // and `Canvas` (independent of the parent) - pub fn direct_draw_acquire(&self) -> Canvas<'_> { + pub fn direct_draw_acquire(&self) -> CanvasView { let gui = self.gui.as_raw(); // SAFETY: `gui` is always a valid pointer @@ -117,7 +118,7 @@ impl Drop for GuiViewPort<'_, VPC> { } } -#[derive(Clone, Copy, Debug)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] pub enum GuiLayer { Desktop, Window, @@ -126,7 +127,7 @@ pub enum GuiLayer { Fullscreen, } -#[derive(Clone, Copy, Debug)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] pub enum FromSysGuiLayerError { Max, Invalid(SysGuiLayer), @@ -179,5 +180,5 @@ impl From for SysGuiLayer { pub trait GuiCallbacks { fn on_draw(&mut self, _canvas: *mut sys::Canvas) {} - fn on_input(&mut self, _event: *mut sys::InputEvent) {} + fn on_input(&mut self, _event: InputEvent) {} } diff --git a/crates/gui/src/input.rs b/crates/gui/src/input.rs new file mode 100644 index 00000000..b30fa8f5 --- /dev/null +++ b/crates/gui/src/input.rs @@ -0,0 +1,172 @@ +// FIXME: in flipperzero-firmware, this is a separate service +use flipperzero_sys::{ + self as sys, InputEvent as SysInputEvent, InputKey as SysInputKey, InputType as SysInputType, +}; + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub struct InputEvent { + pub sequence: u32, + pub key: InputKey, + pub r#type: InputType, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub enum FromSysInputEventError { + InvalidKey(FromSysInputKeyError), + InvalidType(FromSysInputTypeError), +} + +impl From for FromSysInputEventError { + fn from(value: FromSysInputKeyError) -> Self { + Self::InvalidKey(value) + } +} + +impl From for FromSysInputEventError { + fn from(value: FromSysInputTypeError) -> Self { + Self::InvalidType(value) + } +} + +impl<'a> TryFrom<&'a SysInputEvent> for InputEvent { + type Error = FromSysInputEventError; + + fn try_from(value: &'a SysInputEvent) -> Result { + Ok(Self { + sequence: value.sequence, + key: value.key.try_into()?, + r#type: value.type_.try_into()?, + }) + } +} + +impl From for SysInputEvent { + fn from(value: InputEvent) -> Self { + Self { + sequence: value.sequence, + key: value.key.into(), + type_: value.r#type.into(), + } + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub enum InputType { + Press, + Release, + Short, + Long, + Repeat, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub enum FromSysInputTypeError { + Max, + Invalid(SysInputType), +} + +impl TryFrom for InputType { + type Error = FromSysInputTypeError; + + fn try_from(value: SysInputType) -> Result { + use sys::{ + InputType_InputTypeLong as SYS_INPUT_TYPE_LONG, + InputType_InputTypeMAX as SYS_INPUT_TYPE_MAX, + InputType_InputTypePress as SYS_INPUT_TYPE_PRESS, + InputType_InputTypeRelease as SYS_INPUT_TYPE_RELEASE, + InputType_InputTypeRepeat as SYS_INPUT_TYPE_REPEAT, + InputType_InputTypeShort as SYS_INPUT_TYPE_SHORT, + }; + + Ok(match value { + SYS_INPUT_TYPE_PRESS => Self::Press, + SYS_INPUT_TYPE_RELEASE => Self::Release, + SYS_INPUT_TYPE_SHORT => Self::Short, + SYS_INPUT_TYPE_LONG => Self::Long, + SYS_INPUT_TYPE_REPEAT => Self::Repeat, + SYS_INPUT_TYPE_MAX => Err(Self::Error::Max)?, + invalid => Err(Self::Error::Invalid(invalid))?, + }) + } +} + +impl From for SysInputType { + fn from(value: InputType) -> Self { + use sys::{ + InputType_InputTypeLong as SYS_INPUT_TYPE_LONG, + InputType_InputTypePress as SYS_INPUT_TYPE_PRESS, + InputType_InputTypeRelease as SYS_INPUT_TYPE_RELEASE, + InputType_InputTypeRepeat as SYS_INPUT_TYPE_REPEAT, + InputType_InputTypeShort as SYS_INPUT_TYPE_SHORT, + }; + + match value { + InputType::Press => SYS_INPUT_TYPE_PRESS, + InputType::Release => SYS_INPUT_TYPE_RELEASE, + InputType::Short => SYS_INPUT_TYPE_SHORT, + InputType::Long => SYS_INPUT_TYPE_LONG, + InputType::Repeat => SYS_INPUT_TYPE_REPEAT, + } + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub enum InputKey { + Up, + Down, + Right, + Left, + Ok, + Back, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub enum FromSysInputKeyError { + Max, + Invalid(SysInputKey), +} + +impl TryFrom for InputKey { + type Error = FromSysInputKeyError; + + fn try_from(value: SysInputKey) -> Result { + use sys::{ + InputKey_InputKeyBack as SYS_INPUT_KEY_BACK, + InputKey_InputKeyDown as SYS_INPUT_KEY_DOWN, + InputKey_InputKeyLeft as SYS_INPUT_KEY_LEFT, InputKey_InputKeyMAX as SYS_INPUT_KEY_MAX, + InputKey_InputKeyOk as SYS_INPUT_KEY_OK, InputKey_InputKeyRight as SYS_INPUT_KEY_RIGHT, + InputKey_InputKeyUp as SYS_INPUT_KEY_UP, + }; + + Ok(match value { + SYS_INPUT_KEY_UP => Self::Up, + SYS_INPUT_KEY_DOWN => Self::Down, + SYS_INPUT_KEY_RIGHT => Self::Right, + SYS_INPUT_KEY_LEFT => Self::Left, + SYS_INPUT_KEY_OK => Self::Ok, + SYS_INPUT_KEY_BACK => Self::Back, + SYS_INPUT_KEY_MAX => Err(Self::Error::Max)?, + invalid => Err(Self::Error::Invalid(invalid))?, + }) + } +} + +impl From for SysInputKey { + fn from(value: InputKey) -> Self { + use sys::{ + InputKey_InputKeyBack as SYS_INPUT_KEY_BACK, + InputKey_InputKeyDown as SYS_INPUT_KEY_DOWN, + InputKey_InputKeyLeft as SYS_INPUT_KEY_LEFT, InputKey_InputKeyOk as SYS_INPUT_KEY_OK, + InputKey_InputKeyRight as SYS_INPUT_KEY_RIGHT, InputKey_InputKeyUp as SYS_INPUT_KEY_UP, + }; + + match value { + InputKey::Up => SYS_INPUT_KEY_UP, + InputKey::Down => SYS_INPUT_KEY_DOWN, + InputKey::Right => SYS_INPUT_KEY_RIGHT, + InputKey::Left => SYS_INPUT_KEY_LEFT, + InputKey::Ok => SYS_INPUT_KEY_OK, + InputKey::Back => SYS_INPUT_KEY_BACK, + } + } +} diff --git a/crates/gui/src/lib.rs b/crates/gui/src/lib.rs index 8837f739..582d0302 100644 --- a/crates/gui/src/lib.rs +++ b/crates/gui/src/lib.rs @@ -7,5 +7,6 @@ extern crate alloc; pub mod canvas; pub mod gui; +pub mod input; pub mod view; pub mod view_port; diff --git a/crates/gui/src/view.rs b/crates/gui/src/view.rs index 7881164f..4ba08a13 100644 --- a/crates/gui/src/view.rs +++ b/crates/gui/src/view.rs @@ -1,4 +1,3 @@ -use core::ptr::{null_mut, NonNull}; use flipperzero_sys::{self as sys, View as SysView}; pub struct View { diff --git a/crates/gui/src/view_port.rs b/crates/gui/src/view_port.rs index bcae9585..601acf78 100644 --- a/crates/gui/src/view_port.rs +++ b/crates/gui/src/view_port.rs @@ -1,9 +1,11 @@ //! ViewPort APIs +use crate::canvas::CanvasView; +use crate::input::InputEvent; use alloc::boxed::Box; use core::{ffi::c_void, num::NonZeroU8, ptr::NonNull}; use flipperzero_sys::{ - self as sys, ViewPort as SysViewPort, ViewPortOrientation as SysViewPortOrientation, + self as sys, Canvas, ViewPort as SysViewPort, ViewPortOrientation as SysViewPortOrientation, }; /// System ViewPort. @@ -30,29 +32,36 @@ impl ViewPort { let raw = unsafe { NonNull::new_unchecked(sys::view_port_alloc()) }; let callbacks = NonNull::from(Box::leak(Box::new(callbacks))); - let mut view_port = Self { raw, callbacks }; + let view_port = Self { raw, callbacks }; pub unsafe extern "C" fn dispatch_draw( canvas: *mut sys::Canvas, context: *mut c_void, ) { + // SAFETY: `canvas` is guaranteed to be a valid pointer + let mut canvas = unsafe { CanvasView::from_raw(NonNull::new_unchecked(canvas)) }; + let context: *mut C = context.cast(); // SAFETY: `context` is stored in a `Box` which is a member of `ViewPort` // and the callback is accessed exclusively by this function - (unsafe { &mut *context }).on_draw(canvas); + unsafe { &mut *context }.on_draw(&mut canvas); } pub unsafe extern "C" fn dispatch_input( - canvas: *mut sys::InputEvent, + input_event: *mut sys::InputEvent, context: *mut c_void, ) { + let input_event: InputEvent = (&unsafe { *input_event }) + .try_into() + .expect("`input_event` should be a valid event"); + let context: *mut C = context.cast(); // SAFETY: `context` is stored in a pinned Box which is a member of `ViewPort` // and the callback is accessed exclusively by this function - (unsafe { &mut *context }).on_input(canvas); + unsafe { &mut *context }.on_input(input_event); } // SAFETY: `callbacks` is a valid pointer and - let context = unsafe { view_port.callbacks.as_ptr() }.cast(); + let context = view_port.callbacks.as_ptr().cast(); let raw = raw.as_ptr(); unsafe { @@ -314,7 +323,7 @@ impl Drop for ViewPort { } } -#[derive(Clone, Copy, Debug)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] pub enum ViewPortOrientation { Horizontal, HorizontalFlip, @@ -322,7 +331,7 @@ pub enum ViewPortOrientation { VerticalFlip, } -#[derive(Clone, Copy, Debug)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] pub enum FromSysViewPortOrientationError { Max, Invalid(SysViewPortOrientation), @@ -370,6 +379,6 @@ impl From for SysViewPortOrientation { } pub trait ViewPortCallbacks { - fn on_draw(&mut self, _canvas: *mut sys::Canvas) {} - fn on_input(&mut self, _event: *mut sys::InputEvent) {} + fn on_draw(&mut self, _canvas: &mut CanvasView) {} + fn on_input(&mut self, _event: InputEvent) {} } diff --git a/crates/sys/src/furi.rs b/crates/sys/src/furi.rs index 8e51ebd0..19688196 100644 --- a/crates/sys/src/furi.rs +++ b/crates/sys/src/furi.rs @@ -2,7 +2,6 @@ use core::ffi::c_char; use core::fmt::Display; -use core::ptr::NonNull; use core::time::Duration; /// Operation status. diff --git a/crates/sys/src/lib.rs b/crates/sys/src/lib.rs index b8dc85bf..88c6c8cd 100644 --- a/crates/sys/src/lib.rs +++ b/crates/sys/src/lib.rs @@ -2,6 +2,10 @@ #![no_std] +/// Re-export bindings +pub use bindings::*; +use core::hint::unreachable_unchecked; + pub mod furi; #[allow(non_upper_case_globals)] @@ -27,12 +31,33 @@ macro_rules! crash { let msg = $crate::c_string!($msg); core::arch::asm!("", in("r12") msg, options(nomem, nostack)); - $crate::__furi_crash(); + $crate::furi_crash(); // `unreachable!` generates exception machinery, `noreturn` does not core::arch::asm!("", options(noreturn)); } }; } -/// Re-export bindings -pub use bindings::*; +// TODO: find a better place +#[doc(hidden)] +#[inline(always)] +pub fn furi_crash() { + // SAFETY: crash function has no invariants to uphold + // and it always crashes the program + unsafe { + __furi_crash(); + unreachable_unchecked(); + } +} + +// TODO: find a better place +#[doc(hidden)] +#[inline(always)] +pub fn furi_halt() { + // SAFETY: crash function has no invariants to uphold + // and it always crashes the program + unsafe { + __furi_halt(); + unreachable_unchecked(); + } +} diff --git a/examples/gui/src/main.rs b/examples/gui/src/main.rs index ab8428cc..ae374313 100644 --- a/examples/gui/src/main.rs +++ b/examples/gui/src/main.rs @@ -3,6 +3,7 @@ #![no_main] #![no_std] +#![forbid(unsafe_code)] // Required for panic handler extern crate flipperzero_rt; @@ -10,41 +11,86 @@ extern crate flipperzero_rt; extern crate alloc; extern crate flipperzero_alloc; -use core::ffi::c_char; -use core::time::Duration; +use alloc::ffi::CString; +use core::{ffi::CStr, time::Duration}; -use flipperzero::furi::thread::sleep; -use flipperzero_gui::gui::{Gui, GuiLayer}; -use flipperzero_gui::view_port::{ViewPort, ViewPortCallbacks}; +use flipperzero::{furi::message_queue::MessageQueue, println}; +use flipperzero_gui::{ + canvas::CanvasView, + gui::{Gui, GuiLayer}, + input::{InputEvent, InputKey, InputType}, + view_port::{ViewPort, ViewPortCallbacks}, +}; use flipperzero_rt::{entry, manifest}; -use flipperzero_sys as sys; -use flipperzero_sys::Canvas; +use flipperzero_sys::furi::Status; manifest!(name = "Rust GUI example"); entry!(main); fn main(_args: *mut u8) -> i32 { - let view_port = new_view_port(); - let mut gui = Gui::new(); - let mut view_port = gui.add_view_port(view_port, GuiLayer::Fullscreen); + let exit_event_queue = MessageQueue::new(32); - sleep(Duration::from_secs(1)); + struct State<'a> { + text: &'a CStr, + exit_event_queue: &'a MessageQueue<()>, + counter: u8, + } - view_port.view_port_mut().set_enabled(false); + impl ViewPortCallbacks for State<'_> { + fn on_draw(&mut self, canvas: &mut CanvasView) { + canvas.draw_str(10, 31, self.text); + let bottom_text = CString::new(alloc::format!("Value = {}", self.counter).as_bytes()) + .expect("should be a valid string"); + canvas.draw_str(5, 62, bottom_text); + } - 0 -} + fn on_input(&mut self, event: InputEvent) { + if event.r#type == InputType::Press { + match event.key { + InputKey::Up => { + self.counter = (self.counter + 1) % 10; + } + InputKey::Down => { + self.counter = if self.counter == 0 { + 10 + } else { + self.counter - 1 + }; + } + InputKey::Back => { + self.exit_event_queue + .put((), Duration::MAX) + .expect("failed to put event into the queue"); + } + _ => {} + } + } + } + } + let view_port = ViewPort::new(State { + text: CStr::from_bytes_with_nul(b"Hi there!\0").expect("correct string"), + exit_event_queue: &exit_event_queue, + counter: 0, + }); -fn new_view_port() -> ViewPort { - struct Callbacks(*const c_char); + let mut gui = Gui::new(); + let mut view_port = gui.add_view_port(view_port, GuiLayer::Fullscreen); - impl ViewPortCallbacks for Callbacks { - fn on_draw(&mut self, canvas: *mut Canvas) { - // # SAFETY: `canvas` should be a valid pointer - unsafe { - sys::canvas_draw_str(canvas, 39, 31, self.0); + let status = loop { + match exit_event_queue.get(Duration::from_millis(100)) { + Ok(()) => { + println!("Exit pressed"); + break 0; + } + Err(e) => { + if e != Status::ERR_TIMEOUT { + println!("ERROR while receiving event: {:?}", e); + break 1; + } } } - } - ViewPort::new(Callbacks(sys::c_string!("Hello, Rust!"))) + }; + view_port.view_port_mut().set_enabled(false); + + status } From 7a9f9e94c1e86c9cd85c666ebad6d1a084b74734 Mon Sep 17 00:00:00 2001 From: Petr Portnov Date: Thu, 5 Jan 2023 01:06:56 +0300 Subject: [PATCH 10/49] feat: add `icon` and `icon_animation` This also resolves unsoundness in previous callbacks' implementation. --- crates/gui/src/canvas.rs | 236 ++++++++++-------------- crates/gui/src/gui.rs | 17 +- crates/gui/src/icon.rs | 58 ++++++ crates/gui/src/icon_animation.rs | 179 ++++++++++++++++++ crates/gui/src/input.rs | 19 ++ crates/gui/src/lib.rs | 6 +- crates/gui/src/view_port.rs | 82 +++++--- crates/gui/src/xbm.rs | 155 ++++++++++++++++ crates/rust-toolchain.toml | 2 +- examples/dialog/rust-toolchain.toml | 2 +- examples/gui/rust-toolchain.toml | 2 +- examples/gui/src/main.rs | 3 +- examples/hello-rust/rust-toolchain.toml | 2 +- 13 files changed, 586 insertions(+), 177 deletions(-) create mode 100644 crates/gui/src/icon.rs create mode 100644 crates/gui/src/icon_animation.rs create mode 100644 crates/gui/src/xbm.rs diff --git a/crates/gui/src/canvas.rs b/crates/gui/src/canvas.rs index 12cec8e3..a1f8e748 100644 --- a/crates/gui/src/canvas.rs +++ b/crates/gui/src/canvas.rs @@ -1,38 +1,29 @@ //! ViewPort APIs -use crate::gui::Gui; -use core::{ - ffi::CStr, - num::NonZeroU8, - ptr::{null_mut, NonNull}, -}; +use core::{ffi::CStr, marker::PhantomData, num::NonZeroU8, ptr::NonNull}; use flipperzero::furi::canvas::Align; use flipperzero_sys::{ self as sys, Canvas as SysCanvas, CanvasDirection as SysCanvasDirection, CanvasFontParameters as SysCanvasFontParameters, Color as SysColor, Font as SysFont, }; -/// System Canvas. -pub struct CanvasView { - raw: *mut SysCanvas, +/// System Canvas view. +pub struct CanvasView<'a> { + raw: NonNull, + _lifetime: PhantomData<&'a ()>, } -impl CanvasView { - /// Construct a `Canvas` from a raw non-null pointer. - /// - /// After calling this function, the raw pointer is owned by the resulting `Canvas`. - /// Specifically, the `Canvas` destructor will free the allocated memory. +impl CanvasView<'_> { + /// Construct a `CanvasView` from a raw pointer. /// /// # Safety /// - /// - `parent` should be the `Gui` which owns this canvas; - /// - /// - `raw` should be a valid pointer to [`CanvasView`]. + /// `raw` should be a valid non-null pointer to [`SysCanvas`] + /// and the lifetime should be outlived by `raw` validity scope. /// /// # Examples /// - /// Recreate a `Canvas` - /// which vas previously converted to a raw pointer using [`CanvasView::into_raw`]. + /// Basic usage: /// /// ``` /// use flipperzero_gui::canvas::CanvasView; @@ -40,38 +31,12 @@ impl CanvasView { /// let ptr = todo!(); /// let canvas = unsafe { CanvasView::from_raw(ptr) }; /// ``` - pub unsafe fn from_raw(raw: NonNull) -> Self { - Self { raw: raw.as_ptr() } - } - - /// Consumes this wrapper, returning a non-null raw pointer. - /// - /// After calling this function, the caller is responsible - /// for the memory previously managed by the `Canvas`. - /// In particular, the caller should properly destroy `SysCanvas` and release the memory. - /// The easiest way to do this is to convert the raw pointer - /// back into a `Canvas` with the [ViewPort::from_raw] function, - /// allowing the `Canvas` destructor to perform the cleanup. - /// - /// # Example - /// - /// Converting the raw pointer back into a `Canvas` - /// with [`CanvasView::from_raw`] for automatic cleanup: - /// - /// ``` - /// use flipperzero_gui::{canvas::CanvasView, gui::Gui}; - /// - /// let mut gui = Gui::new(); - /// let canvas = gui.direct_draw_acquire(); - /// let ptr = canvas.into_raw(); - /// let canvas = unsafe { CanvasView::from_raw(gui, ptr) }; - /// ``` - pub fn into_raw(mut self) -> NonNull { - let raw_pointer = core::mem::replace(&mut self.raw, null_mut()); - // SAFETY: `self.canvas` is guaranteed to be non-null - // since it only becomes null after call to this function - // which consumes the wrapper - unsafe { NonNull::new_unchecked(raw_pointer) } + pub unsafe fn from_raw(raw: *mut SysCanvas) -> Self { + Self { + // SAFETY: caller should provide a valid pointer + raw: unsafe { NonNull::new_unchecked(raw) }, + _lifetime: PhantomData, + } } // FIXME: @@ -79,70 +44,81 @@ impl CanvasView { // - canvas_commit pub fn width(&self) -> NonZeroU8 { - // SAFETY: `self.canvas` is always a valid pointer - unsafe { sys::canvas_width(self.raw) } + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is always valid + unsafe { sys::canvas_width(raw) } .try_into() .expect("`canvas_width` should produce a positive value") } pub fn height(&self) -> NonZeroU8 { - // SAFETY: `self.canvas` is always a valid pointer - unsafe { sys::canvas_height(self.raw) } + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is always valid + unsafe { sys::canvas_height(raw) } .try_into() .expect("`canvas_height` should produce a positive value") } pub fn current_font_height(&self) -> NonZeroU8 { - // SAFETY: `self.canvas` is always a valid pointer - unsafe { sys::canvas_current_font_height(self.raw) } + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is always valid + unsafe { sys::canvas_current_font_height(raw) } .try_into() .expect("`canvas_current_font_height` should produce a positive value") } pub fn get_font_params(&self, font: Font) -> CanvasFontParameters<'_> { + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is always valid let font = font.into(); - // SAFETY: `self.canvas` is always a valid pointer + // SAFETY: `raw` is always a valid pointer // and `font` is guaranteed to be a valid value by `From` implementation - let raw = unsafe { sys::canvas_get_font_params(self.raw, font) }; - CanvasFontParameters { _parent: self, raw } + let raw = unsafe { NonNull::new_unchecked(sys::canvas_get_font_params(raw, font)) }; + CanvasFontParameters { raw, _parent: self } } pub fn clear(&mut self) { - // SAFETY: `self.canvas` is always a valid pointer - unsafe { sys::canvas_clear(self.raw) }; + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is always valid + unsafe { sys::canvas_clear(raw) }; } pub fn set_color(&mut self, color: Color) { + let raw = self.raw.as_ptr(); let color = color.into(); - // SAFETY: `self.canvas` is always a valid pointer + // SAFETY: `raw` is always valid // and `font` is guaranteed to be a valid value by `From` implementation - unsafe { sys::canvas_set_color(self.raw, color) }; + unsafe { sys::canvas_set_color(raw, color) }; } pub fn set_font_direction(&mut self, font_direction: CanvasDirection) { + let raw = self.raw.as_ptr(); let font_direction = font_direction.into(); - // SAFETY: `self.canvas` is always a valid pointer + // SAFETY: `self.canvas` is always valid // and `font_direction` is guaranteed to be a valid value by `From` implementation - unsafe { sys::canvas_set_font_direction(self.raw, font_direction) }; + unsafe { sys::canvas_set_font_direction(raw, font_direction) }; } pub fn invert_color(&mut self) { - // SAFETY: `self.canvas` is always a valid pointer - unsafe { sys::canvas_invert_color(self.raw) }; + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is always valid + unsafe { sys::canvas_invert_color(raw) }; } pub fn set_font(&mut self, font: Font) { + let raw = self.raw.as_ptr(); let font = font.into(); - // SAFETY: `self.canvas` is always a valid pointer + // SAFETY: `self.canvas` is always valid // and `font` is guaranteed to be a valid value by `From` implementation - unsafe { sys::canvas_set_font(self.raw, font) }; + unsafe { sys::canvas_set_font(raw, font) }; } pub fn draw_str(&mut self, x: u8, y: u8, str: impl AsRef) { + let raw = self.raw.as_ptr(); let str = str.as_ref().as_ptr(); - // SAFETY: `self.canvas` is always a valid pointer + // SAFETY: `self.canvas` is always valid // and `text` is guaranteed to be a valid pointer since it was created from `CStr` - unsafe { sys::canvas_draw_str(self.raw, x, y, str) }; + unsafe { sys::canvas_draw_str(raw, x, y, str) }; } pub fn draw_str_aligned( @@ -153,13 +129,14 @@ impl CanvasView { vertical: Align, str: impl AsRef, ) { + let raw = self.raw.as_ptr(); let horizontal = horizontal.into(); let vertical = vertical.into(); let str = str.as_ref().as_ptr(); - // SAFETY: `self.canvas` is always a valid pointer, + // SAFETY: `self.canvas` is always valid, // `horixontal` and `vertival` are guaranteed to be valid by `From` implementation // and `text` is guaranteed to be a valid pointer since it was created from `CStr` - unsafe { sys::canvas_draw_str_aligned(self.raw, x, y, horizontal, vertical, str) }; + unsafe { sys::canvas_draw_str_aligned(raw, x, y, horizontal, vertical, str) }; } // TODO: @@ -173,43 +150,49 @@ impl CanvasView { // TODO: decide if we want to pack x-y pairs into tuples pub fn draw_dot(&mut self, x: u8, y: u8) { - // SAFETY: `self.canvas` is always a valid pointer, - unsafe { sys::canvas_draw_dot(self.raw, x, y) } + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is always valid + unsafe { sys::canvas_draw_dot(raw, x, y) } } // TODO: do we need range checks? // TODO: do `width` and `height` have to be non-zero pub fn draw_box(&mut self, x: u8, y: u8, width: u8, height: u8) { - // SAFETY: `self.canvas` is always a valid pointer, - unsafe { sys::canvas_draw_box(self.raw, x, y, width, height) } + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is always valid + unsafe { sys::canvas_draw_box(raw, x, y, width, height) } } // TODO: do we need range checks? // TODO: do `width` and `height` have to be non-zero pub fn draw_frame(&mut self, x: u8, y: u8, width: u8, height: u8) { - // SAFETY: `self.canvas` is always a valid pointer, - unsafe { sys::canvas_draw_frame(self.raw, x, y, width, height) } + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is always valid + unsafe { sys::canvas_draw_frame(raw, x, y, width, height) } } // TODO: do we need range checks? // TODO: do `x2` and `y2` have to be non-zero pub fn draw_line(&mut self, x1: u8, y1: u8, x2: u8, y2: u8) { - // SAFETY: `self.canvas` is always a valid pointer, - unsafe { sys::canvas_draw_line(self.raw, x1, y1, x2, y2) } + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is always valid + unsafe { sys::canvas_draw_line(raw, x1, y1, x2, y2) } } // TODO: do we need range checks? // TODO: does `radius` have to be non-zero pub fn draw_circle(&mut self, x: u8, y: u8, radius: u8) { - // SAFETY: `self.canvas` is always a valid pointer, - unsafe { sys::canvas_draw_circle(self.raw, x, y, radius) } + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is always valid + unsafe { sys::canvas_draw_circle(raw, x, y, radius) } } // TODO: do we need range checks? // TODO: does `radius` have to be non-zero pub fn draw_disc(&mut self, x: u8, y: u8, radius: u8) { - // SAFETY: `self.canvas` is always a valid pointer, - unsafe { sys::canvas_draw_disc(self.raw, x, y, radius) } + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is always valid + unsafe { sys::canvas_draw_disc(raw, x, y, radius) } } // TODO: do we need range checks? @@ -222,102 +205,87 @@ impl CanvasView { height: u8, direction: CanvasDirection, ) { + let raw = self.raw.as_ptr(); let direction = direction.into(); - // SAFETY: `self.canvas` is always a valid pointer, + // SAFETY: `raw` is always valid // and `direction` is guaranteed to be valid by `From` implementation - unsafe { sys::canvas_draw_triangle(self.raw, x, y, base, height, direction) } + unsafe { sys::canvas_draw_triangle(raw, x, y, base, height, direction) } } // TODO: do we need range checks? // TODO: does `character` have to be of a wrapper type pub fn draw_glyph(&mut self, x: u8, y: u8, character: u16) { - // SAFETY: `self.canvas` is always a valid pointer, - unsafe { sys::canvas_draw_glyph(self.raw, x, y, character) } + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is always valid, + unsafe { sys::canvas_draw_glyph(raw, x, y, character) } } pub fn set_bitmap_mode(&mut self, alpha: bool) { - // SAFETY: `self.canvas` is always a valid pointer, - unsafe { sys::canvas_set_bitmap_mode(self.raw, alpha) } + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is always valid, + unsafe { sys::canvas_set_bitmap_mode(raw, alpha) } } // TODO: do we need range checks? // TODO: do `width`, `height` and `radius` have to be non-zero pub fn draw_rframe(&mut self, x: u8, y: u8, width: u8, height: u8, radius: u8) { - // SAFETY: `self.canvas` is always a valid pointer, - unsafe { sys::canvas_draw_rframe(self.raw, x, y, width, height, radius) } + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is always valid, + unsafe { sys::canvas_draw_rframe(raw, x, y, width, height, radius) } } // TODO: do we need range checks? // TODO: do `width`, `height` and `radius` have to be non-zero pub fn draw_rbox(&mut self, x: u8, y: u8, width: u8, height: u8, radius: u8) { - // SAFETY: `self.canvas` is always a valid pointer, - unsafe { sys::canvas_draw_rbox(self.raw, x, y, width, height, radius) } - } -} - -impl Drop for CanvasView { - fn drop(&mut self) { - // unsafe { sys::gui_direct_draw_release(self.parent...) } + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is always valid, + unsafe { sys::canvas_draw_rbox(raw, x, y, width, height, radius) } } } pub struct CanvasFontParameters<'a> { - _parent: &'a CanvasView, - raw: *mut SysCanvasFontParameters, + raw: NonNull, + _parent: &'a CanvasView<'a>, } impl<'a> CanvasFontParameters<'a> { fn leading_default(&self) -> NonZeroU8 { - // SAFETY: this allways outlives its parent - unsafe { *self.raw } + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is always valid and this allways outlives its parent + unsafe { *raw } .leading_default .try_into() .expect("`leading_default` should always be positive") } - fn set_leading_default(&mut self, leading_default: NonZeroU8) { - // SAFETY: this allways outlives its parent - unsafe { *self.raw }.leading_default = leading_default.into() - } - fn leading_min(&self) -> NonZeroU8 { - // SAFETY: this allways outlives its parent - unsafe { *self.raw } + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is always valid and this allways outlives its parent + unsafe { *raw } .leading_min .try_into() .expect("`leading_min` should always be positive") } - fn set_leading_min(&mut self, leading_min: NonZeroU8) { - // SAFETY: this allways outlives its parent - unsafe { *self.raw }.leading_min = leading_min.into() - } - fn height(&self) -> NonZeroU8 { - // SAFETY: this allways outlives its parent - unsafe { *self.raw } + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is always valid and this allways outlives its parent + unsafe { *raw } .height .try_into() .expect("`height` should always be positive") } - fn set_height(&mut self, height: NonZeroU8) { - // SAFETY: this allways outlives its parent - unsafe { *self.raw }.height = height.into() - } - fn descender(&self) -> u8 { - // SAFETY: this allways outlives its parent - unsafe { *self.raw }.descender - } - - fn set_descender(&mut self, descender: u8) { - // SAFETY: this allways outlives its parent - unsafe { *self.raw }.descender = descender + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is always valid and this allways outlives its parent + unsafe { *raw }.descender } fn snapshot(&self) -> CanvasFontParametersSnapshot { - unsafe { *self.raw } + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is always valid and this allways outlives its parent + unsafe { *raw } .try_into() .expect("raw `CanvasFontParameters` should be valid") } @@ -363,7 +331,7 @@ impl From for SysCanvasFontParameters { leading_default: value.leading_default.into(), leading_min: value.leading_min.into(), height: value.height.into(), - descender: value.descender.into(), + descender: value.descender, } } } diff --git a/crates/gui/src/gui.rs b/crates/gui/src/gui.rs index 3152fe75..485e744f 100644 --- a/crates/gui/src/gui.rs +++ b/crates/gui/src/gui.rs @@ -1,13 +1,14 @@ //! GUI APIs -use crate::canvas::CanvasView; -use crate::input::InputEvent; -use crate::view_port::{ViewPort, ViewPortCallbacks}; -use core::ffi::c_char; -use core::fmt::Debug; +use crate::{ + canvas::CanvasView, + input::InputEvent, + view_port::{ViewPort, ViewPortCallbacks}, +}; +use core::{ffi::c_char, fmt::Debug}; use flipperzero_sys::{self as sys, furi::UnsafeRecord, Gui as SysGui, GuiLayer as SysGuiLayer}; -/// System ViewPort. +/// System Gui wrapper. pub struct Gui { gui: UnsafeRecord, } @@ -100,7 +101,7 @@ impl<'a, VPC: ViewPortCallbacks> GuiViewPort<'a, VPC> { unsafe { sys::gui_view_port_send_to_front(gui, view_port) }; } - // FIXME(Coles): `gui_view_port_send_to_back` is not present in bindings + // FIXME: `gui_view_port_send_to_back` is not present in bindings // pub fn send_to_back(&mut self) { // let gui = self.gui.as_raw(); // let view_port = self.view_port.as_raw(); @@ -114,6 +115,8 @@ impl Drop for GuiViewPort<'_, VPC> { let gui = self.parent.gui.as_raw(); let view_port = self.view_port().as_raw(); + // SAFETY: `gui` and `view_port` are valid pointers + // and this view port should have been added to the gui on creation unsafe { sys::gui_remove_view_port(gui, view_port) } } } diff --git a/crates/gui/src/icon.rs b/crates/gui/src/icon.rs new file mode 100644 index 00000000..f07944be --- /dev/null +++ b/crates/gui/src/icon.rs @@ -0,0 +1,58 @@ +use crate::xbm::XbmImage; +use core::{ptr::NonNull, slice}; +use flipperzero_sys::{self as sys, Icon as SysIcon}; + +pub struct Icon { + raw: NonNull, +} + +impl Icon { + /// Construct an `Icon` from a raw non-null pointer. + /// + /// # Safety + /// + /// `raw` should be a valid pointer to [`SysCanvas`]. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// use flipperzero_gui::icon::Icon; + /// + /// let ptr = todo!(); + /// let canvas = unsafe { Icon::from_raw(ptr) }; + /// ``` + pub unsafe fn from_raw(raw: NonNull) -> Self { + Self { raw } + } + + pub fn as_raw(&self) -> *mut SysIcon { + self.raw.as_ptr() + } + + pub fn get_width(&self) -> u8 { + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is always valid + unsafe { sys::icon_get_width(raw) } + } + + pub fn get_height(&self) -> u8 { + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is always valid + unsafe { sys::icon_get_height(raw) } + } + + pub fn get_dimensions(&self) -> (u8, u8) { + (self.get_width(), self.get_height()) + } + + pub fn get_data(&self) -> XbmImage<'_> { + let (width, height) = self.get_dimensions(); + + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is always valid + // and `width` and `height` are always in sync with data + unsafe { XbmImage::from_raw(sys::icon_get_data(raw), width, height) } + } +} diff --git a/crates/gui/src/icon_animation.rs b/crates/gui/src/icon_animation.rs new file mode 100644 index 00000000..55457472 --- /dev/null +++ b/crates/gui/src/icon_animation.rs @@ -0,0 +1,179 @@ +use crate::icon::Icon; +use alloc::boxed::Box; +use core::{ + ffi::c_void, + marker::PhantomData, + ptr::{self, NonNull}, +}; +use flipperzero_sys::{self as sys, IconAnimation as SysIconAnimation}; + +pub struct IconAnimation<'a, C: IconAnimationCallbacks> { + raw: NonNull, + callbacks: NonNull, + _parent_lifetime: PhantomData<&'a ()>, +} + +impl<'a, C: IconAnimationCallbacks> IconAnimation<'a, C> { + pub fn new<'b: 'a>(icon: &'b Icon, callbacks: C) -> Self { + let icon = icon.as_raw(); + // SAFETY: allocation either succeeds producing the valid pointer + // or stops the system on OOM, + // `icon` is a valid pointer and `icon` outlives this animation + let raw = unsafe { NonNull::new_unchecked(sys::icon_animation_alloc(icon)) }; + let callbacks = NonNull::from(Box::leak(Box::new(callbacks))); + + let icon_animation = Self { + raw, + callbacks, + _parent_lifetime: PhantomData, + }; + + pub unsafe extern "C" fn dispatch_update( + instance: *mut SysIconAnimation, + context: *mut c_void, + ) { + // SAFETY: `icon_anination` is guaranteed to be a valid pointer + let instance = unsafe { IconAnimationView::from_raw(instance) }; + + let context: *mut C = context.cast(); + // SAFETY: `context` is stored in a `Box` which is a member of `ViewPort` + // and the callback is accessed exclusively by this function + unsafe { &mut *context }.on_update(instance); + } + + if !ptr::eq( + C::on_update as *const c_void, + <() as IconAnimationCallbacks>::on_update as *const c_void, + ) { + let context = icon_animation.callbacks.as_ptr().cast(); + let raw = raw.as_ptr(); + // SAFETY: `raw` is valid + // and `callbacks` is valid and lives with this struct + unsafe { + sys::icon_animation_set_update_callback(raw, Some(dispatch_update::), context) + } + } + + icon_animation + } + + // TODO: callbacks + + pub fn get_width(&self) -> u8 { + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is always valid + unsafe { sys::icon_animation_get_width(raw) } + } + + pub fn get_height(&self) -> u8 { + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is always valid + unsafe { sys::icon_animation_get_height(raw) } + } + + pub fn get_dimensions(&self) -> (u8, u8) { + (self.get_width(), self.get_height()) + } + + pub fn start(&mut self) { + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is always valid + unsafe { sys::icon_animation_start(raw) } + } + + pub fn stop(&mut self) { + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is always valid + unsafe { sys::icon_animation_stop(raw) } + } + + pub fn is_last_frame(&self) -> bool { + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is always valid + unsafe { sys::icon_animation_is_last_frame(raw) } + } +} + +impl Drop for IconAnimation<'_, C> { + fn drop(&mut self) { + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is a valid pointer + // which should have been created via `icon_animation_alloc` + unsafe { sys::icon_animation_free(raw) } + } +} + +pub struct IconAnimationView<'a> { + raw: NonNull, + _lifetime: PhantomData<&'a ()>, +} + +impl IconAnimationView<'_> { + /// Construct a `CanvasView` from a raw pointer. + /// + /// # Safety + /// + /// `raw` should be a valid non-null pointer to [`SysCanvas`] + /// and the lifetime should be outlived by `raw` validity scope. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// use flipperzero_gui::icon_animation::IconAnimationView; + /// + /// let ptr = todo!(); + /// let icon_anumation = unsafe { IconAnimationView::from_raw(ptr) }; + /// ``` + pub unsafe fn from_raw(raw: *mut SysIconAnimation) -> Self { + Self { + // SAFETY: caller should provide a valid pointer + raw: unsafe { NonNull::new_unchecked(raw) }, + _lifetime: PhantomData, + } + } + + // TODO: callbacks + + pub fn get_width(&self) -> u8 { + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is always valid + unsafe { sys::icon_animation_get_width(raw) } + } + + pub fn get_height(&self) -> u8 { + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is always valid + unsafe { sys::icon_animation_get_height(raw) } + } + + pub fn get_dimensions(&self) -> (u8, u8) { + (self.get_width(), self.get_height()) + } + + // TODO: decide if these methods should be available in view + // pub fn start(&mut self) { + // let raw = self.raw.as_ptr(); + // // SAFETY: `raw` is always valid + // unsafe { sys::icon_animation_start(raw) } + // } + // + // pub fn stop(&mut self) { + // let raw = self.raw.as_ptr(); + // // SAFETY: `raw` is always valid + // unsafe { sys::icon_animation_stop(raw) } + // } + + pub fn is_last_frame(&self) -> bool { + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is always valid + unsafe { sys::icon_animation_is_last_frame(raw) } + } +} + +pub trait IconAnimationCallbacks { + fn on_update(&mut self, _icon_animation: IconAnimationView) {} +} + +impl IconAnimationCallbacks for () {} diff --git a/crates/gui/src/input.rs b/crates/gui/src/input.rs index b30fa8f5..58684b72 100644 --- a/crates/gui/src/input.rs +++ b/crates/gui/src/input.rs @@ -1,3 +1,4 @@ +use core::ffi::CStr; // FIXME: in flipperzero-firmware, this is a separate service use flipperzero_sys::{ self as sys, InputEvent as SysInputEvent, InputKey as SysInputKey, InputType as SysInputType, @@ -59,6 +60,15 @@ pub enum InputType { Repeat, } +impl InputType { + pub fn name(self) -> &'static CStr { + let this = SysInputType::from(self); + // SAFETY: `this` is always a valid enum value + // and the returned string is a static string + unsafe { CStr::from_ptr(sys::input_get_type_name(this)) } + } +} + #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] pub enum FromSysInputTypeError { Max, @@ -120,6 +130,15 @@ pub enum InputKey { Back, } +impl InputKey { + pub fn name(self) -> &'static CStr { + let this = SysInputKey::from(self); + // SAFETY: `this` is always a valid enum value + // and the returned string is a static string + unsafe { CStr::from_ptr(sys::input_get_key_name(this)) } + } +} + #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] pub enum FromSysInputKeyError { Max, diff --git a/crates/gui/src/lib.rs b/crates/gui/src/lib.rs index 582d0302..60692f0a 100644 --- a/crates/gui/src/lib.rs +++ b/crates/gui/src/lib.rs @@ -1,12 +1,16 @@ //! Safe wrappers for Flipper GUI APIs. #![no_std] -#![feature(thin_box)] +// requred for effectve calculation of some values +#![feature(int_roundings)] extern crate alloc; pub mod canvas; pub mod gui; +pub mod icon; +pub mod icon_animation; pub mod input; pub mod view; pub mod view_port; +pub mod xbm; diff --git a/crates/gui/src/view_port.rs b/crates/gui/src/view_port.rs index 601acf78..9f6d4add 100644 --- a/crates/gui/src/view_port.rs +++ b/crates/gui/src/view_port.rs @@ -1,11 +1,15 @@ //! ViewPort APIs -use crate::canvas::CanvasView; -use crate::input::InputEvent; +use crate::{canvas::CanvasView, input::InputEvent}; use alloc::boxed::Box; -use core::{ffi::c_void, num::NonZeroU8, ptr::NonNull}; +use core::{ + ffi::c_void, + num::NonZeroU8, + ptr::{self, NonNull}, +}; use flipperzero_sys::{ - self as sys, Canvas, ViewPort as SysViewPort, ViewPortOrientation as SysViewPortOrientation, + self as sys, Canvas as SysCanvas, ViewPort as SysViewPort, + ViewPortOrientation as SysViewPortOrientation, }; /// System ViewPort. @@ -24,7 +28,7 @@ impl ViewPort { /// ``` /// use flipperzero_gui::view_port::ViewPort; /// - /// let view_port = ViewPort::new(todo!()); + /// let view_port = ViewPort::new(()); /// ``` pub fn new(callbacks: C) -> Self { // SAFETY: allocation either succeeds producing the valid pointer @@ -35,16 +39,16 @@ impl ViewPort { let view_port = Self { raw, callbacks }; pub unsafe extern "C" fn dispatch_draw( - canvas: *mut sys::Canvas, + canvas: *mut SysCanvas, context: *mut c_void, ) { // SAFETY: `canvas` is guaranteed to be a valid pointer - let mut canvas = unsafe { CanvasView::from_raw(NonNull::new_unchecked(canvas)) }; + let canvas = unsafe { CanvasView::from_raw(canvas) }; let context: *mut C = context.cast(); // SAFETY: `context` is stored in a `Box` which is a member of `ViewPort` // and the callback is accessed exclusively by this function - unsafe { &mut *context }.on_draw(&mut canvas); + unsafe { &mut *context }.on_draw(canvas); } pub unsafe extern "C" fn dispatch_input( input_event: *mut sys::InputEvent, @@ -60,14 +64,30 @@ impl ViewPort { unsafe { &mut *context }.on_input(input_event); } - // SAFETY: `callbacks` is a valid pointer and - let context = view_port.callbacks.as_ptr().cast(); - - let raw = raw.as_ptr(); - unsafe { - sys::view_port_draw_callback_set(raw, Some(dispatch_draw::), context); - sys::view_port_input_callback_set(raw, Some(dispatch_input::), context); - }; + if !ptr::eq( + C::on_draw as *const c_void, + <() as ViewPortCallbacks>::on_draw as *const c_void, + ) { + let context = view_port.callbacks.as_ptr().cast(); + let raw = raw.as_ptr(); + // SAFETY: `raw` is valid + // and `callbacks` is valid and lives with this struct + unsafe { + sys::view_port_draw_callback_set(raw, Some(dispatch_draw::), context); + } + } + if !ptr::eq( + C::on_input as *const c_void, + <() as ViewPortCallbacks>::on_input as *const c_void, + ) { + let context = view_port.callbacks.as_ptr().cast(); + let raw = raw.as_ptr(); + // SAFETY: `raw` is valid + // and `callbacks` is valid and lives with this struct + unsafe { + sys::view_port_input_callback_set(raw, Some(dispatch_input::), context); + }; + } view_port } @@ -88,7 +108,7 @@ impl ViewPort { /// use std::num::NonZeroU8; /// use flipperzero_gui::view_port::ViewPort; /// - /// let mut view_port = ViewPort::new(todo!()); + /// let mut view_port = ViewPort::new(()); /// view_port.set_width(NonZeroU8::new(128u8)); /// ``` /// @@ -97,7 +117,7 @@ impl ViewPort { /// ``` /// use flipperzero_gui::view_port::ViewPort; /// - /// let mut view_port = ViewPort::new(todo!()); + /// let mut view_port = ViewPort::new(()); /// view_port.set_width(None); /// ``` pub fn set_width(&mut self, width: Option) { @@ -117,7 +137,7 @@ impl ViewPort { /// ``` /// use flipperzero_gui::view_port::ViewPort; /// - /// let view_port = ViewPort::new(todo!()); + /// let view_port = ViewPort::new(()); /// let width = view_port.get_width(); /// ``` pub fn get_width(&self) -> Option { @@ -137,7 +157,7 @@ impl ViewPort { /// use std::num::NonZeroU8; /// use flipperzero_gui::view_port::ViewPort; /// - /// let mut view_port = ViewPort::new(todo!()); + /// let mut view_port = ViewPort::new(()); /// view_port.set_height(NonZeroU8::new(128u8)); /// ``` /// @@ -146,7 +166,7 @@ impl ViewPort { /// ``` /// use flipperzero_gui::view_port::ViewPort; /// - /// let mut view_port = ViewPort::new(todo!()); + /// let mut view_port = ViewPort::new(()); /// view_port.set_height(None); /// ``` pub fn set_height(&mut self, height: Option) { @@ -166,7 +186,7 @@ impl ViewPort { /// ``` /// use flipperzero_gui::view_port::ViewPort; /// - /// let view_port = ViewPort::new(todo!()); + /// let view_port = ViewPort::new(()); /// let height = view_port.get_height(); /// ``` pub fn get_height(&self) -> Option { @@ -186,7 +206,7 @@ impl ViewPort { /// use std::num::NonZeroU8; /// use flipperzero_gui::view_port::ViewPort; /// - /// let mut view_port = ViewPort::new(todo!()); + /// let mut view_port = ViewPort::new(()); /// view_port.set_dimensions(Some((NonZeroU8::new(120).unwrap(), NonZeroU8::new(80).unwrap()))); /// ``` /// @@ -195,7 +215,7 @@ impl ViewPort { /// ``` /// use flipperzero_gui::view_port::ViewPort; /// - /// let mut view_port = ViewPort::new(todo!()); + /// let mut view_port = ViewPort::new(()); /// view_port.set_dimensions(None); /// ``` pub fn set_dimensions(&mut self, dimensions: Option<(NonZeroU8, NonZeroU8)>) { @@ -220,7 +240,7 @@ impl ViewPort { /// ``` /// use flipperzero_gui::view_port::ViewPort; /// - /// let view_port = ViewPort::new(todo!()); + /// let view_port = ViewPort::new(()); /// let (width, height) = view_port.get_dimensions(); /// ``` pub fn get_dimensions(&self) -> (Option, Option) { @@ -235,7 +255,7 @@ impl ViewPort { /// /// ``` /// use flipperzero_gui::view_port::{ViewPort, ViewPortOrientation}; - /// let mut view_port = ViewPort::new(todo!()); + /// let mut view_port = ViewPort::new(()); /// view_port.set_orientation(ViewPortOrientation::Vertical); /// ``` pub fn set_orientation(&mut self, orientation: ViewPortOrientation) { @@ -257,7 +277,7 @@ impl ViewPort { /// use std::num::NonZeroU8; /// use flipperzero_gui::view_port::{ViewPort, ViewPortOrientation}; /// - /// let mut view_port = ViewPort::new(todo!()); + /// let mut view_port = ViewPort::new(()); /// let orientation = view_port.get_orientation(); /// ``` pub fn get_orientation(&self) -> ViewPortOrientation { @@ -279,7 +299,7 @@ impl ViewPort { /// ``` /// use flipperzero_gui::view_port::ViewPort; /// - /// let mut view_port = ViewPort::new(todo!()); + /// let mut view_port = ViewPort::new(()); /// view_port.set_enabled(false); /// ``` pub fn set_enabled(&mut self, enabled: bool) { @@ -298,7 +318,7 @@ impl ViewPort { /// ``` /// use flipperzero_gui::view_port::ViewPort; /// - /// let mut view_port = ViewPort::new(todo!()); + /// let mut view_port = ViewPort::new(()); /// let enabled = view_port.is_enabled(); /// ``` pub fn is_enabled(&self) -> bool { @@ -379,6 +399,8 @@ impl From for SysViewPortOrientation { } pub trait ViewPortCallbacks { - fn on_draw(&mut self, _canvas: &mut CanvasView) {} + fn on_draw(&mut self, _canvas: CanvasView) {} fn on_input(&mut self, _event: InputEvent) {} } + +impl ViewPortCallbacks for () {} diff --git a/crates/gui/src/xbm.rs b/crates/gui/src/xbm.rs new file mode 100644 index 00000000..c0bf6c2c --- /dev/null +++ b/crates/gui/src/xbm.rs @@ -0,0 +1,155 @@ +//! User-friendly wrappers of XDM images. + +use core::slice; + +pub struct XbmImage<'a> { + data: &'a [u8], + width: u8, + height: u8, +} + +impl<'a> XbmImage<'a> { + pub fn new(data: &'a [u8], width: u8, height: u8) -> Self { + assert!( + (width * height).div_ceil(8) as usize == data.len(), + "dimensions should correspond to data size" + ); + + Self { + data, + width, + height, + } + } + + pub unsafe fn from_raw(data: *const u8, width: u8, height: u8) -> Self { + // each byte stores 8 dot-bits, + // if the value is not divisible by 8 then the last byte is used partially + let size = (width * height).div_ceil(8) as usize; + + // SAFETY: the size is exactly calculated based on width and height + // and caller upholds the `data` validity invariant + let data = unsafe { slice::from_raw_parts(data, size) }; + + Self { + data, + width, + height, + } + } + + #[inline] + const fn offset(&self, x: u8, y: u8) -> Option { + if x >= self.width || y >= self.height { + None + } else { + Some(x * self.width + y) + } + } + + #[inline] + const fn offsets(&self, x: u8, y: u8) -> Option<(u8, u8)> { + if let Some(offset) = self.offset(x, y) { + Some((offset / 8, offset % 8)) + } else { + None + } + } + + pub const fn get(&self, (x, y): (u8, u8)) -> Option { + if let Some((byte, shift)) = self.offsets(x, y) { + Some((self.data[byte as usize] >> (7 - shift)) & 0b1 != 0) + } else { + None + } + } +} + +pub struct XbmImageMut<'a> { + data: &'a mut [u8], + width: u8, + height: u8, +} + +impl<'a> XbmImageMut<'a> { + pub fn new(data: &'a mut [u8], width: u8, height: u8) -> Self { + assert!( + (width * height).div_ceil(8) as usize == data.len(), + "dimensions should correspond to data size" + ); + + Self { + data, + width, + height, + } + } + + pub unsafe fn from_raw(data: *mut u8, width: u8, height: u8) -> Self { + // each byte stores 8 dot-bits, + // if the value is not divisible by 8 then the last byte is used partially + let size = (width * height).div_ceil(8) as usize; + + // SAFETY: the size is exactly calculated based on width and height + // and caller upholds the `data` validity invariant + let data = unsafe { slice::from_raw_parts_mut(data, size) }; + + Self { + data, + width, + height, + } + } + + #[inline] + const fn offset(&self, x: u8, y: u8) -> Option { + if x >= self.width || y >= self.height { + None + } else { + Some(x * self.width + y) + } + } + + #[inline] + const fn offsets(&self, x: u8, y: u8) -> Option<(u8, u8)> { + if let Some(offset) = self.offset(x, y) { + Some((offset / 8, offset % 8)) + } else { + None + } + } + + pub const fn get(&self, (x, y): (u8, u8)) -> Option { + if let Some((byte, shift)) = self.offsets(x, y) { + Some((self.data[byte as usize] >> (7 - shift)) & 0b1 != 0) + } else { + None + } + } + + pub fn set(&mut self, coordinates: (u8, u8), value: bool) -> Option<()> { + if value { + self.set_1(coordinates) + } else { + self.set_0(coordinates) + } + } + + pub fn set_1(&mut self, (x, y): (u8, u8)) -> Option<()> { + let (byte, shift) = self.offsets(x, y)?; + self.data[byte as usize] |= 1 << (7 - shift); + Some(()) + } + + pub fn set_0(&mut self, (x, y): (u8, u8)) -> Option<()> { + let (byte, shift) = self.offsets(x, y)?; + self.data[byte as usize] &= !(1 << (7 - shift)); + Some(()) + } + + pub fn xor(&mut self, (x, y): (u8, u8)) -> Option<()> { + let (byte, shift) = self.offsets(x, y)?; + self.data[byte as usize] ^= 1 << (7 - shift); + Some(()) + } +} diff --git a/crates/rust-toolchain.toml b/crates/rust-toolchain.toml index 73254efb..530be42e 100644 --- a/crates/rust-toolchain.toml +++ b/crates/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] -channel = "nightly-2022-11-03" +channel = "nightly-2022-12-26" targets = [ "thumbv7em-none-eabihf" ] diff --git a/examples/dialog/rust-toolchain.toml b/examples/dialog/rust-toolchain.toml index 73254efb..530be42e 100644 --- a/examples/dialog/rust-toolchain.toml +++ b/examples/dialog/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] -channel = "nightly-2022-11-03" +channel = "nightly-2022-12-26" targets = [ "thumbv7em-none-eabihf" ] diff --git a/examples/gui/rust-toolchain.toml b/examples/gui/rust-toolchain.toml index 73254efb..530be42e 100644 --- a/examples/gui/rust-toolchain.toml +++ b/examples/gui/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] -channel = "nightly-2022-11-03" +channel = "nightly-2022-12-26" targets = [ "thumbv7em-none-eabihf" ] diff --git a/examples/gui/src/main.rs b/examples/gui/src/main.rs index ae374313..a316f34b 100644 --- a/examples/gui/src/main.rs +++ b/examples/gui/src/main.rs @@ -37,7 +37,8 @@ fn main(_args: *mut u8) -> i32 { } impl ViewPortCallbacks for State<'_> { - fn on_draw(&mut self, canvas: &mut CanvasView) { + fn on_draw(&mut self, mut canvas: CanvasView) { + println!("Draw callback"); canvas.draw_str(10, 31, self.text); let bottom_text = CString::new(alloc::format!("Value = {}", self.counter).as_bytes()) .expect("should be a valid string"); diff --git a/examples/hello-rust/rust-toolchain.toml b/examples/hello-rust/rust-toolchain.toml index 73254efb..530be42e 100644 --- a/examples/hello-rust/rust-toolchain.toml +++ b/examples/hello-rust/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] -channel = "nightly-2022-11-03" +channel = "nightly-2022-12-26" targets = [ "thumbv7em-none-eabihf" ] From fd1615916d8f46d0ec795feaa83391574c59e777 Mon Sep 17 00:00:00 2001 From: Petr Portnov Date: Thu, 5 Jan 2023 03:01:21 +0300 Subject: [PATCH 11/49] feat: add more drawing methods to `canvas` Most importantly, this adds support for drawing XBMs --- crates/gui/src/canvas.rs | 86 +++++++++++++++++++++++++++----- crates/gui/src/icon.rs | 4 +- crates/gui/src/icon_animation.rs | 4 +- crates/gui/src/xbm.rs | 42 +++++++++++++++- crates/sys/Cargo.toml | 1 + crates/sys/src/lib.rs | 10 +--- examples/dialog/Cargo.lock | 49 ++++++++++++++++++ examples/gui/Cargo.lock | 49 ++++++++++++++++++ examples/gui/src/main.rs | 36 ++++++++++++- examples/hello-rust/Cargo.lock | 49 ++++++++++++++++++ 10 files changed, 302 insertions(+), 28 deletions(-) diff --git a/crates/gui/src/canvas.rs b/crates/gui/src/canvas.rs index a1f8e748..b98dbffe 100644 --- a/crates/gui/src/canvas.rs +++ b/crates/gui/src/canvas.rs @@ -1,5 +1,9 @@ //! ViewPort APIs +use crate::icon::Icon; +use crate::icon_animation::{IconAnimation, IconAnimationCallbacks}; +use crate::xbm::XbmImage; +use core::ffi::c_char; use core::{ffi::CStr, marker::PhantomData, num::NonZeroU8, ptr::NonNull}; use flipperzero::furi::canvas::Align; use flipperzero_sys::{ @@ -94,7 +98,7 @@ impl CanvasView<'_> { pub fn set_font_direction(&mut self, font_direction: CanvasDirection) { let raw = self.raw.as_ptr(); let font_direction = font_direction.into(); - // SAFETY: `self.canvas` is always valid + // SAFETY: `raw` is always valid // and `font_direction` is guaranteed to be a valid value by `From` implementation unsafe { sys::canvas_set_font_direction(raw, font_direction) }; } @@ -108,17 +112,17 @@ impl CanvasView<'_> { pub fn set_font(&mut self, font: Font) { let raw = self.raw.as_ptr(); let font = font.into(); - // SAFETY: `self.canvas` is always valid + // SAFETY: `raw` is always valid // and `font` is guaranteed to be a valid value by `From` implementation unsafe { sys::canvas_set_font(raw, font) }; } - pub fn draw_str(&mut self, x: u8, y: u8, str: impl AsRef) { + pub fn draw_str(&mut self, x: u8, y: u8, string: impl AsRef) { let raw = self.raw.as_ptr(); - let str = str.as_ref().as_ptr(); - // SAFETY: `self.canvas` is always valid - // and `text` is guaranteed to be a valid pointer since it was created from `CStr` - unsafe { sys::canvas_draw_str(raw, x, y, str) }; + let string = string.as_ref().as_ptr(); + // SAFETY: `raw` is always valid + // and `string` is guaranteed to be a valid pointer since it was created from `CStr` + unsafe { sys::canvas_draw_str(raw, x, y, string) }; } pub fn draw_str_aligned( @@ -133,19 +137,75 @@ impl CanvasView<'_> { let horizontal = horizontal.into(); let vertical = vertical.into(); let str = str.as_ref().as_ptr(); - // SAFETY: `self.canvas` is always valid, + // SAFETY: `raw` is always valid, // `horixontal` and `vertival` are guaranteed to be valid by `From` implementation // and `text` is guaranteed to be a valid pointer since it was created from `CStr` unsafe { sys::canvas_draw_str_aligned(raw, x, y, horizontal, vertical, str) }; } + // note: for some reason, this mutates internal state + pub fn string_width(&mut self, string: impl AsRef) -> u16 { + let raw = self.raw.as_ptr(); + let string = string.as_ref().as_ptr(); + // SAFETY: `raw` is always valid + // and `string` is guaranteed to be a valid pointer since it was created from `CStr` + unsafe { sys::canvas_string_width(raw, string) } + } + + // note: for some reason, this mutates internal state + pub fn glyph_width(&mut self, glyph: c_char) -> u8 { + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is always valid + unsafe { sys::canvas_glyph_width(raw, glyph) } + } + + // TODO `canvas_draw_bitmap` compressed bitmap support + + // TODO: do we need range checks? + pub fn draw_icon_animation<'a, 'b: 'a>( + &'a mut self, + x: u8, + y: u8, + icon_animation: &'b IconAnimation<'_, impl IconAnimationCallbacks>, + ) { + let raw = self.raw.as_ptr(); + let icon_animation = icon_animation.as_raw(); + // SAFETY: `raw` is always valid + // and `icon_animation` is always valid and outlives this canvas view + unsafe { sys::canvas_draw_icon_animation(raw, x, y, icon_animation) } + } + + // TODO: do we need range checks? + pub fn draw_icon<'a, 'b: 'a>(&'a mut self, x: u8, y: u8, animation: &'b Icon) { + let raw = self.raw.as_ptr(); + let icon = animation.as_raw(); + // SAFETY: `raw` is always valid + // and `icon` is always valid and outlives this canvas view + unsafe { sys::canvas_draw_icon(raw, x, y, icon) } + } + + // TODO: do we need other range checks? + // what is the best return type? + pub fn draw_xbm(&mut self, x: u8, y: u8, xbm: &XbmImage) -> Option<()> { + let raw = self.raw.as_ptr(); + let width = xbm.width(); + let height = xbm.height(); + + // ensure that the image is not too big + let _ = x.checked_add(width)?; + let _ = y.checked_add(height)?; + + let data = xbm.data().as_ptr(); + + // SAFETY: `raw` is always valid + // and `data` is always valid and does not have to outlive the view + // as it is copied + unsafe { sys::canvas_draw_xbm(raw, x, y, width, height, data) }; + Some(()) + } + // TODO: - // - `canvas_string_width` this API looks quite strange yet - // - `canvas_flyph_width` this API looks quite strange yet - // - `canvas_draw_bitmap` bitmap constraints - // - `canvas_draw_icon_animation` animation lifetimes // - `canvas_draw_icon` icon lifetimes - // - `canvas_draw_xbm` bitmap constraints // TODO: decide if we want to pack x-y pairs into tuples diff --git a/crates/gui/src/icon.rs b/crates/gui/src/icon.rs index f07944be..1dc3fe5a 100644 --- a/crates/gui/src/icon.rs +++ b/crates/gui/src/icon.rs @@ -1,5 +1,5 @@ use crate::xbm::XbmImage; -use core::{ptr::NonNull, slice}; +use core::ptr::NonNull; use flipperzero_sys::{self as sys, Icon as SysIcon}; pub struct Icon { @@ -53,6 +53,6 @@ impl Icon { let raw = self.raw.as_ptr(); // SAFETY: `raw` is always valid // and `width` and `height` are always in sync with data - unsafe { XbmImage::from_raw(sys::icon_get_data(raw), width, height) } + unsafe { XbmImage::from_raw(height, width, sys::icon_get_data(raw)) } } } diff --git a/crates/gui/src/icon_animation.rs b/crates/gui/src/icon_animation.rs index 55457472..e472bf29 100644 --- a/crates/gui/src/icon_animation.rs +++ b/crates/gui/src/icon_animation.rs @@ -57,7 +57,9 @@ impl<'a, C: IconAnimationCallbacks> IconAnimation<'a, C> { icon_animation } - // TODO: callbacks + pub fn as_raw(&self) -> *mut SysIconAnimation { + self.raw.as_ptr() + } pub fn get_width(&self) -> u8 { let raw = self.raw.as_ptr(); diff --git a/crates/gui/src/xbm.rs b/crates/gui/src/xbm.rs index c0bf6c2c..8ab25950 100644 --- a/crates/gui/src/xbm.rs +++ b/crates/gui/src/xbm.rs @@ -9,7 +9,7 @@ pub struct XbmImage<'a> { } impl<'a> XbmImage<'a> { - pub fn new(data: &'a [u8], width: u8, height: u8) -> Self { + pub const fn new(height: u8, width: u8, data: &'a [u8]) -> Self { assert!( (width * height).div_ceil(8) as usize == data.len(), "dimensions should correspond to data size" @@ -22,7 +22,19 @@ impl<'a> XbmImage<'a> { } } - pub unsafe fn from_raw(data: *const u8, width: u8, height: u8) -> Self { + pub const fn width(&self) -> u8 { + self.width + } + + pub const fn height(&self) -> u8 { + self.height + } + + pub const fn data(&self) -> &[u8] { + self.data + } + + pub unsafe fn from_raw(height: u8, width: u8, data: *const u8) -> Self { // each byte stores 8 dot-bits, // if the value is not divisible by 8 then the last byte is used partially let size = (width * height).div_ceil(8) as usize; @@ -101,6 +113,22 @@ impl<'a> XbmImageMut<'a> { } } + pub const fn width(&self) -> u8 { + self.width + } + + pub const fn height(&self) -> u8 { + self.height + } + + pub const fn data(&self) -> &[u8] { + self.data + } + + pub const fn data_mut(&self) -> &[u8] { + self.data + } + #[inline] const fn offset(&self, x: u8, y: u8) -> Option { if x >= self.width || y >= self.height { @@ -153,3 +181,13 @@ impl<'a> XbmImageMut<'a> { Some(()) } } + +impl<'a> From> for XbmImage<'a> { + fn from(value: XbmImageMut<'a>) -> Self { + Self { + data: value.data, + width: value.width, + height: value.height, + } + } +} diff --git a/crates/sys/Cargo.toml b/crates/sys/Cargo.toml index 4a1bfa0d..055334d8 100644 --- a/crates/sys/Cargo.toml +++ b/crates/sys/Cargo.toml @@ -22,3 +22,4 @@ bench = false test = false [dependencies] +real_c_string = "1.0.0" diff --git a/crates/sys/src/lib.rs b/crates/sys/src/lib.rs index 88c6c8cd..79d7ee2f 100644 --- a/crates/sys/src/lib.rs +++ b/crates/sys/src/lib.rs @@ -13,14 +13,8 @@ pub mod furi; #[allow(non_snake_case)] mod bindings; -/// Create a static C string. -/// Will automatically add a NUL terminator. -#[macro_export] -macro_rules! c_string { - ($str:literal $(,)?) => {{ - concat!($str, "\0").as_ptr() as *const core::ffi::c_char - }}; -} +// Re-export macro for safe compile-time c-string creation +pub use real_c_string::real_c_string as c_string; /// Crash the system. #[macro_export] diff --git a/examples/dialog/Cargo.lock b/examples/dialog/Cargo.lock index 11b056fd..398969af 100644 --- a/examples/dialog/Cargo.lock +++ b/examples/dialog/Cargo.lock @@ -36,3 +36,52 @@ dependencies = [ [[package]] name = "flipperzero-sys" version = "0.6.0-alpha" +dependencies = [ + "real_c_string", +] + +[[package]] +name = "proc-macro2" +version = "1.0.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "real_c_string" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a58f0db826345d4f9ce00f375c6132e90e96aba9e38b840572b5b70728ba6ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" diff --git a/examples/gui/Cargo.lock b/examples/gui/Cargo.lock index 36e92dc5..ea4aa127 100644 --- a/examples/gui/Cargo.lock +++ b/examples/gui/Cargo.lock @@ -34,6 +34,9 @@ dependencies = [ [[package]] name = "flipperzero-sys" version = "0.6.0-alpha" +dependencies = [ + "real_c_string", +] [[package]] name = "gui" @@ -45,3 +48,49 @@ dependencies = [ "flipperzero-rt", "flipperzero-sys", ] + +[[package]] +name = "proc-macro2" +version = "1.0.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "real_c_string" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a58f0db826345d4f9ce00f375c6132e90e96aba9e38b840572b5b70728ba6ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" diff --git a/examples/gui/src/main.rs b/examples/gui/src/main.rs index a316f34b..956b7bec 100644 --- a/examples/gui/src/main.rs +++ b/examples/gui/src/main.rs @@ -15,6 +15,7 @@ use alloc::ffi::CString; use core::{ffi::CStr, time::Duration}; use flipperzero::{furi::message_queue::MessageQueue, println}; +use flipperzero_gui::xbm::XbmImage; use flipperzero_gui::{ canvas::CanvasView, gui::{Gui, GuiLayer}, @@ -27,6 +28,36 @@ use flipperzero_sys::furi::Status; manifest!(name = "Rust GUI example"); entry!(main); +const PLUS_IMAGE: XbmImage = XbmImage::new( + 8, + 8, + &[ + 0b00_11_11_00, + 0b00_11_11_00, + 0b11_11_11_11, + 0b11_11_11_11, + 0b11_11_11_11, + 0b11_11_11_11, + 0b00_11_11_00, + 0b10_11_11_01, + ], +); + +const RS_IMAGE: XbmImage = XbmImage::new( + 8, + 8, + &[ + 0b11100000u8.reverse_bits(), + 0b10010000u8.reverse_bits(), + 0b11100000u8.reverse_bits(), + 0b10100110u8.reverse_bits(), + 0b10011000u8.reverse_bits(), + 0b00000110u8.reverse_bits(), + 0b00000001u8.reverse_bits(), + 0b00000110u8.reverse_bits(), + ], +); + fn main(_args: *mut u8) -> i32 { let exit_event_queue = MessageQueue::new(32); @@ -38,11 +69,12 @@ fn main(_args: *mut u8) -> i32 { impl ViewPortCallbacks for State<'_> { fn on_draw(&mut self, mut canvas: CanvasView) { - println!("Draw callback"); + canvas.draw_xbm(2, 2, &PLUS_IMAGE); canvas.draw_str(10, 31, self.text); let bottom_text = CString::new(alloc::format!("Value = {}", self.counter).as_bytes()) .expect("should be a valid string"); - canvas.draw_str(5, 62, bottom_text); + canvas.draw_str(5, 40, bottom_text); + canvas.draw_xbm(100, 50, &RS_IMAGE); } fn on_input(&mut self, event: InputEvent) { diff --git a/examples/hello-rust/Cargo.lock b/examples/hello-rust/Cargo.lock index 376758dd..68aedfe7 100644 --- a/examples/hello-rust/Cargo.lock +++ b/examples/hello-rust/Cargo.lock @@ -19,6 +19,9 @@ dependencies = [ [[package]] name = "flipperzero-sys" version = "0.6.0-alpha" +dependencies = [ + "real_c_string", +] [[package]] name = "hello-rust" @@ -28,3 +31,49 @@ dependencies = [ "flipperzero-rt", "flipperzero-sys", ] + +[[package]] +name = "proc-macro2" +version = "1.0.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "real_c_string" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a58f0db826345d4f9ce00f375c6132e90e96aba9e38b840572b5b70728ba6ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" From 49ea98c77a0a1a4b7c49a48ca1e4cb6b2894ee3f Mon Sep 17 00:00:00 2001 From: Petr Portnov Date: Thu, 5 Jan 2023 04:25:10 +0300 Subject: [PATCH 12/49] feat: add `xbm!` macro --- crates/gui/src/xbm.rs | 45 ++++++++++++++++++++++++---------- examples/gui/src/ferris_xbm.rs | 24 ++++++++++++++++++ examples/gui/src/main.rs | 11 ++++++--- 3 files changed, 63 insertions(+), 17 deletions(-) create mode 100644 examples/gui/src/ferris_xbm.rs diff --git a/crates/gui/src/xbm.rs b/crates/gui/src/xbm.rs index 8ab25950..f7b9a386 100644 --- a/crates/gui/src/xbm.rs +++ b/crates/gui/src/xbm.rs @@ -9,10 +9,11 @@ pub struct XbmImage<'a> { } impl<'a> XbmImage<'a> { - pub const fn new(height: u8, width: u8, data: &'a [u8]) -> Self { + pub const fn new(width: u8, height: u8, data: &'a [u8]) -> Self { + let bytes = xds_bytes(width, height); assert!( - (width * height).div_ceil(8) as usize == data.len(), - "dimensions should correspond to data size" + bytes == data.len(), + "bit-dimensions don't match bit-size of data" ); Self { @@ -35,13 +36,11 @@ impl<'a> XbmImage<'a> { } pub unsafe fn from_raw(height: u8, width: u8, data: *const u8) -> Self { - // each byte stores 8 dot-bits, - // if the value is not divisible by 8 then the last byte is used partially - let size = (width * height).div_ceil(8) as usize; + let bytes = xds_bytes(width, height); // SAFETY: the size is exactly calculated based on width and height // and caller upholds the `data` validity invariant - let data = unsafe { slice::from_raw_parts(data, size) }; + let data = unsafe { slice::from_raw_parts(data, bytes) }; Self { data, @@ -83,11 +82,16 @@ pub struct XbmImageMut<'a> { height: u8, } +const fn xds_bytes(width: u8, height: u8) -> usize { + (width as usize * height as usize).div_ceil(8) +} + impl<'a> XbmImageMut<'a> { pub fn new(data: &'a mut [u8], width: u8, height: u8) -> Self { + let bytes = xds_bytes(width, height); assert!( - (width * height).div_ceil(8) as usize == data.len(), - "dimensions should correspond to data size" + bytes == data.len(), + "bit-dimensions don't match bit-size of data" ); Self { @@ -98,13 +102,11 @@ impl<'a> XbmImageMut<'a> { } pub unsafe fn from_raw(data: *mut u8, width: u8, height: u8) -> Self { - // each byte stores 8 dot-bits, - // if the value is not divisible by 8 then the last byte is used partially - let size = (width * height).div_ceil(8) as usize; + let bytes = xds_bytes(width, height); // SAFETY: the size is exactly calculated based on width and height // and caller upholds the `data` validity invariant - let data = unsafe { slice::from_raw_parts_mut(data, size) }; + let data = unsafe { slice::from_raw_parts_mut(data, bytes) }; Self { data, @@ -191,3 +193,20 @@ impl<'a> From> for XbmImage<'a> { } } } + +#[macro_export] +macro_rules! xbm { + ( + #define $_width_ident:ident $width:literal + #define $_height_ident:ident $height:literal + $( + #define $_hotspot_x_ident:ident $_hotspot_x:literal + #define $_hotspot_y_ident:ident $_hotspot_y:literal + )? + static char $_bits_ident:ident[] = { + $($byte:literal),* $(,)? + }; + ) => {{ + $crate::xbm::XbmImage::new($width, $height, &[$($byte,)*]) + }}; +} diff --git a/examples/gui/src/ferris_xbm.rs b/examples/gui/src/ferris_xbm.rs new file mode 100644 index 00000000..50862d08 --- /dev/null +++ b/examples/gui/src/ferris_xbm.rs @@ -0,0 +1,24 @@ +use flipperzero_gui::xbm::XbmImage; + +pub const IMAGE: XbmImage = flipperzero_gui::xbm! { + #define ferris_width 48 + #define ferris_height 32 + static char ferris_bits[] = { + 0xFF, 0xFF, 0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0xC0, 0xFF, 0xFF, + 0xFF, 0x3F, 0x00, 0x40, 0xFC, 0xFF, 0xFF, 0x3F, 0x00, 0x00, 0xFE, 0xFF, + 0xFF, 0x07, 0x00, 0x00, 0xD0, 0xFF, 0xFF, 0x03, 0x00, 0x00, 0xE0, 0xFF, + 0xFF, 0x02, 0x00, 0x00, 0xC0, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x7F, 0x00, 0x00, 0x00, 0x10, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFE, + 0x0F, 0x00, 0x00, 0x00, 0x80, 0xF8, 0x0F, 0x00, 0x00, 0x00, 0x00, 0xF9, + 0x5F, 0x44, 0x41, 0x00, 0x10, 0xF8, 0x0F, 0x00, 0x00, 0x08, 0x8A, 0xFC, + 0x07, 0x00, 0x85, 0x01, 0x00, 0xC0, 0x1B, 0x22, 0x20, 0x50, 0x24, 0xF5, + 0x03, 0x44, 0xE0, 0x60, 0x21, 0xC5, 0x27, 0x11, 0xE2, 0x41, 0x44, 0xF4, + 0x93, 0x44, 0xF1, 0xFC, 0x31, 0xD0, 0x0B, 0x21, 0xFC, 0xFE, 0x8C, 0xEB, + 0x29, 0x44, 0xFE, 0xFE, 0x42, 0x4A, 0xC1, 0x95, 0x30, 0xB8, 0x39, 0x26, + 0xE6, 0x53, 0xBF, 0xF9, 0xA1, 0x9F, 0x98, 0xAB, 0x7B, 0x8E, 0x5E, 0x63, + 0x93, 0x53, 0xD5, 0x87, 0xD7, 0xEB, 0xAF, 0x6F, 0xA2, 0xB7, 0xE9, 0xF5, + 0x6F, 0xDF, 0xD9, 0x6E, 0xFE, 0xC7, 0x4F, 0xAE, 0xF7, 0xD7, 0xED, 0xF8, + 0x7F, 0xBD, 0x3C, 0xBF, 0xFF, 0xF5, 0xBF, 0xBC, 0xE7, 0xAC, 0x3F, 0xF6, + 0xFF, 0xFE, 0xE7, 0xFF, 0xFF, 0xFB, 0xFF, 0xF9, 0xFC, 0xFF, 0xFF, 0xFF, + }; +}; diff --git a/examples/gui/src/main.rs b/examples/gui/src/main.rs index 956b7bec..1f2bfbf7 100644 --- a/examples/gui/src/main.rs +++ b/examples/gui/src/main.rs @@ -5,22 +5,24 @@ #![no_std] #![forbid(unsafe_code)] +mod ferris_xbm; + // Required for panic handler extern crate flipperzero_rt; // Alloc extern crate alloc; extern crate flipperzero_alloc; -use alloc::ffi::CString; +use alloc::{ffi::CString, format}; use core::{ffi::CStr, time::Duration}; use flipperzero::{furi::message_queue::MessageQueue, println}; -use flipperzero_gui::xbm::XbmImage; use flipperzero_gui::{ canvas::CanvasView, gui::{Gui, GuiLayer}, input::{InputEvent, InputKey, InputType}, view_port::{ViewPort, ViewPortCallbacks}, + xbm::XbmImage, }; use flipperzero_rt::{entry, manifest}; use flipperzero_sys::furi::Status; @@ -71,10 +73,11 @@ fn main(_args: *mut u8) -> i32 { fn on_draw(&mut self, mut canvas: CanvasView) { canvas.draw_xbm(2, 2, &PLUS_IMAGE); canvas.draw_str(10, 31, self.text); - let bottom_text = CString::new(alloc::format!("Value = {}", self.counter).as_bytes()) + let bottom_text = CString::new(format!("Value = {}", self.counter).as_bytes()) .expect("should be a valid string"); - canvas.draw_str(5, 40, bottom_text); + canvas.draw_str(80, 10, bottom_text); canvas.draw_xbm(100, 50, &RS_IMAGE); + canvas.draw_xbm(0, 32, &ferris_xbm::IMAGE); } fn on_input(&mut self, event: InputEvent) { From 4008c30fea2dd4e5c67016138cc1e5f14b8f5509 Mon Sep 17 00:00:00 2001 From: Petr Portnov Date: Thu, 5 Jan 2023 17:33:17 +0300 Subject: [PATCH 13/49] fix: use correct XBM endianness --- crates/gui/src/xbm.rs | 64 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 59 insertions(+), 5 deletions(-) diff --git a/crates/gui/src/xbm.rs b/crates/gui/src/xbm.rs index f7b9a386..b2de8257 100644 --- a/crates/gui/src/xbm.rs +++ b/crates/gui/src/xbm.rs @@ -69,7 +69,7 @@ impl<'a> XbmImage<'a> { pub const fn get(&self, (x, y): (u8, u8)) -> Option { if let Some((byte, shift)) = self.offsets(x, y) { - Some((self.data[byte as usize] >> (7 - shift)) & 0b1 != 0) + Some((self.data[byte as usize] >> shift) & 0b1 != 0) } else { None } @@ -151,7 +151,7 @@ impl<'a> XbmImageMut<'a> { pub const fn get(&self, (x, y): (u8, u8)) -> Option { if let Some((byte, shift)) = self.offsets(x, y) { - Some((self.data[byte as usize] >> (7 - shift)) & 0b1 != 0) + Some((self.data[byte as usize] >> shift) & 0b1 != 0) } else { None } @@ -167,19 +167,19 @@ impl<'a> XbmImageMut<'a> { pub fn set_1(&mut self, (x, y): (u8, u8)) -> Option<()> { let (byte, shift) = self.offsets(x, y)?; - self.data[byte as usize] |= 1 << (7 - shift); + self.data[byte as usize] |= 1 << shift; Some(()) } pub fn set_0(&mut self, (x, y): (u8, u8)) -> Option<()> { let (byte, shift) = self.offsets(x, y)?; - self.data[byte as usize] &= !(1 << (7 - shift)); + self.data[byte as usize] &= !(1 << shift); Some(()) } pub fn xor(&mut self, (x, y): (u8, u8)) -> Option<()> { let (byte, shift) = self.offsets(x, y)?; - self.data[byte as usize] ^= 1 << (7 - shift); + self.data[byte as usize] ^= 1 << shift; Some(()) } } @@ -210,3 +210,57 @@ macro_rules! xbm { $crate::xbm::XbmImage::new($width, $height, &[$($byte,)*]) }}; } + +// TODO: enable test execution +#[cfg(test)] +mod tests { + + #[test] + fn valid_byte_reading() { + // 0100110000111100 + // 0000001111111100 + let image = xbm!( + #define xbm_test_width 16 + #define xbm_test_height 2 + static char xbm_test_bits[] = { + 0x32, // 0b00110010 ~ 0b01001100 + 0x3C, // 0b00111100 ~ 0b00111100 + 0xC0, // 0b11000000 ~ 0b00000011 + 0x3F, // 0b00111111 ~ 0b11111100 + }; + ); + + assert!(!image.get((0, 0))); + assert!(image.get((0, 1))); + assert!(!image.get((0, 2))); + assert!(!image.get((0, 3))); + assert!(image.get((0, 4))); + assert!(image.get((0, 5))); + assert!(!image.get((0, 6))); + assert!(!image.get((0, 7))); + assert!(!image.get((0, 8))); + assert!(!image.get((0, 9))); + assert!(image.get((0, 10))); + assert!(image.get((0, 11))); + assert!(image.get((0, 12))); + assert!(image.get((0, 13))); + assert!(!image.get((0, 14))); + assert!(!image.get((0, 15))); + assert!(!image.get((1, 0))); + assert!(!image.get((1, 1))); + assert!(!image.get((1, 2))); + assert!(!image.get((1, 3))); + assert!(!image.get((1, 4))); + assert!(!image.get((1, 5))); + assert!(image.get((1, 6))); + assert!(image.get((1, 7))); + assert!(image.get((1, 8))); + assert!(image.get((1, 9))); + assert!(image.get((1, 10))); + assert!(image.get((1, 11))); + assert!(image.get((1, 12))); + assert!(image.get((1, 13))); + assert!(!image.get((1, 14))); + assert!(!image.get((1, 15))); + } +} From 79a0c9e1d6a878b9255e51879c59903f6916f97c Mon Sep 17 00:00:00 2001 From: Petr Portnov Date: Thu, 5 Jan 2023 19:34:21 +0300 Subject: [PATCH 14/49] refactor: make XbmImage generic Co-authored-by: Yaroslav Bolyukin --- crates/gui/src/canvas.rs | 8 +- crates/gui/src/icon.rs | 4 +- crates/gui/src/xbm.rs | 244 ++++++++++++++++++++------------- examples/gui/src/ferris_xbm.rs | 4 +- examples/gui/src/main.rs | 4 +- 5 files changed, 163 insertions(+), 101 deletions(-) diff --git a/crates/gui/src/canvas.rs b/crates/gui/src/canvas.rs index b98dbffe..5b52b2e8 100644 --- a/crates/gui/src/canvas.rs +++ b/crates/gui/src/canvas.rs @@ -4,6 +4,7 @@ use crate::icon::Icon; use crate::icon_animation::{IconAnimation, IconAnimationCallbacks}; use crate::xbm::XbmImage; use core::ffi::c_char; +use core::ops::Deref; use core::{ffi::CStr, marker::PhantomData, num::NonZeroU8, ptr::NonNull}; use flipperzero::furi::canvas::Align; use flipperzero_sys::{ @@ -186,7 +187,12 @@ impl CanvasView<'_> { // TODO: do we need other range checks? // what is the best return type? - pub fn draw_xbm(&mut self, x: u8, y: u8, xbm: &XbmImage) -> Option<()> { + pub fn draw_xbm( + &mut self, + x: u8, + y: u8, + xbm: &XbmImage>, + ) -> Option<()> { let raw = self.raw.as_ptr(); let width = xbm.width(); let height = xbm.height(); diff --git a/crates/gui/src/icon.rs b/crates/gui/src/icon.rs index 1dc3fe5a..51fb6a63 100644 --- a/crates/gui/src/icon.rs +++ b/crates/gui/src/icon.rs @@ -47,12 +47,12 @@ impl Icon { (self.get_width(), self.get_height()) } - pub fn get_data(&self) -> XbmImage<'_> { + pub fn get_data(&self) -> XbmImage<&'_ [u8]> { let (width, height) = self.get_dimensions(); let raw = self.raw.as_ptr(); // SAFETY: `raw` is always valid // and `width` and `height` are always in sync with data - unsafe { XbmImage::from_raw(height, width, sys::icon_get_data(raw)) } + unsafe { XbmImage::from_raw(width, height, sys::icon_get_data(raw)) } } } diff --git a/crates/gui/src/xbm.rs b/crates/gui/src/xbm.rs index b2de8257..28321835 100644 --- a/crates/gui/src/xbm.rs +++ b/crates/gui/src/xbm.rs @@ -1,28 +1,18 @@ //! User-friendly wrappers of XDM images. +use alloc::vec; +use alloc::vec::Vec; +use core::ops::{Deref, DerefMut}; use core::slice; -pub struct XbmImage<'a> { - data: &'a [u8], +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct XbmImage { + data: D, width: u8, height: u8, } -impl<'a> XbmImage<'a> { - pub const fn new(width: u8, height: u8, data: &'a [u8]) -> Self { - let bytes = xds_bytes(width, height); - assert!( - bytes == data.len(), - "bit-dimensions don't match bit-size of data" - ); - - Self { - data, - width, - height, - } - } - +impl XbmImage { pub const fn width(&self) -> u8 { self.width } @@ -31,22 +21,23 @@ impl<'a> XbmImage<'a> { self.height } - pub const fn data(&self) -> &[u8] { - self.data + pub const fn dimensions(&self) -> (u8, u8) { + (self.width, self.height) } - pub unsafe fn from_raw(height: u8, width: u8, data: *const u8) -> Self { - let bytes = xds_bytes(width, height); + #[inline] + const fn dimension_bits(width: u8, height: u8) -> u16 { + width as u16 * height as u16 + } - // SAFETY: the size is exactly calculated based on width and height - // and caller upholds the `data` validity invariant - let data = unsafe { slice::from_raw_parts(data, bytes) }; + #[inline] + const fn bits_to_min_required_bytes(bits: u16) -> u16 { + bits.div_ceil(8) + } - Self { - data, - width, - height, - } + #[inline] + const fn dimension_bytes(width: u8, height: u8) -> u16 { + Self::bits_to_min_required_bytes(Self::dimension_bits(width, height)) } #[inline] @@ -66,32 +57,34 @@ impl<'a> XbmImage<'a> { None } } +} - pub const fn get(&self, (x, y): (u8, u8)) -> Option { - if let Some((byte, shift)) = self.offsets(x, y) { - Some((self.data[byte as usize] >> shift) & 0b1 != 0) - } else { - None - } +impl> XbmImage { + pub fn data(&self) -> &D::Target { + &self.data } -} -pub struct XbmImageMut<'a> { - data: &'a mut [u8], - width: u8, - height: u8, + pub fn data_mut(&mut self) -> &mut D::Target + where + D: DerefMut, + { + &mut self.data + } } -const fn xds_bytes(width: u8, height: u8) -> usize { - (width as usize * height as usize).div_ceil(8) -} +impl> XbmImage { + pub fn new_from(width: u8, height: u8, data: D) -> Self { + let bits = Self::dimension_bits(width, height); + let bytes = Self::bits_to_min_required_bytes(bits); -impl<'a> XbmImageMut<'a> { - pub fn new(data: &'a mut [u8], width: u8, height: u8) -> Self { - let bytes = xds_bytes(width, height); assert!( - bytes == data.len(), - "bit-dimensions don't match bit-size of data" + bytes as usize == data.len(), + "width={} * height={} = {} should correspond to {} bytes, but data has length {}", + width, + height, + bits, + bytes, + data.len() ); Self { @@ -101,12 +94,22 @@ impl<'a> XbmImageMut<'a> { } } - pub unsafe fn from_raw(data: *mut u8, width: u8, height: u8) -> Self { - let bytes = xds_bytes(width, height); + pub fn get(&self, (x, y): (u8, u8)) -> Option { + if let Some((byte, shift)) = self.offsets(x, y) { + Some((self.data[byte as usize] >> shift) & 0b1 != 0) + } else { + None + } + } +} + +impl<'a> XbmImage<&'a [u8]> { + pub unsafe fn from_raw(width: u8, height: u8, data: *const u8) -> Self { + let bytes = Self::dimension_bytes(width, height) as usize; // SAFETY: the size is exactly calculated based on width and height // and caller upholds the `data` validity invariant - let data = unsafe { slice::from_raw_parts_mut(data, bytes) }; + let data = unsafe { slice::from_raw_parts(data, bytes) }; Self { data, @@ -114,49 +117,25 @@ impl<'a> XbmImageMut<'a> { height, } } +} - pub const fn width(&self) -> u8 { - self.width - } - - pub const fn height(&self) -> u8 { - self.height - } - - pub const fn data(&self) -> &[u8] { - self.data - } - - pub const fn data_mut(&self) -> &[u8] { - self.data - } - - #[inline] - const fn offset(&self, x: u8, y: u8) -> Option { - if x >= self.width || y >= self.height { - None - } else { - Some(x * self.width + y) - } - } +impl<'a> XbmImage<&'a mut [u8]> { + pub unsafe fn from_raw_mut(width: u8, height: u8, data: *mut u8) -> Self { + let bytes = Self::dimension_bytes(width, height) as usize; - #[inline] - const fn offsets(&self, x: u8, y: u8) -> Option<(u8, u8)> { - if let Some(offset) = self.offset(x, y) { - Some((offset / 8, offset % 8)) - } else { - None - } - } + // SAFETY: the size is exactly calculated based on width and height + // and caller upholds the `data` validity invariant + let data = unsafe { slice::from_raw_parts_mut(data, bytes) }; - pub const fn get(&self, (x, y): (u8, u8)) -> Option { - if let Some((byte, shift)) = self.offsets(x, y) { - Some((self.data[byte as usize] >> shift) & 0b1 != 0) - } else { - None + Self { + data, + width, + height, } } +} +impl + DerefMut> XbmImage { pub fn set(&mut self, coordinates: (u8, u8), value: bool) -> Option<()> { if value { self.set_1(coordinates) @@ -184,16 +163,93 @@ impl<'a> XbmImageMut<'a> { } } -impl<'a> From> for XbmImage<'a> { - fn from(value: XbmImageMut<'a>) -> Self { +// Specializations + +impl XbmImage> { + pub fn new(width: u8, height: u8) -> Self { + let bytes = Self::dimension_bytes(width, height) as usize; + Self { + data: vec![0; bytes], + width, + height, + } + } +} + +impl XbmImage<&'static [u8]> { + /// Creates a referencing `XbmImage` from a static `u8` slice. + /// + /// This is a constant specialization of [`XbmImage::new_from`] + /// existing since the latter cannot be generically `const` + /// until `const_fn_trait_bound` Rust feature becomes stable. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ```rust + /// use flipperzero_gui::xbm::XbmImage; + /// + /// const IMAGE: XbmImage<&'static [u8]> = XbmImage::new_from_static(4, 4, &[0xFE, 0x12]); + /// ``` + pub const fn new_from_static(width: u8, height: u8, data: &'static [u8]) -> Self { + let bytes = Self::dimension_bytes(width, height); + + assert!( + bytes as usize == data.len(), + "dimensions don't match data length", + ); + + Self { + data, + width: 0, + height: 0, + } + } +} + +impl XbmImage> { + /// Creates a referencing `XbmImage` from a `u8` array. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ```rust + /// use flipperzero_gui::xbm::XbmImage; + /// + /// const IMAGE: XbmImage<[u8; 2]> = XbmImage::new_from_array::<4, 4>([0xFE, 0x12]); + /// ``` + pub const fn new_from_array(data: [u8; SIZE]) -> Self { + let bytes = Self::dimension_bytes(WIDTH, HEIGHT); + + assert!(bytes as usize == SIZE, "dimensions don't match data length"); + Self { - data: value.data, - width: value.width, - height: value.height, + data: ByteArray(data), + width: 0, + height: 0, } } } +#[derive(Debug, PartialEq, Eq)] +pub struct ByteArray(pub [u8; N]); + +impl Deref for ByteArray { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + self.0.as_slice() + } +} + +impl DerefMut for ByteArray { + fn deref_mut(&mut self) -> &mut Self::Target { + self.0.as_mut_slice() + } +} + #[macro_export] macro_rules! xbm { ( @@ -207,7 +263,7 @@ macro_rules! xbm { $($byte:literal),* $(,)? }; ) => {{ - $crate::xbm::XbmImage::new($width, $height, &[$($byte,)*]) + $crate::xbm::XbmImage::new_from_array::<$width, $height>([$($byte,)*]) }}; } diff --git a/examples/gui/src/ferris_xbm.rs b/examples/gui/src/ferris_xbm.rs index 50862d08..c9d9f38a 100644 --- a/examples/gui/src/ferris_xbm.rs +++ b/examples/gui/src/ferris_xbm.rs @@ -1,6 +1,6 @@ -use flipperzero_gui::xbm::XbmImage; +use flipperzero_gui::xbm::{ByteArray, XbmImage}; -pub const IMAGE: XbmImage = flipperzero_gui::xbm! { +pub const IMAGE: XbmImage> = flipperzero_gui::xbm! { #define ferris_width 48 #define ferris_height 32 static char ferris_bits[] = { diff --git a/examples/gui/src/main.rs b/examples/gui/src/main.rs index 1f2bfbf7..f552b11f 100644 --- a/examples/gui/src/main.rs +++ b/examples/gui/src/main.rs @@ -30,7 +30,7 @@ use flipperzero_sys::furi::Status; manifest!(name = "Rust GUI example"); entry!(main); -const PLUS_IMAGE: XbmImage = XbmImage::new( +const PLUS_IMAGE: XbmImage<&'static [u8]> = XbmImage::new_from_static( 8, 8, &[ @@ -45,7 +45,7 @@ const PLUS_IMAGE: XbmImage = XbmImage::new( ], ); -const RS_IMAGE: XbmImage = XbmImage::new( +const RS_IMAGE: XbmImage<&'static [u8]> = XbmImage::new_from_static( 8, 8, &[ From e248c56861220124abd7e66ed10c3664fecc8838 Mon Sep 17 00:00:00 2001 From: Petr Portnov Date: Thu, 5 Jan 2023 19:43:32 +0300 Subject: [PATCH 15/49] chore: use `XbmImage::new_from_array` where possible --- examples/gui/src/main.rs | 49 +++++++++++++++++----------------------- 1 file changed, 21 insertions(+), 28 deletions(-) diff --git a/examples/gui/src/main.rs b/examples/gui/src/main.rs index f552b11f..f104c3ab 100644 --- a/examples/gui/src/main.rs +++ b/examples/gui/src/main.rs @@ -17,6 +17,7 @@ use alloc::{ffi::CString, format}; use core::{ffi::CStr, time::Duration}; use flipperzero::{furi::message_queue::MessageQueue, println}; +use flipperzero_gui::xbm::ByteArray; use flipperzero_gui::{ canvas::CanvasView, gui::{Gui, GuiLayer}, @@ -30,35 +31,27 @@ use flipperzero_sys::furi::Status; manifest!(name = "Rust GUI example"); entry!(main); -const PLUS_IMAGE: XbmImage<&'static [u8]> = XbmImage::new_from_static( - 8, - 8, - &[ - 0b00_11_11_00, - 0b00_11_11_00, - 0b11_11_11_11, - 0b11_11_11_11, - 0b11_11_11_11, - 0b11_11_11_11, - 0b00_11_11_00, - 0b10_11_11_01, - ], -); +const PLUS_IMAGE: XbmImage> = XbmImage::new_from_array::<8, 8>([ + 0b00_11_11_00, + 0b00_11_11_00, + 0b11_11_11_11, + 0b11_11_11_11, + 0b11_11_11_11, + 0b11_11_11_11, + 0b00_11_11_00, + 0b10_11_11_01, +]); -const RS_IMAGE: XbmImage<&'static [u8]> = XbmImage::new_from_static( - 8, - 8, - &[ - 0b11100000u8.reverse_bits(), - 0b10010000u8.reverse_bits(), - 0b11100000u8.reverse_bits(), - 0b10100110u8.reverse_bits(), - 0b10011000u8.reverse_bits(), - 0b00000110u8.reverse_bits(), - 0b00000001u8.reverse_bits(), - 0b00000110u8.reverse_bits(), - ], -); +const RS_IMAGE: XbmImage> = XbmImage::new_from_array::<8, 8>([ + 0b11100000u8.reverse_bits(), + 0b10010000u8.reverse_bits(), + 0b11100000u8.reverse_bits(), + 0b10100110u8.reverse_bits(), + 0b10011000u8.reverse_bits(), + 0b00000110u8.reverse_bits(), + 0b00000001u8.reverse_bits(), + 0b00000110u8.reverse_bits(), +]); fn main(_args: *mut u8) -> i32 { let exit_event_queue = MessageQueue::new(32); From c90380869e8c3fd65ce7abb8d4fabb678968c524 Mon Sep 17 00:00:00 2001 From: Petr Portnov Date: Sat, 14 Jan 2023 19:56:28 +0300 Subject: [PATCH 16/49] chore: minor cleanups in core APIs This also adds new `unstable_lints` feature --- crates/flipperzero/Cargo.toml | 3 + crates/flipperzero/examples/example_images.rs | 35 +++++-- crates/flipperzero/src/furi/dialog.rs | 95 +++++++++++-------- crates/flipperzero/src/furi/io.rs | 6 +- crates/flipperzero/src/furi/sync.rs | 22 +++-- crates/flipperzero/src/furi/thread.rs | 28 +++--- crates/flipperzero/src/internals.rs | 48 ++++++++++ crates/flipperzero/src/kernel.rs | 77 +++++++++++++++ crates/flipperzero/src/lib.rs | 3 + crates/flipperzero/src/macros.rs | 4 +- crates/sys/src/furi.rs | 26 ++--- 11 files changed, 261 insertions(+), 86 deletions(-) create mode 100644 crates/flipperzero/src/internals.rs create mode 100644 crates/flipperzero/src/kernel.rs diff --git a/crates/flipperzero/Cargo.toml b/crates/flipperzero/Cargo.toml index 395a4a25..6c8d4acb 100644 --- a/crates/flipperzero/Cargo.toml +++ b/crates/flipperzero/Cargo.toml @@ -27,5 +27,8 @@ flipperzero-sys = { path = "../sys", version = "0.6.0-alpha" } flipperzero-rt = { path = "../rt", version = "0.6.0-alpha" } [features] +default = ["unstable_lints"] # enables features requiring an allocator alloc = [] +# enables unstable Rust lints on types provided by this crate +unstable_lints = [] diff --git a/crates/flipperzero/examples/example_images.rs b/crates/flipperzero/examples/example_images.rs index 9efa62a8..c3d7c669 100644 --- a/crates/flipperzero/examples/example_images.rs +++ b/crates/flipperzero/examples/example_images.rs @@ -22,7 +22,7 @@ static mut TARGET_ICON: Icon = Icon { frame_rate: 0, frames: unsafe { TARGET_FRAMES.as_ptr() }, }; -static mut TARGET_FRAMES: [*const u8; 1] = [ include_bytes!("icons/rustacean-48x32.icon").as_ptr() ]; +static mut TARGET_FRAMES: [*const u8; 1] = [include_bytes!("icons/rustacean-48x32.icon").as_ptr()]; static mut IMAGE_POSITION: ImagePosition = ImagePosition { x: 0, y: 0 }; @@ -46,7 +46,12 @@ struct Icon { extern "C" fn app_draw_callback(canvas: *mut sys::Canvas, _ctx: *mut c_void) { unsafe { sys::canvas_clear(canvas); - sys::canvas_draw_icon(canvas, IMAGE_POSITION.x % 128, IMAGE_POSITION.y % 128, &TARGET_ICON as *const Icon as *const c_void as *const sys::Icon); + sys::canvas_draw_icon( + canvas, + IMAGE_POSITION.x % 128, + IMAGE_POSITION.y % 128, + &TARGET_ICON as *const Icon as *const c_void as *const sys::Icon, + ); } } @@ -59,12 +64,21 @@ extern "C" fn app_input_callback(input_event: *mut sys::InputEvent, ctx: *mut c_ fn main(_args: *mut u8) -> i32 { unsafe { - let event_queue = sys::furi_message_queue_alloc(8, mem::size_of::() as u32) as *mut sys::FuriMessageQueue; + let event_queue = sys::furi_message_queue_alloc(8, mem::size_of::() as u32) + as *mut sys::FuriMessageQueue; // Configure view port let view_port = sys::view_port_alloc(); - sys::view_port_draw_callback_set(view_port, Some(app_draw_callback), view_port as *mut c_void); - sys::view_port_input_callback_set(view_port, Some(app_input_callback), event_queue as *mut c_void); + sys::view_port_draw_callback_set( + view_port, + Some(app_draw_callback), + view_port as *mut c_void, + ); + sys::view_port_input_callback_set( + view_port, + Some(app_input_callback), + event_queue as *mut c_void, + ); // Register view port in GUI let gui = sys::furi_record_open(RECORD_GUI) as *mut sys::Gui; @@ -74,9 +88,16 @@ fn main(_args: *mut u8) -> i32 { let mut running = true; while running { - if sys::furi_message_queue_get(event_queue, event.as_mut_ptr() as *mut sys::InputEvent as *mut c_void, 100) == sys::FuriStatus_FuriStatusOk { + if sys::furi_message_queue_get( + event_queue, + event.as_mut_ptr() as *mut sys::InputEvent as *mut c_void, + 100, + ) == sys::FuriStatus_FuriStatusOk + { let event = event.assume_init(); - if event.type_ == sys::InputType_InputTypePress || event.type_ == sys::InputType_InputTypeRepeat { + if event.type_ == sys::InputType_InputTypePress + || event.type_ == sys::InputType_InputTypeRepeat + { match event.key { sys::InputKey_InputKeyLeft => IMAGE_POSITION.x -= 2, sys::InputKey_InputKeyRight => IMAGE_POSITION.x += 2, diff --git a/crates/flipperzero/src/furi/dialog.rs b/crates/flipperzero/src/furi/dialog.rs index 37ef0df5..11bb411b 100644 --- a/crates/flipperzero/src/furi/dialog.rs +++ b/crates/flipperzero/src/furi/dialog.rs @@ -6,17 +6,13 @@ use alloc::ffi::CString; use core::ffi::{c_char, CStr}; use core::marker::PhantomData; use core::ptr; +use core::ptr::NonNull; use flipperzero_sys as sys; use flipperzero_sys::furi::UnsafeRecord; use super::canvas::Align; -const RECORD_DIALOGS: *const c_char = sys::c_string!("dialogs"); - -#[cfg(feature = "alloc")] -const BUTTON_OK: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"OK\0") }; - /// A handle to the Dialogs app. pub struct DialogsApp { data: UnsafeRecord, @@ -24,7 +20,7 @@ pub struct DialogsApp { /// A dialog message. pub struct DialogMessage<'a> { - data: *mut sys::DialogMessage, + raw: NonNull, _phantom: PhantomData<&'a CStr>, } @@ -37,17 +33,20 @@ pub enum DialogMessageButton { } impl DialogsApp { + const RECORD_DIALOGS: *const c_char = sys::c_string!("dialogs"); + /// Obtains a handle to the Dialogs app. pub fn open() -> Self { - Self { - data: unsafe { UnsafeRecord::open(RECORD_DIALOGS) }, - } + // SAFETY: `RECORD_DIALOGS` is a constant + let data = unsafe { UnsafeRecord::open(Self::RECORD_DIALOGS) }; + Self { data } } /// Displays a message. pub fn show(&mut self, message: &DialogMessage) -> DialogMessageButton { let data = self.data.as_raw(); - let button_sys = unsafe { sys::dialog_message_show(data, message.data) }; + let message = message.as_raw(); + let button_sys = unsafe { sys::dialog_message_show(data, message) }; DialogMessageButton::from_sys(button_sys).expect("Invalid button") } @@ -56,15 +55,19 @@ impl DialogsApp { impl<'a> DialogMessage<'a> { /// Allocates a new dialog message. pub fn new() -> Self { - let data = unsafe { sys::dialog_message_alloc() }; - assert!(!data.is_null()); + // SAFETY: allocation either suceeds producing a valid pointer or terminates execution + let data = unsafe { NonNull::new_unchecked(sys::dialog_message_alloc()) }; Self { - data, + raw: data, _phantom: PhantomData, } } + pub fn as_raw(&self) -> *mut sys::DialogMessage { + self.raw.as_ptr() + } + /// Sets the labels of the buttons. pub fn set_buttons( &mut self, @@ -72,12 +75,15 @@ impl<'a> DialogMessage<'a> { center: Option<&'a CStr>, right: Option<&'a CStr>, ) { - let left = left.map_or(ptr::null(), |l| l.as_ptr()); - let center = center.map_or(ptr::null(), |l| l.as_ptr()); - let right = right.map_or(ptr::null(), |l| l.as_ptr()); + let data = self.raw.as_ptr(); + let left = left.map_or(ptr::null(), |c_str| c_str.as_ptr()); + let center = center.map_or(ptr::null(), |c_str| c_str.as_ptr()); + let right = right.map_or(ptr::null(), |c_str| c_str.as_ptr()); + // SAFTY: `data` is always a valid pointer + // and all other pointers are valid or null unsafe { - sys::dialog_message_set_buttons(self.data, left, center, right); + sys::dialog_message_set_buttons(data, left, center, right); } } @@ -90,38 +96,40 @@ impl<'a> DialogMessage<'a> { horizontal: Align, vertical: Align, ) { + let data = self.raw.as_ptr(); + let header = header.as_ptr(); + let horizontal = horizontal.into(); + let vertical = vertical.into(); + // SAFTY: `data` and `header` are always valid pointers + // and all values are corrrect unsafe { - sys::dialog_message_set_header( - self.data, - header.as_ptr(), - x, - y, - horizontal.into(), - vertical.into(), - ); + sys::dialog_message_set_header(data, header, x, y, horizontal, vertical); } } /// Sets the body text. pub fn set_text(&mut self, text: &'a CStr, x: u8, y: u8, horizontal: Align, vertical: Align) { + let data = self.raw.as_ptr(); + let text = text.as_ptr(); + let horizontal = horizontal.into(); + let vertical = vertical.into(); + // SAFTY: `data` and `text` are always valid pointers + // and all values are corrrect unsafe { - sys::dialog_message_set_text( - self.data, - text.as_ptr(), - x, - y, - horizontal.into(), - vertical.into(), - ); + sys::dialog_message_set_text(data, text, x, y, horizontal, vertical); } } /// Clears the header text. pub fn clear_header(&mut self) { + let data = self.raw.as_ptr(); + let text = ptr::null(); + // SAFTY: `data` is always a valid pointer + // and all values are corrrect unsafe { sys::dialog_message_set_header( - self.data, - ptr::null(), + data, + text, 0, 0, sys::Align_AlignLeft, @@ -132,10 +140,13 @@ impl<'a> DialogMessage<'a> { /// Clears the body text. pub fn clear_text(&mut self) { + let data = self.raw.as_ptr(); + let text = ptr::null(); + // SAFTY: `data` is always a valid pointer and all values are corrrect unsafe { sys::dialog_message_set_text( - self.data, - ptr::null(), + data, + text, 0, 0, sys::Align_AlignLeft, @@ -147,9 +158,10 @@ impl<'a> DialogMessage<'a> { impl<'a> Drop for DialogMessage<'a> { fn drop(&mut self) { - unsafe { - sys::dialog_message_free(self.data); - } + let data = self.raw.as_ptr(); + // SAFETY: `data` is a valid pointer + // which has been created by a call to `dialog_message_alloc` + unsafe { sys::dialog_message_free(data) }; } } @@ -174,6 +186,9 @@ impl Default for DialogMessage<'_> { /// Displays a simple dialog. #[cfg(feature = "alloc")] pub fn alert(text: &str) { + // SAFETY: string is known to end with NUL + const BUTTON_OK: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"OK\0") }; + let text = CString::new(text.as_bytes()).unwrap(); let mut dialogs = DialogsApp::open(); diff --git a/crates/flipperzero/src/furi/io.rs b/crates/flipperzero/src/furi/io.rs index 7f7704d8..2c69a99d 100644 --- a/crates/flipperzero/src/furi/io.rs +++ b/crates/flipperzero/src/furi/io.rs @@ -1,7 +1,7 @@ //! Furi I/O API. use core::ffi::c_char; -use core::fmt::{Write, Arguments}; +use core::fmt::{Arguments, Write}; use flipperzero_sys as sys; @@ -39,7 +39,7 @@ pub fn _print(args: Arguments) { } #[doc(hidden)] -pub fn _write_str(s: &str) { - // Adoid generating exception machinery +pub fn _print_str(s: &str) { + // Avoid generating exception machinery Stdout.write_str(s).ok(); } diff --git a/crates/flipperzero/src/furi/sync.rs b/crates/flipperzero/src/furi/sync.rs index f26c2b39..91ebba8e 100644 --- a/crates/flipperzero/src/furi/sync.rs +++ b/crates/flipperzero/src/furi/sync.rs @@ -1,20 +1,18 @@ //! Furi syncronization primitives. -use core::cell::UnsafeCell; -use core::marker::PhantomData; -use core::ops::{Deref, DerefMut}; +use core::{ + cell::UnsafeCell, + marker::PhantomData, + ops::{Deref, DerefMut}, +}; use flipperzero_sys as sys; use sys::furi::Status; -use crate::furi; +use crate::{furi, internals::UnsendUnsync}; const MUTEX_TYPE: u8 = sys::FuriMutexType_FuriMutexTypeNormal; -/// Negative trait bounds are not implemented (see rust-lang/rust#68318). -/// As a workaround we can force `!Send`/`!Sync` by pretending we own a raw pointer. -type UnsendUnsync = PhantomData<*const ()>; - /// A mutual exclusion primitive useful for protecting shared data. pub struct Mutex { mutex: *mut sys::FuriMutex, @@ -50,7 +48,13 @@ unsafe impl Sync for Mutex {} /// An RAII implementation of a "scoped lock" of a mutex. /// When this structure is dropped (falls out of scope), the lock will be unlocked. -pub struct MutexGuard<'a, T: ?Sized + 'a>(&'a Mutex, UnsendUnsync); +#[cfg_attr( + feature = "unstable_lints", + must_not_suspend = "holding a MutexGuard across suspend \ + points can cause deadlocks, delays, \ + and cause Futures to not implement `Send`" +)] +pub struct MutexGuard<'a, T: ?Sized + 'a>(&'a Mutex, PhantomData); impl Deref for MutexGuard<'_, T> { type Target = T; diff --git a/crates/flipperzero/src/furi/thread.rs b/crates/flipperzero/src/furi/thread.rs index 70dfac09..ac67f30f 100644 --- a/crates/flipperzero/src/furi/thread.rs +++ b/crates/flipperzero/src/furi/thread.rs @@ -1,19 +1,23 @@ //! Furi Thread API. -use core::time::Duration; -use flipperzero_sys as sys; +// TODO: decide what to do with delays not fitting in 32 bits -/// Puts the current thread to sleep for at least the specified amount of time. -pub fn sleep(duration: Duration) { - const MAX_US_DURATION: Duration = Duration::from_secs(3600); +pub mod sync { + use core::time::Duration; + use flipperzero_sys as sys; - unsafe { - // For durations of 1h+, use delay_ms so uint32_t doesn't overflow - if duration < MAX_US_DURATION { - sys::furi_delay_us(duration.as_micros() as u32); - } else { - sys::furi_delay_ms(duration.as_millis() as u32); - // TODO: add reamining us-part + /// Puts the current thread to sleep for at least the specified amount of time. + pub fn sleep(duration: Duration) { + const MAX_US_DURATION: Duration = Duration::from_secs(3600); + + unsafe { + // For durations of 1h+, use delay_ms so uint32_t doesn't overflow + if duration < MAX_US_DURATION { + sys::furi_delay_us(duration.as_micros() as u32); + } else { + sys::furi_delay_ms(duration.as_millis() as u32); + // TODO: add reamining us-part + } } } } diff --git a/crates/flipperzero/src/internals.rs b/crates/flipperzero/src/internals.rs new file mode 100644 index 00000000..50b14a90 --- /dev/null +++ b/crates/flipperzero/src/internals.rs @@ -0,0 +1,48 @@ +//! Internal implementation details. + +use core::{marker::PhantomData, mem}; + +/// Marker type which is neither [`Send`] nor [`Sync`]. +/// This should be used until `negative_trait_bounds` Rust feature is stable. +/// +/// # Example +/// +/// Make type `Foo` `impl !Sync` and `impl !Send`: +/// +/// ```no_run +/// struct Foo { +/// _marker: UnsendUnsync, +/// } +/// ``` +pub(crate) struct UnsendUnsync(*const ()); + +const _: () = { + assert!( + mem::size_of::>() == 0, + "`PhantomData` should be a ZST" + ); +}; + +/// Marker type which is not [`Send`] but is [`Sync`]. +/// This should be used until `negative_trait_bounds` Rust feature is stable. +/// +/// # Example +/// +/// Make type `Foo` `impl !Send`: +/// +/// ```no_run +/// struct Foo { +/// _marker: Unsend, +/// } +/// ``` +pub(crate) struct Unsend(*const ()); + +// SAFETY: `Unsend` is just a marker struct +unsafe impl Sync for Unsend {} + +const _: () = { + assert!( + mem::size_of::>() == 0, + "`PhantomData` should be a ZST" + ); +}; diff --git a/crates/flipperzero/src/kernel.rs b/crates/flipperzero/src/kernel.rs new file mode 100644 index 00000000..b2884f0f --- /dev/null +++ b/crates/flipperzero/src/kernel.rs @@ -0,0 +1,77 @@ +use crate::internals::Unsend; +use core::{ + fmt::{self, Display, Formatter}, + marker::PhantomData, +}; +use flipperzero_sys::{self as sys, furi::Status}; + +// FIXME: make this available via flipperzero-firnmware +fn interrupted() -> bool { + // FIXME: this is currently obviously unsound and cannot be implemmented, + // see https://github.com/flipperdevices/flipperzero-firmware/pull/2276 for details + // // SAFETY: this function has no invariant to uphold + // unsafe { furi_is_irq_context() } + false +} + +pub fn lock() -> Result { + if interrupted() { + Err(LockError::Interrupted) + } else { + // SAFETY: kernel is not interrupted + let status = unsafe { sys::furi_kernel_lock() }; + + Ok(match status { + 0 => LockGuard { + was_locked: false, + _marker: PhantomData, + }, + 1 => LockGuard { + was_locked: true, + _marker: PhantomData, + }, + status => Err(LockError::ErrorStatus(Status(status)))?, + }) + } +} + +#[derive(Debug, PartialEq, Eq)] +#[cfg_attr( + feature = "unstable_lints", + must_not_suspend = "holding a MutexGuard across suspend \ + points can cause deadlocks, delays, \ + and cause Futures to not implement `Send`" +)] +pub struct LockGuard { + was_locked: bool, + _marker: PhantomData, +} + +impl LockGuard { + pub const fn was_locked(&self) -> bool { + self.was_locked + } +} + +impl Drop for LockGuard { + fn drop(&mut self) { + // SAFETY: no invariant has to be upheld + let _ = unsafe { sys::furi_kernel_unlock() }; + } +} + +/// A type of error which can be returned whenever a lock is acquired. +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub enum LockError { + Interrupted, + ErrorStatus(Status), +} + +impl Display for LockError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Self::Interrupted => write!(f, "context is in interruption state"), + Self::ErrorStatus(status) => write!(f, "error status: {status}"), + } + } +} diff --git a/crates/flipperzero/src/lib.rs b/crates/flipperzero/src/lib.rs index a2b6966b..740d019b 100644 --- a/crates/flipperzero/src/lib.rs +++ b/crates/flipperzero/src/lib.rs @@ -1,9 +1,12 @@ //! High-level bindings for the Flipper Zero. #![no_std] +#![cfg_attr(feature = "unstable_lints", feature(must_not_suspend))] #[cfg(feature = "alloc")] extern crate alloc; pub mod furi; +pub(crate) mod internals; +pub mod kernel; pub mod macros; diff --git a/crates/flipperzero/src/macros.rs b/crates/flipperzero/src/macros.rs index 5b22fcb8..e4fd1680 100644 --- a/crates/flipperzero/src/macros.rs +++ b/crates/flipperzero/src/macros.rs @@ -7,7 +7,7 @@ macro_rules! print { }}; ($msg:expr $(,)?) => {{ - $crate::furi::io::_write_str($msg); + $crate::furi::io::_print_str($msg); }}; } @@ -18,6 +18,6 @@ macro_rules! println { }}; ($msg:expr $(,)?) => {{ - $crate::furi::io::_write_str(concat!($msg, "\r\n")); + $crate::furi::io::_print_str(concat!($msg, "\r\n")); }}; } diff --git a/crates/sys/src/furi.rs b/crates/sys/src/furi.rs index 19688196..8526ff75 100644 --- a/crates/sys/src/furi.rs +++ b/crates/sys/src/furi.rs @@ -2,13 +2,14 @@ use core::ffi::c_char; use core::fmt::Display; +use core::ptr::NonNull; use core::time::Duration; /// Operation status. /// The Furi API switches between using `enum FuriStatus`, `int32_t` and `uint32_t`. /// Since these all use the same bit representation, we can just "cast" the returns to this type. #[repr(transparent)] -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Ord, PartialOrd)] pub struct Status(pub i32); impl Status { @@ -85,7 +86,7 @@ impl From for Status { /// Low-level wrapper of a record handle. pub struct UnsafeRecord { name: *const c_char, - data: *mut T, + data: NonNull, } impl UnsafeRecord { @@ -96,26 +97,25 @@ impl UnsafeRecord { /// The caller must ensure that `record_name` lives for the /// duration of the object lifetime. pub unsafe fn open(name: *const c_char) -> Self { - Self { - name, - data: crate::furi_record_open(name) as *mut T, - } + // SAFETY: the created pointer is guaranteed to be valid + let data = unsafe { crate::furi_record_open(name) } as *mut T; + // SAFETY: the created pointer is guaranteed to be non-null + let data = unsafe { NonNull::new_unchecked(data) }; + Self { name, data } } /// Returns the record data as a raw pointer. pub fn as_raw(&self) -> *mut T { - self.data + self.data.as_ptr() } } impl Drop for UnsafeRecord { fn drop(&mut self) { - if !self.data.is_null() { - unsafe { - // SAFETY: `self.name` is valid since it was used to construct this istance - // and ownership has not been taken - crate::furi_record_close(self.name); - } + unsafe { + // SAFETY: `self.name` is valid since it was used to construct this istance + // and ownership has not been taken + crate::furi_record_close(self.name); } } } From da0e7c2b930da41824507f7c12402c107a338590 Mon Sep 17 00:00:00 2001 From: Petr Portnov Date: Sat, 14 Jan 2023 20:55:11 +0300 Subject: [PATCH 17/49] refactor: move gui APIs to `flipperzero` --- crates/flipperzero/Cargo.toml | 8 +- crates/flipperzero/src/gui/canvas.rs | 611 +++++++++++++++++- .../{gui/src => flipperzero/src/gui}/gui.rs | 6 +- .../{gui/src => flipperzero/src/gui}/icon.rs | 4 +- .../src/gui}/icon_animation.rs | 4 +- crates/flipperzero/src/gui/mod.rs | 6 + .../{gui/src => flipperzero/src/gui}/view.rs | 2 +- .../src => flipperzero/src/gui}/view_port.rs | 30 +- .../{gui/src => flipperzero/src/gui}/xbm.rs | 17 +- .../input.rs => flipperzero/src/input/mod.rs} | 1 - crates/flipperzero/src/internals.rs | 22 + crates/flipperzero/src/lib.rs | 3 + crates/gui/src/canvas.rs | 606 ----------------- crates/gui/src/lib.rs | 9 - examples/gui/Cargo.lock | 9 - examples/gui/Cargo.toml | 3 +- examples/gui/src/ferris_xbm.rs | 4 +- examples/gui/src/main.rs | 16 +- 18 files changed, 693 insertions(+), 668 deletions(-) rename crates/{gui/src => flipperzero/src/gui}/gui.rs (98%) rename crates/{gui/src => flipperzero/src/gui}/icon.rs (95%) rename crates/{gui/src => flipperzero/src/gui}/icon_animation.rs (98%) rename crates/{gui/src => flipperzero/src/gui}/view.rs (96%) rename crates/{gui/src => flipperzero/src/gui}/view_port.rs (93%) rename crates/{gui/src => flipperzero/src/gui}/xbm.rs (96%) rename crates/{gui/src/input.rs => flipperzero/src/input/mod.rs} (98%) delete mode 100644 crates/gui/src/canvas.rs diff --git a/crates/flipperzero/Cargo.toml b/crates/flipperzero/Cargo.toml index 5bf6382a..441a552b 100644 --- a/crates/flipperzero/Cargo.toml +++ b/crates/flipperzero/Cargo.toml @@ -27,8 +27,14 @@ flipperzero-sys = { path = "../sys", version = "0.6.0" } flipperzero-rt = { path = "../rt", version = "0.6.0" } [features] -default = ["unstable_lints"] +default = ["unstable_lints", "unstable_intrinsics"] +# enables all optional services +all-services = ["service-gui"] # enables features requiring an allocator alloc = [] +# enables unstable Rust intrinsics +unstable_intrinsics = [] # enables unstable Rust lints on types provided by this crate unstable_lints = [] +# enables GUI APIs of Flipper +service-gui = ["alloc"] diff --git a/crates/flipperzero/src/gui/canvas.rs b/crates/flipperzero/src/gui/canvas.rs index 760f8500..4eb24576 100644 --- a/crates/flipperzero/src/gui/canvas.rs +++ b/crates/flipperzero/src/gui/canvas.rs @@ -1,6 +1,615 @@ //! Canvases. -use flipperzero_sys::{self as sys, Align as SysAlign}; +use crate::gui::{ + icon::Icon, + icon_animation::{IconAnimation, IconAnimationCallbacks}, + xbm::XbmImage, +}; +use core::{ + ffi::{c_char, CStr}, + marker::PhantomData, + num::NonZeroU8, + ops::Deref, + ptr::NonNull, +}; +use flipperzero_sys::Align as SysAlign; +use flipperzero_sys::{ + self as sys, Canvas as SysCanvas, CanvasDirection as SysCanvasDirection, + CanvasFontParameters as SysCanvasFontParameters, Color as SysColor, Font as SysFont, +}; + +/// System Canvas view. +pub struct CanvasView<'a> { + raw: NonNull, + _lifetime: PhantomData<&'a ()>, +} + +impl CanvasView<'_> { + /// Construct a `CanvasView` from a raw pointer. + /// + /// # Safety + /// + /// `raw` should be a valid non-null pointer to [`SysCanvas`] + /// and the lifetime should be outlived by `raw` validity scope. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// use flipperzero::gui::canvas::CanvasView; + /// + /// let ptr = todo!(); + /// let canvas = unsafe { CanvasView::from_raw(ptr) }; + /// ``` + pub unsafe fn from_raw(raw: *mut SysCanvas) -> Self { + Self { + // SAFETY: caller should provide a valid pointer + raw: unsafe { NonNull::new_unchecked(raw) }, + _lifetime: PhantomData, + } + } + + // FIXME: + // - canvas_reset + // - canvas_commit + + pub fn width(&self) -> NonZeroU8 { + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is always valid + unsafe { sys::canvas_width(raw) } + .try_into() + .expect("`canvas_width` should produce a positive value") + } + + pub fn height(&self) -> NonZeroU8 { + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is always valid + unsafe { sys::canvas_height(raw) } + .try_into() + .expect("`canvas_height` should produce a positive value") + } + + pub fn current_font_height(&self) -> NonZeroU8 { + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is always valid + unsafe { sys::canvas_current_font_height(raw) } + .try_into() + .expect("`canvas_current_font_height` should produce a positive value") + } + + pub fn get_font_params(&self, font: Font) -> CanvasFontParameters<'_> { + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is always valid + let font = font.into(); + // SAFETY: `raw` is always a valid pointer + // and `font` is guaranteed to be a valid value by `From` implementation + let raw = unsafe { NonNull::new_unchecked(sys::canvas_get_font_params(raw, font)) }; + CanvasFontParameters { raw, _parent: self } + } + + pub fn clear(&mut self) { + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is always valid + unsafe { sys::canvas_clear(raw) }; + } + + pub fn set_color(&mut self, color: Color) { + let raw = self.raw.as_ptr(); + let color = color.into(); + // SAFETY: `raw` is always valid + // and `font` is guaranteed to be a valid value by `From` implementation + unsafe { sys::canvas_set_color(raw, color) }; + } + + pub fn set_font_direction(&mut self, font_direction: CanvasDirection) { + let raw = self.raw.as_ptr(); + let font_direction = font_direction.into(); + // SAFETY: `raw` is always valid + // and `font_direction` is guaranteed to be a valid value by `From` implementation + unsafe { sys::canvas_set_font_direction(raw, font_direction) }; + } + + pub fn invert_color(&mut self) { + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is always valid + unsafe { sys::canvas_invert_color(raw) }; + } + + pub fn set_font(&mut self, font: Font) { + let raw = self.raw.as_ptr(); + let font = font.into(); + // SAFETY: `raw` is always valid + // and `font` is guaranteed to be a valid value by `From` implementation + unsafe { sys::canvas_set_font(raw, font) }; + } + + pub fn draw_str(&mut self, x: u8, y: u8, string: impl AsRef) { + let raw = self.raw.as_ptr(); + let string = string.as_ref().as_ptr(); + // SAFETY: `raw` is always valid + // and `string` is guaranteed to be a valid pointer since it was created from `CStr` + unsafe { sys::canvas_draw_str(raw, x, y, string) }; + } + + pub fn draw_str_aligned( + &mut self, + x: u8, + y: u8, + horizontal: Align, + vertical: Align, + str: impl AsRef, + ) { + let raw = self.raw.as_ptr(); + let horizontal = horizontal.into(); + let vertical = vertical.into(); + let str = str.as_ref().as_ptr(); + // SAFETY: `raw` is always valid, + // `horixontal` and `vertival` are guaranteed to be valid by `From` implementation + // and `text` is guaranteed to be a valid pointer since it was created from `CStr` + unsafe { sys::canvas_draw_str_aligned(raw, x, y, horizontal, vertical, str) }; + } + + // note: for some reason, this mutates internal state + pub fn string_width(&mut self, string: impl AsRef) -> u16 { + let raw = self.raw.as_ptr(); + let string = string.as_ref().as_ptr(); + // SAFETY: `raw` is always valid + // and `string` is guaranteed to be a valid pointer since it was created from `CStr` + unsafe { sys::canvas_string_width(raw, string) } + } + + // note: for some reason, this mutates internal state + pub fn glyph_width(&mut self, glyph: c_char) -> u8 { + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is always valid + unsafe { sys::canvas_glyph_width(raw, glyph) } + } + + // TODO `canvas_draw_bitmap` compressed bitmap support + + // TODO: do we need range checks? + pub fn draw_icon_animation<'a, 'b: 'a>( + &'a mut self, + x: u8, + y: u8, + icon_animation: &'b IconAnimation<'_, impl IconAnimationCallbacks>, + ) { + let raw = self.raw.as_ptr(); + let icon_animation = icon_animation.as_raw(); + // SAFETY: `raw` is always valid + // and `icon_animation` is always valid and outlives this canvas view + unsafe { sys::canvas_draw_icon_animation(raw, x, y, icon_animation) } + } + + // TODO: do we need range checks? + pub fn draw_icon<'a, 'b: 'a>(&'a mut self, x: u8, y: u8, animation: &'b Icon) { + let raw = self.raw.as_ptr(); + let icon = animation.as_raw(); + // SAFETY: `raw` is always valid + // and `icon` is always valid and outlives this canvas view + unsafe { sys::canvas_draw_icon(raw, x, y, icon) } + } + + // TODO: do we need other range checks? + // what is the best return type? + pub fn draw_xbm( + &mut self, + x: u8, + y: u8, + xbm: &XbmImage>, + ) -> Option<()> { + let raw = self.raw.as_ptr(); + let width = xbm.width(); + let height = xbm.height(); + + // ensure that the image is not too big + let _ = x.checked_add(width)?; + let _ = y.checked_add(height)?; + + let data = xbm.data().as_ptr(); + + // SAFETY: `raw` is always valid + // and `data` is always valid and does not have to outlive the view + // as it is copied + unsafe { sys::canvas_draw_xbm(raw, x, y, width, height, data) }; + Some(()) + } + + // TODO: + // - `canvas_draw_icon` icon lifetimes + + // TODO: decide if we want to pack x-y pairs into tuples + + pub fn draw_dot(&mut self, x: u8, y: u8) { + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is always valid + unsafe { sys::canvas_draw_dot(raw, x, y) } + } + + // TODO: do we need range checks? + // TODO: do `width` and `height` have to be non-zero + pub fn draw_box(&mut self, x: u8, y: u8, width: u8, height: u8) { + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is always valid + unsafe { sys::canvas_draw_box(raw, x, y, width, height) } + } + + // TODO: do we need range checks? + // TODO: do `width` and `height` have to be non-zero + pub fn draw_frame(&mut self, x: u8, y: u8, width: u8, height: u8) { + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is always valid + unsafe { sys::canvas_draw_frame(raw, x, y, width, height) } + } + + // TODO: do we need range checks? + // TODO: do `x2` and `y2` have to be non-zero + pub fn draw_line(&mut self, x1: u8, y1: u8, x2: u8, y2: u8) { + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is always valid + unsafe { sys::canvas_draw_line(raw, x1, y1, x2, y2) } + } + + // TODO: do we need range checks? + // TODO: does `radius` have to be non-zero + pub fn draw_circle(&mut self, x: u8, y: u8, radius: u8) { + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is always valid + unsafe { sys::canvas_draw_circle(raw, x, y, radius) } + } + + // TODO: do we need range checks? + // TODO: does `radius` have to be non-zero + pub fn draw_disc(&mut self, x: u8, y: u8, radius: u8) { + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is always valid + unsafe { sys::canvas_draw_disc(raw, x, y, radius) } + } + + // TODO: do we need range checks? + // TODO: do `base` and `height` have to be non-zero + pub fn draw_triangle( + &mut self, + x: u8, + y: u8, + base: u8, + height: u8, + direction: CanvasDirection, + ) { + let raw = self.raw.as_ptr(); + let direction = direction.into(); + // SAFETY: `raw` is always valid + // and `direction` is guaranteed to be valid by `From` implementation + unsafe { sys::canvas_draw_triangle(raw, x, y, base, height, direction) } + } + + // TODO: do we need range checks? + // TODO: does `character` have to be of a wrapper type + pub fn draw_glyph(&mut self, x: u8, y: u8, character: u16) { + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is always valid, + unsafe { sys::canvas_draw_glyph(raw, x, y, character) } + } + + pub fn set_bitmap_mode(&mut self, alpha: bool) { + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is always valid, + unsafe { sys::canvas_set_bitmap_mode(raw, alpha) } + } + + // TODO: do we need range checks? + // TODO: do `width`, `height` and `radius` have to be non-zero + pub fn draw_rframe(&mut self, x: u8, y: u8, width: u8, height: u8, radius: u8) { + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is always valid, + unsafe { sys::canvas_draw_rframe(raw, x, y, width, height, radius) } + } + + // TODO: do we need range checks? + // TODO: do `width`, `height` and `radius` have to be non-zero + pub fn draw_rbox(&mut self, x: u8, y: u8, width: u8, height: u8, radius: u8) { + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is always valid, + unsafe { sys::canvas_draw_rbox(raw, x, y, width, height, radius) } + } +} + +pub struct CanvasFontParameters<'a> { + raw: NonNull, + _parent: &'a CanvasView<'a>, +} + +impl<'a> CanvasFontParameters<'a> { + fn leading_default(&self) -> NonZeroU8 { + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is always valid and this allways outlives its parent + unsafe { *raw } + .leading_default + .try_into() + .expect("`leading_default` should always be positive") + } + + fn leading_min(&self) -> NonZeroU8 { + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is always valid and this allways outlives its parent + unsafe { *raw } + .leading_min + .try_into() + .expect("`leading_min` should always be positive") + } + + fn height(&self) -> NonZeroU8 { + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is always valid and this allways outlives its parent + unsafe { *raw } + .height + .try_into() + .expect("`height` should always be positive") + } + + fn descender(&self) -> u8 { + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is always valid and this allways outlives its parent + unsafe { *raw }.descender + } + + fn snapshot(&self) -> CanvasFontParametersSnapshot { + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is always valid and this allways outlives its parent + unsafe { *raw } + .try_into() + .expect("raw `CanvasFontParameters` should be valid") + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub struct CanvasFontParametersSnapshot { + leading_default: NonZeroU8, + leading_min: NonZeroU8, + height: NonZeroU8, + descender: u8, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub enum FromSysGuiLayerError { + ZeroLeadingDefault, + ZeroLeadingMin, + ZeroHeight, +} + +impl TryFrom for CanvasFontParametersSnapshot { + type Error = FromSysGuiLayerError; + + fn try_from(value: SysCanvasFontParameters) -> Result { + Ok(Self { + leading_default: value + .leading_default + .try_into() + .or(Err(Self::Error::ZeroLeadingDefault))?, + leading_min: value + .leading_min + .try_into() + .or(Err(Self::Error::ZeroLeadingMin))?, + height: value.height.try_into().or(Err(Self::Error::ZeroHeight))?, + descender: value.descender, + }) + } +} + +impl From for SysCanvasFontParameters { + fn from(value: CanvasFontParametersSnapshot) -> Self { + Self { + leading_default: value.leading_default.into(), + leading_min: value.leading_min.into(), + height: value.height.into(), + descender: value.descender, + } + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub enum Color { + White, + Black, + // TDOO: add this color + // Xor, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub enum FromSysColor { + Invalid(SysColor), +} + +impl TryFrom for Color { + type Error = FromSysColor; + + fn try_from(value: SysColor) -> Result { + use sys::{ + Color_ColorBlack as SYS_COLOR_BLACK, + Color_ColorWhite as SYS_COLOR_WHITE, + // Color_ColorXOR as SYS_COLOR_XOR, + }; + + Ok(match value { + SYS_COLOR_WHITE => Self::White, + SYS_COLOR_BLACK => Self::Black, + // SYS_COLOR_XOR => Ok(Self::Xor), + invalid => Err(Self::Error::Invalid(invalid))?, + }) + } +} + +impl From for SysColor { + fn from(value: Color) -> Self { + use sys::{ + Color_ColorBlack as SYS_COLOR_BLACK, + Color_ColorWhite as SYS_COLOR_WHITE, + // Color_ColorXOR as SYS_COLOR_XOR, + }; + + match value { + Color::White => SYS_COLOR_WHITE, + Color::Black => SYS_COLOR_BLACK, + // Color::Xor => SYS_COLOR_XOR, + } + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub enum Font { + Primary, + Secondary, + Keyboard, + BigNumbers, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub enum FromSysFont { + TotalNumber, + Invalid(SysFont), +} + +impl TryFrom for Font { + type Error = FromSysFont; + + fn try_from(value: SysFont) -> Result { + use sys::{ + Font_FontBigNumbers as SYS_FONT_BIG_NUMBERS, Font_FontKeyboard as SYS_FONT_KEYBOARD, + Font_FontPrimary as SYS_FONT_PRIMARY, Font_FontSecondary as SYS_FONT_SECONDARY, + Font_FontTotalNumber as SYS_FONT_TOTAL_NUMBER, + }; + + Ok(match value { + SYS_FONT_PRIMARY => Self::Primary, + SYS_FONT_SECONDARY => Self::Secondary, + SYS_FONT_KEYBOARD => Self::Keyboard, + SYS_FONT_BIG_NUMBERS => Self::BigNumbers, + SYS_FONT_TOTAL_NUMBER => Err(Self::Error::TotalNumber)?, + invalid => Err(Self::Error::Invalid(invalid))?, + }) + } +} + +impl From for SysFont { + fn from(value: Font) -> Self { + use sys::{ + Font_FontBigNumbers as SYS_FONT_BIG_NUMBERS, Font_FontKeyboard as SYS_FONT_KEYBOARD, + Font_FontPrimary as SYS_FONT_PRIMARY, Font_FontSecondary as SYS_FONT_SECONDARY, + }; + + match value { + Font::Primary => SYS_FONT_PRIMARY, + Font::Secondary => SYS_FONT_SECONDARY, + Font::Keyboard => SYS_FONT_KEYBOARD, + Font::BigNumbers => SYS_FONT_BIG_NUMBERS, + } + } +} + +// #[derive(Clone, Copy, Debug)] +// pub enum CanvasOrientation { +// Horizontal, +// HorizontalFlip, +// Vertical, +// VerticalFlip, +// } +// +// #[derive(Clone, Copy, Debug)] +// pub enum FromSysCanvasOrientationError { +// Invalid(SysCanvasOrientation), +// } +// +// impl TryFrom for CanvasOrientation { +// type Error = FromSysCanvasOrientationError; +// +// fn try_from(value: SysCanvasOrientation) -> Result { +// use sys::{ +// CanvasOrientation_CanvasOrientationHorizontal as SYS_CANVAS_ORIENTATION_HORIZONTAL, +// CanvasOrientation_CanvasOrientationHorizontalFlip as SYS_CANVAS_ORIENTATION_HORIZONTAL_FLIP, +// CanvasOrientation_CanvasOrientationVertical as SYS_CANVAS_ORIENTATION_VERTICAL, +// CanvasOrientation_CanvasOrientationVerticalFlip as SYS_CANVAS_ORIENTATION_VERTICAL_FLIP, +// }; +// +// Ok(match value { +// SYS_CANVAS_ORIENTATION_HORIZONTAL => Self::Horizontal, +// SYS_CANVAS_ORIENTATION_HORIZONTAL_FLIP => Self::HorizontalFlip, +// SYS_CANVAS_ORIENTATION_VERTICAL => Self::Vertical, +// SYS_CANVAS_ORIENTATION_VERTICAL_FLIP => Self::VerticalFlip, +// invalid => Err(Self::Error::Invalid(invalid))?, +// }) +// } +// } +// +// impl From for SysCanvasOrientation { +// fn from(value: CanvasOrientation) -> Self { +// use sys::{ +// CanvasOrientation_CanvasOrientationHorizontal as SYS_CANVAS_ORIENTATION_HORIZONTAL, +// CanvasOrientation_CanvasOrientationHorizontalFlip as SYS_CANVAS_ORIENTATION_HORIZONTAL_FLIP, +// CanvasOrientation_CanvasOrientationVertical as SYS_CANVAS_ORIENTATION_VERTICAL, +// CanvasOrientation_CanvasOrientationVerticalFlip as SYS_CANVAS_ORIENTATION_VERTICAL_FLIP, +// }; +// +// match value { +// CanvasOrientation::Horizontal => SYS_CANVAS_ORIENTATION_HORIZONTAL, +// CanvasOrientation::HorizontalFlip => SYS_CANVAS_ORIENTATION_HORIZONTAL_FLIP, +// CanvasOrientation::Vertical => SYS_CANVAS_ORIENTATION_VERTICAL, +// CanvasOrientation::VerticalFlip => SYS_CANVAS_ORIENTATION_VERTICAL_FLIP, +// } +// } +// } + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub enum CanvasDirection { + LeftToRight, + TopToBottom, + RightToLeft, + BottomToTop, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub enum FromSysCanvasDirectionError { + Invalid(SysCanvasDirection), +} + +impl TryFrom for CanvasDirection { + type Error = FromSysCanvasDirectionError; + + fn try_from(value: SysCanvasDirection) -> Result { + use sys::{ + CanvasDirection_CanvasDirectionBottomToTop as SYS_CANVAS_DIRECTION_BOTTOM_TO_TOP, + CanvasDirection_CanvasDirectionLeftToRight as SYS_CANVAS_DIRECTION_LEFT_TO_RIGHT, + CanvasDirection_CanvasDirectionRightToLeft as SYS_CANVAS_DIRECTION_RIGHT_TO_LEFT, + CanvasDirection_CanvasDirectionTopToBottom as SYS_CANVAS_DIRECTION_TOP_TO_BOTTOM, + }; + + Ok(match value { + SYS_CANVAS_DIRECTION_LEFT_TO_RIGHT => Self::LeftToRight, + SYS_CANVAS_DIRECTION_TOP_TO_BOTTOM => Self::TopToBottom, + SYS_CANVAS_DIRECTION_RIGHT_TO_LEFT => Self::RightToLeft, + SYS_CANVAS_DIRECTION_BOTTOM_TO_TOP => Self::BottomToTop, + invalid => Err(Self::Error::Invalid(invalid))?, + }) + } +} + +impl From for SysCanvasDirection { + fn from(value: CanvasDirection) -> Self { + use sys::{ + CanvasDirection_CanvasDirectionBottomToTop as SYS_CANVAS_DIRECTION_BOTTOM_TO_TOP, + CanvasDirection_CanvasDirectionLeftToRight as SYS_CANVAS_DIRECTION_LEFT_TO_RIGHT, + CanvasDirection_CanvasDirectionRightToLeft as SYS_CANVAS_DIRECTION_RIGHT_TO_LEFT, + CanvasDirection_CanvasDirectionTopToBottom as SYS_CANVAS_DIRECTION_TOP_TO_BOTTOM, + }; + + match value { + CanvasDirection::BottomToTop => SYS_CANVAS_DIRECTION_BOTTOM_TO_TOP, + CanvasDirection::LeftToRight => SYS_CANVAS_DIRECTION_LEFT_TO_RIGHT, + CanvasDirection::RightToLeft => SYS_CANVAS_DIRECTION_RIGHT_TO_LEFT, + CanvasDirection::TopToBottom => SYS_CANVAS_DIRECTION_TOP_TO_BOTTOM, + } + } +} #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum Align { diff --git a/crates/gui/src/gui.rs b/crates/flipperzero/src/gui/gui.rs similarity index 98% rename from crates/gui/src/gui.rs rename to crates/flipperzero/src/gui/gui.rs index 485e744f..5b11a629 100644 --- a/crates/gui/src/gui.rs +++ b/crates/flipperzero/src/gui/gui.rs @@ -1,9 +1,11 @@ //! GUI APIs use crate::{ - canvas::CanvasView, + gui::{ + canvas::CanvasView, + view_port::{ViewPort, ViewPortCallbacks}, + }, input::InputEvent, - view_port::{ViewPort, ViewPortCallbacks}, }; use core::{ffi::c_char, fmt::Debug}; use flipperzero_sys::{self as sys, furi::UnsafeRecord, Gui as SysGui, GuiLayer as SysGuiLayer}; diff --git a/crates/gui/src/icon.rs b/crates/flipperzero/src/gui/icon.rs similarity index 95% rename from crates/gui/src/icon.rs rename to crates/flipperzero/src/gui/icon.rs index 51fb6a63..a633f250 100644 --- a/crates/gui/src/icon.rs +++ b/crates/flipperzero/src/gui/icon.rs @@ -1,4 +1,4 @@ -use crate::xbm::XbmImage; +use crate::gui::xbm::XbmImage; use core::ptr::NonNull; use flipperzero_sys::{self as sys, Icon as SysIcon}; @@ -18,7 +18,7 @@ impl Icon { /// Basic usage: /// /// ``` - /// use flipperzero_gui::icon::Icon; + /// use flipperzero::gui::icon::Icon; /// /// let ptr = todo!(); /// let canvas = unsafe { Icon::from_raw(ptr) }; diff --git a/crates/gui/src/icon_animation.rs b/crates/flipperzero/src/gui/icon_animation.rs similarity index 98% rename from crates/gui/src/icon_animation.rs rename to crates/flipperzero/src/gui/icon_animation.rs index e472bf29..2074e01f 100644 --- a/crates/gui/src/icon_animation.rs +++ b/crates/flipperzero/src/gui/icon_animation.rs @@ -1,4 +1,4 @@ -use crate::icon::Icon; +use crate::gui::icon::Icon; use alloc::boxed::Box; use core::{ ffi::c_void, @@ -123,7 +123,7 @@ impl IconAnimationView<'_> { /// Basic usage: /// /// ``` - /// use flipperzero_gui::icon_animation::IconAnimationView; + /// use flipperzero::gui::icon_animation::IconAnimationView; /// /// let ptr = todo!(); /// let icon_anumation = unsafe { IconAnimationView::from_raw(ptr) }; diff --git a/crates/flipperzero/src/gui/mod.rs b/crates/flipperzero/src/gui/mod.rs index d4f767e8..63c3d8f6 100644 --- a/crates/flipperzero/src/gui/mod.rs +++ b/crates/flipperzero/src/gui/mod.rs @@ -1,3 +1,9 @@ //! GUI service. pub mod canvas; +pub mod gui; +pub mod icon; +pub mod icon_animation; +pub mod view; +pub mod view_port; +pub mod xbm; diff --git a/crates/gui/src/view.rs b/crates/flipperzero/src/gui/view.rs similarity index 96% rename from crates/gui/src/view.rs rename to crates/flipperzero/src/gui/view.rs index 4ba08a13..bde6974c 100644 --- a/crates/gui/src/view.rs +++ b/crates/flipperzero/src/gui/view.rs @@ -12,7 +12,7 @@ impl View { /// Basic usage: /// /// ``` - /// use flipperzero_gui::view::View; + /// use flipperzero::gui::view::View; /// /// let view = View::new(); /// ``` diff --git a/crates/gui/src/view_port.rs b/crates/flipperzero/src/gui/view_port.rs similarity index 93% rename from crates/gui/src/view_port.rs rename to crates/flipperzero/src/gui/view_port.rs index 9f6d4add..40c7a536 100644 --- a/crates/gui/src/view_port.rs +++ b/crates/flipperzero/src/gui/view_port.rs @@ -1,6 +1,6 @@ //! ViewPort APIs -use crate::{canvas::CanvasView, input::InputEvent}; +use crate::{gui::canvas::CanvasView, input::InputEvent}; use alloc::boxed::Box; use core::{ ffi::c_void, @@ -26,7 +26,7 @@ impl ViewPort { /// Basic usage: /// /// ``` - /// use flipperzero_gui::view_port::ViewPort; + /// use flipperzero::gui::view_port::ViewPort; /// /// let view_port = ViewPort::new(()); /// ``` @@ -106,7 +106,7 @@ impl ViewPort { /// /// ``` /// use std::num::NonZeroU8; - /// use flipperzero_gui::view_port::ViewPort; + /// use flipperzero::gui::view_port::ViewPort; /// /// let mut view_port = ViewPort::new(()); /// view_port.set_width(NonZeroU8::new(128u8)); @@ -115,7 +115,7 @@ impl ViewPort { /// Resize `ViewPort` to automatically selected width: /// /// ``` - /// use flipperzero_gui::view_port::ViewPort; + /// use flipperzero::gui::view_port::ViewPort; /// /// let mut view_port = ViewPort::new(()); /// view_port.set_width(None); @@ -135,7 +135,7 @@ impl ViewPort { /// Basic usage: /// /// ``` - /// use flipperzero_gui::view_port::ViewPort; + /// use flipperzero::gui::view_port::ViewPort; /// /// let view_port = ViewPort::new(()); /// let width = view_port.get_width(); @@ -155,7 +155,7 @@ impl ViewPort { /// /// ``` /// use std::num::NonZeroU8; - /// use flipperzero_gui::view_port::ViewPort; + /// use flipperzero::gui::view_port::ViewPort; /// /// let mut view_port = ViewPort::new(()); /// view_port.set_height(NonZeroU8::new(128u8)); @@ -164,7 +164,7 @@ impl ViewPort { /// Resize `ViewPort` to automatically selected height: /// /// ``` - /// use flipperzero_gui::view_port::ViewPort; + /// use flipperzero::gui::view_port::ViewPort; /// /// let mut view_port = ViewPort::new(()); /// view_port.set_height(None); @@ -184,7 +184,7 @@ impl ViewPort { /// Basic usage: /// /// ``` - /// use flipperzero_gui::view_port::ViewPort; + /// use flipperzero::gui::view_port::ViewPort; /// /// let view_port = ViewPort::new(()); /// let height = view_port.get_height(); @@ -204,7 +204,7 @@ impl ViewPort { /// /// ``` /// use std::num::NonZeroU8; - /// use flipperzero_gui::view_port::ViewPort; + /// use flipperzero::gui::view_port::ViewPort; /// /// let mut view_port = ViewPort::new(()); /// view_port.set_dimensions(Some((NonZeroU8::new(120).unwrap(), NonZeroU8::new(80).unwrap()))); @@ -213,7 +213,7 @@ impl ViewPort { /// Resize `ViewPort` to automatically selected dimensions: /// /// ``` - /// use flipperzero_gui::view_port::ViewPort; + /// use flipperzero::gui::view_port::ViewPort; /// /// let mut view_port = ViewPort::new(()); /// view_port.set_dimensions(None); @@ -238,7 +238,7 @@ impl ViewPort { /// Basic usage: /// /// ``` - /// use flipperzero_gui::view_port::ViewPort; + /// use flipperzero::gui::view_port::ViewPort; /// /// let view_port = ViewPort::new(()); /// let (width, height) = view_port.get_dimensions(); @@ -254,7 +254,7 @@ impl ViewPort { /// Basic usage: /// /// ``` - /// use flipperzero_gui::view_port::{ViewPort, ViewPortOrientation}; + /// use flipperzero::gui::view_port::{ViewPort, ViewPortOrientation}; /// let mut view_port = ViewPort::new(()); /// view_port.set_orientation(ViewPortOrientation::Vertical); /// ``` @@ -275,7 +275,7 @@ impl ViewPort { /// /// ``` /// use std::num::NonZeroU8; - /// use flipperzero_gui::view_port::{ViewPort, ViewPortOrientation}; + /// use flipperzero::gui::view_port::{ViewPort, ViewPortOrientation}; /// /// let mut view_port = ViewPort::new(()); /// let orientation = view_port.get_orientation(); @@ -297,7 +297,7 @@ impl ViewPort { /// Basic usage: /// /// ``` - /// use flipperzero_gui::view_port::ViewPort; + /// use flipperzero::gui::view_port::ViewPort; /// /// let mut view_port = ViewPort::new(()); /// view_port.set_enabled(false); @@ -316,7 +316,7 @@ impl ViewPort { /// /// /// ``` - /// use flipperzero_gui::view_port::ViewPort; + /// use flipperzero::gui::view_port::ViewPort; /// /// let mut view_port = ViewPort::new(()); /// let enabled = view_port.is_enabled(); diff --git a/crates/gui/src/xbm.rs b/crates/flipperzero/src/gui/xbm.rs similarity index 96% rename from crates/gui/src/xbm.rs rename to crates/flipperzero/src/gui/xbm.rs index 28321835..d377e1b8 100644 --- a/crates/gui/src/xbm.rs +++ b/crates/flipperzero/src/gui/xbm.rs @@ -1,9 +1,10 @@ //! User-friendly wrappers of XDM images. -use alloc::vec; -use alloc::vec::Vec; -use core::ops::{Deref, DerefMut}; -use core::slice; +use alloc::{vec, vec::Vec}; +use core::{ + ops::{Deref, DerefMut}, + slice, +}; #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct XbmImage { @@ -32,7 +33,7 @@ impl XbmImage { #[inline] const fn bits_to_min_required_bytes(bits: u16) -> u16 { - bits.div_ceil(8) + crate::internals::ops::div_ceil_u16(bits, 8) } #[inline] @@ -188,7 +189,7 @@ impl XbmImage<&'static [u8]> { /// Basic usage: /// /// ```rust - /// use flipperzero_gui::xbm::XbmImage; + /// use flipperzero::gui::xbm::XbmImage; /// /// const IMAGE: XbmImage<&'static [u8]> = XbmImage::new_from_static(4, 4, &[0xFE, 0x12]); /// ``` @@ -216,7 +217,7 @@ impl XbmImage> { /// Basic usage: /// /// ```rust - /// use flipperzero_gui::xbm::XbmImage; + /// use flipperzero::gui::xbm::XbmImage; /// /// const IMAGE: XbmImage<[u8; 2]> = XbmImage::new_from_array::<4, 4>([0xFE, 0x12]); /// ``` @@ -263,7 +264,7 @@ macro_rules! xbm { $($byte:literal),* $(,)? }; ) => {{ - $crate::xbm::XbmImage::new_from_array::<$width, $height>([$($byte,)*]) + $crate::gui::xbm::XbmImage::new_from_array::<$width, $height>([$($byte,)*]) }}; } diff --git a/crates/gui/src/input.rs b/crates/flipperzero/src/input/mod.rs similarity index 98% rename from crates/gui/src/input.rs rename to crates/flipperzero/src/input/mod.rs index 58684b72..1df54ab2 100644 --- a/crates/gui/src/input.rs +++ b/crates/flipperzero/src/input/mod.rs @@ -1,5 +1,4 @@ use core::ffi::CStr; -// FIXME: in flipperzero-firmware, this is a separate service use flipperzero_sys::{ self as sys, InputEvent as SysInputEvent, InputKey as SysInputKey, InputType as SysInputType, }; diff --git a/crates/flipperzero/src/internals.rs b/crates/flipperzero/src/internals.rs index 50b14a90..6e4a5167 100644 --- a/crates/flipperzero/src/internals.rs +++ b/crates/flipperzero/src/internals.rs @@ -46,3 +46,25 @@ const _: () = { "`PhantomData` should be a ZST" ); }; + +/// Operations which have unstable implementations +/// but still may be implemented manually on `stable` channel. +/// +/// This will use core implementations if `unstable_intrinsics` feature is enabled +/// falling back to ad-hoc implementations otherwise. +pub(crate) mod ops { + pub const fn div_ceil_u16(divident: u16, divisor: u16) -> u16 { + #[cfg(feature = "unstable_intrinsics")] + { + divident.div_ceil(divisor) + } + #[cfg(not(feature = "unstable_intrinsics"))] + { + if divident % divisor == 0 { + divident / divisor + } else { + divident / divisor + 1 + } + } + } +} diff --git a/crates/flipperzero/src/lib.rs b/crates/flipperzero/src/lib.rs index 432d1cc8..f27c525e 100644 --- a/crates/flipperzero/src/lib.rs +++ b/crates/flipperzero/src/lib.rs @@ -1,6 +1,7 @@ //! High-level bindings for the Flipper Zero. #![no_std] +#![cfg_attr(feature = "unstable_intrinsics", feature(int_roundings))] #![cfg_attr(feature = "unstable_lints", feature(must_not_suspend))] #[cfg(feature = "alloc")] @@ -8,7 +9,9 @@ extern crate alloc; pub mod dialogs; pub mod furi; +#[cfg(feature = "service-gui")] pub mod gui; +pub mod input; pub(crate) mod internals; pub mod kernel; pub mod macros; diff --git a/crates/gui/src/canvas.rs b/crates/gui/src/canvas.rs deleted file mode 100644 index d5ae866b..00000000 --- a/crates/gui/src/canvas.rs +++ /dev/null @@ -1,606 +0,0 @@ -//! ViewPort APIs - -use crate::icon::Icon; -use crate::icon_animation::{IconAnimation, IconAnimationCallbacks}; -use crate::xbm::XbmImage; -use core::ffi::c_char; -use core::ops::Deref; -use core::{ffi::CStr, marker::PhantomData, num::NonZeroU8, ptr::NonNull}; -use flipperzero::gui::canvas::Align; -use flipperzero_sys::{ - self as sys, Canvas as SysCanvas, CanvasDirection as SysCanvasDirection, - CanvasFontParameters as SysCanvasFontParameters, Color as SysColor, Font as SysFont, -}; - -/// System Canvas view. -pub struct CanvasView<'a> { - raw: NonNull, - _lifetime: PhantomData<&'a ()>, -} - -impl CanvasView<'_> { - /// Construct a `CanvasView` from a raw pointer. - /// - /// # Safety - /// - /// `raw` should be a valid non-null pointer to [`SysCanvas`] - /// and the lifetime should be outlived by `raw` validity scope. - /// - /// # Examples - /// - /// Basic usage: - /// - /// ``` - /// use flipperzero_gui::canvas::CanvasView; - /// - /// let ptr = todo!(); - /// let canvas = unsafe { CanvasView::from_raw(ptr) }; - /// ``` - pub unsafe fn from_raw(raw: *mut SysCanvas) -> Self { - Self { - // SAFETY: caller should provide a valid pointer - raw: unsafe { NonNull::new_unchecked(raw) }, - _lifetime: PhantomData, - } - } - - // FIXME: - // - canvas_reset - // - canvas_commit - - pub fn width(&self) -> NonZeroU8 { - let raw = self.raw.as_ptr(); - // SAFETY: `raw` is always valid - unsafe { sys::canvas_width(raw) } - .try_into() - .expect("`canvas_width` should produce a positive value") - } - - pub fn height(&self) -> NonZeroU8 { - let raw = self.raw.as_ptr(); - // SAFETY: `raw` is always valid - unsafe { sys::canvas_height(raw) } - .try_into() - .expect("`canvas_height` should produce a positive value") - } - - pub fn current_font_height(&self) -> NonZeroU8 { - let raw = self.raw.as_ptr(); - // SAFETY: `raw` is always valid - unsafe { sys::canvas_current_font_height(raw) } - .try_into() - .expect("`canvas_current_font_height` should produce a positive value") - } - - pub fn get_font_params(&self, font: Font) -> CanvasFontParameters<'_> { - let raw = self.raw.as_ptr(); - // SAFETY: `raw` is always valid - let font = font.into(); - // SAFETY: `raw` is always a valid pointer - // and `font` is guaranteed to be a valid value by `From` implementation - let raw = unsafe { NonNull::new_unchecked(sys::canvas_get_font_params(raw, font)) }; - CanvasFontParameters { raw, _parent: self } - } - - pub fn clear(&mut self) { - let raw = self.raw.as_ptr(); - // SAFETY: `raw` is always valid - unsafe { sys::canvas_clear(raw) }; - } - - pub fn set_color(&mut self, color: Color) { - let raw = self.raw.as_ptr(); - let color = color.into(); - // SAFETY: `raw` is always valid - // and `font` is guaranteed to be a valid value by `From` implementation - unsafe { sys::canvas_set_color(raw, color) }; - } - - pub fn set_font_direction(&mut self, font_direction: CanvasDirection) { - let raw = self.raw.as_ptr(); - let font_direction = font_direction.into(); - // SAFETY: `raw` is always valid - // and `font_direction` is guaranteed to be a valid value by `From` implementation - unsafe { sys::canvas_set_font_direction(raw, font_direction) }; - } - - pub fn invert_color(&mut self) { - let raw = self.raw.as_ptr(); - // SAFETY: `raw` is always valid - unsafe { sys::canvas_invert_color(raw) }; - } - - pub fn set_font(&mut self, font: Font) { - let raw = self.raw.as_ptr(); - let font = font.into(); - // SAFETY: `raw` is always valid - // and `font` is guaranteed to be a valid value by `From` implementation - unsafe { sys::canvas_set_font(raw, font) }; - } - - pub fn draw_str(&mut self, x: u8, y: u8, string: impl AsRef) { - let raw = self.raw.as_ptr(); - let string = string.as_ref().as_ptr(); - // SAFETY: `raw` is always valid - // and `string` is guaranteed to be a valid pointer since it was created from `CStr` - unsafe { sys::canvas_draw_str(raw, x, y, string) }; - } - - pub fn draw_str_aligned( - &mut self, - x: u8, - y: u8, - horizontal: Align, - vertical: Align, - str: impl AsRef, - ) { - let raw = self.raw.as_ptr(); - let horizontal = horizontal.into(); - let vertical = vertical.into(); - let str = str.as_ref().as_ptr(); - // SAFETY: `raw` is always valid, - // `horixontal` and `vertival` are guaranteed to be valid by `From` implementation - // and `text` is guaranteed to be a valid pointer since it was created from `CStr` - unsafe { sys::canvas_draw_str_aligned(raw, x, y, horizontal, vertical, str) }; - } - - // note: for some reason, this mutates internal state - pub fn string_width(&mut self, string: impl AsRef) -> u16 { - let raw = self.raw.as_ptr(); - let string = string.as_ref().as_ptr(); - // SAFETY: `raw` is always valid - // and `string` is guaranteed to be a valid pointer since it was created from `CStr` - unsafe { sys::canvas_string_width(raw, string) } - } - - // note: for some reason, this mutates internal state - pub fn glyph_width(&mut self, glyph: c_char) -> u8 { - let raw = self.raw.as_ptr(); - // SAFETY: `raw` is always valid - unsafe { sys::canvas_glyph_width(raw, glyph) } - } - - // TODO `canvas_draw_bitmap` compressed bitmap support - - // TODO: do we need range checks? - pub fn draw_icon_animation<'a, 'b: 'a>( - &'a mut self, - x: u8, - y: u8, - icon_animation: &'b IconAnimation<'_, impl IconAnimationCallbacks>, - ) { - let raw = self.raw.as_ptr(); - let icon_animation = icon_animation.as_raw(); - // SAFETY: `raw` is always valid - // and `icon_animation` is always valid and outlives this canvas view - unsafe { sys::canvas_draw_icon_animation(raw, x, y, icon_animation) } - } - - // TODO: do we need range checks? - pub fn draw_icon<'a, 'b: 'a>(&'a mut self, x: u8, y: u8, animation: &'b Icon) { - let raw = self.raw.as_ptr(); - let icon = animation.as_raw(); - // SAFETY: `raw` is always valid - // and `icon` is always valid and outlives this canvas view - unsafe { sys::canvas_draw_icon(raw, x, y, icon) } - } - - // TODO: do we need other range checks? - // what is the best return type? - pub fn draw_xbm( - &mut self, - x: u8, - y: u8, - xbm: &XbmImage>, - ) -> Option<()> { - let raw = self.raw.as_ptr(); - let width = xbm.width(); - let height = xbm.height(); - - // ensure that the image is not too big - let _ = x.checked_add(width)?; - let _ = y.checked_add(height)?; - - let data = xbm.data().as_ptr(); - - // SAFETY: `raw` is always valid - // and `data` is always valid and does not have to outlive the view - // as it is copied - unsafe { sys::canvas_draw_xbm(raw, x, y, width, height, data) }; - Some(()) - } - - // TODO: - // - `canvas_draw_icon` icon lifetimes - - // TODO: decide if we want to pack x-y pairs into tuples - - pub fn draw_dot(&mut self, x: u8, y: u8) { - let raw = self.raw.as_ptr(); - // SAFETY: `raw` is always valid - unsafe { sys::canvas_draw_dot(raw, x, y) } - } - - // TODO: do we need range checks? - // TODO: do `width` and `height` have to be non-zero - pub fn draw_box(&mut self, x: u8, y: u8, width: u8, height: u8) { - let raw = self.raw.as_ptr(); - // SAFETY: `raw` is always valid - unsafe { sys::canvas_draw_box(raw, x, y, width, height) } - } - - // TODO: do we need range checks? - // TODO: do `width` and `height` have to be non-zero - pub fn draw_frame(&mut self, x: u8, y: u8, width: u8, height: u8) { - let raw = self.raw.as_ptr(); - // SAFETY: `raw` is always valid - unsafe { sys::canvas_draw_frame(raw, x, y, width, height) } - } - - // TODO: do we need range checks? - // TODO: do `x2` and `y2` have to be non-zero - pub fn draw_line(&mut self, x1: u8, y1: u8, x2: u8, y2: u8) { - let raw = self.raw.as_ptr(); - // SAFETY: `raw` is always valid - unsafe { sys::canvas_draw_line(raw, x1, y1, x2, y2) } - } - - // TODO: do we need range checks? - // TODO: does `radius` have to be non-zero - pub fn draw_circle(&mut self, x: u8, y: u8, radius: u8) { - let raw = self.raw.as_ptr(); - // SAFETY: `raw` is always valid - unsafe { sys::canvas_draw_circle(raw, x, y, radius) } - } - - // TODO: do we need range checks? - // TODO: does `radius` have to be non-zero - pub fn draw_disc(&mut self, x: u8, y: u8, radius: u8) { - let raw = self.raw.as_ptr(); - // SAFETY: `raw` is always valid - unsafe { sys::canvas_draw_disc(raw, x, y, radius) } - } - - // TODO: do we need range checks? - // TODO: do `base` and `height` have to be non-zero - pub fn draw_triangle( - &mut self, - x: u8, - y: u8, - base: u8, - height: u8, - direction: CanvasDirection, - ) { - let raw = self.raw.as_ptr(); - let direction = direction.into(); - // SAFETY: `raw` is always valid - // and `direction` is guaranteed to be valid by `From` implementation - unsafe { sys::canvas_draw_triangle(raw, x, y, base, height, direction) } - } - - // TODO: do we need range checks? - // TODO: does `character` have to be of a wrapper type - pub fn draw_glyph(&mut self, x: u8, y: u8, character: u16) { - let raw = self.raw.as_ptr(); - // SAFETY: `raw` is always valid, - unsafe { sys::canvas_draw_glyph(raw, x, y, character) } - } - - pub fn set_bitmap_mode(&mut self, alpha: bool) { - let raw = self.raw.as_ptr(); - // SAFETY: `raw` is always valid, - unsafe { sys::canvas_set_bitmap_mode(raw, alpha) } - } - - // TODO: do we need range checks? - // TODO: do `width`, `height` and `radius` have to be non-zero - pub fn draw_rframe(&mut self, x: u8, y: u8, width: u8, height: u8, radius: u8) { - let raw = self.raw.as_ptr(); - // SAFETY: `raw` is always valid, - unsafe { sys::canvas_draw_rframe(raw, x, y, width, height, radius) } - } - - // TODO: do we need range checks? - // TODO: do `width`, `height` and `radius` have to be non-zero - pub fn draw_rbox(&mut self, x: u8, y: u8, width: u8, height: u8, radius: u8) { - let raw = self.raw.as_ptr(); - // SAFETY: `raw` is always valid, - unsafe { sys::canvas_draw_rbox(raw, x, y, width, height, radius) } - } -} - -pub struct CanvasFontParameters<'a> { - raw: NonNull, - _parent: &'a CanvasView<'a>, -} - -impl<'a> CanvasFontParameters<'a> { - fn leading_default(&self) -> NonZeroU8 { - let raw = self.raw.as_ptr(); - // SAFETY: `raw` is always valid and this allways outlives its parent - unsafe { *raw } - .leading_default - .try_into() - .expect("`leading_default` should always be positive") - } - - fn leading_min(&self) -> NonZeroU8 { - let raw = self.raw.as_ptr(); - // SAFETY: `raw` is always valid and this allways outlives its parent - unsafe { *raw } - .leading_min - .try_into() - .expect("`leading_min` should always be positive") - } - - fn height(&self) -> NonZeroU8 { - let raw = self.raw.as_ptr(); - // SAFETY: `raw` is always valid and this allways outlives its parent - unsafe { *raw } - .height - .try_into() - .expect("`height` should always be positive") - } - - fn descender(&self) -> u8 { - let raw = self.raw.as_ptr(); - // SAFETY: `raw` is always valid and this allways outlives its parent - unsafe { *raw }.descender - } - - fn snapshot(&self) -> CanvasFontParametersSnapshot { - let raw = self.raw.as_ptr(); - // SAFETY: `raw` is always valid and this allways outlives its parent - unsafe { *raw } - .try_into() - .expect("raw `CanvasFontParameters` should be valid") - } -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] -pub struct CanvasFontParametersSnapshot { - leading_default: NonZeroU8, - leading_min: NonZeroU8, - height: NonZeroU8, - descender: u8, -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] -pub enum FromSysGuiLayerError { - ZeroLeadingDefault, - ZeroLeadingMin, - ZeroHeight, -} - -impl TryFrom for CanvasFontParametersSnapshot { - type Error = FromSysGuiLayerError; - - fn try_from(value: SysCanvasFontParameters) -> Result { - Ok(Self { - leading_default: value - .leading_default - .try_into() - .or(Err(Self::Error::ZeroLeadingDefault))?, - leading_min: value - .leading_min - .try_into() - .or(Err(Self::Error::ZeroLeadingMin))?, - height: value.height.try_into().or(Err(Self::Error::ZeroHeight))?, - descender: value.descender, - }) - } -} - -impl From for SysCanvasFontParameters { - fn from(value: CanvasFontParametersSnapshot) -> Self { - Self { - leading_default: value.leading_default.into(), - leading_min: value.leading_min.into(), - height: value.height.into(), - descender: value.descender, - } - } -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] -pub enum Color { - White, - Black, - // TDOO: add this color - // Xor, -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] -pub enum FromSysColor { - Invalid(SysColor), -} - -impl TryFrom for Color { - type Error = FromSysColor; - - fn try_from(value: SysColor) -> Result { - use sys::{ - Color_ColorBlack as SYS_COLOR_BLACK, - Color_ColorWhite as SYS_COLOR_WHITE, - // Color_ColorXOR as SYS_COLOR_XOR, - }; - - Ok(match value { - SYS_COLOR_WHITE => Self::White, - SYS_COLOR_BLACK => Self::Black, - // SYS_COLOR_XOR => Ok(Self::Xor), - invalid => Err(Self::Error::Invalid(invalid))?, - }) - } -} - -impl From for SysColor { - fn from(value: Color) -> Self { - use sys::{ - Color_ColorBlack as SYS_COLOR_BLACK, - Color_ColorWhite as SYS_COLOR_WHITE, - // Color_ColorXOR as SYS_COLOR_XOR, - }; - - match value { - Color::White => SYS_COLOR_WHITE, - Color::Black => SYS_COLOR_BLACK, - // Color::Xor => SYS_COLOR_XOR, - } - } -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] -pub enum Font { - Primary, - Secondary, - Keyboard, - BigNumbers, -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] -pub enum FromSysFont { - TotalNumber, - Invalid(SysFont), -} - -impl TryFrom for Font { - type Error = FromSysFont; - - fn try_from(value: SysFont) -> Result { - use sys::{ - Font_FontBigNumbers as SYS_FONT_BIG_NUMBERS, Font_FontKeyboard as SYS_FONT_KEYBOARD, - Font_FontPrimary as SYS_FONT_PRIMARY, Font_FontSecondary as SYS_FONT_SECONDARY, - Font_FontTotalNumber as SYS_FONT_TOTAL_NUMBER, - }; - - Ok(match value { - SYS_FONT_PRIMARY => Self::Primary, - SYS_FONT_SECONDARY => Self::Secondary, - SYS_FONT_KEYBOARD => Self::Keyboard, - SYS_FONT_BIG_NUMBERS => Self::BigNumbers, - SYS_FONT_TOTAL_NUMBER => Err(Self::Error::TotalNumber)?, - invalid => Err(Self::Error::Invalid(invalid))?, - }) - } -} - -impl From for SysFont { - fn from(value: Font) -> Self { - use sys::{ - Font_FontBigNumbers as SYS_FONT_BIG_NUMBERS, Font_FontKeyboard as SYS_FONT_KEYBOARD, - Font_FontPrimary as SYS_FONT_PRIMARY, Font_FontSecondary as SYS_FONT_SECONDARY, - }; - - match value { - Font::Primary => SYS_FONT_PRIMARY, - Font::Secondary => SYS_FONT_SECONDARY, - Font::Keyboard => SYS_FONT_KEYBOARD, - Font::BigNumbers => SYS_FONT_BIG_NUMBERS, - } - } -} - -// #[derive(Clone, Copy, Debug)] -// pub enum CanvasOrientation { -// Horizontal, -// HorizontalFlip, -// Vertical, -// VerticalFlip, -// } -// -// #[derive(Clone, Copy, Debug)] -// pub enum FromSysCanvasOrientationError { -// Invalid(SysCanvasOrientation), -// } -// -// impl TryFrom for CanvasOrientation { -// type Error = FromSysCanvasOrientationError; -// -// fn try_from(value: SysCanvasOrientation) -> Result { -// use sys::{ -// CanvasOrientation_CanvasOrientationHorizontal as SYS_CANVAS_ORIENTATION_HORIZONTAL, -// CanvasOrientation_CanvasOrientationHorizontalFlip as SYS_CANVAS_ORIENTATION_HORIZONTAL_FLIP, -// CanvasOrientation_CanvasOrientationVertical as SYS_CANVAS_ORIENTATION_VERTICAL, -// CanvasOrientation_CanvasOrientationVerticalFlip as SYS_CANVAS_ORIENTATION_VERTICAL_FLIP, -// }; -// -// Ok(match value { -// SYS_CANVAS_ORIENTATION_HORIZONTAL => Self::Horizontal, -// SYS_CANVAS_ORIENTATION_HORIZONTAL_FLIP => Self::HorizontalFlip, -// SYS_CANVAS_ORIENTATION_VERTICAL => Self::Vertical, -// SYS_CANVAS_ORIENTATION_VERTICAL_FLIP => Self::VerticalFlip, -// invalid => Err(Self::Error::Invalid(invalid))?, -// }) -// } -// } -// -// impl From for SysCanvasOrientation { -// fn from(value: CanvasOrientation) -> Self { -// use sys::{ -// CanvasOrientation_CanvasOrientationHorizontal as SYS_CANVAS_ORIENTATION_HORIZONTAL, -// CanvasOrientation_CanvasOrientationHorizontalFlip as SYS_CANVAS_ORIENTATION_HORIZONTAL_FLIP, -// CanvasOrientation_CanvasOrientationVertical as SYS_CANVAS_ORIENTATION_VERTICAL, -// CanvasOrientation_CanvasOrientationVerticalFlip as SYS_CANVAS_ORIENTATION_VERTICAL_FLIP, -// }; -// -// match value { -// CanvasOrientation::Horizontal => SYS_CANVAS_ORIENTATION_HORIZONTAL, -// CanvasOrientation::HorizontalFlip => SYS_CANVAS_ORIENTATION_HORIZONTAL_FLIP, -// CanvasOrientation::Vertical => SYS_CANVAS_ORIENTATION_VERTICAL, -// CanvasOrientation::VerticalFlip => SYS_CANVAS_ORIENTATION_VERTICAL_FLIP, -// } -// } -// } - -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] -pub enum CanvasDirection { - LeftToRight, - TopToBottom, - RightToLeft, - BottomToTop, -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] -pub enum FromSysCanvasDirectionError { - Invalid(SysCanvasDirection), -} - -impl TryFrom for CanvasDirection { - type Error = FromSysCanvasDirectionError; - - fn try_from(value: SysCanvasDirection) -> Result { - use sys::{ - CanvasDirection_CanvasDirectionBottomToTop as SYS_CANVAS_DIRECTION_BOTTOM_TO_TOP, - CanvasDirection_CanvasDirectionLeftToRight as SYS_CANVAS_DIRECTION_LEFT_TO_RIGHT, - CanvasDirection_CanvasDirectionRightToLeft as SYS_CANVAS_DIRECTION_RIGHT_TO_LEFT, - CanvasDirection_CanvasDirectionTopToBottom as SYS_CANVAS_DIRECTION_TOP_TO_BOTTOM, - }; - - Ok(match value { - SYS_CANVAS_DIRECTION_LEFT_TO_RIGHT => Self::LeftToRight, - SYS_CANVAS_DIRECTION_TOP_TO_BOTTOM => Self::TopToBottom, - SYS_CANVAS_DIRECTION_RIGHT_TO_LEFT => Self::RightToLeft, - SYS_CANVAS_DIRECTION_BOTTOM_TO_TOP => Self::BottomToTop, - invalid => Err(Self::Error::Invalid(invalid))?, - }) - } -} - -impl From for SysCanvasDirection { - fn from(value: CanvasDirection) -> Self { - use sys::{ - CanvasDirection_CanvasDirectionBottomToTop as SYS_CANVAS_DIRECTION_BOTTOM_TO_TOP, - CanvasDirection_CanvasDirectionLeftToRight as SYS_CANVAS_DIRECTION_LEFT_TO_RIGHT, - CanvasDirection_CanvasDirectionRightToLeft as SYS_CANVAS_DIRECTION_RIGHT_TO_LEFT, - CanvasDirection_CanvasDirectionTopToBottom as SYS_CANVAS_DIRECTION_TOP_TO_BOTTOM, - }; - - match value { - CanvasDirection::BottomToTop => SYS_CANVAS_DIRECTION_BOTTOM_TO_TOP, - CanvasDirection::LeftToRight => SYS_CANVAS_DIRECTION_LEFT_TO_RIGHT, - CanvasDirection::RightToLeft => SYS_CANVAS_DIRECTION_RIGHT_TO_LEFT, - CanvasDirection::TopToBottom => SYS_CANVAS_DIRECTION_TOP_TO_BOTTOM, - } - } -} diff --git a/crates/gui/src/lib.rs b/crates/gui/src/lib.rs index 60692f0a..057fb539 100644 --- a/crates/gui/src/lib.rs +++ b/crates/gui/src/lib.rs @@ -5,12 +5,3 @@ #![feature(int_roundings)] extern crate alloc; - -pub mod canvas; -pub mod gui; -pub mod icon; -pub mod icon_animation; -pub mod input; -pub mod view; -pub mod view_port; -pub mod xbm; diff --git a/examples/gui/Cargo.lock b/examples/gui/Cargo.lock index f005eae1..567dbbd6 100644 --- a/examples/gui/Cargo.lock +++ b/examples/gui/Cargo.lock @@ -16,14 +16,6 @@ dependencies = [ "flipperzero-sys", ] -[[package]] -name = "flipperzero-gui" -version = "0.6.0" -dependencies = [ - "flipperzero", - "flipperzero-sys", -] - [[package]] name = "flipperzero-rt" version = "0.6.0" @@ -44,7 +36,6 @@ version = "0.1.0" dependencies = [ "flipperzero", "flipperzero-alloc", - "flipperzero-gui", "flipperzero-rt", "flipperzero-sys", ] diff --git a/examples/gui/Cargo.toml b/examples/gui/Cargo.toml index 5858a993..daaecbb5 100644 --- a/examples/gui/Cargo.toml +++ b/examples/gui/Cargo.toml @@ -17,8 +17,7 @@ bench = false test = false [dependencies] -flipperzero = { version = "0.6.0", path = "../../crates/flipperzero" } +flipperzero = { version = "0.6.0", path = "../../crates/flipperzero", features = ["service-gui"] } flipperzero-sys = { version = "0.6.0", path = "../../crates/sys" } flipperzero-rt = { version = "0.6.0", path = "../../crates/rt" } flipperzero-alloc = { version = "0.6.0", path = "../../crates/alloc" } -flipperzero-gui = { version = "0.6.0", path = "../../crates/gui" } diff --git a/examples/gui/src/ferris_xbm.rs b/examples/gui/src/ferris_xbm.rs index c9d9f38a..0e14e0ea 100644 --- a/examples/gui/src/ferris_xbm.rs +++ b/examples/gui/src/ferris_xbm.rs @@ -1,6 +1,6 @@ -use flipperzero_gui::xbm::{ByteArray, XbmImage}; +use flipperzero::gui::xbm::{ByteArray, XbmImage}; -pub const IMAGE: XbmImage> = flipperzero_gui::xbm! { +pub const IMAGE: XbmImage> = flipperzero::xbm! { #define ferris_width 48 #define ferris_height 32 static char ferris_bits[] = { diff --git a/examples/gui/src/main.rs b/examples/gui/src/main.rs index f104c3ab..1dcd325e 100644 --- a/examples/gui/src/main.rs +++ b/examples/gui/src/main.rs @@ -16,14 +16,16 @@ extern crate flipperzero_alloc; use alloc::{ffi::CString, format}; use core::{ffi::CStr, time::Duration}; -use flipperzero::{furi::message_queue::MessageQueue, println}; -use flipperzero_gui::xbm::ByteArray; -use flipperzero_gui::{ - canvas::CanvasView, - gui::{Gui, GuiLayer}, +use flipperzero::{ + furi::message_queue::MessageQueue, + gui::xbm::{ByteArray, XbmImage}, + gui::{ + canvas::CanvasView, + gui::{Gui, GuiLayer}, + view_port::{ViewPort, ViewPortCallbacks}, + }, input::{InputEvent, InputKey, InputType}, - view_port::{ViewPort, ViewPortCallbacks}, - xbm::XbmImage, + println, }; use flipperzero_rt::{entry, manifest}; use flipperzero_sys::furi::Status; From 0898485789594cf7e0e9af5c2233efb83937613a Mon Sep 17 00:00:00 2001 From: Petr Portnov Date: Sat, 14 Jan 2023 21:02:30 +0300 Subject: [PATCH 18/49] chore: make `dialogs` service depend on `gui` service --- crates/flipperzero/Cargo.toml | 4 +++- crates/flipperzero/src/dialogs/mod.rs | 15 +++++++-------- crates/flipperzero/src/gui/mod.rs | 1 + crates/flipperzero/src/internals.rs | 1 + crates/flipperzero/src/lib.rs | 1 + 5 files changed, 13 insertions(+), 9 deletions(-) diff --git a/crates/flipperzero/Cargo.toml b/crates/flipperzero/Cargo.toml index 441a552b..733a1fe4 100644 --- a/crates/flipperzero/Cargo.toml +++ b/crates/flipperzero/Cargo.toml @@ -29,7 +29,7 @@ flipperzero-rt = { path = "../rt", version = "0.6.0" } [features] default = ["unstable_lints", "unstable_intrinsics"] # enables all optional services -all-services = ["service-gui"] +all-services = ["service-gui", "service-dialogs"] # enables features requiring an allocator alloc = [] # enables unstable Rust intrinsics @@ -38,3 +38,5 @@ unstable_intrinsics = [] unstable_lints = [] # enables GUI APIs of Flipper service-gui = ["alloc"] +# enables Dialogs APIs of Flipper +service-dialogs = ["service-gui"] diff --git a/crates/flipperzero/src/dialogs/mod.rs b/crates/flipperzero/src/dialogs/mod.rs index d1ffc6f1..b833be20 100644 --- a/crates/flipperzero/src/dialogs/mod.rs +++ b/crates/flipperzero/src/dialogs/mod.rs @@ -3,15 +3,14 @@ #[cfg(feature = "alloc")] use alloc::ffi::CString; -use core::ffi::{c_char, CStr}; -use core::marker::PhantomData; -use core::ptr; -use core::ptr::NonNull; - -use flipperzero_sys as sys; -use flipperzero_sys::furi::UnsafeRecord; - use crate::gui::canvas::Align; +use core::{ + ffi::{c_char, CStr}, + marker::PhantomData, + ptr, + ptr::NonNull, +}; +use flipperzero_sys::{self as sys, furi::UnsafeRecord}; /// A handle to the Dialogs app. pub struct DialogsApp { diff --git a/crates/flipperzero/src/gui/mod.rs b/crates/flipperzero/src/gui/mod.rs index 63c3d8f6..78533ad7 100644 --- a/crates/flipperzero/src/gui/mod.rs +++ b/crates/flipperzero/src/gui/mod.rs @@ -1,6 +1,7 @@ //! GUI service. pub mod canvas; +#[cfg(feature = "service-gui")] pub mod gui; pub mod icon; pub mod icon_animation; diff --git a/crates/flipperzero/src/internals.rs b/crates/flipperzero/src/internals.rs index 6e4a5167..880adff6 100644 --- a/crates/flipperzero/src/internals.rs +++ b/crates/flipperzero/src/internals.rs @@ -52,6 +52,7 @@ const _: () = { /// /// This will use core implementations if `unstable_intrinsics` feature is enabled /// falling back to ad-hoc implementations otherwise. +#[allow(dead_code)] // this functions may be unused if a specific feature set does not require them pub(crate) mod ops { pub const fn div_ceil_u16(divident: u16, divisor: u16) -> u16 { #[cfg(feature = "unstable_intrinsics")] diff --git a/crates/flipperzero/src/lib.rs b/crates/flipperzero/src/lib.rs index f27c525e..fa70a03d 100644 --- a/crates/flipperzero/src/lib.rs +++ b/crates/flipperzero/src/lib.rs @@ -7,6 +7,7 @@ #[cfg(feature = "alloc")] extern crate alloc; +#[cfg(feature = "service-dialogs")] pub mod dialogs; pub mod furi; #[cfg(feature = "service-gui")] From cc85081640f25aa34b8504c8a0d56ed987186da2 Mon Sep 17 00:00:00 2001 From: Petr Portnov Date: Sat, 14 Jan 2023 21:19:58 +0300 Subject: [PATCH 19/49] chore: resolve most TODOs in `IconAnimation` --- crates/flipperzero/src/gui/icon_animation.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/crates/flipperzero/src/gui/icon_animation.rs b/crates/flipperzero/src/gui/icon_animation.rs index 2074e01f..f596fdde 100644 --- a/crates/flipperzero/src/gui/icon_animation.rs +++ b/crates/flipperzero/src/gui/icon_animation.rs @@ -7,6 +7,7 @@ use core::{ }; use flipperzero_sys::{self as sys, IconAnimation as SysIconAnimation}; +/// System Icon Animation wrapper. pub struct IconAnimation<'a, C: IconAnimationCallbacks> { raw: NonNull, callbacks: NonNull, @@ -105,13 +106,16 @@ impl Drop for IconAnimation<'_, C> { } } +/// View over system Icon Animation. +/// +/// This is passed to [callbacks](IconAnimationCallbacks) of [`IconAnimation`]. pub struct IconAnimationView<'a> { raw: NonNull, _lifetime: PhantomData<&'a ()>, } impl IconAnimationView<'_> { - /// Construct a `CanvasView` from a raw pointer. + /// Construct an `IconAnimationView` from a raw pointer. /// /// # Safety /// @@ -126,7 +130,7 @@ impl IconAnimationView<'_> { /// use flipperzero::gui::icon_animation::IconAnimationView; /// /// let ptr = todo!(); - /// let icon_anumation = unsafe { IconAnimationView::from_raw(ptr) }; + /// let icon_animation = unsafe { IconAnimationView::from_raw(ptr) }; /// ``` pub unsafe fn from_raw(raw: *mut SysIconAnimation) -> Self { Self { @@ -136,8 +140,6 @@ impl IconAnimationView<'_> { } } - // TODO: callbacks - pub fn get_width(&self) -> u8 { let raw = self.raw.as_ptr(); // SAFETY: `raw` is always valid @@ -154,7 +156,8 @@ impl IconAnimationView<'_> { (self.get_width(), self.get_height()) } - // TODO: decide if these methods should be available in view + // TODO: decide if these methods should be available in view, + // i.e. if it is sound to call start/stop from callbacks // pub fn start(&mut self) { // let raw = self.raw.as_ptr(); // // SAFETY: `raw` is always valid From 6ece5f25bc6417ef06bc474958419edd9171db5e Mon Sep 17 00:00:00 2001 From: Petr Portnov Date: Sun, 22 Jan 2023 00:01:53 +0300 Subject: [PATCH 20/49] chore: cleanup `canvas.rs` --- crates/flipperzero/src/gui/canvas.rs | 82 ++++++---------------------- crates/flipperzero/src/gui/gui.rs | 2 +- 2 files changed, 18 insertions(+), 66 deletions(-) diff --git a/crates/flipperzero/src/gui/canvas.rs b/crates/flipperzero/src/gui/canvas.rs index 4eb24576..647cdec9 100644 --- a/crates/flipperzero/src/gui/canvas.rs +++ b/crates/flipperzero/src/gui/canvas.rs @@ -51,8 +51,9 @@ impl CanvasView<'_> { } // FIXME: - // - canvas_reset - // - canvas_commit + // - canvas_reset + // - canvas_commit + // This are currently not available in bindings pub fn width(&self) -> NonZeroU8 { let raw = self.raw.as_ptr(); @@ -85,7 +86,10 @@ impl CanvasView<'_> { // SAFETY: `raw` is always a valid pointer // and `font` is guaranteed to be a valid value by `From` implementation let raw = unsafe { NonNull::new_unchecked(sys::canvas_get_font_params(raw, font)) }; - CanvasFontParameters { raw, _parent: self } + CanvasFontParameters { + raw, + _parent: PhantomData, + } } pub fn clear(&mut self) { @@ -317,11 +321,11 @@ impl CanvasView<'_> { pub struct CanvasFontParameters<'a> { raw: NonNull, - _parent: &'a CanvasView<'a>, + _parent: PhantomData<&'a CanvasView<'a>>, } impl<'a> CanvasFontParameters<'a> { - fn leading_default(&self) -> NonZeroU8 { + pub fn leading_default(&self) -> NonZeroU8 { let raw = self.raw.as_ptr(); // SAFETY: `raw` is always valid and this allways outlives its parent unsafe { *raw } @@ -330,7 +334,7 @@ impl<'a> CanvasFontParameters<'a> { .expect("`leading_default` should always be positive") } - fn leading_min(&self) -> NonZeroU8 { + pub fn leading_min(&self) -> NonZeroU8 { let raw = self.raw.as_ptr(); // SAFETY: `raw` is always valid and this allways outlives its parent unsafe { *raw } @@ -339,7 +343,7 @@ impl<'a> CanvasFontParameters<'a> { .expect("`leading_min` should always be positive") } - fn height(&self) -> NonZeroU8 { + pub fn height(&self) -> NonZeroU8 { let raw = self.raw.as_ptr(); // SAFETY: `raw` is always valid and this allways outlives its parent unsafe { *raw } @@ -348,13 +352,13 @@ impl<'a> CanvasFontParameters<'a> { .expect("`height` should always be positive") } - fn descender(&self) -> u8 { + pub fn descender(&self) -> u8 { let raw = self.raw.as_ptr(); // SAFETY: `raw` is always valid and this allways outlives its parent unsafe { *raw }.descender } - fn snapshot(&self) -> CanvasFontParametersSnapshot { + pub fn snapshot(&self) -> CanvasFontParametersSnapshot { let raw = self.raw.as_ptr(); // SAFETY: `raw` is always valid and this allways outlives its parent unsafe { *raw } @@ -365,10 +369,10 @@ impl<'a> CanvasFontParameters<'a> { #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] pub struct CanvasFontParametersSnapshot { - leading_default: NonZeroU8, - leading_min: NonZeroU8, - height: NonZeroU8, - descender: u8, + pub leading_default: NonZeroU8, + pub leading_min: NonZeroU8, + pub height: NonZeroU8, + pub descender: u8, } #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] @@ -507,58 +511,6 @@ impl From for SysFont { } } -// #[derive(Clone, Copy, Debug)] -// pub enum CanvasOrientation { -// Horizontal, -// HorizontalFlip, -// Vertical, -// VerticalFlip, -// } -// -// #[derive(Clone, Copy, Debug)] -// pub enum FromSysCanvasOrientationError { -// Invalid(SysCanvasOrientation), -// } -// -// impl TryFrom for CanvasOrientation { -// type Error = FromSysCanvasOrientationError; -// -// fn try_from(value: SysCanvasOrientation) -> Result { -// use sys::{ -// CanvasOrientation_CanvasOrientationHorizontal as SYS_CANVAS_ORIENTATION_HORIZONTAL, -// CanvasOrientation_CanvasOrientationHorizontalFlip as SYS_CANVAS_ORIENTATION_HORIZONTAL_FLIP, -// CanvasOrientation_CanvasOrientationVertical as SYS_CANVAS_ORIENTATION_VERTICAL, -// CanvasOrientation_CanvasOrientationVerticalFlip as SYS_CANVAS_ORIENTATION_VERTICAL_FLIP, -// }; -// -// Ok(match value { -// SYS_CANVAS_ORIENTATION_HORIZONTAL => Self::Horizontal, -// SYS_CANVAS_ORIENTATION_HORIZONTAL_FLIP => Self::HorizontalFlip, -// SYS_CANVAS_ORIENTATION_VERTICAL => Self::Vertical, -// SYS_CANVAS_ORIENTATION_VERTICAL_FLIP => Self::VerticalFlip, -// invalid => Err(Self::Error::Invalid(invalid))?, -// }) -// } -// } -// -// impl From for SysCanvasOrientation { -// fn from(value: CanvasOrientation) -> Self { -// use sys::{ -// CanvasOrientation_CanvasOrientationHorizontal as SYS_CANVAS_ORIENTATION_HORIZONTAL, -// CanvasOrientation_CanvasOrientationHorizontalFlip as SYS_CANVAS_ORIENTATION_HORIZONTAL_FLIP, -// CanvasOrientation_CanvasOrientationVertical as SYS_CANVAS_ORIENTATION_VERTICAL, -// CanvasOrientation_CanvasOrientationVerticalFlip as SYS_CANVAS_ORIENTATION_VERTICAL_FLIP, -// }; -// -// match value { -// CanvasOrientation::Horizontal => SYS_CANVAS_ORIENTATION_HORIZONTAL, -// CanvasOrientation::HorizontalFlip => SYS_CANVAS_ORIENTATION_HORIZONTAL_FLIP, -// CanvasOrientation::Vertical => SYS_CANVAS_ORIENTATION_VERTICAL, -// CanvasOrientation::VerticalFlip => SYS_CANVAS_ORIENTATION_VERTICAL_FLIP, -// } -// } -// } - #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] pub enum CanvasDirection { LeftToRight, diff --git a/crates/flipperzero/src/gui/gui.rs b/crates/flipperzero/src/gui/gui.rs index 5b11a629..ccefeab1 100644 --- a/crates/flipperzero/src/gui/gui.rs +++ b/crates/flipperzero/src/gui/gui.rs @@ -99,7 +99,7 @@ impl<'a, VPC: ViewPortCallbacks> GuiViewPort<'a, VPC> { let gui = self.parent.gui.as_raw(); let view_port = self.view_port.as_raw(); - // # SAFETY: `self.parent` outlives this `GuiVewPort` + // SAFETY: `self.parent` outlives this `GuiVewPort` unsafe { sys::gui_view_port_send_to_front(gui, view_port) }; } From 902ea0a3204dcb002f23a9ce947bab0af2f99b98 Mon Sep 17 00:00:00 2001 From: Petr Portnov Date: Sat, 18 Feb 2023 01:56:15 +0300 Subject: [PATCH 21/49] feat: add `is_irq_or_masked()` --- crates/flipperzero/src/kernel.rs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/crates/flipperzero/src/kernel.rs b/crates/flipperzero/src/kernel.rs index b2884f0f..ed5ac3d7 100644 --- a/crates/flipperzero/src/kernel.rs +++ b/crates/flipperzero/src/kernel.rs @@ -5,17 +5,14 @@ use core::{ }; use flipperzero_sys::{self as sys, furi::Status}; -// FIXME: make this available via flipperzero-firnmware -fn interrupted() -> bool { - // FIXME: this is currently obviously unsound and cannot be implemmented, - // see https://github.com/flipperdevices/flipperzero-firmware/pull/2276 for details - // // SAFETY: this function has no invariant to uphold - // unsafe { furi_is_irq_context() } - false +#[inline(always)] +fn is_irq_or_masked() -> bool { + // SAFETY: this function has no invariants to uphold + unsafe { sys::furi_kernel_is_irq_or_masked() } } pub fn lock() -> Result { - if interrupted() { + if is_irq_or_masked() { Err(LockError::Interrupted) } else { // SAFETY: kernel is not interrupted @@ -55,7 +52,7 @@ impl LockGuard { impl Drop for LockGuard { fn drop(&mut self) { - // SAFETY: no invariant has to be upheld + // SAFETY: since `LockGuard` is `!Send` it cannot escape valid (non-interrupt) context let _ = unsafe { sys::furi_kernel_unlock() }; } } From 917137214ce66dd06b64f1ccad88b13792d36fcf Mon Sep 17 00:00:00 2001 From: Petr Portnov Date: Thu, 23 Feb 2023 22:38:20 +0300 Subject: [PATCH 22/49] chore: enhance callbacks implementation --- crates/flipperzero/src/furi/thread.rs | 2 +- crates/flipperzero/src/gui/gui.rs | 85 +++++++++++---- crates/flipperzero/src/gui/icon_animation.rs | 16 +-- crates/flipperzero/src/gui/view.rs | 57 +++++----- crates/flipperzero/src/gui/view_port.rs | 104 +++++++++---------- 5 files changed, 148 insertions(+), 116 deletions(-) diff --git a/crates/flipperzero/src/furi/thread.rs b/crates/flipperzero/src/furi/thread.rs index ac67f30f..a71c77eb 100644 --- a/crates/flipperzero/src/furi/thread.rs +++ b/crates/flipperzero/src/furi/thread.rs @@ -16,7 +16,7 @@ pub mod sync { sys::furi_delay_us(duration.as_micros() as u32); } else { sys::furi_delay_ms(duration.as_millis() as u32); - // TODO: add reamining us-part + // TODO: add remaining us-part } } } diff --git a/crates/flipperzero/src/gui/gui.rs b/crates/flipperzero/src/gui/gui.rs index ccefeab1..3559acfe 100644 --- a/crates/flipperzero/src/gui/gui.rs +++ b/crates/flipperzero/src/gui/gui.rs @@ -7,12 +7,18 @@ use crate::{ }, input::InputEvent, }; -use core::{ffi::c_char, fmt::Debug}; -use flipperzero_sys::{self as sys, furi::UnsafeRecord, Gui as SysGui, GuiLayer as SysGuiLayer}; +use core::{ + ffi::c_char, + fmt::Debug, + ops::{Deref, DerefMut}, +}; +use flipperzero_sys::{ + self as sys, furi::UnsafeRecord, Canvas as SysCanvas, Gui as SysGui, GuiLayer as SysGuiLayer, +}; /// System Gui wrapper. pub struct Gui { - gui: UnsafeRecord, + raw: UnsafeRecord, } impl Gui { @@ -23,7 +29,11 @@ impl Gui { // SAFETY: `RECORD` is a constant let gui = unsafe { UnsafeRecord::open(Self::RECORD) }; - Self { gui } + Self { raw: gui } + } + + pub fn as_raw(&self) -> *mut SysGui { + self.raw.as_raw() } pub fn add_view_port( @@ -31,12 +41,12 @@ impl Gui { view_port: ViewPort, layer: GuiLayer, ) -> GuiViewPort<'_, VPC> { - let gui = self.gui.as_raw(); + let raw = self.as_raw(); let view_port_ptr = view_port.as_raw(); let layer = layer.into(); // SAFETY: all pointers are valid and `view_port` outlives this `Gui` - unsafe { sys::gui_add_view_port(gui, view_port_ptr, layer) }; + unsafe { sys::gui_add_view_port(raw, view_port_ptr, layer) }; GuiViewPort { parent: self, @@ -45,29 +55,26 @@ impl Gui { } pub fn get_frame_buffer_size(&self) -> usize { - let gui = self.gui.as_raw(); - // SAFETY: `gui` is always a valid pointer - unsafe { sys::gui_get_framebuffer_size(gui) } + let raw = self.as_raw(); + // SAFETY: `raw` is always a valid pointer + unsafe { sys::gui_get_framebuffer_size(raw) } } pub fn set_lockdown(&self, lockdown: bool) { - let gui = self.gui.as_raw(); - // SAFETY: `gui` is always a valid pointer - unsafe { sys::gui_set_lockdown(gui, lockdown) } + let raw = self.raw.as_raw(); + // SAFETY: `raw` is always a valid pointer + unsafe { sys::gui_set_lockdown(raw, lockdown) } } // TODO: separate `GuiCanvas` (locking the parent) // and `Canvas` (independent of the parent) - pub fn direct_draw_acquire(&self) -> CanvasView { - let gui = self.gui.as_raw(); + pub fn direct_draw_acquire(&mut self) -> ExclusiveCanvas<'_> { + let raw = self.as_raw(); - // SAFETY: `gui` is always a valid pointer - // let canvas = unsafe { sys::gui_direct_draw_acquire(gui) } - let canvas = unimplemented!(""); + // SAFETY: `raw` is always a valid pointer + let canvas = unsafe { CanvasView::from_raw(sys::gui_direct_draw_acquire(raw)) }; - // SAFETY: `self` os the parent of `canvas` - // and `canvas` is a freshly created valid pointer - // unsafe { Canvas::from_raw(self, canvas) } + ExclusiveCanvas { gui: self, canvas } } // TODO: canvas method @@ -96,7 +103,7 @@ impl<'a, VPC: ViewPortCallbacks> GuiViewPort<'a, VPC> { } pub fn send_to_front(&mut self) { - let gui = self.parent.gui.as_raw(); + let gui = self.parent.raw.as_raw(); let view_port = self.view_port.as_raw(); // SAFETY: `self.parent` outlives this `GuiVewPort` @@ -114,7 +121,7 @@ impl<'a, VPC: ViewPortCallbacks> GuiViewPort<'a, VPC> { impl Drop for GuiViewPort<'_, VPC> { fn drop(&mut self) { - let gui = self.parent.gui.as_raw(); + let gui = self.parent.raw.as_raw(); let view_port = self.view_port().as_raw(); // SAFETY: `gui` and `view_port` are valid pointers @@ -184,6 +191,38 @@ impl From for SysGuiLayer { } pub trait GuiCallbacks { - fn on_draw(&mut self, _canvas: *mut sys::Canvas) {} + fn on_draw(&mut self, _canvas: *mut SysCanvas) {} fn on_input(&mut self, _event: InputEvent) {} } + +impl GuiCallbacks for () {} + +/// Exclusively accessible canvas. +pub struct ExclusiveCanvas<'a> { + gui: &'a mut Gui, + canvas: CanvasView<'a>, +} + +impl Drop for ExclusiveCanvas<'_> { + fn drop(&mut self) { + let gui = self.gui.as_raw(); + // SAFETY: this instance should have been created from `gui` + // using `gui_direct_draw_acquire` + // and will no longer be available since it is dropped + unsafe { sys::gui_direct_draw_release(gui) }; + } +} + +impl<'a> Deref for ExclusiveCanvas<'a> { + type Target = CanvasView<'a>; + + fn deref(&self) -> &Self::Target { + &self.canvas + } +} + +impl<'a> DerefMut for ExclusiveCanvas<'a> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.canvas + } +} diff --git a/crates/flipperzero/src/gui/icon_animation.rs b/crates/flipperzero/src/gui/icon_animation.rs index f596fdde..a07d1295 100644 --- a/crates/flipperzero/src/gui/icon_animation.rs +++ b/crates/flipperzero/src/gui/icon_animation.rs @@ -10,7 +10,7 @@ use flipperzero_sys::{self as sys, IconAnimation as SysIconAnimation}; /// System Icon Animation wrapper. pub struct IconAnimation<'a, C: IconAnimationCallbacks> { raw: NonNull, - callbacks: NonNull, + callbacks: Box, _parent_lifetime: PhantomData<&'a ()>, } @@ -21,7 +21,7 @@ impl<'a, C: IconAnimationCallbacks> IconAnimation<'a, C> { // or stops the system on OOM, // `icon` is a valid pointer and `icon` outlives this animation let raw = unsafe { NonNull::new_unchecked(sys::icon_animation_alloc(icon)) }; - let callbacks = NonNull::from(Box::leak(Box::new(callbacks))); + let callbacks = Box::new(callbacks); let icon_animation = Self { raw, @@ -46,13 +46,13 @@ impl<'a, C: IconAnimationCallbacks> IconAnimation<'a, C> { C::on_update as *const c_void, <() as IconAnimationCallbacks>::on_update as *const c_void, ) { - let context = icon_animation.callbacks.as_ptr().cast(); + let context = (&*icon_animation.callbacks as *const C).cast_mut().cast(); let raw = raw.as_ptr(); - // SAFETY: `raw` is valid - // and `callbacks` is valid and lives with this struct - unsafe { - sys::icon_animation_set_update_callback(raw, Some(dispatch_update::), context) - } + let callback = Some(dispatch_update:: as _); + + // SAFETY: `raw` and `callback` are valid + // and `context` is valid as the box lives with this struct + unsafe { sys::icon_animation_set_update_callback(raw, callback, context) } } icon_animation diff --git a/crates/flipperzero/src/gui/view.rs b/crates/flipperzero/src/gui/view.rs index bde6974c..80319b59 100644 --- a/crates/flipperzero/src/gui/view.rs +++ b/crates/flipperzero/src/gui/view.rs @@ -1,47 +1,40 @@ +use crate::{gui::canvas::CanvasView, gui::view_port::ViewPortCallbacks, input::InputEvent}; +use alloc::boxed::Box; +use core::ptr::NonNull; use flipperzero_sys::{self as sys, View as SysView}; -pub struct View { - raw: *mut SysView, +pub struct View { + raw: NonNull, + callbacks: Box, } -impl View { - /// Creates a new `View`. - /// - /// # Example - /// - /// Basic usage: - /// - /// ``` - /// use flipperzero::gui::view::View; - /// - /// let view = View::new(); - /// ``` - pub fn new() -> View { - // SAFETY: allocation either succeeds producing the valid pointer +impl View { + pub fn new(callbacks: C) -> Self { + // SAFETY: allocation either succeeds producing a valid non-null pointer // or stops the system on OOM - let view = unsafe { sys::view_alloc() }; - Self { raw: view } + let raw = unsafe { NonNull::new_unchecked(sys::view_alloc()) }; + let callbacks = Box::new(callbacks); + + Self { raw, callbacks } } /// Creates a copy of raw pointer to the [`SysView`]. - pub unsafe fn as_raw(&self) -> *mut SysView { - self.raw + pub fn as_raw(&self) -> *mut SysView { + self.raw.as_ptr() } } -impl Default for View { - fn default() -> Self { - Self::new() +impl Drop for View { + fn drop(&mut self) { + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is always valid + unsafe { sys::view_free(raw) } } } -impl Drop for View { - fn drop(&mut self) { - // `self.raw` is `null` iff it has been taken by call to `into_raw()` - if !self.raw.is_null() { - // SAFETY: `self.raw` is always valid - // and it should have been unregistered from the system by now - unsafe { sys::view_free(self.raw) } - } - } +pub trait ViewCallbacks { + fn on_draw(&mut self, _canvas: CanvasView) {} + fn on_input(&mut self, _event: InputEvent) {} } + +impl ViewCallbacks for () {} diff --git a/crates/flipperzero/src/gui/view_port.rs b/crates/flipperzero/src/gui/view_port.rs index 40c7a536..ec79871a 100644 --- a/crates/flipperzero/src/gui/view_port.rs +++ b/crates/flipperzero/src/gui/view_port.rs @@ -15,7 +15,7 @@ use flipperzero_sys::{ /// System ViewPort. pub struct ViewPort { raw: NonNull, - callbacks: NonNull, + callbacks: Box, } impl ViewPort { @@ -34,59 +34,63 @@ impl ViewPort { // SAFETY: allocation either succeeds producing the valid pointer // or stops the system on OOM let raw = unsafe { NonNull::new_unchecked(sys::view_port_alloc()) }; - let callbacks = NonNull::from(Box::leak(Box::new(callbacks))); + let callbacks = Box::new(callbacks); let view_port = Self { raw, callbacks }; - pub unsafe extern "C" fn dispatch_draw( - canvas: *mut SysCanvas, - context: *mut c_void, - ) { - // SAFETY: `canvas` is guaranteed to be a valid pointer - let canvas = unsafe { CanvasView::from_raw(canvas) }; - - let context: *mut C = context.cast(); - // SAFETY: `context` is stored in a `Box` which is a member of `ViewPort` - // and the callback is accessed exclusively by this function - unsafe { &mut *context }.on_draw(canvas); - } - pub unsafe extern "C" fn dispatch_input( - input_event: *mut sys::InputEvent, - context: *mut c_void, - ) { - let input_event: InputEvent = (&unsafe { *input_event }) - .try_into() - .expect("`input_event` should be a valid event"); - - let context: *mut C = context.cast(); - // SAFETY: `context` is stored in a pinned Box which is a member of `ViewPort` - // and the callback is accessed exclusively by this function - unsafe { &mut *context }.on_input(input_event); - } + { + pub unsafe extern "C" fn dispatch_draw( + canvas: *mut SysCanvas, + context: *mut c_void, + ) { + // SAFETY: `canvas` is guaranteed to be a valid pointer + let canvas = unsafe { CanvasView::from_raw(canvas) }; + + let context: *mut C = context.cast(); + // SAFETY: `context` is stored in a `Box` which is a member of `ViewPort` + // and the callback is accessed exclusively by this function + unsafe { &mut *context }.on_draw(canvas); + } - if !ptr::eq( - C::on_draw as *const c_void, - <() as ViewPortCallbacks>::on_draw as *const c_void, - ) { - let context = view_port.callbacks.as_ptr().cast(); - let raw = raw.as_ptr(); - // SAFETY: `raw` is valid - // and `callbacks` is valid and lives with this struct - unsafe { - sys::view_port_draw_callback_set(raw, Some(dispatch_draw::), context); + if !ptr::eq( + C::on_draw as *const c_void, + <() as ViewPortCallbacks>::on_draw as *const c_void, + ) { + let context = (&*view_port.callbacks as *const C).cast_mut().cast(); + let raw = raw.as_ptr(); + let callback = Some(dispatch_draw:: as _); + // SAFETY: `raw` is valid + // and `callbacks` is valid and lives with this struct + unsafe { sys::view_port_draw_callback_set(raw, callback, context) }; } } - if !ptr::eq( - C::on_input as *const c_void, - <() as ViewPortCallbacks>::on_input as *const c_void, - ) { - let context = view_port.callbacks.as_ptr().cast(); - let raw = raw.as_ptr(); - // SAFETY: `raw` is valid - // and `callbacks` is valid and lives with this struct - unsafe { - sys::view_port_input_callback_set(raw, Some(dispatch_input::), context); - }; + { + pub unsafe extern "C" fn dispatch_input( + input_event: *mut sys::InputEvent, + context: *mut c_void, + ) { + let input_event: InputEvent = (&unsafe { *input_event }) + .try_into() + .expect("`input_event` should be a valid event"); + + let context: *mut C = context.cast(); + // SAFETY: `context` is stored in a pinned Box which is a member of `ViewPort` + // and the callback is accessed exclusively by this function + unsafe { &mut *context }.on_input(input_event); + } + + if !ptr::eq( + C::on_input as *const c_void, + <() as ViewPortCallbacks>::on_input as *const c_void, + ) { + let context = (&*view_port.callbacks as *const C).cast_mut().cast(); + let raw = raw.as_ptr(); + let callback = Some(dispatch_input:: as _); + + // SAFETY: `raw` is valid + // and `callbacks` is valid and lives with this struct + unsafe { sys::view_port_input_callback_set(raw, callback, context) }; + } } view_port @@ -336,10 +340,6 @@ impl Drop for ViewPort { // SAFETY: `self.raw` is always valid // and it should have been unregistered from the system by now unsafe { sys::view_port_free(raw) } - - let callbacks = self.callbacks.as_ptr(); - // SAFETY: `callbacks` was created using `Box::into_raw()` on `ViewPort` creation - let _ = unsafe { Box::from_raw(callbacks) }; } } From 2d289a0e35c367be1d869198ba41a1890c67ec14 Mon Sep 17 00:00:00 2001 From: Petr Portnov Date: Sun, 12 Mar 2023 01:17:18 +0300 Subject: [PATCH 23/49] chore: remove `.idea` directory --- crates/.idea/.gitignore | 8 -------- crates/.idea/crates.iml | 8 -------- crates/.idea/inspectionProfiles/Project_Default.xml | 7 ------- crates/.idea/markdown.xml | 9 --------- crates/.idea/modules.xml | 8 -------- crates/.idea/vcs.xml | 12 ------------ 6 files changed, 52 deletions(-) delete mode 100644 crates/.idea/.gitignore delete mode 100644 crates/.idea/crates.iml delete mode 100644 crates/.idea/inspectionProfiles/Project_Default.xml delete mode 100644 crates/.idea/markdown.xml delete mode 100644 crates/.idea/modules.xml delete mode 100644 crates/.idea/vcs.xml diff --git a/crates/.idea/.gitignore b/crates/.idea/.gitignore deleted file mode 100644 index 13566b81..00000000 --- a/crates/.idea/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Editor-based HTTP Client requests -/httpRequests/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/crates/.idea/crates.iml b/crates/.idea/crates.iml deleted file mode 100644 index bc2cd874..00000000 --- a/crates/.idea/crates.iml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/crates/.idea/inspectionProfiles/Project_Default.xml b/crates/.idea/inspectionProfiles/Project_Default.xml deleted file mode 100644 index 24e7e67c..00000000 --- a/crates/.idea/inspectionProfiles/Project_Default.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - \ No newline at end of file diff --git a/crates/.idea/markdown.xml b/crates/.idea/markdown.xml deleted file mode 100644 index 1e340941..00000000 --- a/crates/.idea/markdown.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/crates/.idea/modules.xml b/crates/.idea/modules.xml deleted file mode 100644 index f16f6815..00000000 --- a/crates/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/crates/.idea/vcs.xml b/crates/.idea/vcs.xml deleted file mode 100644 index efccd083..00000000 --- a/crates/.idea/vcs.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file From 4670f74efdcc787e75562dcd720472250dd44c00 Mon Sep 17 00:00:00 2001 From: Petr Portnov Date: Sun, 12 Mar 2023 01:52:44 +0300 Subject: [PATCH 24/49] chore: cleanup macros and internals --- crates/flipperzero/src/internals.rs | 16 +++++++++++++--- crates/rt/src/manifest.rs | 25 ++++++++++++++++++------- crates/sys/src/lib.rs | 11 +++-------- 3 files changed, 34 insertions(+), 18 deletions(-) diff --git a/crates/flipperzero/src/internals.rs b/crates/flipperzero/src/internals.rs index 880adff6..cdfec6ea 100644 --- a/crates/flipperzero/src/internals.rs +++ b/crates/flipperzero/src/internals.rs @@ -10,8 +10,9 @@ use core::{marker::PhantomData, mem}; /// Make type `Foo` `impl !Sync` and `impl !Send`: /// /// ```no_run +/// use std::marker::PhantomData; /// struct Foo { -/// _marker: UnsendUnsync, +/// _marker: PhantomData, /// } /// ``` pub(crate) struct UnsendUnsync(*const ()); @@ -19,7 +20,7 @@ pub(crate) struct UnsendUnsync(*const ()); const _: () = { assert!( mem::size_of::>() == 0, - "`PhantomData` should be a ZST" + "`PhantomData` should be a ZST", ); }; @@ -31,8 +32,9 @@ const _: () = { /// Make type `Foo` `impl !Send`: /// /// ```no_run +/// use std::marker::PhantomData; /// struct Foo { -/// _marker: Unsend, +/// _marker: PhantomData, /// } /// ``` pub(crate) struct Unsend(*const ()); @@ -61,6 +63,14 @@ pub(crate) mod ops { } #[cfg(not(feature = "unstable_intrinsics"))] { + let quotient = divident / divisor; + let remainder = divident % divisor; + if remainder > 0 && divisor > 0 { + quotient + 1 + } else { + quotient + } + if divident % divisor == 0 { divident / divisor } else { diff --git a/crates/rt/src/manifest.rs b/crates/rt/src/manifest.rs index aa3b6734..25fe079f 100644 --- a/crates/rt/src/manifest.rs +++ b/crates/rt/src/manifest.rs @@ -9,10 +9,11 @@ const HARDWARE_TARGET: u16 = 7; const DEFAULT_STACK_SIZE: u16 = 2048; // 2 KiB /// Define application manifest. -/// +/// /// # Examples -/// +/// /// ``` +/// # use flipperzero_rt::manifest; /// manifest!( /// name = "MyApp", /// stack_size = 1024, @@ -36,11 +37,21 @@ macro_rules! manifest { #[macro_export] #[doc(hidden)] macro_rules! _manifest_field { - (stack_size = $value:expr) => { $value }; - (app_version = $value:expr) => { $value }; - (name = $value:expr) => { $crate::manifest::_padded($value.as_bytes()) }; - (has_icon = $value:expr) => { $value as core::ffi::c_char }; - (icon = $value:expr) => { $crate::manifest::_padded(include_bytes!($value)) }; + (stack_size = $value:expr) => { + $value + }; + (app_version = $value:expr) => { + $value + }; + (name = $value:expr) => { + $crate::manifest::_padded($value.as_bytes()) + }; + (has_icon = $value:expr) => { + $value as core::ffi::c_char + }; + (icon = $value:expr) => { + $crate::manifest::_padded(include_bytes!($value)) + }; } #[repr(C, packed)] diff --git a/crates/sys/src/lib.rs b/crates/sys/src/lib.rs index 9a6b0dc7..06dedbcd 100644 --- a/crates/sys/src/lib.rs +++ b/crates/sys/src/lib.rs @@ -2,11 +2,6 @@ #![no_std] -/// Re-export bindings -pub use bindings::*; -/// Definition of inline functions -pub use inlines::furi_hal_gpio::*; - pub mod furi; mod inlines; @@ -20,7 +15,7 @@ mod bindings; #[macro_export] macro_rules! c_string { ($str:expr $(,)?) => {{ - concat!($str, "\0").as_ptr() as *const core::ffi::c_char + ::core::concat!($str, "\0").as_ptr() as *const core::ffi::c_char }}; } @@ -31,10 +26,10 @@ macro_rules! crash { unsafe { // Crash message is passed via r12 let msg = $crate::c_string!($msg); - core::arch::asm!("", in("r12") msg, options(nomem, nostack)); + ::core::arch::asm!("", in("r12") msg, options(nomem, nostack)); $crate::__furi_crash(); - core::hint::unreachable_unchecked(); + ::core::hint::unreachable_unchecked(); } }; } From 7b504d65747e43e51097a83dbee77cd66849d983 Mon Sep 17 00:00:00 2001 From: Petr Portnov Date: Sat, 3 Jun 2023 16:02:35 +0300 Subject: [PATCH 25/49] docs: enhance exammples of `UnsendUnsync` and `Unsend` --- crates/flipperzero/src/gui/icon_animation.rs | 50 ++++++++++---------- crates/flipperzero/src/internals.rs | 22 +++++---- 2 files changed, 40 insertions(+), 32 deletions(-) diff --git a/crates/flipperzero/src/gui/icon_animation.rs b/crates/flipperzero/src/gui/icon_animation.rs index a07d1295..395dbbcc 100644 --- a/crates/flipperzero/src/gui/icon_animation.rs +++ b/crates/flipperzero/src/gui/icon_animation.rs @@ -29,30 +29,32 @@ impl<'a, C: IconAnimationCallbacks> IconAnimation<'a, C> { _parent_lifetime: PhantomData, }; - pub unsafe extern "C" fn dispatch_update( - instance: *mut SysIconAnimation, - context: *mut c_void, - ) { - // SAFETY: `icon_anination` is guaranteed to be a valid pointer - let instance = unsafe { IconAnimationView::from_raw(instance) }; - - let context: *mut C = context.cast(); - // SAFETY: `context` is stored in a `Box` which is a member of `ViewPort` - // and the callback is accessed exclusively by this function - unsafe { &mut *context }.on_update(instance); - } - - if !ptr::eq( - C::on_update as *const c_void, - <() as IconAnimationCallbacks>::on_update as *const c_void, - ) { - let context = (&*icon_animation.callbacks as *const C).cast_mut().cast(); - let raw = raw.as_ptr(); - let callback = Some(dispatch_update:: as _); - - // SAFETY: `raw` and `callback` are valid - // and `context` is valid as the box lives with this struct - unsafe { sys::icon_animation_set_update_callback(raw, callback, context) } + { + pub unsafe extern "C" fn dispatch_update( + instance: *mut SysIconAnimation, + context: *mut c_void, + ) { + // SAFETY: `icon_animation` is guaranteed to be a valid pointer + let instance = unsafe { IconAnimationView::from_raw(instance) }; + + let context: *mut C = context.cast(); + // SAFETY: `context` is stored in a `Box` which is a member of `ViewPort` + // and the callback is accessed exclusively by this function + unsafe { &mut *context }.on_update(instance); + } + + if !ptr::eq( + C::on_update as *const c_void, + <() as IconAnimationCallbacks>::on_update as *const c_void, + ) { + let raw = raw.as_ptr(); + let callback = Some(dispatch_update:: as _); + let context = (&*icon_animation.callbacks as *const C).cast_mut().cast(); + + // SAFETY: `raw` and `callback` are valid + // and `context` is valid as the box lives with this struct + unsafe { sys::icon_animation_set_update_callback(raw, callback, context) }; + } } icon_animation diff --git a/crates/flipperzero/src/internals.rs b/crates/flipperzero/src/internals.rs index cdfec6ea..b10479c5 100644 --- a/crates/flipperzero/src/internals.rs +++ b/crates/flipperzero/src/internals.rs @@ -9,11 +9,18 @@ use core::{marker::PhantomData, mem}; /// /// Make type `Foo` `impl !Sync` and `impl !Send`: /// -/// ```no_run +/// ```compile_fail /// use std::marker::PhantomData; /// struct Foo { /// _marker: PhantomData, /// } +/// +/// fn require_send(_: impl Send) {} +/// fn require_sync(_: impl Sync) {} +/// +/// let x = Foo { _marker: PhantomData }; +/// require_send(x); +/// require_sync(x); /// ``` pub(crate) struct UnsendUnsync(*const ()); @@ -31,11 +38,16 @@ const _: () = { /// /// Make type `Foo` `impl !Send`: /// -/// ```no_run +/// ```compile_fail /// use std::marker::PhantomData; /// struct Foo { /// _marker: PhantomData, /// } +/// +/// fn require_send(_: impl Send) {} +/// +/// let x = Foo { _marker: PhantomData }; +/// require_send(x); /// ``` pub(crate) struct Unsend(*const ()); @@ -70,12 +82,6 @@ pub(crate) mod ops { } else { quotient } - - if divident % divisor == 0 { - divident / divisor - } else { - divident / divisor + 1 - } } } } From f28c4a78b39f422a468d827a6ce29524b19c48c4 Mon Sep 17 00:00:00 2001 From: Petr Portnov Date: Sat, 3 Jun 2023 19:51:12 +0300 Subject: [PATCH 26/49] feat: add ident-checks to `xbm` macro --- crates/flipperzero/src/gui/xbm.rs | 121 +++++++++++++++++++-- crates/flipperzero/src/notification/mod.rs | 4 +- crates/flipperzero/src/storage.rs | 4 +- crates/test/macros/Cargo.toml | 1 + 4 files changed, 115 insertions(+), 15 deletions(-) diff --git a/crates/flipperzero/src/gui/xbm.rs b/crates/flipperzero/src/gui/xbm.rs index d377e1b8..7e1d7e18 100644 --- a/crates/flipperzero/src/gui/xbm.rs +++ b/crates/flipperzero/src/gui/xbm.rs @@ -50,6 +50,7 @@ impl XbmImage { } } + // FIXME: XBM trails on line ends #[inline] const fn offsets(&self, x: u8, y: u8) -> Option<(u8, u8)> { if let Some(offset) = self.offset(x, y) { @@ -189,8 +190,7 @@ impl XbmImage<&'static [u8]> { /// Basic usage: /// /// ```rust - /// use flipperzero::gui::xbm::XbmImage; - /// + /// # use flipperzero::gui::xbm::XbmImage; /// const IMAGE: XbmImage<&'static [u8]> = XbmImage::new_from_static(4, 4, &[0xFE, 0x12]); /// ``` pub const fn new_from_static(width: u8, height: u8, data: &'static [u8]) -> Self { @@ -217,9 +217,8 @@ impl XbmImage> { /// Basic usage: /// /// ```rust - /// use flipperzero::gui::xbm::XbmImage; - /// - /// const IMAGE: XbmImage<[u8; 2]> = XbmImage::new_from_array::<4, 4>([0xFE, 0x12]); + /// # use flipperzero::gui::xbm::{XbmImage, ByteArray}; + /// const IMAGE: XbmImage> = XbmImage::new_from_array::<4, 4>([0xFE, 0x12]); /// ``` pub const fn new_from_array(data: [u8; SIZE]) -> Self { let bytes = Self::dimension_bytes(WIDTH, HEIGHT); @@ -251,20 +250,120 @@ impl DerefMut for ByteArray { } } +/// Creates #[macro_export] macro_rules! xbm { ( - #define $_width_ident:ident $width:literal - #define $_height_ident:ident $height:literal + unsafe { + #define $_width_ident:ident $width:literal + #define $_height_ident:ident $height:literal + $( + #define $_x_hotspot_ident:ident $_hotspot_x:literal + #define $_y_hotspot_ident:ident $_hotspot_y:literal + )? + static char $_bits_ident:ident[] = { + $($byte:literal),* $(,)? + }; + } + ) => {{ + $crate::gui::xbm::XbmImage::new_from_array::<$width, $height>([$($byte,)*]) + }}; + ( + #define $width_ident:ident $width:literal + #define $height_ident:ident $height:literal $( - #define $_hotspot_x_ident:ident $_hotspot_x:literal - #define $_hotspot_y_ident:ident $_hotspot_y:literal + #define $x_hotspot_ident:ident $_hotspot_x:literal + #define $y_hotspot_ident:ident $_hotspot_y:literal )? - static char $_bits_ident:ident[] = { + static char $bits_ident:ident[] = { $($byte:literal),* $(,)? }; ) => {{ - $crate::gui::xbm::XbmImage::new_from_array::<$width, $height>([$($byte,)*]) + { // name assertions + let bits_ident = stringify!($bits_ident).as_bytes(); + assert!( + matches!(bits_ident, [.., b'_', b'b', b'i', b't', b's']), + "width identifier should end with `_bits`", + ); + let significant_len = bits_ident.len() - b"_bits".len(); + + const fn str_eq(left: &[u8], right: &[u8], limit: usize) -> bool { + match (left.split_first(), right.split_first()) { + ( + Some((&left_first, left_remaining)), + Some((&right_first, right_remaining)), + ) => { + left_first == right_first + && (limit == 1 || str_eq(left_remaining, right_remaining, limit - 1)) + } + (None, None) => true, + _ => false, + } + } + + let width_ident = stringify!($width_ident).as_bytes(); + assert!( + matches!(width_ident, [.., b'_', b'w', b'i', b'd', b't', b'h']), + "width identifier should end with `_width`", + ); + assert!( + str_eq(bits_ident, width_ident, significant_len), + "bits identifier and width identifier should have the same prefix" + ); + + let height_ident = stringify!($height_ident).as_bytes(); + assert!( + matches!(height_ident, [.., b'_', b'h', b'e', b'i', b'g', b'h', b't']), + "width identifier should end with `_height`", + ); + assert!( + str_eq(bits_ident, height_ident, significant_len), + "bits identifier and height identifier should have the same prefix" + ); + + $( + let x_hotspot_ident = stringify!($x_hotspot_ident).as_bytes(); + assert!( + matches!(bits_ident, [.., b'_', b'x', b'_', b'h', b'o', b't']), + "x-hotspot identifier should end with `_x_hot`", + ); + assert!( + str_eq(bits_ident, x_hotspot_ident, significant_len), + "bits identifier and x-hotspot identifier should have the same prefix" + ); + + let y_hotspot_ident = stringify!($y_hotspot_ident).as_bytes(); + assert!( + matches!(bits_ident, [.., b'_', b'y', b'_', b'h', b'o', b't']), + "y-hotspot identifier should end with `_y_hot`", + ); + assert!( + str_eq(bits_ident, y_hotspot_ident, significant_len), + "bits identifier and y-hotspot identifier should have the same prefix" + ); + )? + + // assert!(::core::matches!( + // width_ident.get(width_ident.len() - 5), + // ::core::option::Option::Some(b'w') + // ), "sad"); + // match width_ident.get(width_ident.len() - 5..) { + // ::core::option::Option::Some(b"width") => {}, + // _ => panic!("the first identifier should end with `_width") + // }; + } + + $crate::xbm!(unsafe { + #define $width_ident $width + #define $height_ident $height + $( + #define $x_hotspot_ident $_hotspot_x + #define $y_hotspot_ident $_hotspot_y + )? + static char $bits_ident[] = { + $($byte),* + }; + }) }}; } diff --git a/crates/flipperzero/src/notification/mod.rs b/crates/flipperzero/src/notification/mod.rs index a33edaac..93e19fa8 100644 --- a/crates/flipperzero/src/notification/mod.rs +++ b/crates/flipperzero/src/notification/mod.rs @@ -43,12 +43,12 @@ impl NotificationService { /// sequence before the firmware has finished reading it. At any time where this is an issue /// `notify_blocking` should be used instead.. pub fn notify(&mut self, sequence: &'static NotificationSequence) { - unsafe { sys::notification_message(self.data.as_ptr(), sequence.to_sys()) }; + unsafe { sys::notification_message(self.data.as_raw(), sequence.to_sys()) }; } /// Runs a notification sequence and blocks the thread. pub fn notify_blocking(&mut self, sequence: &'static NotificationSequence) { - unsafe { sys::notification_message_block(self.data.as_ptr(), sequence.to_sys()) }; + unsafe { sys::notification_message_block(self.data.as_raw(), sequence.to_sys()) }; } } diff --git a/crates/flipperzero/src/storage.rs b/crates/flipperzero/src/storage.rs index 43437470..b12e1d65 100644 --- a/crates/flipperzero/src/storage.rs +++ b/crates/flipperzero/src/storage.rs @@ -155,7 +155,7 @@ impl File { unsafe { let record = UnsafeRecord::open(RECORD_STORAGE); File( - NonNull::new_unchecked(sys::storage_file_alloc(record.as_ptr())), + NonNull::new_unchecked(sys::storage_file_alloc(record.as_raw())), record, ) } @@ -166,7 +166,7 @@ impl Drop for File { fn drop(&mut self) { unsafe { // `storage_file_close` calls `storage_file_sync` - // internally, so it's not necesssary to call it here. + // internally, so it's not necessary to call it here. sys::storage_file_close(self.0.as_ptr()); } } diff --git a/crates/test/macros/Cargo.toml b/crates/test/macros/Cargo.toml index 2e747210..9ea08ea7 100644 --- a/crates/test/macros/Cargo.toml +++ b/crates/test/macros/Cargo.toml @@ -10,6 +10,7 @@ license.workspace = true [lib] proc-macro = true +doc = false [dependencies] proc-macro2 = "1" From b166c5e377acdd42c3fad12973cb0670bf93ed64 Mon Sep 17 00:00:00 2001 From: Petr Portnov Date: Sat, 3 Jun 2023 20:24:31 +0300 Subject: [PATCH 27/49] fix: correctly set XBM image dimensions --- crates/flipperzero/src/gui/canvas.rs | 11 +++++++++++ crates/flipperzero/src/gui/xbm.rs | 8 ++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/crates/flipperzero/src/gui/canvas.rs b/crates/flipperzero/src/gui/canvas.rs index 88747f35..c717d2ae 100644 --- a/crates/flipperzero/src/gui/canvas.rs +++ b/crates/flipperzero/src/gui/canvas.rs @@ -5,6 +5,7 @@ use crate::gui::{ icon_animation::{IconAnimation, IconAnimationCallbacks}, xbm::XbmImage, }; +use crate::{debug, warn}; use core::{ ffi::{c_char, CStr}, marker::PhantomData, @@ -219,6 +220,16 @@ impl CanvasView<'_> { // SAFETY: `raw` is always valid // and `data` is always valid and does not have to outlive the view // as it is copied + if x == 2 && y == 2 { + warn!( + "Printing image at {}:{} of dims {}:{}: {:?}", + x, + y, + width, + height, + xbm.data() + ); + } unsafe { sys::canvas_draw_xbm(raw, x, y, width, height, data) }; Some(()) } diff --git a/crates/flipperzero/src/gui/xbm.rs b/crates/flipperzero/src/gui/xbm.rs index 7e1d7e18..450cfe4e 100644 --- a/crates/flipperzero/src/gui/xbm.rs +++ b/crates/flipperzero/src/gui/xbm.rs @@ -203,8 +203,8 @@ impl XbmImage<&'static [u8]> { Self { data, - width: 0, - height: 0, + width, + height, } } } @@ -227,8 +227,8 @@ impl XbmImage> { Self { data: ByteArray(data), - width: 0, - height: 0, + width: WIDTH, + height: HEIGHT, } } } From 045b72feb03c4b7f8c20f679fca6a2fa38d46a92 Mon Sep 17 00:00:00 2001 From: Petr Portnov Date: Sat, 3 Jun 2023 21:19:50 +0300 Subject: [PATCH 28/49] chore: fix remaining compilation errors --- crates/flipperzero/src/dialogs/mod.rs | 12 ++--- crates/flipperzero/src/gui/canvas.rs | 12 ++--- crates/flipperzero/src/internals.rs | 17 +++++++ crates/flipperzero/src/lib.rs | 1 + crates/sys/src/inlines/furi_hal_gpio.rs | 2 +- tools/src/bin/run-fap.rs | 4 +- tools/src/bin/storage.rs | 68 +++++++++++-------------- 7 files changed, 64 insertions(+), 52 deletions(-) diff --git a/crates/flipperzero/src/dialogs/mod.rs b/crates/flipperzero/src/dialogs/mod.rs index 7610ef80..d1e34896 100644 --- a/crates/flipperzero/src/dialogs/mod.rs +++ b/crates/flipperzero/src/dialogs/mod.rs @@ -37,14 +37,14 @@ impl DialogsApp { /// Obtains a handle to the Dialogs app. pub fn open() -> Self { Self { - data: unsafe { UnsafeRecord::open(RECORD_DIALOGS) }, + data: unsafe { UnsafeRecord::open(Self::RECORD_DIALOGS) }, } } /// Displays a message. pub fn show(&mut self, message: &DialogMessage) -> DialogMessageButton { let button_sys = - unsafe { sys::dialog_message_show(self.data.as_ptr(), message.data.as_ptr()) }; + unsafe { sys::dialog_message_show(self.data.as_raw(), message.data.as_ptr()) }; DialogMessageButton::from_sys(button_sys).expect("Invalid button") } @@ -96,8 +96,8 @@ impl<'a> DialogMessage<'a> { header.as_ptr(), x, y, - horizontal.to_sys(), - vertical.to_sys(), + horizontal.into(), + vertical.into(), ); } } @@ -110,8 +110,8 @@ impl<'a> DialogMessage<'a> { text.as_ptr(), x, y, - horizontal.to_sys(), - vertical.to_sys(), + horizontal.into(), + vertical.into(), ); } } diff --git a/crates/flipperzero/src/gui/canvas.rs b/crates/flipperzero/src/gui/canvas.rs index c717d2ae..2bee04fc 100644 --- a/crates/flipperzero/src/gui/canvas.rs +++ b/crates/flipperzero/src/gui/canvas.rs @@ -436,12 +436,12 @@ pub enum Color { } #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] -pub enum FromSysColor { +pub enum FromSysColorError { Invalid(SysColor), } impl TryFrom for Color { - type Error = FromSysColor; + type Error = FromSysColorError; fn try_from(value: SysColor) -> Result { use sys::{ @@ -484,13 +484,13 @@ pub enum Font { } #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] -pub enum FromSysFont { +pub enum FromSysFontError { TotalNumber, Invalid(SysFont), } impl TryFrom for Font { - type Error = FromSysFont; + type Error = FromSysFontError; fn try_from(value: SysFont) -> Result { use sys::{ @@ -588,12 +588,12 @@ pub enum Align { } #[derive(Clone, Copy, Debug)] -pub enum FromSysAlign { +pub enum FromSysAlignError { Invalid(SysAlign), } impl TryFrom for Align { - type Error = FromSysAlign; + type Error = FromSysAlignError; fn try_from(value: SysAlign) -> Result { use sys::{ diff --git a/crates/flipperzero/src/internals.rs b/crates/flipperzero/src/internals.rs index b10479c5..370c0a93 100644 --- a/crates/flipperzero/src/internals.rs +++ b/crates/flipperzero/src/internals.rs @@ -68,6 +68,23 @@ const _: () = { /// falling back to ad-hoc implementations otherwise. #[allow(dead_code)] // this functions may be unused if a specific feature set does not require them pub(crate) mod ops { + pub const fn div_ceil_u8(divident: u8, divisor: u8) -> u8 { + #[cfg(feature = "unstable_intrinsics")] + { + divident.div_ceil(divisor) + } + #[cfg(not(feature = "unstable_intrinsics"))] + { + let quotient = divident / divisor; + let remainder = divident % divisor; + if remainder > 0 && divisor > 0 { + quotient + 1 + } else { + quotient + } + } + } + pub const fn div_ceil_u16(divident: u16, divisor: u16) -> u16 { #[cfg(feature = "unstable_intrinsics")] { diff --git a/crates/flipperzero/src/lib.rs b/crates/flipperzero/src/lib.rs index aae3f129..d383f888 100644 --- a/crates/flipperzero/src/lib.rs +++ b/crates/flipperzero/src/lib.rs @@ -63,5 +63,6 @@ flipperzero_test::tests_runner!( crate::toolbox::crc32::tests, crate::toolbox::md5::tests, crate::toolbox::sha256::tests, + crate::gui::xbm::tests, ] ); diff --git a/crates/sys/src/inlines/furi_hal_gpio.rs b/crates/sys/src/inlines/furi_hal_gpio.rs index 6fb0627c..862d3150 100644 --- a/crates/sys/src/inlines/furi_hal_gpio.rs +++ b/crates/sys/src/inlines/furi_hal_gpio.rs @@ -1,6 +1,6 @@ //! Inlines for Furi HAL GPIO interface. //! -//! See: https://github.com/flipperdevices/flipperzero-firmware/blob/release/firmware/targets/f7/furi_hal/furi_hal_gpio.h +//! See: use crate as sys; diff --git a/tools/src/bin/run-fap.rs b/tools/src/bin/run-fap.rs index 3e5bdcf1..6f11ea9b 100644 --- a/tools/src/bin/run-fap.rs +++ b/tools/src/bin/run-fap.rs @@ -22,7 +22,9 @@ struct Cli { /// Arguments to provide to the FAP binary. /// - /// Ignored until https://github.com/flipperdevices/flipperzero-firmware/issues/2505 is resolved. + /// Ignored until [flipperdevices/flipperzero-firmware#2505][1] is resolved. + /// + /// [1]: https://github.com/flipperdevices/flipperzero-firmware/issues/2505 args: Vec, } diff --git a/tools/src/bin/storage.rs b/tools/src/bin/storage.rs index 61af09ec..10afb54a 100644 --- a/tools/src/bin/storage.rs +++ b/tools/src/bin/storage.rs @@ -1,9 +1,11 @@ //! Storage CLI. //! -//! See: https://github.com/flipperdevices/flipperzero-firmware/blob/dev/scripts/storage.py +//! See: [storage.py][1] script. +//! +//! [1]: (https://github.com/flipperdevices/flipperzero-firmware/blob/dev/scripts/storage.py) -use std::process; use std::path::PathBuf; +use std::process; use std::time::Duration; use clap::{Parser, Subcommand}; @@ -19,7 +21,7 @@ struct Cli { port: Option, /// Commands #[command(subcommand)] - command: Option + command: Option, } #[derive(Subcommand)] @@ -77,11 +79,12 @@ fn main() { None => { eprintln!("No subcommand specified"); process::exit(2); - }, + } Some(c) => c, }; - let port_info = serial::find_flipperzero(cli.port.as_deref()).expect("unable to find Flipper Zero"); + let port_info = + serial::find_flipperzero(cli.port.as_deref()).expect("unable to find Flipper Zero"); let port = serialport::new(&port_info.port_name, serial::BAUD_115200) .timeout(Duration::from_secs(30)) .open() @@ -91,46 +94,35 @@ fn main() { store.start().expect("failed to start storage"); let result = match command { - Commands::Mkdir { flipper_path } => { - store.mkdir(flipper_path) - }, - Commands::Format => { - store.format_ext() - }, - Commands::Remove { flipper_path } => { - store.remove(flipper_path) - }, + Commands::Mkdir { flipper_path } => store.mkdir(flipper_path), + Commands::Format => store.format_ext(), + Commands::Remove { flipper_path } => store.remove(flipper_path), Commands::Read => todo!(), - Commands::Size { flipper_path } => { - match store.size(flipper_path) { - Err(err) => Err(err), - Ok(size) => { - println!("{size}"); + Commands::Size { flipper_path } => match store.size(flipper_path) { + Err(err) => Err(err), + Ok(size) => { + println!("{size}"); - Ok(()) - } + Ok(()) } }, - Commands::Receive { flipper_path, local_path } => { - store.receive_file(flipper_path, &local_path) - }, - Commands::Send { local_path, flipper_path } => { - store.send_file(local_path.as_path(), flipper_path) - }, - Commands::List { flipper_path } => { - store.list_tree(flipper_path) - }, - Commands::Md5sum { flipper_path } => { - match store.md5sum(flipper_path) { - Err(err) => Err(err), - Ok(md5sum) => { - println!("{md5sum}"); + Commands::Receive { + flipper_path, + local_path, + } => store.receive_file(flipper_path, &local_path), + Commands::Send { + local_path, + flipper_path, + } => store.send_file(local_path.as_path(), flipper_path), + Commands::List { flipper_path } => store.list_tree(flipper_path), + Commands::Md5sum { flipper_path } => match store.md5sum(flipper_path) { + Err(err) => Err(err), + Ok(md5sum) => { + println!("{md5sum}"); - Ok(()) - } + Ok(()) } }, - }; if let Err(err) = result { From 3af3d7efb977f43300324d72590fd0e9a0ae6145 Mon Sep 17 00:00:00 2001 From: Petr Portnov Date: Sat, 3 Jun 2023 21:24:33 +0300 Subject: [PATCH 29/49] chore: bring back `.vscode` --- .vscode/extensions.json | 5 +++++ .vscode/settings.json | 3 +++ 2 files changed, 8 insertions(+) create mode 100644 .vscode/extensions.json create mode 100644 .vscode/settings.json diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 00000000..64604562 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "rust-lang.rust-analyzer", + ], +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..f89ed5f1 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "editor.formatOnSave": true +} From 46b70034d4d644565bb3fe16b408e75e40869a19 Mon Sep 17 00:00:00 2001 From: Petr Portnov Date: Sat, 3 Jun 2023 22:08:00 +0300 Subject: [PATCH 30/49] docs: enhance alignment of doc attribute in `kernel::LockGuard` --- crates/flipperzero/src/kernel.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/flipperzero/src/kernel.rs b/crates/flipperzero/src/kernel.rs index ed5ac3d7..2789c07c 100644 --- a/crates/flipperzero/src/kernel.rs +++ b/crates/flipperzero/src/kernel.rs @@ -36,8 +36,8 @@ pub fn lock() -> Result { #[cfg_attr( feature = "unstable_lints", must_not_suspend = "holding a MutexGuard across suspend \ - points can cause deadlocks, delays, \ - and cause Futures to not implement `Send`" + points can cause deadlocks, delays, \ + and cause Futures to not implement `Send`" )] pub struct LockGuard { was_locked: bool, From 2b138c5ec85e50dc5dbfd48408dbea58e48217d8 Mon Sep 17 00:00:00 2001 From: Petr Portnov Date: Sat, 3 Jun 2023 23:23:35 +0300 Subject: [PATCH 31/49] chore(gui): flatten module structure --- crates/flipperzero/Cargo.toml | 1 - crates/flipperzero/examples/gui.rs | 2 +- crates/flipperzero/src/gui/canvas.rs | 133 +++++--------- crates/flipperzero/src/gui/gui.rs | 228 ------------------------ crates/flipperzero/src/gui/icon.rs | 6 +- crates/flipperzero/src/gui/mod.rs | 212 +++++++++++++++++++++- crates/flipperzero/src/gui/view_port.rs | 39 ++-- crates/flipperzero/src/input/mod.rs | 81 +++------ 8 files changed, 304 insertions(+), 398 deletions(-) delete mode 100644 crates/flipperzero/src/gui/gui.rs diff --git a/crates/flipperzero/Cargo.toml b/crates/flipperzero/Cargo.toml index 0f982a62..7ed2b542 100644 --- a/crates/flipperzero/Cargo.toml +++ b/crates/flipperzero/Cargo.toml @@ -8,7 +8,6 @@ license.workspace = true edition.workspace = true rust-version.workspace = true autobins = false -autotests = false autobenches = false [package.metadata.docs.rs] diff --git a/crates/flipperzero/examples/gui.rs b/crates/flipperzero/examples/gui.rs index f96352f6..6957387a 100644 --- a/crates/flipperzero/examples/gui.rs +++ b/crates/flipperzero/examples/gui.rs @@ -23,8 +23,8 @@ use flipperzero::{ gui::xbm::{ByteArray, XbmImage}, gui::{ canvas::CanvasView, - gui::{Gui, GuiLayer}, view_port::{ViewPort, ViewPortCallbacks}, + Gui, GuiLayer, }, input::{InputEvent, InputKey, InputType}, println, diff --git a/crates/flipperzero/src/gui/canvas.rs b/crates/flipperzero/src/gui/canvas.rs index 2bee04fc..23d8c029 100644 --- a/crates/flipperzero/src/gui/canvas.rs +++ b/crates/flipperzero/src/gui/canvas.rs @@ -6,6 +6,7 @@ use crate::gui::{ xbm::XbmImage, }; use crate::{debug, warn}; +use core::fmt::Display; use core::{ ffi::{c_char, CStr}, marker::PhantomData, @@ -431,8 +432,7 @@ impl From for SysCanvasFontParameters { pub enum Color { White, Black, - // TDOO: add this color - // Xor, + Xor, } #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] @@ -444,16 +444,10 @@ impl TryFrom for Color { type Error = FromSysColorError; fn try_from(value: SysColor) -> Result { - use sys::{ - Color_ColorBlack as SYS_COLOR_BLACK, - Color_ColorWhite as SYS_COLOR_WHITE, - // Color_ColorXOR as SYS_COLOR_XOR, - }; - Ok(match value { - SYS_COLOR_WHITE => Self::White, - SYS_COLOR_BLACK => Self::Black, - // SYS_COLOR_XOR => Ok(Self::Xor), + sys::Color_ColorWhite => Self::White, + sys::Color_ColorBlack => Self::Black, + sys::Color_ColorXOR => Self::Xor, invalid => Err(Self::Error::Invalid(invalid))?, }) } @@ -461,16 +455,10 @@ impl TryFrom for Color { impl From for SysColor { fn from(value: Color) -> Self { - use sys::{ - Color_ColorBlack as SYS_COLOR_BLACK, - Color_ColorWhite as SYS_COLOR_WHITE, - // Color_ColorXOR as SYS_COLOR_XOR, - }; - match value { - Color::White => SYS_COLOR_WHITE, - Color::Black => SYS_COLOR_BLACK, - // Color::Xor => SYS_COLOR_XOR, + Color::White => sys::Color_ColorWhite, + Color::Black => sys::Color_ColorBlack, + Color::Xor => sys::Color_ColorXOR, } } } @@ -483,6 +471,20 @@ pub enum Font { BigNumbers, } +impl Font { + /// Gets the total number of available fonts. + /// + /// # Example + /// + /// ``` + /// # use flipperzero::gui::canvas::Font; + /// assert_eq!(Font::total_number(), 4); + /// ``` + pub const fn total_number() -> usize { + sys::Font_FontTotalNumber as usize + } +} + #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] pub enum FromSysFontError { TotalNumber, @@ -493,18 +495,12 @@ impl TryFrom for Font { type Error = FromSysFontError; fn try_from(value: SysFont) -> Result { - use sys::{ - Font_FontBigNumbers as SYS_FONT_BIG_NUMBERS, Font_FontKeyboard as SYS_FONT_KEYBOARD, - Font_FontPrimary as SYS_FONT_PRIMARY, Font_FontSecondary as SYS_FONT_SECONDARY, - Font_FontTotalNumber as SYS_FONT_TOTAL_NUMBER, - }; - Ok(match value { - SYS_FONT_PRIMARY => Self::Primary, - SYS_FONT_SECONDARY => Self::Secondary, - SYS_FONT_KEYBOARD => Self::Keyboard, - SYS_FONT_BIG_NUMBERS => Self::BigNumbers, - SYS_FONT_TOTAL_NUMBER => Err(Self::Error::TotalNumber)?, + sys::Font_FontPrimary => Self::Primary, + sys::Font_FontSecondary => Self::Secondary, + sys::Font_FontKeyboard => Self::Keyboard, + sys::Font_FontBigNumbers => Self::BigNumbers, + sys::Font_FontTotalNumber => Err(Self::Error::TotalNumber)?, invalid => Err(Self::Error::Invalid(invalid))?, }) } @@ -512,16 +508,11 @@ impl TryFrom for Font { impl From for SysFont { fn from(value: Font) -> Self { - use sys::{ - Font_FontBigNumbers as SYS_FONT_BIG_NUMBERS, Font_FontKeyboard as SYS_FONT_KEYBOARD, - Font_FontPrimary as SYS_FONT_PRIMARY, Font_FontSecondary as SYS_FONT_SECONDARY, - }; - match value { - Font::Primary => SYS_FONT_PRIMARY, - Font::Secondary => SYS_FONT_SECONDARY, - Font::Keyboard => SYS_FONT_KEYBOARD, - Font::BigNumbers => SYS_FONT_BIG_NUMBERS, + Font::Primary => sys::Font_FontPrimary, + Font::Secondary => sys::Font_FontSecondary, + Font::Keyboard => sys::Font_FontKeyboard, + Font::BigNumbers => sys::Font_FontBigNumbers, } } } @@ -543,18 +534,11 @@ impl TryFrom for CanvasDirection { type Error = FromSysCanvasDirectionError; fn try_from(value: SysCanvasDirection) -> Result { - use sys::{ - CanvasDirection_CanvasDirectionBottomToTop as SYS_CANVAS_DIRECTION_BOTTOM_TO_TOP, - CanvasDirection_CanvasDirectionLeftToRight as SYS_CANVAS_DIRECTION_LEFT_TO_RIGHT, - CanvasDirection_CanvasDirectionRightToLeft as SYS_CANVAS_DIRECTION_RIGHT_TO_LEFT, - CanvasDirection_CanvasDirectionTopToBottom as SYS_CANVAS_DIRECTION_TOP_TO_BOTTOM, - }; - Ok(match value { - SYS_CANVAS_DIRECTION_LEFT_TO_RIGHT => Self::LeftToRight, - SYS_CANVAS_DIRECTION_TOP_TO_BOTTOM => Self::TopToBottom, - SYS_CANVAS_DIRECTION_RIGHT_TO_LEFT => Self::RightToLeft, - SYS_CANVAS_DIRECTION_BOTTOM_TO_TOP => Self::BottomToTop, + sys::CanvasDirection_CanvasDirectionLeftToRight => Self::LeftToRight, + sys::CanvasDirection_CanvasDirectionTopToBottom => Self::TopToBottom, + sys::CanvasDirection_CanvasDirectionRightToLeft => Self::RightToLeft, + sys::CanvasDirection_CanvasDirectionBottomToTop => Self::BottomToTop, invalid => Err(Self::Error::Invalid(invalid))?, }) } @@ -562,18 +546,11 @@ impl TryFrom for CanvasDirection { impl From for SysCanvasDirection { fn from(value: CanvasDirection) -> Self { - use sys::{ - CanvasDirection_CanvasDirectionBottomToTop as SYS_CANVAS_DIRECTION_BOTTOM_TO_TOP, - CanvasDirection_CanvasDirectionLeftToRight as SYS_CANVAS_DIRECTION_LEFT_TO_RIGHT, - CanvasDirection_CanvasDirectionRightToLeft as SYS_CANVAS_DIRECTION_RIGHT_TO_LEFT, - CanvasDirection_CanvasDirectionTopToBottom as SYS_CANVAS_DIRECTION_TOP_TO_BOTTOM, - }; - match value { - CanvasDirection::BottomToTop => SYS_CANVAS_DIRECTION_BOTTOM_TO_TOP, - CanvasDirection::LeftToRight => SYS_CANVAS_DIRECTION_LEFT_TO_RIGHT, - CanvasDirection::RightToLeft => SYS_CANVAS_DIRECTION_RIGHT_TO_LEFT, - CanvasDirection::TopToBottom => SYS_CANVAS_DIRECTION_TOP_TO_BOTTOM, + CanvasDirection::BottomToTop => sys::CanvasDirection_CanvasDirectionBottomToTop, + CanvasDirection::LeftToRight => sys::CanvasDirection_CanvasDirectionLeftToRight, + CanvasDirection::RightToLeft => sys::CanvasDirection_CanvasDirectionRightToLeft, + CanvasDirection::TopToBottom => sys::CanvasDirection_CanvasDirectionTopToBottom, } } } @@ -596,18 +573,12 @@ impl TryFrom for Align { type Error = FromSysAlignError; fn try_from(value: SysAlign) -> Result { - use sys::{ - Align_AlignBottom as SYS_ALIGN_BOTTOM, Align_AlignCenter as SYS_ALIGN_CENTER, - Align_AlignLeft as SYS_ALIGN_LEFT, Align_AlignRight as SYS_ALIGN_RIGHT, - Align_AlignTop as SYS_ALIGN_TOP, - }; - Ok(match value { - SYS_ALIGN_LEFT => Self::Left, - SYS_ALIGN_RIGHT => Self::Right, - SYS_ALIGN_TOP => Self::Top, - SYS_ALIGN_BOTTOM => Self::Bottom, - SYS_ALIGN_CENTER => Self::Center, + sys::Align_AlignLeft => Self::Left, + sys::Align_AlignRight => Self::Right, + sys::Align_AlignTop => Self::Top, + sys::Align_AlignBottom => Self::Bottom, + sys::Align_AlignCenter => Self::Center, invalid => Err(Self::Error::Invalid(invalid))?, }) } @@ -615,18 +586,12 @@ impl TryFrom for Align { impl From for SysAlign { fn from(value: Align) -> Self { - use sys::{ - Align_AlignBottom as SYS_ALIGN_BOTTOM, Align_AlignCenter as SYS_ALIGN_CENTER, - Align_AlignLeft as SYS_ALIGN_LEFT, Align_AlignRight as SYS_ALIGN_RIGHT, - Align_AlignTop as SYS_ALIGN_TOP, - }; - match value { - Align::Left => SYS_ALIGN_LEFT, - Align::Right => SYS_ALIGN_RIGHT, - Align::Top => SYS_ALIGN_TOP, - Align::Bottom => SYS_ALIGN_BOTTOM, - Align::Center => SYS_ALIGN_CENTER, + Align::Left => sys::Align_AlignLeft, + Align::Right => sys::Align_AlignRight, + Align::Top => sys::Align_AlignTop, + Align::Bottom => sys::Align_AlignBottom, + Align::Center => sys::Align_AlignCenter, } } } diff --git a/crates/flipperzero/src/gui/gui.rs b/crates/flipperzero/src/gui/gui.rs deleted file mode 100644 index 3559acfe..00000000 --- a/crates/flipperzero/src/gui/gui.rs +++ /dev/null @@ -1,228 +0,0 @@ -//! GUI APIs - -use crate::{ - gui::{ - canvas::CanvasView, - view_port::{ViewPort, ViewPortCallbacks}, - }, - input::InputEvent, -}; -use core::{ - ffi::c_char, - fmt::Debug, - ops::{Deref, DerefMut}, -}; -use flipperzero_sys::{ - self as sys, furi::UnsafeRecord, Canvas as SysCanvas, Gui as SysGui, GuiLayer as SysGuiLayer, -}; - -/// System Gui wrapper. -pub struct Gui { - raw: UnsafeRecord, -} - -impl Gui { - /// Furi record corresponding to GUI. - pub const RECORD: *const c_char = sys::c_string!("gui"); - - pub fn new() -> Self { - // SAFETY: `RECORD` is a constant - let gui = unsafe { UnsafeRecord::open(Self::RECORD) }; - - Self { raw: gui } - } - - pub fn as_raw(&self) -> *mut SysGui { - self.raw.as_raw() - } - - pub fn add_view_port( - &mut self, - view_port: ViewPort, - layer: GuiLayer, - ) -> GuiViewPort<'_, VPC> { - let raw = self.as_raw(); - let view_port_ptr = view_port.as_raw(); - let layer = layer.into(); - - // SAFETY: all pointers are valid and `view_port` outlives this `Gui` - unsafe { sys::gui_add_view_port(raw, view_port_ptr, layer) }; - - GuiViewPort { - parent: self, - view_port, - } - } - - pub fn get_frame_buffer_size(&self) -> usize { - let raw = self.as_raw(); - // SAFETY: `raw` is always a valid pointer - unsafe { sys::gui_get_framebuffer_size(raw) } - } - - pub fn set_lockdown(&self, lockdown: bool) { - let raw = self.raw.as_raw(); - // SAFETY: `raw` is always a valid pointer - unsafe { sys::gui_set_lockdown(raw, lockdown) } - } - - // TODO: separate `GuiCanvas` (locking the parent) - // and `Canvas` (independent of the parent) - pub fn direct_draw_acquire(&mut self) -> ExclusiveCanvas<'_> { - let raw = self.as_raw(); - - // SAFETY: `raw` is always a valid pointer - let canvas = unsafe { CanvasView::from_raw(sys::gui_direct_draw_acquire(raw)) }; - - ExclusiveCanvas { gui: self, canvas } - } - - // TODO: canvas method - // TODO: callback methods -} - -impl Default for Gui { - fn default() -> Self { - Self::new() - } -} - -/// `ViewPort` bound to a `Gui`. -pub struct GuiViewPort<'a, VPC: ViewPortCallbacks> { - parent: &'a Gui, - view_port: ViewPort, -} - -impl<'a, VPC: ViewPortCallbacks> GuiViewPort<'a, VPC> { - pub fn view_port(&self) -> &ViewPort { - &self.view_port - } - - pub fn view_port_mut(&mut self) -> &mut ViewPort { - &mut self.view_port - } - - pub fn send_to_front(&mut self) { - let gui = self.parent.raw.as_raw(); - let view_port = self.view_port.as_raw(); - - // SAFETY: `self.parent` outlives this `GuiVewPort` - unsafe { sys::gui_view_port_send_to_front(gui, view_port) }; - } - - // FIXME: `gui_view_port_send_to_back` is not present in bindings - // pub fn send_to_back(&mut self) { - // let gui = self.gui.as_raw(); - // let view_port = self.view_port.as_raw(); - // - // unsafe { sys::gui_view_port_send_to_back(gui, view_port) }; - // } -} - -impl Drop for GuiViewPort<'_, VPC> { - fn drop(&mut self) { - let gui = self.parent.raw.as_raw(); - let view_port = self.view_port().as_raw(); - - // SAFETY: `gui` and `view_port` are valid pointers - // and this view port should have been added to the gui on creation - unsafe { sys::gui_remove_view_port(gui, view_port) } - } -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] -pub enum GuiLayer { - Desktop, - Window, - StatusBarLeft, - StatusBarRight, - Fullscreen, -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] -pub enum FromSysGuiLayerError { - Max, - Invalid(SysGuiLayer), -} - -impl TryFrom for GuiLayer { - type Error = FromSysGuiLayerError; - - fn try_from(value: SysGuiLayer) -> Result { - use sys::{ - GuiLayer_GuiLayerDesktop as SYS_GUI_LAYER_DESKTOP, - GuiLayer_GuiLayerFullscreen as SYS_GUI_LAYER_FULLSCREN, - GuiLayer_GuiLayerMAX as SYS_GUI_LAYER_MAX, - GuiLayer_GuiLayerStatusBarLeft as SYS_GUI_LAYER_BAR_LEFT, - GuiLayer_GuiLayerStatusBarRight as SYS_GUI_LAYER_BAR_RIGHT, - GuiLayer_GuiLayerWindow as SYS_GUI_LAYER_WINDOW, - }; - - Ok(match value { - SYS_GUI_LAYER_DESKTOP => Self::Desktop, - SYS_GUI_LAYER_WINDOW => Self::Window, - SYS_GUI_LAYER_BAR_LEFT => Self::StatusBarLeft, - SYS_GUI_LAYER_BAR_RIGHT => Self::StatusBarRight, - SYS_GUI_LAYER_FULLSCREN => Self::Fullscreen, - SYS_GUI_LAYER_MAX => Err(Self::Error::Max)?, - invalid => Err(Self::Error::Invalid(invalid))?, - }) - } -} - -impl From for SysGuiLayer { - fn from(value: GuiLayer) -> Self { - use sys::{ - GuiLayer_GuiLayerDesktop as SYS_GUI_LAYER_DESKTOP, - GuiLayer_GuiLayerFullscreen as SYS_GUI_LAYER_FULLSCREN, - GuiLayer_GuiLayerStatusBarLeft as SYS_GUI_LAYER_BAR_LEFT, - GuiLayer_GuiLayerStatusBarRight as SYS_GUI_LAYER_BAR_RIGHT, - GuiLayer_GuiLayerWindow as SYS_GUI_LAYER_WINDOW, - }; - - match value { - GuiLayer::Desktop => SYS_GUI_LAYER_DESKTOP, - GuiLayer::Window => SYS_GUI_LAYER_WINDOW, - GuiLayer::StatusBarLeft => SYS_GUI_LAYER_BAR_LEFT, - GuiLayer::StatusBarRight => SYS_GUI_LAYER_BAR_RIGHT, - GuiLayer::Fullscreen => SYS_GUI_LAYER_FULLSCREN, - } - } -} - -pub trait GuiCallbacks { - fn on_draw(&mut self, _canvas: *mut SysCanvas) {} - fn on_input(&mut self, _event: InputEvent) {} -} - -impl GuiCallbacks for () {} - -/// Exclusively accessible canvas. -pub struct ExclusiveCanvas<'a> { - gui: &'a mut Gui, - canvas: CanvasView<'a>, -} - -impl Drop for ExclusiveCanvas<'_> { - fn drop(&mut self) { - let gui = self.gui.as_raw(); - // SAFETY: this instance should have been created from `gui` - // using `gui_direct_draw_acquire` - // and will no longer be available since it is dropped - unsafe { sys::gui_direct_draw_release(gui) }; - } -} - -impl<'a> Deref for ExclusiveCanvas<'a> { - type Target = CanvasView<'a>; - - fn deref(&self) -> &Self::Target { - &self.canvas - } -} - -impl<'a> DerefMut for ExclusiveCanvas<'a> { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.canvas - } -} diff --git a/crates/flipperzero/src/gui/icon.rs b/crates/flipperzero/src/gui/icon.rs index a633f250..0d0f8163 100644 --- a/crates/flipperzero/src/gui/icon.rs +++ b/crates/flipperzero/src/gui/icon.rs @@ -11,7 +11,7 @@ impl Icon { /// /// # Safety /// - /// `raw` should be a valid pointer to [`SysCanvas`]. + /// `raw` should be a valid non-null pointer to [`SysCanvas`]. /// /// # Examples /// @@ -23,7 +23,9 @@ impl Icon { /// let ptr = todo!(); /// let canvas = unsafe { Icon::from_raw(ptr) }; /// ``` - pub unsafe fn from_raw(raw: NonNull) -> Self { + pub unsafe fn from_raw(raw: *mut SysIcon) -> Self { + // SAFETY: the caller is required to provide the valid pointer + let raw = NonNull::new_unchecked(raw); Self { raw } } diff --git a/crates/flipperzero/src/gui/mod.rs b/crates/flipperzero/src/gui/mod.rs index 78533ad7..35a19c8d 100644 --- a/crates/flipperzero/src/gui/mod.rs +++ b/crates/flipperzero/src/gui/mod.rs @@ -1,10 +1,218 @@ //! GUI service. pub mod canvas; -#[cfg(feature = "service-gui")] -pub mod gui; pub mod icon; pub mod icon_animation; pub mod view; pub mod view_port; pub mod xbm; + +use crate::{ + gui::{ + canvas::CanvasView, + view_port::{ViewPort, ViewPortCallbacks}, + }, + input::InputEvent, +}; +use core::{ + ffi::c_char, + fmt::Debug, + ops::{Deref, DerefMut}, +}; +use flipperzero_sys::{ + self as sys, furi::UnsafeRecord, Canvas as SysCanvas, Gui as SysGui, GuiLayer as SysGuiLayer, +}; + +/// System Gui wrapper. +pub struct Gui { + raw: UnsafeRecord, +} + +impl Gui { + /// Furi record corresponding to GUI. + pub const RECORD: *const c_char = sys::c_string!("gui"); + + pub fn new() -> Self { + // SAFETY: `RECORD` is a constant + let gui = unsafe { UnsafeRecord::open(Self::RECORD) }; + + Self { raw: gui } + } + + pub fn as_raw(&self) -> *mut SysGui { + self.raw.as_raw() + } + + pub fn add_view_port( + &mut self, + view_port: ViewPort, + layer: GuiLayer, + ) -> GuiViewPort<'_, VPC> { + let raw = self.as_raw(); + let view_port_ptr = view_port.as_raw(); + let layer = layer.into(); + + // SAFETY: all pointers are valid and `view_port` outlives this `Gui` + unsafe { sys::gui_add_view_port(raw, view_port_ptr, layer) }; + + GuiViewPort { + parent: self, + view_port, + } + } + + pub fn get_frame_buffer_size(&self) -> usize { + let raw = self.as_raw(); + // SAFETY: `raw` is always a valid pointer + unsafe { sys::gui_get_framebuffer_size(raw) } + } + + pub fn set_lockdown(&self, lockdown: bool) { + let raw = self.raw.as_raw(); + // SAFETY: `raw` is always a valid pointer + unsafe { sys::gui_set_lockdown(raw, lockdown) } + } + + // TODO: separate `GuiCanvas` (locking the parent) + // and `Canvas` (independent of the parent) + pub fn direct_draw_acquire(&mut self) -> ExclusiveCanvas<'_> { + let raw = self.as_raw(); + + // SAFETY: `raw` is always a valid pointer + let canvas = unsafe { CanvasView::from_raw(sys::gui_direct_draw_acquire(raw)) }; + + ExclusiveCanvas { gui: self, canvas } + } + + // TODO: canvas method + // TODO: callback methods +} + +impl Default for Gui { + fn default() -> Self { + Self::new() + } +} + +/// `ViewPort` bound to a `Gui`. +pub struct GuiViewPort<'a, VPC: ViewPortCallbacks> { + parent: &'a Gui, + view_port: ViewPort, +} + +impl<'a, VPC: ViewPortCallbacks> GuiViewPort<'a, VPC> { + pub fn view_port(&self) -> &ViewPort { + &self.view_port + } + + pub fn view_port_mut(&mut self) -> &mut ViewPort { + &mut self.view_port + } + + pub fn send_to_front(&mut self) { + let gui = self.parent.raw.as_raw(); + let view_port = self.view_port.as_raw(); + + // SAFETY: `self.parent` outlives this `GuiVewPort` + unsafe { sys::gui_view_port_send_to_front(gui, view_port) }; + } + + // FIXME: `gui_view_port_send_to_back` is not present in bindings + // pub fn send_to_back(&mut self) { + // let gui = self.gui.as_raw(); + // let view_port = self.view_port.as_raw(); + // + // unsafe { sys::gui_view_port_send_to_back(gui, view_port) }; + // } +} + +impl Drop for GuiViewPort<'_, VPC> { + fn drop(&mut self) { + let gui = self.parent.raw.as_raw(); + let view_port = self.view_port().as_raw(); + + // SAFETY: `gui` and `view_port` are valid pointers + // and this view port should have been added to the gui on creation + unsafe { sys::gui_remove_view_port(gui, view_port) } + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub enum GuiLayer { + Desktop, + Window, + StatusBarLeft, + StatusBarRight, + Fullscreen, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub enum FromSysGuiLayerError { + Max, + Invalid(SysGuiLayer), +} + +impl TryFrom for GuiLayer { + type Error = FromSysGuiLayerError; + + fn try_from(value: SysGuiLayer) -> Result { + Ok(match value { + sys::GuiLayer_GuiLayerDesktop => Self::Desktop, + sys::GuiLayer_GuiLayerWindow => Self::Window, + sys::GuiLayer_GuiLayerStatusBarLeft => Self::StatusBarLeft, + sys::GuiLayer_GuiLayerStatusBarRight => Self::StatusBarRight, + sys::GuiLayer_GuiLayerFullscreen => Self::Fullscreen, + sys::GuiLayer_GuiLayerMAX => Err(Self::Error::Max)?, + invalid => Err(Self::Error::Invalid(invalid))?, + }) + } +} + +impl From for SysGuiLayer { + fn from(value: GuiLayer) -> Self { + match value { + GuiLayer::Desktop => sys::GuiLayer_GuiLayerDesktop, + GuiLayer::Window => sys::GuiLayer_GuiLayerWindow, + GuiLayer::StatusBarLeft => sys::GuiLayer_GuiLayerStatusBarLeft, + GuiLayer::StatusBarRight => sys::GuiLayer_GuiLayerStatusBarRight, + GuiLayer::Fullscreen => sys::GuiLayer_GuiLayerFullscreen, + } + } +} + +pub trait GuiCallbacks { + fn on_draw(&mut self, _canvas: *mut SysCanvas) {} + fn on_input(&mut self, _event: InputEvent) {} +} + +impl GuiCallbacks for () {} + +/// Exclusively accessible canvas. +pub struct ExclusiveCanvas<'a> { + gui: &'a mut Gui, + canvas: CanvasView<'a>, +} + +impl Drop for ExclusiveCanvas<'_> { + fn drop(&mut self) { + let gui = self.gui.as_raw(); + // SAFETY: this instance should have been created from `gui` + // using `gui_direct_draw_acquire` + // and will no longer be available since it is dropped + unsafe { sys::gui_direct_draw_release(gui) }; + } +} + +impl<'a> Deref for ExclusiveCanvas<'a> { + type Target = CanvasView<'a>; + + fn deref(&self) -> &Self::Target { + &self.canvas + } +} + +impl<'a> DerefMut for ExclusiveCanvas<'a> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.canvas + } +} diff --git a/crates/flipperzero/src/gui/view_port.rs b/crates/flipperzero/src/gui/view_port.rs index ec79871a..436aaa0a 100644 --- a/crates/flipperzero/src/gui/view_port.rs +++ b/crates/flipperzero/src/gui/view_port.rs @@ -361,20 +361,12 @@ impl TryFrom for ViewPortOrientation { type Error = FromSysViewPortOrientationError; fn try_from(value: SysViewPortOrientation) -> Result { - use sys::{ - ViewPortOrientation_ViewPortOrientationHorizontal as SYS_VIEW_PORT_ORIENTATION_HORIZONTAL, - ViewPortOrientation_ViewPortOrientationHorizontalFlip as SYS_VIEW_PORT_ORIENTATION_HORIZONTAL_FLIP, - ViewPortOrientation_ViewPortOrientationMAX as SYS_VIEW_PORT_ORIENTATION_MAX, - ViewPortOrientation_ViewPortOrientationVertical as SYS_VIEW_PORT_ORIENTATION_VERTICAL, - ViewPortOrientation_ViewPortOrientationVerticalFlip as SYS_VIEW_PORT_ORIENTATION_VERTICAL_FLIP, - }; - Ok(match value { - SYS_VIEW_PORT_ORIENTATION_HORIZONTAL => Self::Horizontal, - SYS_VIEW_PORT_ORIENTATION_HORIZONTAL_FLIP => Self::HorizontalFlip, - SYS_VIEW_PORT_ORIENTATION_VERTICAL => Self::Vertical, - SYS_VIEW_PORT_ORIENTATION_VERTICAL_FLIP => Self::VerticalFlip, - SYS_VIEW_PORT_ORIENTATION_MAX => Err(Self::Error::Max)?, + sys::ViewPortOrientation_ViewPortOrientationHorizontal => Self::Horizontal, + sys::ViewPortOrientation_ViewPortOrientationHorizontalFlip => Self::HorizontalFlip, + sys::ViewPortOrientation_ViewPortOrientationVertical => Self::Vertical, + sys::ViewPortOrientation_ViewPortOrientationVerticalFlip => Self::VerticalFlip, + sys::ViewPortOrientation_ViewPortOrientationMAX => Err(Self::Error::Max)?, invalid => Err(Self::Error::Invalid(invalid))?, }) } @@ -382,18 +374,17 @@ impl TryFrom for ViewPortOrientation { impl From for SysViewPortOrientation { fn from(value: ViewPortOrientation) -> Self { - use sys::{ - ViewPortOrientation_ViewPortOrientationHorizontal as SYS_VIEW_PORT_ORIENTATION_HORIZONTAL, - ViewPortOrientation_ViewPortOrientationHorizontalFlip as SYS_VIEW_PORT_ORIENTATION_HORIZONTAL_FLIP, - ViewPortOrientation_ViewPortOrientationVertical as SYS_VIEW_PORT_ORIENTATION_VERTICAL, - ViewPortOrientation_ViewPortOrientationVerticalFlip as SYS_VIEW_PORT_ORIENTATION_VERTICAL_FLIP, - }; - match value { - ViewPortOrientation::Horizontal => SYS_VIEW_PORT_ORIENTATION_HORIZONTAL, - ViewPortOrientation::HorizontalFlip => SYS_VIEW_PORT_ORIENTATION_HORIZONTAL_FLIP, - ViewPortOrientation::Vertical => SYS_VIEW_PORT_ORIENTATION_VERTICAL, - ViewPortOrientation::VerticalFlip => SYS_VIEW_PORT_ORIENTATION_VERTICAL_FLIP, + ViewPortOrientation::Horizontal => { + sys::ViewPortOrientation_ViewPortOrientationHorizontal + } + ViewPortOrientation::HorizontalFlip => { + sys::ViewPortOrientation_ViewPortOrientationHorizontalFlip + } + ViewPortOrientation::Vertical => sys::ViewPortOrientation_ViewPortOrientationVertical, + ViewPortOrientation::VerticalFlip => { + sys::ViewPortOrientation_ViewPortOrientationVerticalFlip + } } } } diff --git a/crates/flipperzero/src/input/mod.rs b/crates/flipperzero/src/input/mod.rs index 3e4ba32c..0acc4fdf 100644 --- a/crates/flipperzero/src/input/mod.rs +++ b/crates/flipperzero/src/input/mod.rs @@ -2,6 +2,7 @@ use core::ffi::CStr; use flipperzero_sys::{ self as sys, InputEvent as SysInputEvent, InputKey as SysInputKey, InputType as SysInputType, }; +// public type alias for an anonymous union pub use sys::InputEvent__bindgen_ty_1 as SysInputEventSequence; #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] @@ -113,22 +114,13 @@ impl TryFrom for InputType { type Error = FromSysInputTypeError; fn try_from(value: SysInputType) -> Result { - use sys::{ - InputType_InputTypeLong as SYS_INPUT_TYPE_LONG, - InputType_InputTypeMAX as SYS_INPUT_TYPE_MAX, - InputType_InputTypePress as SYS_INPUT_TYPE_PRESS, - InputType_InputTypeRelease as SYS_INPUT_TYPE_RELEASE, - InputType_InputTypeRepeat as SYS_INPUT_TYPE_REPEAT, - InputType_InputTypeShort as SYS_INPUT_TYPE_SHORT, - }; - Ok(match value { - SYS_INPUT_TYPE_PRESS => Self::Press, - SYS_INPUT_TYPE_RELEASE => Self::Release, - SYS_INPUT_TYPE_SHORT => Self::Short, - SYS_INPUT_TYPE_LONG => Self::Long, - SYS_INPUT_TYPE_REPEAT => Self::Repeat, - SYS_INPUT_TYPE_MAX => Err(Self::Error::Max)?, + sys::InputType_InputTypePress => Self::Press, + sys::InputType_InputTypeRelease => Self::Release, + sys::InputType_InputTypeShort => Self::Short, + sys::InputType_InputTypeLong => Self::Long, + sys::InputType_InputTypeRepeat => Self::Repeat, + sys::InputType_InputTypeMAX => Err(Self::Error::Max)?, invalid => Err(Self::Error::Invalid(invalid))?, }) } @@ -136,20 +128,12 @@ impl TryFrom for InputType { impl From for SysInputType { fn from(value: InputType) -> Self { - use sys::{ - InputType_InputTypeLong as SYS_INPUT_TYPE_LONG, - InputType_InputTypePress as SYS_INPUT_TYPE_PRESS, - InputType_InputTypeRelease as SYS_INPUT_TYPE_RELEASE, - InputType_InputTypeRepeat as SYS_INPUT_TYPE_REPEAT, - InputType_InputTypeShort as SYS_INPUT_TYPE_SHORT, - }; - match value { - InputType::Press => SYS_INPUT_TYPE_PRESS, - InputType::Release => SYS_INPUT_TYPE_RELEASE, - InputType::Short => SYS_INPUT_TYPE_SHORT, - InputType::Long => SYS_INPUT_TYPE_LONG, - InputType::Repeat => SYS_INPUT_TYPE_REPEAT, + InputType::Press => sys::InputType_InputTypePress, + InputType::Release => sys::InputType_InputTypeRelease, + InputType::Short => sys::InputType_InputTypeShort, + InputType::Long => sys::InputType_InputTypeLong, + InputType::Repeat => sys::InputType_InputTypeRepeat, } } } @@ -183,22 +167,14 @@ impl TryFrom for InputKey { type Error = FromSysInputKeyError; fn try_from(value: SysInputKey) -> Result { - use sys::{ - InputKey_InputKeyBack as SYS_INPUT_KEY_BACK, - InputKey_InputKeyDown as SYS_INPUT_KEY_DOWN, - InputKey_InputKeyLeft as SYS_INPUT_KEY_LEFT, InputKey_InputKeyMAX as SYS_INPUT_KEY_MAX, - InputKey_InputKeyOk as SYS_INPUT_KEY_OK, InputKey_InputKeyRight as SYS_INPUT_KEY_RIGHT, - InputKey_InputKeyUp as SYS_INPUT_KEY_UP, - }; - Ok(match value { - SYS_INPUT_KEY_UP => Self::Up, - SYS_INPUT_KEY_DOWN => Self::Down, - SYS_INPUT_KEY_RIGHT => Self::Right, - SYS_INPUT_KEY_LEFT => Self::Left, - SYS_INPUT_KEY_OK => Self::Ok, - SYS_INPUT_KEY_BACK => Self::Back, - SYS_INPUT_KEY_MAX => Err(Self::Error::Max)?, + sys::InputKey_InputKeyUp => Self::Up, + sys::InputKey_InputKeyDown => Self::Down, + sys::InputKey_InputKeyRight => Self::Right, + sys::InputKey_InputKeyLeft => Self::Left, + sys::InputKey_InputKeyOk => Self::Ok, + sys::InputKey_InputKeyBack => Self::Back, + sys::InputKey_InputKeyMAX => Err(Self::Error::Max)?, invalid => Err(Self::Error::Invalid(invalid))?, }) } @@ -206,20 +182,13 @@ impl TryFrom for InputKey { impl From for SysInputKey { fn from(value: InputKey) -> Self { - use sys::{ - InputKey_InputKeyBack as SYS_INPUT_KEY_BACK, - InputKey_InputKeyDown as SYS_INPUT_KEY_DOWN, - InputKey_InputKeyLeft as SYS_INPUT_KEY_LEFT, InputKey_InputKeyOk as SYS_INPUT_KEY_OK, - InputKey_InputKeyRight as SYS_INPUT_KEY_RIGHT, InputKey_InputKeyUp as SYS_INPUT_KEY_UP, - }; - match value { - InputKey::Up => SYS_INPUT_KEY_UP, - InputKey::Down => SYS_INPUT_KEY_DOWN, - InputKey::Right => SYS_INPUT_KEY_RIGHT, - InputKey::Left => SYS_INPUT_KEY_LEFT, - InputKey::Ok => SYS_INPUT_KEY_OK, - InputKey::Back => SYS_INPUT_KEY_BACK, + InputKey::Up => sys::InputKey_InputKeyUp, + InputKey::Down => sys::InputKey_InputKeyDown, + InputKey::Right => sys::InputKey_InputKeyRight, + InputKey::Left => sys::InputKey_InputKeyLeft, + InputKey::Ok => sys::InputKey_InputKeyOk, + InputKey::Back => sys::InputKey_InputKeyBack, } } } From b48da0a840e49cacc7e6f56c632174853e2730ba Mon Sep 17 00:00:00 2001 From: Petr Portnov Date: Sat, 3 Jun 2023 23:27:39 +0300 Subject: [PATCH 32/49] test(gui): remove `xbm` tests from `tests_runner` since they require a feature --- crates/flipperzero/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/flipperzero/src/lib.rs b/crates/flipperzero/src/lib.rs index d383f888..aae3f129 100644 --- a/crates/flipperzero/src/lib.rs +++ b/crates/flipperzero/src/lib.rs @@ -63,6 +63,5 @@ flipperzero_test::tests_runner!( crate::toolbox::crc32::tests, crate::toolbox::md5::tests, crate::toolbox::sha256::tests, - crate::gui::xbm::tests, ] ); From 09d70ed30e1f94c7b7da3a578f3e506eb4acf71a Mon Sep 17 00:00:00 2001 From: Petr Portnov Date: Sun, 4 Jun 2023 01:38:33 +0300 Subject: [PATCH 33/49] docs: enable automatic cfg documentation --- crates/Cargo.toml | 1 + crates/alloc/src/lib.rs | 4 + crates/flipperzero/Cargo.toml | 75 ++++++++++++++++--- crates/flipperzero/examples/gui.rs | 3 +- crates/flipperzero/examples/threads.rs | 4 +- .../flipperzero/examples/view_dispatcher.rs | 3 +- crates/flipperzero/src/dialogs/mod.rs | 1 + crates/flipperzero/src/furi/log/metadata.rs | 3 +- crates/flipperzero/src/furi/string.rs | 73 ++++++++---------- crates/flipperzero/src/furi/thread.rs | 15 ++++ crates/flipperzero/src/gui/canvas.rs | 5 +- crates/flipperzero/src/gui/icon.rs | 2 +- crates/flipperzero/src/gui/icon_animation.rs | 2 +- crates/flipperzero/src/gui/view.rs | 2 +- crates/flipperzero/src/gui/view_port.rs | 2 +- crates/flipperzero/src/lib.rs | 17 ++++- 16 files changed, 142 insertions(+), 70 deletions(-) diff --git a/crates/Cargo.toml b/crates/Cargo.toml index 17118183..b37e958b 100644 --- a/crates/Cargo.toml +++ b/crates/Cargo.toml @@ -24,6 +24,7 @@ flipperzero-rt = { path = "rt", version = "0.10.0" } flipperzero-alloc = { path = "alloc", version = "0.10.0" } flipperzero-test = { path = "test", version = "0.10.0" } ufmt = "0.2.0" +document-features = { version = "0.2.0" } [profile.dev] opt-level = "z" diff --git a/crates/alloc/src/lib.rs b/crates/alloc/src/lib.rs index ed3f1168..6c1993d1 100644 --- a/crates/alloc/src/lib.rs +++ b/crates/alloc/src/lib.rs @@ -6,6 +6,10 @@ use core::alloc::{GlobalAlloc, Layout}; use core::ffi::c_void; +extern crate alloc; +// re-export all items from `alloc` so that the API user can only extern this crate +pub use alloc::*; + use flipperzero_sys as sys; pub struct FuriAlloc; diff --git a/crates/flipperzero/Cargo.toml b/crates/flipperzero/Cargo.toml index 7ed2b542..d879621f 100644 --- a/crates/flipperzero/Cargo.toml +++ b/crates/flipperzero/Cargo.toml @@ -14,6 +14,7 @@ autobenches = false default-target = "thumbv7em-none-eabihf" targets = [] all-features = true +cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"] [lib] bench = false @@ -34,6 +35,9 @@ lock_api = "0.4" digest = "0.10" bitflags = "1.0" +# Docs +document-features = { workspace = true, optional = true } + [dev-dependencies] flipperzero-alloc.workspace = true flipperzero-rt.workspace = true @@ -44,22 +48,71 @@ md-5 = { version = "0.10", default-features = false } sha2 = { version = "0.10", default-features = false } [features] -default = ["unstable_lints", "unstable_intrinsics"] -# enables all optional services -all-services = ["service-gui", "service-dialogs"] -# enables features requiring an allocator + +## Default features. +default = [] + +## Enables features requiring an allocator. +## +## This will require you to set up an allocator such as by the following: +## +## ``` +## extern crate flipperzero_alloc as alloc; +## ``` +## +## Note that the `as alloc` part is optional but is helpful +## if you want to reference items from `alloc` crate all of which are re-exported by `flipperzero_alloc` +## such as `alloc::string::ToString` instead of `flipperzero_alloc::string::ToString`. alloc = [] -# enables unstable Rust intrinsics -unstable_intrinsics = [] -# enables unstable Rust lints on types provided by this crate -unstable_lints = [] -# enables GUI APIs of Flipper + +#! ## Service features. +#! +#! These enables specific Flipper Service APIs. + +## enables all Service APIs +all-services = [ + "service-storage", + "service-notification", + "service-input", + "service-toolbox", + "service-gui", + "service-dialogs", + "service-dolphin", +] +## Enables Storage Service APIs. +service-storage = [] +## Enables Notification Service APIs. +service-notification = [] +## Enables Input Service APIs. +service-input = [] +## Enables Toolbox APIs. +service-toolbox = [] +## Enables GUI APIs of Flipper. service-gui = ["alloc"] -# enables Dialogs APIs of Flipper +## Enables Dialogs APIs of Flipper. service-dialogs = ["service-gui"] +## Enables Dolphin APIs of Flipper. +service-dolphin = [] + +##! ## Unstable features +##! +##! This features are unstable and are yet a subject to dramatic changes. +##! They may provide experimental APIs, nightly-only features and optimizations etc. + +## Enables unstable Rust intrinsics. This requires `nightly` compiler version. +unstable_intrinsics = [] +## Enables unstable Rust lints on types provided by this crate. +## This requires `nightly` compiler version. +unstable_lints = [] + +# Internal features + +# Enables unstable doc +unstable_docs = [] [[test]] name = "dolphin" +required-features = ["service-dolphin"] harness = false [[test]] @@ -68,7 +121,7 @@ harness = false [[example]] name = "dialog" -required-features = ["service-dialogs"] +required-features = ["service-dialogs", "service-input"] [[example]] name = "gui" diff --git a/crates/flipperzero/examples/gui.rs b/crates/flipperzero/examples/gui.rs index 6957387a..bfdee7d6 100644 --- a/crates/flipperzero/examples/gui.rs +++ b/crates/flipperzero/examples/gui.rs @@ -12,8 +12,7 @@ mod xbms; extern crate flipperzero_rt; // Required for allocator -extern crate alloc; -extern crate flipperzero_alloc; +extern crate flipperzero_alloc as alloc; use alloc::{ffi::CString, string::ToString}; use core::{ffi::CStr, time::Duration}; diff --git a/crates/flipperzero/examples/threads.rs b/crates/flipperzero/examples/threads.rs index 4bfbf735..71017780 100644 --- a/crates/flipperzero/examples/threads.rs +++ b/crates/flipperzero/examples/threads.rs @@ -7,9 +7,7 @@ extern crate flipperzero_rt; // Required for allocator -extern crate flipperzero_alloc; - -extern crate alloc; +extern crate flipperzero_alloc as alloc; use alloc::borrow::ToOwned; use core::time::Duration; diff --git a/crates/flipperzero/examples/view_dispatcher.rs b/crates/flipperzero/examples/view_dispatcher.rs index 3a0d8f41..36322c9f 100644 --- a/crates/flipperzero/examples/view_dispatcher.rs +++ b/crates/flipperzero/examples/view_dispatcher.rs @@ -5,8 +5,7 @@ #![no_main] #![no_std] -extern crate alloc; -extern crate flipperzero_alloc; +extern crate flipperzero_alloc as alloc; extern crate flipperzero_rt; use alloc::boxed::Box; diff --git a/crates/flipperzero/src/dialogs/mod.rs b/crates/flipperzero/src/dialogs/mod.rs index d1e34896..be8bed1c 100644 --- a/crates/flipperzero/src/dialogs/mod.rs +++ b/crates/flipperzero/src/dialogs/mod.rs @@ -173,6 +173,7 @@ impl Default for DialogMessage<'_> { /// Displays a simple dialog. #[cfg(feature = "alloc")] +#[cfg_attr(feature = "unstable_docs", doc(cfg(feature = "alloc")))] pub fn alert(text: &str) { // SAFETY: string is known to end with NUL const BUTTON_OK: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"OK\0") }; diff --git a/crates/flipperzero/src/furi/log/metadata.rs b/crates/flipperzero/src/furi/log/metadata.rs index 2a6f35d4..65285f03 100644 --- a/crates/flipperzero/src/furi/log/metadata.rs +++ b/crates/flipperzero/src/furi/log/metadata.rs @@ -159,7 +159,7 @@ impl ufmt::uDisplay for Level { } #[cfg(feature = "std")] -#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +#[cfg_attr(feature = "unstable_docs", doc(cfg(feature = "std")))] impl std::error::Error for ParseLevelError {} impl FromStr for Level { @@ -466,6 +466,7 @@ impl ufmt::uDisplay for ParseLevelFilterError { } #[cfg(feature = "std")] +#[cfg_attr(feature = "unstable_docs", doc(cfg(feature = "std")))] impl std::error::Error for ParseLevelFilterError {} #[repr(usize)] diff --git a/crates/flipperzero/src/furi/string.rs b/crates/flipperzero/src/furi/string.rs index bb38b02e..6af87d25 100644 --- a/crates/flipperzero/src/furi/string.rs +++ b/crates/flipperzero/src/furi/string.rs @@ -35,8 +35,8 @@ const WHITESPACE: &[char] = &[ /// /// This is similar to Rust's [`CString`] in that it represents an owned, C-compatible, /// nul-terminated string with no nul bytes in the middle. It also has additional methods -/// to provide the flexibility of Rust's [`String`]. It is used in various APIs of the -/// Flipper Zero SDK. +/// to provide the flexibility of Rust's [`String`](alloc::string::String). +/// It is used in various APIs of the Flipper Zero SDK. /// /// This type does not requre the `alloc` feature flag, because it does not use the Rust /// allocator. Very short strings (7 bytes or fewer) are stored directly inside the @@ -292,70 +292,61 @@ impl FuriString { Bytes(self.to_bytes().iter().copied()) } - /// Returns `true` if the given pattern matches a sub-slice of this string slice. + /// Returns `true` if the given pattern `pat` matches a sub-slice of this string slice. /// /// Returns `false` if it does not. /// - /// The [pattern] can be a `&FuriString`, [`c_char`], `&CStr`, [`char`], or a slice of - /// [`char`]s. + /// The pattern can be a `&FuriString`, [`c_char`], `&CStr`, [`char`], + /// or a slice of [`char`]s. /// /// [`char`]: prim@char - /// [pattern]: self::pattern #[inline] pub fn contains(&self, pat: P) -> bool { pat.is_contained_in(self) } - /// Returns `true` if the given pattern matches a prefix of this string slice. + /// Returns `true` if the given pattern `pat` matches a prefix of this string slice. /// /// Returns `false` if it does not. /// - /// The [pattern] can be a `&FuriString`, [`c_char`], `&CStr`, [`char`], or a slice of - /// [`char`]s. + /// The pattern can be a `&FuriString`, [`c_char`], `&CStr`, [`char`], or a slice of [`char`]s. /// /// [`char`]: prim@char - /// [pattern]: self::pattern pub fn starts_with(&self, pat: P) -> bool { pat.is_prefix_of(self) } - /// Returns `true` if the given pattern matches a suffix of this string slice. + /// Returns `true` if the given pattern `pat` matches a suffix of this string slice. /// /// Returns `false` if it does not. /// - /// The [pattern] can be a `&FuriString`, [`c_char`], `&CStr`, [`char`], or a slice of - /// [`char`]s. + /// The pattern can be a `&FuriString`, [`c_char`], `&CStr`, [`char`], or a slice of [`char`]s. /// /// [`char`]: prim@char - /// [pattern]: self::pattern pub fn ends_with(&self, pat: P) -> bool { pat.is_suffix_of(self) } - /// Returns the byte index of the first byte of this string that matches the pattern. + /// Returns the byte index of the first byte of this string that matches the pattern `pat`. /// /// Returns [`None`] if the pattern doesn't match. /// - /// The [pattern] can be a `&FuriString`, [`c_char`], `&CStr`, [`char`], or a slice of - /// [`char`]s. + /// The pattern can be a `&FuriString`, [`c_char`], `&CStr`, [`char`], or a slice of [`char`]s. /// /// [`char`]: prim@char - /// [pattern]: self::pattern #[inline] pub fn find(&self, pat: P) -> Option { pat.find_in(self) } - /// Returns the byte index for the first byte of the last match of the pattern in this - /// string. + /// Returns the byte index for the first byte of the last match of the pattern `pat` + /// in this string. /// /// Returns [`None`] if the pattern doesn't match. /// - /// The [pattern] can be a `&FuriString`, [`c_char`], `&CStr`, [`char`], or a slice of - /// [`char`]s. + /// The pattern can be a `&FuriString`, [`c_char`], `&CStr`, [`char`], or a slice of [`char`]s. /// /// [`char`]: prim@char - /// [pattern]: self::pattern #[inline] pub fn rfind(&self, pat: P) -> Option { pat.rfind_in(self) @@ -404,23 +395,19 @@ impl FuriString { /// Repeatedly removes from this string all prefixes and suffixes that match a pattern. /// - /// The [pattern] can be a `&FuriString`, [`c_char`], `&CStr`, [`char`], or a slice of - /// [`char`]s. + /// The pattern can be a `&FuriString`, [`c_char`], `&CStr`, [`char`] or a slice of [`char`]s. /// /// [`char`]: prim@char - /// [pattern]: self::pattern pub fn trim_matches(&mut self, pat: P) { self.trim_start_matches(pat); self.trim_end_matches(pat); } - /// Repeatedly removes from this string all prefixes that match a pattern. + /// Repeatedly removes from this string all prefixes that match a pattern `pat`. /// - /// The [pattern] can be a `&FuriString`, [`c_char`], `&CStr`, [`char`], or a slice of - /// [`char`]s. + /// The pattern can be a `&FuriString`, [`c_char`], `&CStr`, [`char`] or a slice of [`char`]s. /// /// [`char`]: prim@char - /// [pattern]: self::pattern /// /// # Text directionality /// @@ -432,13 +419,11 @@ impl FuriString { while self.strip_prefix(pat) {} } - /// Repeatedly removes from this string all suffixes that match a pattern. + /// Repeatedly removes from this string all suffixes that match a pattern `pat`. /// - /// The [pattern] can be a `&FuriString`, [`c_char`], `&CStr`, [`char`], or a slice of - /// [`char`]s. + /// The pattern can be a `&FuriString`, [`c_char`], `&CStr`, [`char`] or a slice of [`char`]s. /// /// [`char`]: prim@char - /// [pattern]: self::pattern /// /// # Text directionality /// @@ -450,35 +435,31 @@ impl FuriString { while self.strip_suffix(pat) {} } - /// Removes the given prefix from this string. + /// Removes the given `prefix` from this string. /// /// If the string starts with the pattern `prefix`, returns `true`. Unlike /// [`Self::trim_start_matches`], this method removes the prefix exactly once. /// /// If the string does not start with `prefix`, returns `false`. /// - /// The [pattern] can be a `&FuriString`, [`c_char`], `&CStr`, [`char`], or a slice of - /// [`char`]s. + /// The prefix can be a `&FuriString`, [`c_char`], `&CStr`, [`char`], or a slice of [`char`]s. /// /// [`char`]: prim@char - /// [pattern]: self::pattern #[must_use] pub fn strip_prefix(&mut self, prefix: P) -> bool { prefix.strip_prefix_of(self) } - /// Removes the given suffix from this string. + /// Removes the given `suffix` from this string. /// /// If the string ends with the pattern `suffix`, returns `true`. Unlike /// [`Self::trim_end_matches`], this method removes the suffix exactly once. /// /// If the string does not end with `suffix`, returns `false`. /// - /// The [pattern] can be a `&FuriString`, [`c_char`], `&CStr`, [`char`], or a slice of - /// [`char`]s. + /// The suffix can be a `&FuriString`, [`c_char`], `&CStr`, [`char`], or a slice of [`char`]s. /// /// [`char`]: prim@char - /// [pattern]: self::pattern #[must_use] pub fn strip_suffix(&mut self, suffix: P) -> bool { suffix.strip_suffix_of(self) @@ -539,6 +520,7 @@ impl From<&CStr> for FuriString { } #[cfg(feature = "alloc")] +#[cfg_attr(feature = "unstable_docs", doc(cfg(feature = "alloc")))] impl From> for FuriString { fn from(value: Box) -> Self { FuriString::from(value.as_ref()) @@ -546,6 +528,7 @@ impl From> for FuriString { } #[cfg(feature = "alloc")] +#[cfg_attr(feature = "unstable_docs", doc(cfg(feature = "alloc")))] impl<'a> From> for FuriString { fn from(value: Cow<'a, str>) -> Self { FuriString::from(value.as_ref()) @@ -586,6 +569,7 @@ impl<'a> Extend<&'a CStr> for FuriString { } #[cfg(feature = "alloc")] +#[cfg_attr(feature = "unstable_docs", doc(cfg(feature = "alloc")))] impl Extend> for FuriString { fn extend>>(&mut self, iter: T) { iter.into_iter().for_each(move |s| self.push_str(&s)); @@ -593,6 +577,7 @@ impl Extend> for FuriString { } #[cfg(feature = "alloc")] +#[cfg_attr(feature = "unstable_docs", doc(cfg(feature = "alloc")))] impl<'a> Extend> for FuriString { fn extend>>(&mut self, iter: T) { iter.into_iter().for_each(move |s| self.push_str(&s)); @@ -649,6 +634,7 @@ impl<'a> FromIterator<&'a CStr> for FuriString { } #[cfg(feature = "alloc")] +#[cfg_attr(feature = "unstable_docs", doc(cfg(feature = "alloc")))] impl FromIterator> for FuriString { fn from_iter>>(iter: T) -> Self { let mut buf = FuriString::new(); @@ -658,6 +644,7 @@ impl FromIterator> for FuriString { } #[cfg(feature = "alloc")] +#[cfg_attr(feature = "unstable_docs", doc(cfg(feature = "alloc")))] impl<'a> FromIterator> for FuriString { fn from_iter>>(iter: T) -> Self { let mut buf = FuriString::new(); @@ -726,6 +713,7 @@ impl PartialEq for &str { } #[cfg(feature = "alloc")] +#[cfg_attr(feature = "unstable_docs", doc(cfg(feature = "alloc")))] impl PartialEq for FuriString { fn eq(&self, other: &CString) -> bool { self.eq(other.as_c_str()) @@ -733,6 +721,7 @@ impl PartialEq for FuriString { } #[cfg(feature = "alloc")] +#[cfg_attr(feature = "unstable_docs", doc(cfg(feature = "alloc")))] impl PartialEq for CString { fn eq(&self, other: &FuriString) -> bool { other.eq(self.as_c_str()) diff --git a/crates/flipperzero/src/furi/thread.rs b/crates/flipperzero/src/furi/thread.rs index 8aa007fc..d4f804e8 100644 --- a/crates/flipperzero/src/furi/thread.rs +++ b/crates/flipperzero/src/furi/thread.rs @@ -19,10 +19,12 @@ use alloc::{ use flipperzero_sys as sys; #[cfg(feature = "alloc")] +#[cfg_attr(feature = "unstable_docs", doc(cfg(feature = "alloc")))] const MIN_STACK_SIZE: usize = 1024; /// Thread factory, which can be used in order to configure the properties of a new thread. #[cfg(feature = "alloc")] +#[cfg_attr(feature = "unstable_docs", doc(cfg(feature = "alloc")))] pub struct Builder { /// Guaranteed to be UTF-8. name: Option, @@ -31,6 +33,7 @@ pub struct Builder { } #[cfg(feature = "alloc")] +#[cfg_attr(feature = "unstable_docs", doc(cfg(feature = "alloc")))] impl Builder { /// Generates the base configuration for spawning a thread, from which configuration /// methods can be chained. @@ -141,6 +144,7 @@ impl Builder { /// This call will create a thread using default parameters of [`Builder`]. If you want to /// specify the stack size or the name of the thread, use that API instead. #[cfg(feature = "alloc")] +#[cfg_attr(feature = "unstable_docs", doc(cfg(feature = "alloc")))] pub fn spawn(f: F) -> JoinHandle where F: FnOnce() -> i32, @@ -151,6 +155,7 @@ where /// Gets a handle to the thread that invokes it. #[cfg(feature = "alloc")] +#[cfg_attr(feature = "unstable_docs", doc(cfg(feature = "alloc")))] pub fn current() -> Thread { use alloc::borrow::ToOwned; @@ -194,10 +199,12 @@ pub fn sleep(duration: core::time::Duration) { /// A unique identifier for a running thread. #[cfg(feature = "alloc")] +#[cfg_attr(feature = "unstable_docs", doc(cfg(feature = "alloc")))] pub struct ThreadId(sys::FuriThreadId); /// A handle to a thread. #[cfg(feature = "alloc")] +#[cfg_attr(feature = "unstable_docs", doc(cfg(feature = "alloc")))] pub struct Thread { /// Guaranteed to be UTF-8. name: Option, @@ -205,6 +212,7 @@ pub struct Thread { } #[cfg(feature = "alloc")] +#[cfg_attr(feature = "unstable_docs", doc(cfg(feature = "alloc")))] impl Thread { fn new( name: Option, @@ -260,6 +268,7 @@ impl Thread { } #[cfg(feature = "alloc")] +#[cfg_attr(feature = "unstable_docs", doc(cfg(feature = "alloc")))] impl fmt::Debug for Thread { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Thread") @@ -269,6 +278,7 @@ impl fmt::Debug for Thread { } #[cfg(feature = "alloc")] +#[cfg_attr(feature = "unstable_docs", doc(cfg(feature = "alloc")))] impl ufmt::uDebug for Thread { fn fmt(&self, f: &mut ufmt::Formatter<'_, W>) -> Result<(), W::Error> where @@ -281,11 +291,13 @@ impl ufmt::uDebug for Thread { /// An owned permission to join on a thread (block on its termination). #[cfg(feature = "alloc")] +#[cfg_attr(feature = "unstable_docs", doc(cfg(feature = "alloc")))] pub struct JoinHandle { context: Option>, } #[cfg(feature = "alloc")] +#[cfg_attr(feature = "unstable_docs", doc(cfg(feature = "alloc")))] impl Drop for JoinHandle { fn drop(&mut self) { let context = self @@ -303,6 +315,7 @@ impl Drop for JoinHandle { } #[cfg(feature = "alloc")] +#[cfg_attr(feature = "unstable_docs", doc(cfg(feature = "alloc")))] impl JoinHandle { /// Extracts a handle to the underlying thread. pub fn thread(&self) -> &Thread { @@ -328,6 +341,7 @@ impl JoinHandle { } #[cfg(feature = "alloc")] +#[cfg_attr(feature = "unstable_docs", doc(cfg(feature = "alloc")))] impl fmt::Debug for JoinHandle { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("JoinHandle").finish_non_exhaustive() @@ -335,6 +349,7 @@ impl fmt::Debug for JoinHandle { } #[cfg(feature = "alloc")] +#[cfg_attr(feature = "unstable_docs", doc(cfg(feature = "alloc")))] impl ufmt::uDebug for JoinHandle { fn fmt(&self, f: &mut ufmt::Formatter<'_, W>) -> Result<(), W::Error> where diff --git a/crates/flipperzero/src/gui/canvas.rs b/crates/flipperzero/src/gui/canvas.rs index 23d8c029..76af39c5 100644 --- a/crates/flipperzero/src/gui/canvas.rs +++ b/crates/flipperzero/src/gui/canvas.rs @@ -5,8 +5,7 @@ use crate::gui::{ icon_animation::{IconAnimation, IconAnimationCallbacks}, xbm::XbmImage, }; -use crate::{debug, warn}; -use core::fmt::Display; +use crate::warn; use core::{ ffi::{c_char, CStr}, marker::PhantomData, @@ -31,7 +30,7 @@ impl CanvasView<'_> { /// /// # Safety /// - /// `raw` should be a valid non-null pointer to [`SysCanvas`] + /// `raw` should be a valid non-null pointer to [`sys::Canvas`] /// and the lifetime should be outlived by `raw` validity scope. /// /// # Examples diff --git a/crates/flipperzero/src/gui/icon.rs b/crates/flipperzero/src/gui/icon.rs index 0d0f8163..4359d57d 100644 --- a/crates/flipperzero/src/gui/icon.rs +++ b/crates/flipperzero/src/gui/icon.rs @@ -11,7 +11,7 @@ impl Icon { /// /// # Safety /// - /// `raw` should be a valid non-null pointer to [`SysCanvas`]. + /// `raw` should be a valid non-null pointer to [`sys::Canvas`]. /// /// # Examples /// diff --git a/crates/flipperzero/src/gui/icon_animation.rs b/crates/flipperzero/src/gui/icon_animation.rs index 395dbbcc..c9417a1f 100644 --- a/crates/flipperzero/src/gui/icon_animation.rs +++ b/crates/flipperzero/src/gui/icon_animation.rs @@ -121,7 +121,7 @@ impl IconAnimationView<'_> { /// /// # Safety /// - /// `raw` should be a valid non-null pointer to [`SysCanvas`] + /// `raw` should be a valid non-null pointer to [`sys::Canvas`] /// and the lifetime should be outlived by `raw` validity scope. /// /// # Examples diff --git a/crates/flipperzero/src/gui/view.rs b/crates/flipperzero/src/gui/view.rs index c77cfedb..501479fe 100644 --- a/crates/flipperzero/src/gui/view.rs +++ b/crates/flipperzero/src/gui/view.rs @@ -18,7 +18,7 @@ impl View { Self { raw, callbacks } } - /// Creates a copy of raw pointer to the [`SysView`]. + /// Creates a copy of raw pointer to the [`sys::View`]. pub fn as_raw(&self) -> *mut SysView { self.raw.as_ptr() } diff --git a/crates/flipperzero/src/gui/view_port.rs b/crates/flipperzero/src/gui/view_port.rs index 436aaa0a..82e726a8 100644 --- a/crates/flipperzero/src/gui/view_port.rs +++ b/crates/flipperzero/src/gui/view_port.rs @@ -96,7 +96,7 @@ impl ViewPort { view_port } - /// Creates a copy of the raw pointer to the [`SysViewPort`]. + /// Creates a copy of the raw pointer to the [`sys::ViewPort`]. pub fn as_raw(&self) -> *mut SysViewPort { self.raw.as_ptr() } diff --git a/crates/flipperzero/src/lib.rs b/crates/flipperzero/src/lib.rs index aae3f129..cfc0c5f4 100644 --- a/crates/flipperzero/src/lib.rs +++ b/crates/flipperzero/src/lib.rs @@ -1,26 +1,39 @@ //! High-level bindings for the Flipper Zero. - +#![cfg_attr(feature = "document-features", doc = document_features::document_features!())] #![no_std] +#![cfg_attr(test, no_main)] #![cfg_attr(feature = "unstable_intrinsics", feature(int_roundings))] #![cfg_attr(feature = "unstable_lints", feature(must_not_suspend))] -#![cfg_attr(test, no_main)] +#![cfg_attr(feature = "unstable_docs", feature(doc_cfg))] #[cfg(feature = "alloc")] extern crate alloc; #[cfg(feature = "service-dialogs")] +#[cfg_attr(feature = "unstable_docs", doc(cfg(feature = "service-dialogs")))] pub mod dialogs; +#[cfg(feature = "service-dolphin")] +#[cfg_attr(feature = "unstable_docs", doc(cfg(feature = "service-dolphin")))] pub mod dolphin; pub mod furi; #[cfg(feature = "service-gui")] +#[cfg_attr(feature = "unstable_docs", doc(cfg(feature = "service-gui")))] pub mod gui; +#[cfg(feature = "service-input")] +#[cfg_attr(feature = "unstable_docs", doc(cfg(feature = "service-input")))] pub mod input; pub(crate) mod internals; pub mod io; pub mod kernel; pub mod macros; +#[cfg(feature = "service-notification")] +#[cfg_attr(feature = "unstable_docs", doc(cfg(feature = "service-notification")))] pub mod notification; +#[cfg(feature = "service-storage")] +#[cfg_attr(feature = "unstable_docs", doc(cfg(feature = "service-storage")))] pub mod storage; +#[cfg(feature = "service-toolbox")] +#[cfg_attr(feature = "unstable_docs", doc(cfg(feature = "service-toolbox")))] pub mod toolbox; #[doc(hidden)] From 8d689b99af0d925163d38d206baf8361dfb5d377 Mon Sep 17 00:00:00 2001 From: Petr Portnov Date: Sun, 4 Jun 2023 01:47:03 +0300 Subject: [PATCH 34/49] build: specify required example features --- crates/flipperzero/Cargo.toml | 14 +++++++++++--- crates/flipperzero/examples/images.rs | 4 ---- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/crates/flipperzero/Cargo.toml b/crates/flipperzero/Cargo.toml index d879621f..c9fec1fc 100644 --- a/crates/flipperzero/Cargo.toml +++ b/crates/flipperzero/Cargo.toml @@ -120,12 +120,20 @@ name = "string" harness = false [[example]] -name = "dialog" -required-features = ["service-dialogs", "service-input"] +name = "storage" +required-features = ["service-storage"] + +[[example]] +name = "notification" +required-features = ["service-notification"] [[example]] name = "gui" -required-features = ["service-gui"] +required-features = ["service-gui", "service-input"] + +[[example]] +name = "dialog" +required-features = ["service-dialogs", "service-input"] [[example]] name = "threads" diff --git a/crates/flipperzero/examples/images.rs b/crates/flipperzero/examples/images.rs index d89040d0..c3d7c669 100644 --- a/crates/flipperzero/examples/images.rs +++ b/crates/flipperzero/examples/images.rs @@ -10,10 +10,6 @@ use core::mem::{self, MaybeUninit}; use flipperzero_rt as rt; use flipperzero_sys as sys; -// Required for allocator -#[cfg(feature = "alloc")] -extern crate flipperzero_alloc; - rt::manifest!(name = "Example: Images"); rt::entry!(main); From 5de6811ddff1a384640da4e26fa722ae7c170811 Mon Sep 17 00:00:00 2001 From: Petr Portnov Date: Sun, 4 Jun 2023 01:54:48 +0300 Subject: [PATCH 35/49] chore: start using `gui` features in `view_dispatcher` --- crates/flipperzero/Cargo.toml | 8 ++++++-- crates/flipperzero/examples/view_dispatcher.rs | 7 +++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/crates/flipperzero/Cargo.toml b/crates/flipperzero/Cargo.toml index c9fec1fc..893b06ef 100644 --- a/crates/flipperzero/Cargo.toml +++ b/crates/flipperzero/Cargo.toml @@ -88,7 +88,7 @@ service-input = [] ## Enables Toolbox APIs. service-toolbox = [] ## Enables GUI APIs of Flipper. -service-gui = ["alloc"] +service-gui = ["alloc", "service-input"] ## Enables Dialogs APIs of Flipper. service-dialogs = ["service-gui"] ## Enables Dolphin APIs of Flipper. @@ -129,7 +129,11 @@ required-features = ["service-notification"] [[example]] name = "gui" -required-features = ["service-gui", "service-input"] +required-features = ["service-gui"] + +[[example]] +name = "view_dispatcher" +required-features = ["service-gui"] [[example]] name = "dialog" diff --git a/crates/flipperzero/examples/view_dispatcher.rs b/crates/flipperzero/examples/view_dispatcher.rs index 36322c9f..5343881e 100644 --- a/crates/flipperzero/examples/view_dispatcher.rs +++ b/crates/flipperzero/examples/view_dispatcher.rs @@ -12,11 +12,10 @@ use alloc::boxed::Box; use core::ffi::{c_char, c_void, CStr}; use core::ptr::NonNull; use flipperzero::furi::string::FuriString; +use flipperzero::gui::Gui; use flipperzero_rt::{entry, manifest}; use flipperzero_sys as sys; -const RECORD_GUI: *const c_char = sys::c_string!("gui"); - manifest!(name = "Rust ViewDispatcher example"); entry!(main); @@ -102,11 +101,11 @@ fn main(_args: *mut u8) -> i32 { ); } + let gui = Gui::new(); unsafe { - let gui = sys::furi_record_open(RECORD_GUI) as *mut sys::Gui; sys::view_dispatcher_attach_to_gui( app.view_dispatcher.as_ptr(), - gui, + gui.as_raw(), sys::ViewDispatcherType_ViewDispatcherTypeFullscreen, ); From 1329195caa1f8409903ddafeeee1cd5de165fe47 Mon Sep 17 00:00:00 2001 From: Petr Portnov Date: Sun, 4 Jun 2023 03:15:44 +0300 Subject: [PATCH 36/49] feat: make `images` example almost safe --- crates/flipperzero/Cargo.toml | 4 + crates/flipperzero/examples/images.rs | 166 +++++++++---------- crates/flipperzero/src/furi/message_queue.rs | 12 +- crates/flipperzero/src/gui/mod.rs | 14 +- crates/flipperzero/src/gui/view_port.rs | 11 +- 5 files changed, 119 insertions(+), 88 deletions(-) diff --git a/crates/flipperzero/Cargo.toml b/crates/flipperzero/Cargo.toml index 893b06ef..9cc2c164 100644 --- a/crates/flipperzero/Cargo.toml +++ b/crates/flipperzero/Cargo.toml @@ -131,6 +131,10 @@ required-features = ["service-notification"] name = "gui" required-features = ["service-gui"] +[[example]] +name = "images" +required-features = ["service-gui"] + [[example]] name = "view_dispatcher" required-features = ["service-gui"] diff --git a/crates/flipperzero/examples/images.rs b/crates/flipperzero/examples/images.rs index c3d7c669..2343ac36 100644 --- a/crates/flipperzero/examples/images.rs +++ b/crates/flipperzero/examples/images.rs @@ -1,11 +1,22 @@ //! Example Images application. -//! See https://github.com/flipperdevices/flipperzero-firmware/blob/dev/applications/examples/example_images/example_images.c +//! See #![no_std] #![no_main] -use core::ffi::{c_char, c_void}; -use core::mem::{self, MaybeUninit}; +use core::time::Duration; +use flipperzero::{ + furi::message_queue::MessageQueue, + gui::{ + canvas::CanvasView, + icon::Icon, + view_port::{ViewPort, ViewPortCallbacks}, + Gui, GuiLayer, + }, + input::{InputEvent, InputKey, InputType}, +}; + +extern crate flipperzero_alloc; use flipperzero_rt as rt; use flipperzero_sys as sys; @@ -13,18 +24,15 @@ use flipperzero_sys as sys; rt::manifest!(name = "Example: Images"); rt::entry!(main); -const RECORD_GUI: *const c_char = sys::c_string!("gui"); - -static mut TARGET_ICON: Icon = Icon { +// NOTE: `*mut`s are required to enforce `unsafe` since there are raw pointers involved +static mut TARGET_FRAMES: [*const u8; 1] = [include_bytes!("icons/rustacean-48x32.icon").as_ptr()]; +static mut SYS_ICON: sys::Icon = sys::Icon { width: 48, height: 32, frame_count: 1, frame_rate: 0, frames: unsafe { TARGET_FRAMES.as_ptr() }, }; -static mut TARGET_FRAMES: [*const u8; 1] = [include_bytes!("icons/rustacean-48x32.icon").as_ptr()]; - -static mut IMAGE_POSITION: ImagePosition = ImagePosition { x: 0, y: 0 }; #[repr(C)] struct ImagePosition { @@ -32,90 +40,78 @@ struct ImagePosition { pub y: u8, } -/// Internal icon representation. -#[repr(C)] -struct Icon { - width: u8, - height: u8, - frame_count: u8, - frame_rate: u8, - frames: *const *const u8, -} - -// Screen is 128x64 px -extern "C" fn app_draw_callback(canvas: *mut sys::Canvas, _ctx: *mut c_void) { - unsafe { - sys::canvas_clear(canvas); - sys::canvas_draw_icon( - canvas, - IMAGE_POSITION.x % 128, - IMAGE_POSITION.y % 128, - &TARGET_ICON as *const Icon as *const c_void as *const sys::Icon, - ); - } -} - -extern "C" fn app_input_callback(input_event: *mut sys::InputEvent, ctx: *mut c_void) { - unsafe { - let event_queue = ctx as *mut sys::FuriMessageQueue; - sys::furi_message_queue_put(event_queue, input_event as *mut c_void, 0); - } -} - fn main(_args: *mut u8) -> i32 { - unsafe { - let event_queue = sys::furi_message_queue_alloc(8, mem::size_of::() as u32) - as *mut sys::FuriMessageQueue; - - // Configure view port - let view_port = sys::view_port_alloc(); - sys::view_port_draw_callback_set( - view_port, - Some(app_draw_callback), - view_port as *mut c_void, - ); - sys::view_port_input_callback_set( - view_port, - Some(app_input_callback), - event_queue as *mut c_void, - ); - - // Register view port in GUI - let gui = sys::furi_record_open(RECORD_GUI) as *mut sys::Gui; - sys::gui_add_view_port(gui, view_port, sys::GuiLayer_GuiLayerFullscreen); + // SAFETY: `Icon` is a read-only; + // there will be a safe API for this in this future + let icon = unsafe { Icon::from_raw(&SYS_ICON as *const _ as *mut _) }; + + // Configure view port + struct State<'a> { + exit_queue: &'a MessageQueue<()>, + image_position: ImagePosition, + target_icon: &'a Icon, + hidden: bool, + } - let mut event: MaybeUninit = MaybeUninit::uninit(); + impl ViewPortCallbacks for State<'_> { + fn on_draw(&mut self, mut canvas: CanvasView) { + canvas.clear(); + if !self.hidden { + // Screen is 128x64 px + canvas.draw_icon( + self.image_position.x % 128, + self.image_position.y % 64, + self.target_icon, + ); + } + } - let mut running = true; - while running { - if sys::furi_message_queue_get( - event_queue, - event.as_mut_ptr() as *mut sys::InputEvent as *mut c_void, - 100, - ) == sys::FuriStatus_FuriStatusOk - { - let event = event.assume_init(); - if event.type_ == sys::InputType_InputTypePress - || event.type_ == sys::InputType_InputTypeRepeat - { - match event.key { - sys::InputKey_InputKeyLeft => IMAGE_POSITION.x -= 2, - sys::InputKey_InputKeyRight => IMAGE_POSITION.x += 2, - sys::InputKey_InputKeyUp => IMAGE_POSITION.y -= 2, - sys::InputKey_InputKeyDown => IMAGE_POSITION.y += 2, - _ => running = false, + fn on_input(&mut self, event: InputEvent) { + if matches!(event.r#type, InputType::Press | InputType::Repeat) { + match event.key { + InputKey::Left => { + self.image_position.x = self.image_position.x.saturating_sub(2) + } + InputKey::Right => { + self.image_position.x = self.image_position.x.saturating_add(2) + } + InputKey::Up => self.image_position.y = self.image_position.y.saturating_sub(2), + InputKey::Down => { + self.image_position.y = self.image_position.y.saturating_add(2) + } + // to be a bit more creative than the original example + // we make `Ok` button (un)hide the canvas + InputKey::Ok => self.hidden = !self.hidden, + _ => { + let _ = self.exit_queue.put_now(()); } } } - sys::view_port_update(view_port); } + } - sys::view_port_enabled_set(view_port, false); - sys::gui_remove_view_port(gui, view_port); - sys::view_port_free(view_port); - sys::furi_message_queue_free(event_queue); - - sys::furi_record_close(RECORD_GUI); + // The original example has all `InputEvent`s transferred via `MessageQueue` + // While this is possible, there is no need for this + // since we do all the handling in `on_input_event` + // thus we only have to send a single object indicating shutdown + let exit_queue = MessageQueue::new(1); + let view_port = ViewPort::new(State { + exit_queue: &exit_queue, + image_position: ImagePosition { x: 0, y: 0 }, + target_icon: &icon, + hidden: false, + }); + + // Register view port in GUI + let mut gui = Gui::new(); + let mut view_port = gui.add_view_port(view_port, GuiLayer::Fullscreen); + + let mut running = true; + while running { + if exit_queue.get(Duration::from_millis(100)).is_ok() { + running = false + } + view_port.update(); } 0 diff --git a/crates/flipperzero/src/furi/message_queue.rs b/crates/flipperzero/src/furi/message_queue.rs index 1c977bd3..41d146a1 100644 --- a/crates/flipperzero/src/furi/message_queue.rs +++ b/crates/flipperzero/src/furi/message_queue.rs @@ -30,7 +30,7 @@ impl MessageQueue { // Attempts to add the message to the end of the queue, waiting up to timeout ticks. pub fn put(&self, msg: M, timeout: Duration) -> furi::Result<()> { let mut msg = core::mem::ManuallyDrop::new(msg); - let timeout_ticks = sys::furi::duration_to_ticks(timeout); + let timeout_ticks = duration_to_ticks(timeout); let status: Status = unsafe { sys::furi_message_queue_put( @@ -44,6 +44,11 @@ impl MessageQueue { status.err_or(()) } + #[inline] + pub fn put_now(&self, msg: M) -> furi::Result<()> { + self.put(msg, Duration::ZERO) + } + // Attempts to read a message from the front of the queue within timeout ticks. pub fn get(&self, timeout: Duration) -> furi::Result { let timeout_ticks = duration_to_ticks(timeout); @@ -64,6 +69,11 @@ impl MessageQueue { } } + #[inline] + pub fn get_now(&self) -> furi::Result { + self.get(Duration::ZERO) + } + /// Returns the capacity of the queue. pub fn capacity(&self) -> usize { unsafe { sys::furi_message_queue_get_capacity(self.hnd.as_ptr()) as usize } diff --git a/crates/flipperzero/src/gui/mod.rs b/crates/flipperzero/src/gui/mod.rs index 35a19c8d..c34b79ee 100644 --- a/crates/flipperzero/src/gui/mod.rs +++ b/crates/flipperzero/src/gui/mod.rs @@ -124,6 +124,13 @@ impl<'a, VPC: ViewPortCallbacks> GuiViewPort<'a, VPC> { // // unsafe { sys::gui_view_port_send_to_back(gui, view_port) }; // } + + pub fn update(&mut self) { + let view_port = self.view_port.as_raw(); + + // SAFETY: `view_port` is a valid pointer + unsafe { sys::view_port_update(view_port) } + } } impl Drop for GuiViewPort<'_, VPC> { @@ -133,7 +140,12 @@ impl Drop for GuiViewPort<'_, VPC> { // SAFETY: `gui` and `view_port` are valid pointers // and this view port should have been added to the gui on creation - unsafe { sys::gui_remove_view_port(gui, view_port) } + unsafe { + sys::view_port_enabled_set(view_port, false); + sys::gui_remove_view_port(gui, view_port); + // the object has to be deallocated since the ownership was transferred to the `Gui` + sys::view_port_free(view_port); + } } } diff --git a/crates/flipperzero/src/gui/view_port.rs b/crates/flipperzero/src/gui/view_port.rs index 82e726a8..19f5cb4c 100644 --- a/crates/flipperzero/src/gui/view_port.rs +++ b/crates/flipperzero/src/gui/view_port.rs @@ -235,6 +235,12 @@ impl ViewPort { } } + pub fn update(&mut self) { + let raw = self.as_raw(); + // SAFETY: `raw` is always valid + unsafe { sys::view_port_update(raw) } + } + /// Gets the dimensions of this `ViewPort`. /// /// # Examples @@ -339,7 +345,10 @@ impl Drop for ViewPort { let raw = self.raw.as_ptr(); // SAFETY: `self.raw` is always valid // and it should have been unregistered from the system by now - unsafe { sys::view_port_free(raw) } + unsafe { + sys::view_port_enabled_set(raw, false); + sys::view_port_free(raw); + } } } From feed3e99abfb0846d88620fe41ff6ae48fc30cb6 Mon Sep 17 00:00:00 2001 From: Petr Portnov Date: Sun, 4 Jun 2023 03:19:23 +0300 Subject: [PATCH 37/49] chore: change XMB coordinate formulas --- crates/flipperzero/src/gui/xbm.rs | 132 ++++++++++++++++++------------ 1 file changed, 78 insertions(+), 54 deletions(-) diff --git a/crates/flipperzero/src/gui/xbm.rs b/crates/flipperzero/src/gui/xbm.rs index 450cfe4e..2a08bd9c 100644 --- a/crates/flipperzero/src/gui/xbm.rs +++ b/crates/flipperzero/src/gui/xbm.rs @@ -50,13 +50,23 @@ impl XbmImage { } } + // IMPORTANT: XBM images have trailing bits per-rows + // rather than at the end of the whole byte-array // FIXME: XBM trails on line ends #[inline] const fn offsets(&self, x: u8, y: u8) -> Option<(u8, u8)> { - if let Some(offset) = self.offset(x, y) { - Some((offset / 8, offset % 8)) - } else { + let row_bytes = crate::internals::ops::div_ceil_u8(self.width, 8); + + if x >= self.width || y >= self.height { None + } else { + Some(( + // Per each y we skip a row of `row_bytes` bytes + // then we also have to skip all previous + (row_bytes * y) + x / 8, + // Since all rowas are aligned, only x ffects the bit ofset + x % 8, + )) } } } @@ -250,24 +260,23 @@ impl DerefMut for ByteArray { } } -/// Creates +/// Creates a compile-time XBM image. +/// The type of this expression is [`XbmImage`] with [`ByteArray`] backend of the calculated size. +/// +/// The syntax is an [XBM image definition](XBM format) +/// optionally wrapped in an `unsafe` block. +/// +/// Unless the expression is wrapped in an `unsafe` block +/// the macro will perform identifier validation: +/// it will check that identifiers are in the order +/// `_width`, `_height`, optionally `_x_hot` and `_y_hot` and `_bits` +/// and that the `` is the same for all identifiers. +/// The `unsafe` form omits this validations +/// while still ensuring that the size constraints are valid. +/// +/// [XBM format]: https://www.fileformat.info/format/xbm/egff.htm #[macro_export] macro_rules! xbm { - ( - unsafe { - #define $_width_ident:ident $width:literal - #define $_height_ident:ident $height:literal - $( - #define $_x_hotspot_ident:ident $_hotspot_x:literal - #define $_y_hotspot_ident:ident $_hotspot_y:literal - )? - static char $_bits_ident:ident[] = { - $($byte:literal),* $(,)? - }; - } - ) => {{ - $crate::gui::xbm::XbmImage::new_from_array::<$width, $height>([$($byte,)*]) - }}; ( #define $width_ident:ident $width:literal #define $height_ident:ident $height:literal @@ -275,7 +284,7 @@ macro_rules! xbm { #define $x_hotspot_ident:ident $_hotspot_x:literal #define $y_hotspot_ident:ident $_hotspot_y:literal )? - static char $bits_ident:ident[] = { + static $(unsigned)? char $bits_ident:ident[] = { $($byte:literal),* $(,)? }; ) => {{ @@ -365,10 +374,25 @@ macro_rules! xbm { }; }) }}; + ( + unsafe { + #define $_width_ident:ident $width:literal + #define $_height_ident:ident $height:literal + $( + #define $_x_hotspot_ident:ident $_hotspot_x:literal + #define $_y_hotspot_ident:ident $_hotspot_y:literal + )? + static $(unsigned)? char $_bits_ident:ident[] = { + $($byte:literal),* $(,)? + }; + } + ) => {{ + $crate::gui::xbm::XbmImage::new_from_array::<$width, $height>([$($byte,)*]) + }}; } -// TODO: enable test execution -#[cfg(test)] +#[flipperzero_test::tests] +// TODO: ensure that tests actually pass :pekaface: mod tests { #[test] @@ -386,37 +410,37 @@ mod tests { }; ); - assert!(!image.get((0, 0))); - assert!(image.get((0, 1))); - assert!(!image.get((0, 2))); - assert!(!image.get((0, 3))); - assert!(image.get((0, 4))); - assert!(image.get((0, 5))); - assert!(!image.get((0, 6))); - assert!(!image.get((0, 7))); - assert!(!image.get((0, 8))); - assert!(!image.get((0, 9))); - assert!(image.get((0, 10))); - assert!(image.get((0, 11))); - assert!(image.get((0, 12))); - assert!(image.get((0, 13))); - assert!(!image.get((0, 14))); - assert!(!image.get((0, 15))); - assert!(!image.get((1, 0))); - assert!(!image.get((1, 1))); - assert!(!image.get((1, 2))); - assert!(!image.get((1, 3))); - assert!(!image.get((1, 4))); - assert!(!image.get((1, 5))); - assert!(image.get((1, 6))); - assert!(image.get((1, 7))); - assert!(image.get((1, 8))); - assert!(image.get((1, 9))); - assert!(image.get((1, 10))); - assert!(image.get((1, 11))); - assert!(image.get((1, 12))); - assert!(image.get((1, 13))); - assert!(!image.get((1, 14))); - assert!(!image.get((1, 15))); + assert!(!image.get((0, 0)).unwrap()); + assert!(image.get((0, 1)).unwrap()); + assert!(!image.get((0, 2)).unwrap()); + assert!(!image.get((0, 3)).unwrap()); + assert!(image.get((0, 4)).unwrap()); + assert!(image.get((0, 5)).unwrap()); + assert!(!image.get((0, 6)).unwrap()); + assert!(!image.get((0, 7)).unwrap()); + assert!(!image.get((0, 8)).unwrap()); + assert!(!image.get((0, 9)).unwrap()); + assert!(image.get((0, 10)).unwrap()); + assert!(image.get((0, 11)).unwrap()); + assert!(image.get((0, 12)).unwrap()); + assert!(image.get((0, 13)).unwrap()); + assert!(!image.get((0, 14)).unwrap()); + assert!(!image.get((0, 15)).unwrap()); + assert!(!image.get((1, 0)).unwrap()); + assert!(!image.get((1, 1)).unwrap()); + assert!(!image.get((1, 2)).unwrap()); + assert!(!image.get((1, 3)).unwrap()); + assert!(!image.get((1, 4)).unwrap()); + assert!(!image.get((1, 5)).unwrap()); + assert!(image.get((1, 6)).unwrap()); + assert!(image.get((1, 7)).unwrap()); + assert!(image.get((1, 8)).unwrap()); + assert!(image.get((1, 9)).unwrap()); + assert!(image.get((1, 10)).unwrap()); + assert!(image.get((1, 11)).unwrap()); + assert!(image.get((1, 12)).unwrap()); + assert!(image.get((1, 13)).unwrap()); + assert!(!image.get((1, 14)).unwrap()); + assert!(!image.get((1, 15)).unwrap()); } } From 025b7f8bde96c1a43b5664c229303898ea74741e Mon Sep 17 00:00:00 2001 From: Petr Portnov Date: Sun, 4 Jun 2023 03:59:11 +0300 Subject: [PATCH 38/49] fix: resolve math errors in `xbm.rs` --- crates/flipperzero/src/gui/xbm.rs | 166 +++++++++++++++++----------- crates/flipperzero/src/internals.rs | 17 --- 2 files changed, 100 insertions(+), 83 deletions(-) diff --git a/crates/flipperzero/src/gui/xbm.rs b/crates/flipperzero/src/gui/xbm.rs index 2a08bd9c..870a11ed 100644 --- a/crates/flipperzero/src/gui/xbm.rs +++ b/crates/flipperzero/src/gui/xbm.rs @@ -1,5 +1,6 @@ -//! User-friendly wrappers of XDM images. +//! User-friendly wrappers of XBM images. +use crate::internals::ops::div_ceil_u8; use alloc::{vec, vec::Vec}; use core::{ ops::{Deref, DerefMut}, @@ -27,44 +28,27 @@ impl XbmImage { } #[inline] - const fn dimension_bits(width: u8, height: u8) -> u16 { - width as u16 * height as u16 + const fn row_bytes(width: u8) -> u8 { + div_ceil_u8(width, 8) } #[inline] - const fn bits_to_min_required_bytes(bits: u16) -> u16 { - crate::internals::ops::div_ceil_u16(bits, 8) - } - - #[inline] - const fn dimension_bytes(width: u8, height: u8) -> u16 { - Self::bits_to_min_required_bytes(Self::dimension_bits(width, height)) - } - - #[inline] - const fn offset(&self, x: u8, y: u8) -> Option { - if x >= self.width || y >= self.height { - None - } else { - Some(x * self.width + y) - } + const fn total_bytes(width: u8, height: u8) -> u16 { + Self::row_bytes(width) as u16 * height as u16 } // IMPORTANT: XBM images have trailing bits per-rows // rather than at the end of the whole byte-array - // FIXME: XBM trails on line ends #[inline] const fn offsets(&self, x: u8, y: u8) -> Option<(u8, u8)> { - let row_bytes = crate::internals::ops::div_ceil_u8(self.width, 8); - if x >= self.width || y >= self.height { None } else { Some(( // Per each y we skip a row of `row_bytes` bytes // then we also have to skip all previous - (row_bytes * y) + x / 8, - // Since all rowas are aligned, only x ffects the bit ofset + (Self::row_bytes(self.width) * y) + x / 8, + // Since all rows are aligned, only x affects the bit offset x % 8, )) } @@ -86,15 +70,13 @@ impl> XbmImage { impl> XbmImage { pub fn new_from(width: u8, height: u8, data: D) -> Self { - let bits = Self::dimension_bits(width, height); - let bytes = Self::bits_to_min_required_bytes(bits); + let bytes = Self::total_bytes(width, height); assert!( bytes as usize == data.len(), - "width={} * height={} = {} should correspond to {} bytes, but data has length {}", + "width={}bits * height={}bits should correspond to {}bytes, but data has length {}", width, height, - bits, bytes, data.len() ); @@ -117,7 +99,7 @@ impl> XbmImage { impl<'a> XbmImage<&'a [u8]> { pub unsafe fn from_raw(width: u8, height: u8, data: *const u8) -> Self { - let bytes = Self::dimension_bytes(width, height) as usize; + let bytes = Self::total_bytes(width, height) as usize; // SAFETY: the size is exactly calculated based on width and height // and caller upholds the `data` validity invariant @@ -133,7 +115,7 @@ impl<'a> XbmImage<&'a [u8]> { impl<'a> XbmImage<&'a mut [u8]> { pub unsafe fn from_raw_mut(width: u8, height: u8, data: *mut u8) -> Self { - let bytes = Self::dimension_bytes(width, height) as usize; + let bytes = Self::total_bytes(width, height) as usize; // SAFETY: the size is exactly calculated based on width and height // and caller upholds the `data` validity invariant @@ -179,7 +161,7 @@ impl + DerefMut> XbmImage { impl XbmImage> { pub fn new(width: u8, height: u8) -> Self { - let bytes = Self::dimension_bytes(width, height) as usize; + let bytes = Self::total_bytes(width, height) as usize; Self { data: vec![0; bytes], width, @@ -204,7 +186,7 @@ impl XbmImage<&'static [u8]> { /// const IMAGE: XbmImage<&'static [u8]> = XbmImage::new_from_static(4, 4, &[0xFE, 0x12]); /// ``` pub const fn new_from_static(width: u8, height: u8, data: &'static [u8]) -> Self { - let bytes = Self::dimension_bytes(width, height); + let bytes = Self::total_bytes(width, height); assert!( bytes as usize == data.len(), @@ -231,7 +213,7 @@ impl XbmImage> { /// const IMAGE: XbmImage> = XbmImage::new_from_array::<4, 4>([0xFE, 0x12]); /// ``` pub const fn new_from_array(data: [u8; SIZE]) -> Self { - let bytes = Self::dimension_bytes(WIDTH, HEIGHT); + let bytes = Self::total_bytes(WIDTH, HEIGHT); assert!(bytes as usize == SIZE, "dimensions don't match data length"); @@ -392,11 +374,10 @@ macro_rules! xbm { } #[flipperzero_test::tests] -// TODO: ensure that tests actually pass :pekaface: +// TODO: add this tests to some harness mod tests { - #[test] - fn valid_byte_reading() { + fn valid_byte_reading_aligned() { // 0100110000111100 // 0000001111111100 let image = xbm!( @@ -411,36 +392,89 @@ mod tests { ); assert!(!image.get((0, 0)).unwrap()); - assert!(image.get((0, 1)).unwrap()); - assert!(!image.get((0, 2)).unwrap()); - assert!(!image.get((0, 3)).unwrap()); - assert!(image.get((0, 4)).unwrap()); - assert!(image.get((0, 5)).unwrap()); - assert!(!image.get((0, 6)).unwrap()); - assert!(!image.get((0, 7)).unwrap()); - assert!(!image.get((0, 8)).unwrap()); - assert!(!image.get((0, 9)).unwrap()); - assert!(image.get((0, 10)).unwrap()); - assert!(image.get((0, 11)).unwrap()); - assert!(image.get((0, 12)).unwrap()); - assert!(image.get((0, 13)).unwrap()); - assert!(!image.get((0, 14)).unwrap()); - assert!(!image.get((0, 15)).unwrap()); - assert!(!image.get((1, 0)).unwrap()); + assert!(image.get((1, 0)).unwrap()); + assert!(!image.get((2, 0)).unwrap()); + assert!(!image.get((3, 0)).unwrap()); + assert!(image.get((4, 0)).unwrap()); + assert!(image.get((5, 0)).unwrap()); + assert!(!image.get((6, 0)).unwrap()); + assert!(!image.get((7, 0)).unwrap()); + assert!(!image.get((8, 0)).unwrap()); + assert!(!image.get((9, 0)).unwrap()); + assert!(image.get((10, 0)).unwrap()); + assert!(image.get((11, 0)).unwrap()); + assert!(image.get((12, 0)).unwrap()); + assert!(image.get((13, 0)).unwrap()); + assert!(!image.get((14, 0)).unwrap()); + assert!(!image.get((15, 0)).unwrap()); + assert!(!image.get((0, 1)).unwrap()); + assert!(!image.get((1, 1)).unwrap()); + assert!(!image.get((2, 1)).unwrap()); + assert!(!image.get((3, 1)).unwrap()); + assert!(!image.get((4, 1)).unwrap()); + assert!(!image.get((5, 1)).unwrap()); + assert!(image.get((6, 1)).unwrap()); + assert!(image.get((7, 1)).unwrap()); + assert!(image.get((8, 1)).unwrap()); + assert!(image.get((9, 1)).unwrap()); + assert!(image.get((10, 1)).unwrap()); + assert!(image.get((11, 1)).unwrap()); + assert!(image.get((12, 1)).unwrap()); + assert!(image.get((13, 1)).unwrap()); + assert!(!image.get((14, 1)).unwrap()); + assert!(!image.get((15, 1)).unwrap()); + } + + #[test] + fn valid_byte_reading_misaligned() { + // 01001100 00111100 0******* + // 00000011 11111100 1******* + let image = xbm!( + #define xbm_test_width 17 + #define xbm_test_height 2 + static char xbm_test_bits[] = { + 0x32, // 0b00110010 ~ 0b01001100 + 0x3C, // 0b00111100 ~ 0b00111100 + 0xF0, // 0b00001111 ~ 0b11110000 + 0xC0, // 0b11000000 ~ 0b00000011 + 0x3F, // 0b00111111 ~ 0b11111100 + 0x0F, // 0b11110000 ~ 0b00001111 + }; + ); + + assert!(!image.get((0, 0)).unwrap()); + assert!(image.get((1, 0)).unwrap()); + assert!(!image.get((2, 0)).unwrap()); + assert!(!image.get((3, 0)).unwrap()); + assert!(image.get((4, 0)).unwrap()); + assert!(image.get((5, 0)).unwrap()); + assert!(!image.get((6, 0)).unwrap()); + assert!(!image.get((7, 0)).unwrap()); + assert!(!image.get((8, 0)).unwrap()); + assert!(!image.get((9, 0)).unwrap()); + assert!(image.get((10, 0)).unwrap()); + assert!(image.get((11, 0)).unwrap()); + assert!(image.get((12, 0)).unwrap()); + assert!(image.get((13, 0)).unwrap()); + assert!(!image.get((14, 0)).unwrap()); + assert!(!image.get((15, 0)).unwrap()); + assert!(!image.get((16, 0)).unwrap()); + assert!(!image.get((0, 1)).unwrap()); assert!(!image.get((1, 1)).unwrap()); - assert!(!image.get((1, 2)).unwrap()); - assert!(!image.get((1, 3)).unwrap()); - assert!(!image.get((1, 4)).unwrap()); - assert!(!image.get((1, 5)).unwrap()); - assert!(image.get((1, 6)).unwrap()); - assert!(image.get((1, 7)).unwrap()); - assert!(image.get((1, 8)).unwrap()); - assert!(image.get((1, 9)).unwrap()); - assert!(image.get((1, 10)).unwrap()); - assert!(image.get((1, 11)).unwrap()); - assert!(image.get((1, 12)).unwrap()); - assert!(image.get((1, 13)).unwrap()); - assert!(!image.get((1, 14)).unwrap()); - assert!(!image.get((1, 15)).unwrap()); + assert!(!image.get((2, 1)).unwrap()); + assert!(!image.get((3, 1)).unwrap()); + assert!(!image.get((4, 1)).unwrap()); + assert!(!image.get((5, 1)).unwrap()); + assert!(image.get((6, 1)).unwrap()); + assert!(image.get((7, 1)).unwrap()); + assert!(image.get((8, 1)).unwrap()); + assert!(image.get((9, 1)).unwrap()); + assert!(image.get((10, 1)).unwrap()); + assert!(image.get((11, 1)).unwrap()); + assert!(image.get((12, 1)).unwrap()); + assert!(image.get((13, 1)).unwrap()); + assert!(!image.get((14, 1)).unwrap()); + assert!(!image.get((15, 1)).unwrap()); + assert!(image.get((16, 1)).unwrap()); } } diff --git a/crates/flipperzero/src/internals.rs b/crates/flipperzero/src/internals.rs index 370c0a93..842286ae 100644 --- a/crates/flipperzero/src/internals.rs +++ b/crates/flipperzero/src/internals.rs @@ -84,21 +84,4 @@ pub(crate) mod ops { } } } - - pub const fn div_ceil_u16(divident: u16, divisor: u16) -> u16 { - #[cfg(feature = "unstable_intrinsics")] - { - divident.div_ceil(divisor) - } - #[cfg(not(feature = "unstable_intrinsics"))] - { - let quotient = divident / divisor; - let remainder = divident % divisor; - if remainder > 0 && divisor > 0 { - quotient + 1 - } else { - quotient - } - } - } } From e0a0391b5f68ae46d86e5d78afa6f699b008c769 Mon Sep 17 00:00:00 2001 From: Petr Portnov Date: Sun, 4 Jun 2023 15:32:38 +0300 Subject: [PATCH 39/49] docs: fix docs for unstable features --- crates/flipperzero/Cargo.toml | 13 +++++++------ crates/flipperzero/src/furi/log/mod.rs | 3 ++- crates/flipperzero/src/gui/xbm.rs | 3 +-- crates/flipperzero/src/lib.rs | 1 + 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/crates/flipperzero/Cargo.toml b/crates/flipperzero/Cargo.toml index 9cc2c164..1ec17699 100644 --- a/crates/flipperzero/Cargo.toml +++ b/crates/flipperzero/Cargo.toml @@ -94,10 +94,10 @@ service-dialogs = ["service-gui"] ## Enables Dolphin APIs of Flipper. service-dolphin = [] -##! ## Unstable features -##! -##! This features are unstable and are yet a subject to dramatic changes. -##! They may provide experimental APIs, nightly-only features and optimizations etc. +#! ## Unstable features +#! +#! This features are unstable and are yet a subject to drastic changes. +#! They may provide experimental APIs, nightly-only features and optimizations etc. ## Enables unstable Rust intrinsics. This requires `nightly` compiler version. unstable_intrinsics = [] @@ -105,9 +105,10 @@ unstable_intrinsics = [] ## This requires `nightly` compiler version. unstable_lints = [] -# Internal features +# Internal features. -# Enables unstable doc +# Enables unstable documentation features. +# This is only intended to be used by `cargo doc`. unstable_docs = [] [[test]] diff --git a/crates/flipperzero/src/furi/log/mod.rs b/crates/flipperzero/src/furi/log/mod.rs index 22647b25..daef0982 100644 --- a/crates/flipperzero/src/furi/log/mod.rs +++ b/crates/flipperzero/src/furi/log/mod.rs @@ -11,10 +11,11 @@ pub use metadata::{Level, LevelFilter}; /// # Examples /// /// ``` -/// use flipperzero::{error, furi::log::Level}; +/// use flipperzero::{log, furi::log::Level}; /// /// # fn main() { /// let error_code = 42; +/// /// log!(Level::ERROR, "Failed to handle the florp: {}", error_code); /// log!(target: "events", Level::INFO, "Finished the documentation!"); /// # } diff --git a/crates/flipperzero/src/gui/xbm.rs b/crates/flipperzero/src/gui/xbm.rs index 870a11ed..977894ad 100644 --- a/crates/flipperzero/src/gui/xbm.rs +++ b/crates/flipperzero/src/gui/xbm.rs @@ -245,7 +245,7 @@ impl DerefMut for ByteArray { /// Creates a compile-time XBM image. /// The type of this expression is [`XbmImage`] with [`ByteArray`] backend of the calculated size. /// -/// The syntax is an [XBM image definition](XBM format) +/// The syntax is an [XBM image definition][XBM format] /// optionally wrapped in an `unsafe` block. /// /// Unless the expression is wrapped in an `unsafe` block @@ -374,7 +374,6 @@ macro_rules! xbm { } #[flipperzero_test::tests] -// TODO: add this tests to some harness mod tests { #[test] fn valid_byte_reading_aligned() { diff --git a/crates/flipperzero/src/lib.rs b/crates/flipperzero/src/lib.rs index cfc0c5f4..f6b0f717 100644 --- a/crates/flipperzero/src/lib.rs +++ b/crates/flipperzero/src/lib.rs @@ -76,5 +76,6 @@ flipperzero_test::tests_runner!( crate::toolbox::crc32::tests, crate::toolbox::md5::tests, crate::toolbox::sha256::tests, + crate::gui::xbm::tests, ] ); From 5ed96dcf14f09abf79bc25ae9d31ae5ffde63cbb Mon Sep 17 00:00:00 2001 From: Petr Portnov Date: Sun, 4 Jun 2023 15:58:52 +0300 Subject: [PATCH 40/49] docs: disable scrape examples --- crates/flipperzero/Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/flipperzero/Cargo.toml b/crates/flipperzero/Cargo.toml index 1ec17699..02e630ce 100644 --- a/crates/flipperzero/Cargo.toml +++ b/crates/flipperzero/Cargo.toml @@ -14,7 +14,6 @@ autobenches = false default-target = "thumbv7em-none-eabihf" targets = [] all-features = true -cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"] [lib] bench = false From e8f77088a92b0e275c42f7c71a3f1b53e54cef98 Mon Sep 17 00:00:00 2001 From: Petr Portnov Date: Thu, 8 Jun 2023 14:54:02 +0300 Subject: [PATCH 41/49] docs: configure docs.rs metadata in crate root --- crates/Cargo.toml | 5 +++++ crates/alloc/Cargo.toml | 5 ----- crates/flipperzero/Cargo.toml | 5 ----- crates/rt/Cargo.toml | 5 ----- crates/sys/Cargo.toml | 5 ----- 5 files changed, 5 insertions(+), 20 deletions(-) diff --git a/crates/Cargo.toml b/crates/Cargo.toml index b37e958b..d4e37f3e 100644 --- a/crates/Cargo.toml +++ b/crates/Cargo.toml @@ -18,6 +18,11 @@ repository = "https://github.com/dcoles/flipperzero-rs" readme = "../README.md" license = "MIT" +[workspace.metadata.docs.rs] +default-target = "thumbv7em-none-eabihf" +targets = [] +all-features = true + [workspace.dependencies] flipperzero-sys = { path = "sys", version = "0.10.0" } flipperzero-rt = { path = "rt", version = "0.10.0" } diff --git a/crates/alloc/Cargo.toml b/crates/alloc/Cargo.toml index e2ba21e7..838599a6 100644 --- a/crates/alloc/Cargo.toml +++ b/crates/alloc/Cargo.toml @@ -12,11 +12,6 @@ autoexamples = false autotests = false autobenches = false -[package.metadata.docs.rs] -default-target = "thumbv7em-none-eabihf" -targets = [] -all-features = true - [lib] bench = false test = false diff --git a/crates/flipperzero/Cargo.toml b/crates/flipperzero/Cargo.toml index 02e630ce..70f20a0b 100644 --- a/crates/flipperzero/Cargo.toml +++ b/crates/flipperzero/Cargo.toml @@ -10,11 +10,6 @@ rust-version.workspace = true autobins = false autobenches = false -[package.metadata.docs.rs] -default-target = "thumbv7em-none-eabihf" -targets = [] -all-features = true - [lib] bench = false harness = false diff --git a/crates/rt/Cargo.toml b/crates/rt/Cargo.toml index e9506d39..82e14bf3 100644 --- a/crates/rt/Cargo.toml +++ b/crates/rt/Cargo.toml @@ -12,11 +12,6 @@ autoexamples = false autotests = false autobenches = false -[package.metadata.docs.rs] -default-target = "thumbv7em-none-eabihf" -targets = [] -all-features = true - [lib] bench = false test = false diff --git a/crates/sys/Cargo.toml b/crates/sys/Cargo.toml index 93a56f9e..42155c0f 100644 --- a/crates/sys/Cargo.toml +++ b/crates/sys/Cargo.toml @@ -12,11 +12,6 @@ autoexamples = false autotests = false autobenches = false -[package.metadata.docs.rs] -default-target = "thumbv7em-none-eabihf" -targets = [] -all-features = true - [lib] bench = false test = false From 2035e0642c4cd06febcbf8959e27cb016458d5d3 Mon Sep 17 00:00:00 2001 From: Petr Portnov Date: Mon, 12 Jun 2023 20:07:17 +0300 Subject: [PATCH 42/49] chore: move error types to separate files --- crates/alloc/Cargo.toml | 5 + crates/flipperzero/src/furi/log/metadata.rs | 9 +- crates/flipperzero/src/gui/canvas.rs | 255 ++---------------- crates/flipperzero/src/gui/canvas/align.rs | 65 +++++ .../src/gui/canvas/canvas_direction.rs | 62 +++++ crates/flipperzero/src/gui/canvas/color.rs | 59 ++++ crates/flipperzero/src/gui/canvas/font.rs | 90 +++++++ .../src/gui/canvas/font_parameters.rs | 78 ++++++ crates/flipperzero/src/gui/gui_layer.rs | 78 ++++++ crates/flipperzero/src/gui/mod.rs | 52 +--- crates/flipperzero/src/gui/view_port.rs | 50 +--- .../src/gui/view_port/orientation.rs | 82 ++++++ crates/flipperzero/src/input/key.rs | 91 +++++++ crates/flipperzero/src/input/mod.rs | 118 +------- crates/flipperzero/src/input/type.rs | 88 ++++++ crates/flipperzero/src/internals.rs | 12 + crates/flipperzero/src/io.rs | 3 + crates/flipperzero/src/kernel.rs | 4 +- crates/flipperzero/src/xbm.rs | 4 +- 19 files changed, 754 insertions(+), 451 deletions(-) create mode 100644 crates/flipperzero/src/gui/canvas/align.rs create mode 100644 crates/flipperzero/src/gui/canvas/canvas_direction.rs create mode 100644 crates/flipperzero/src/gui/canvas/color.rs create mode 100644 crates/flipperzero/src/gui/canvas/font.rs create mode 100644 crates/flipperzero/src/gui/canvas/font_parameters.rs create mode 100644 crates/flipperzero/src/gui/gui_layer.rs create mode 100644 crates/flipperzero/src/gui/view_port/orientation.rs create mode 100644 crates/flipperzero/src/input/key.rs create mode 100644 crates/flipperzero/src/input/type.rs diff --git a/crates/alloc/Cargo.toml b/crates/alloc/Cargo.toml index 838599a6..e2ba21e7 100644 --- a/crates/alloc/Cargo.toml +++ b/crates/alloc/Cargo.toml @@ -12,6 +12,11 @@ autoexamples = false autotests = false autobenches = false +[package.metadata.docs.rs] +default-target = "thumbv7em-none-eabihf" +targets = [] +all-features = true + [lib] bench = false test = false diff --git a/crates/flipperzero/src/furi/log/metadata.rs b/crates/flipperzero/src/furi/log/metadata.rs index c3b5e3b6..06f40074 100644 --- a/crates/flipperzero/src/furi/log/metadata.rs +++ b/crates/flipperzero/src/furi/log/metadata.rs @@ -5,6 +5,7 @@ use core::{cmp, fmt, str::FromStr}; +use crate::internals; use flipperzero_sys as sys; use ufmt::derive::uDebug; @@ -158,9 +159,7 @@ impl ufmt::uDisplay for Level { } } -#[cfg(feature = "std")] -#[cfg_attr(docsrs, doc(cfg(feature = "std")))] -impl std::error::Error for ParseLevelError {} +internals::macros::impl_std_error!(ParseLevelError); impl FromStr for Level { type Err = ParseLevelError; @@ -465,9 +464,7 @@ impl ufmt::uDisplay for ParseLevelFilterError { } } -#[cfg(feature = "std")] -#[cfg_attr(docsrs, doc(cfg(feature = "std")))] -impl std::error::Error for ParseLevelFilterError {} +internals::macros::impl_std_error!(ParseLevelFilterError); #[repr(usize)] #[derive(Copy, Clone, Debug, uDebug, Hash, Eq, PartialEq, PartialOrd, Ord)] diff --git a/crates/flipperzero/src/gui/canvas.rs b/crates/flipperzero/src/gui/canvas.rs index 1e824852..9a4df78d 100644 --- a/crates/flipperzero/src/gui/canvas.rs +++ b/crates/flipperzero/src/gui/canvas.rs @@ -1,5 +1,11 @@ //! Canvases. +mod align; +mod canvas_direction; +mod color; +mod font; +mod font_parameters; + use crate::{ gui::{ icon::Icon, @@ -16,10 +22,15 @@ use core::{ ptr::NonNull, }; use flipperzero_sys::{ - self as sys, Align as SysAlign, Canvas as SysCanvas, CanvasDirection as SysCanvasDirection, - CanvasFontParameters as SysCanvasFontParameters, Color as SysColor, Font as SysFont, + self as sys, Canvas as SysCanvas, CanvasFontParameters as SysCanvasFontParameters, }; +pub use align::*; +pub use canvas_direction::*; +pub use color::*; +pub use font::*; +pub use font_parameters::*; + /// System Canvas view. pub struct CanvasView<'a> { raw: NonNull, @@ -175,9 +186,11 @@ impl CanvasView<'_> { unsafe { sys::canvas_glyph_width(raw, glyph) } } + // Note: FURI is guaranteed to correctly handle out-of-bounds draws + // so we don't need to check the bounds + // TODO `canvas_draw_bitmap` compressed bitmap support - // TODO: do we need range checks? pub fn draw_icon_animation<'a, 'b: 'a>( &'a mut self, x: u8, @@ -191,7 +204,6 @@ impl CanvasView<'_> { unsafe { sys::canvas_draw_icon_animation(raw, x, y, icon_animation) } } - // TODO: do we need range checks? pub fn draw_icon<'a, 'b: 'a>(&'a mut self, x: u8, y: u8, animation: &'b Icon) { let raw = self.raw.as_ptr(); let icon = animation.as_raw(); @@ -201,21 +213,11 @@ impl CanvasView<'_> { } // TODO: do we need other range checks? - // what is the best return type? - pub fn draw_xbm( - &mut self, - x: u8, - y: u8, - xbm: &XbmImage>, - ) -> Option<()> { + pub fn draw_xbm(&mut self, x: u8, y: u8, xbm: &XbmImage>) { let raw = self.raw.as_ptr(); let width = xbm.width(); let height = xbm.height(); - // ensure that the image is not too big - let _ = x.checked_add(width)?; - let _ = y.checked_add(height)?; - let data = xbm.data().as_ptr(); // SAFETY: `raw` is always valid @@ -232,7 +234,6 @@ impl CanvasView<'_> { ); } unsafe { sys::canvas_draw_xbm(raw, x, y, width, height, data) }; - Some(()) } // TODO: @@ -246,7 +247,6 @@ impl CanvasView<'_> { unsafe { sys::canvas_draw_dot(raw, x, y) } } - // TODO: do we need range checks? // TODO: do `width` and `height` have to be non-zero pub fn draw_box(&mut self, x: u8, y: u8, width: u8, height: u8) { let raw = self.raw.as_ptr(); @@ -254,7 +254,6 @@ impl CanvasView<'_> { unsafe { sys::canvas_draw_box(raw, x, y, width, height) } } - // TODO: do we need range checks? // TODO: do `width` and `height` have to be non-zero pub fn draw_frame(&mut self, x: u8, y: u8, width: u8, height: u8) { let raw = self.raw.as_ptr(); @@ -262,7 +261,6 @@ impl CanvasView<'_> { unsafe { sys::canvas_draw_frame(raw, x, y, width, height) } } - // TODO: do we need range checks? // TODO: do `x2` and `y2` have to be non-zero pub fn draw_line(&mut self, x1: u8, y1: u8, x2: u8, y2: u8) { let raw = self.raw.as_ptr(); @@ -270,7 +268,6 @@ impl CanvasView<'_> { unsafe { sys::canvas_draw_line(raw, x1, y1, x2, y2) } } - // TODO: do we need range checks? // TODO: does `radius` have to be non-zero pub fn draw_circle(&mut self, x: u8, y: u8, radius: u8) { let raw = self.raw.as_ptr(); @@ -278,7 +275,6 @@ impl CanvasView<'_> { unsafe { sys::canvas_draw_circle(raw, x, y, radius) } } - // TODO: do we need range checks? // TODO: does `radius` have to be non-zero pub fn draw_disc(&mut self, x: u8, y: u8, radius: u8) { let raw = self.raw.as_ptr(); @@ -286,7 +282,6 @@ impl CanvasView<'_> { unsafe { sys::canvas_draw_disc(raw, x, y, radius) } } - // TODO: do we need range checks? // TODO: do `base` and `height` have to be non-zero pub fn draw_triangle( &mut self, @@ -303,7 +298,6 @@ impl CanvasView<'_> { unsafe { sys::canvas_draw_triangle(raw, x, y, base, height, direction) } } - // TODO: do we need range checks? // TODO: does `character` have to be of a wrapper type pub fn draw_glyph(&mut self, x: u8, y: u8, character: u16) { let raw = self.raw.as_ptr(); @@ -317,7 +311,6 @@ impl CanvasView<'_> { unsafe { sys::canvas_set_bitmap_mode(raw, alpha) } } - // TODO: do we need range checks? // TODO: do `width`, `height` and `radius` have to be non-zero pub fn draw_rframe(&mut self, x: u8, y: u8, width: u8, height: u8, radius: u8) { let raw = self.raw.as_ptr(); @@ -325,7 +318,6 @@ impl CanvasView<'_> { unsafe { sys::canvas_draw_rframe(raw, x, y, width, height, radius) } } - // TODO: do we need range checks? // TODO: do `width`, `height` and `radius` have to be non-zero pub fn draw_rbox(&mut self, x: u8, y: u8, width: u8, height: u8, radius: u8) { let raw = self.raw.as_ptr(); @@ -382,216 +374,3 @@ impl<'a> CanvasFontParameters<'a> { .expect("raw `CanvasFontParameters` should be valid") } } - -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] -pub struct CanvasFontParametersSnapshot { - pub leading_default: NonZeroU8, - pub leading_min: NonZeroU8, - pub height: NonZeroU8, - pub descender: u8, -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] -pub enum FromSysGuiLayerError { - ZeroLeadingDefault, - ZeroLeadingMin, - ZeroHeight, -} - -impl TryFrom for CanvasFontParametersSnapshot { - type Error = FromSysGuiLayerError; - - fn try_from(value: SysCanvasFontParameters) -> Result { - Ok(Self { - leading_default: value - .leading_default - .try_into() - .or(Err(Self::Error::ZeroLeadingDefault))?, - leading_min: value - .leading_min - .try_into() - .or(Err(Self::Error::ZeroLeadingMin))?, - height: value.height.try_into().or(Err(Self::Error::ZeroHeight))?, - descender: value.descender, - }) - } -} - -impl From for SysCanvasFontParameters { - fn from(value: CanvasFontParametersSnapshot) -> Self { - Self { - leading_default: value.leading_default.into(), - leading_min: value.leading_min.into(), - height: value.height.into(), - descender: value.descender, - } - } -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] -pub enum Color { - White, - Black, - Xor, -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] -pub enum FromSysColorError { - Invalid(SysColor), -} - -impl TryFrom for Color { - type Error = FromSysColorError; - - fn try_from(value: SysColor) -> Result { - Ok(match value { - sys::Color_ColorWhite => Self::White, - sys::Color_ColorBlack => Self::Black, - sys::Color_ColorXOR => Self::Xor, - invalid => Err(Self::Error::Invalid(invalid))?, - }) - } -} - -impl From for SysColor { - fn from(value: Color) -> Self { - match value { - Color::White => sys::Color_ColorWhite, - Color::Black => sys::Color_ColorBlack, - Color::Xor => sys::Color_ColorXOR, - } - } -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] -pub enum Font { - Primary, - Secondary, - Keyboard, - BigNumbers, -} - -impl Font { - /// Gets the total number of available fonts. - /// - /// # Example - /// - /// ``` - /// # use flipperzero::gui::canvas::Font; - /// assert_eq!(Font::total_number(), 4); - /// ``` - pub const fn total_number() -> usize { - sys::Font_FontTotalNumber as usize - } -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] -pub enum FromSysFontError { - TotalNumber, - Invalid(SysFont), -} - -impl TryFrom for Font { - type Error = FromSysFontError; - - fn try_from(value: SysFont) -> Result { - Ok(match value { - sys::Font_FontPrimary => Self::Primary, - sys::Font_FontSecondary => Self::Secondary, - sys::Font_FontKeyboard => Self::Keyboard, - sys::Font_FontBigNumbers => Self::BigNumbers, - sys::Font_FontTotalNumber => Err(Self::Error::TotalNumber)?, - invalid => Err(Self::Error::Invalid(invalid))?, - }) - } -} - -impl From for SysFont { - fn from(value: Font) -> Self { - match value { - Font::Primary => sys::Font_FontPrimary, - Font::Secondary => sys::Font_FontSecondary, - Font::Keyboard => sys::Font_FontKeyboard, - Font::BigNumbers => sys::Font_FontBigNumbers, - } - } -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] -pub enum CanvasDirection { - LeftToRight, - TopToBottom, - RightToLeft, - BottomToTop, -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] -pub enum FromSysCanvasDirectionError { - Invalid(SysCanvasDirection), -} - -impl TryFrom for CanvasDirection { - type Error = FromSysCanvasDirectionError; - - fn try_from(value: SysCanvasDirection) -> Result { - Ok(match value { - sys::CanvasDirection_CanvasDirectionLeftToRight => Self::LeftToRight, - sys::CanvasDirection_CanvasDirectionTopToBottom => Self::TopToBottom, - sys::CanvasDirection_CanvasDirectionRightToLeft => Self::RightToLeft, - sys::CanvasDirection_CanvasDirectionBottomToTop => Self::BottomToTop, - invalid => Err(Self::Error::Invalid(invalid))?, - }) - } -} - -impl From for SysCanvasDirection { - fn from(value: CanvasDirection) -> Self { - match value { - CanvasDirection::BottomToTop => sys::CanvasDirection_CanvasDirectionBottomToTop, - CanvasDirection::LeftToRight => sys::CanvasDirection_CanvasDirectionLeftToRight, - CanvasDirection::RightToLeft => sys::CanvasDirection_CanvasDirectionRightToLeft, - CanvasDirection::TopToBottom => sys::CanvasDirection_CanvasDirectionTopToBottom, - } - } -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub enum Align { - Left, - Right, - Top, - Bottom, - Center, -} - -#[derive(Clone, Copy, Debug)] -pub enum FromSysAlignError { - Invalid(SysAlign), -} - -impl TryFrom for Align { - type Error = FromSysAlignError; - - fn try_from(value: SysAlign) -> Result { - Ok(match value { - sys::Align_AlignLeft => Self::Left, - sys::Align_AlignRight => Self::Right, - sys::Align_AlignTop => Self::Top, - sys::Align_AlignBottom => Self::Bottom, - sys::Align_AlignCenter => Self::Center, - invalid => Err(Self::Error::Invalid(invalid))?, - }) - } -} - -impl From for SysAlign { - fn from(value: Align) -> Self { - match value { - Align::Left => sys::Align_AlignLeft, - Align::Right => sys::Align_AlignRight, - Align::Top => sys::Align_AlignTop, - Align::Bottom => sys::Align_AlignBottom, - Align::Center => sys::Align_AlignCenter, - } - } -} diff --git a/crates/flipperzero/src/gui/canvas/align.rs b/crates/flipperzero/src/gui/canvas/align.rs new file mode 100644 index 00000000..98e80c55 --- /dev/null +++ b/crates/flipperzero/src/gui/canvas/align.rs @@ -0,0 +1,65 @@ +use crate::internals::macros::impl_std_error; +use core::fmt::{self, Display, Formatter}; +use flipperzero_sys::{self as sys, Align as SysAlign}; +use ufmt::{derive::uDebug, uDisplay, uWrite, uwrite}; + +#[derive(Copy, Clone, Debug, uDebug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub enum Align { + Left, + Right, + Top, + Bottom, + Center, +} + +impl TryFrom for Align { + type Error = FromSysAlignError; + + fn try_from(value: SysAlign) -> Result { + Ok(match value { + sys::Align_AlignLeft => Self::Left, + sys::Align_AlignRight => Self::Right, + sys::Align_AlignTop => Self::Top, + sys::Align_AlignBottom => Self::Bottom, + sys::Align_AlignCenter => Self::Center, + invalid => Err(Self::Error::Invalid(invalid))?, + }) + } +} + +impl From for SysAlign { + fn from(value: Align) -> Self { + match value { + Align::Left => sys::Align_AlignLeft, + Align::Right => sys::Align_AlignRight, + Align::Top => sys::Align_AlignTop, + Align::Bottom => sys::Align_AlignBottom, + Align::Center => sys::Align_AlignCenter, + } + } +} + +#[non_exhaustive] +#[derive(Copy, Clone, Debug, uDebug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub enum FromSysAlignError { + Invalid(SysAlign), +} + +impl Display for FromSysAlignError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let Self::Invalid(id) = self; + write!(f, "align ID {id} is invalid") + } +} + +impl uDisplay for FromSysAlignError { + fn fmt(&self, f: &mut ufmt::Formatter<'_, W>) -> Result<(), W::Error> + where + W: uWrite + ?Sized, + { + let Self::Invalid(id) = self; + uwrite!(f, "align ID {} is invalid", id) + } +} + +impl_std_error!(FromSysAlignError); diff --git a/crates/flipperzero/src/gui/canvas/canvas_direction.rs b/crates/flipperzero/src/gui/canvas/canvas_direction.rs new file mode 100644 index 00000000..62ecc9a9 --- /dev/null +++ b/crates/flipperzero/src/gui/canvas/canvas_direction.rs @@ -0,0 +1,62 @@ +use crate::internals::macros::impl_std_error; +use core::fmt::{self, Display, Formatter}; +use flipperzero_sys::{self as sys, CanvasDirection as SysCanvasDirection}; +use ufmt::{derive::uDebug, uDisplay, uWrite, uwrite}; + +#[derive(Copy, Clone, Debug, uDebug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub enum CanvasDirection { + LeftToRight, + TopToBottom, + RightToLeft, + BottomToTop, +} + +impl TryFrom for CanvasDirection { + type Error = FromSysCanvasDirectionError; + + fn try_from(value: SysCanvasDirection) -> Result { + Ok(match value { + sys::CanvasDirection_CanvasDirectionLeftToRight => Self::LeftToRight, + sys::CanvasDirection_CanvasDirectionTopToBottom => Self::TopToBottom, + sys::CanvasDirection_CanvasDirectionRightToLeft => Self::RightToLeft, + sys::CanvasDirection_CanvasDirectionBottomToTop => Self::BottomToTop, + invalid => Err(Self::Error::Invalid(invalid))?, + }) + } +} + +impl From for SysCanvasDirection { + fn from(value: CanvasDirection) -> Self { + match value { + CanvasDirection::BottomToTop => sys::CanvasDirection_CanvasDirectionBottomToTop, + CanvasDirection::LeftToRight => sys::CanvasDirection_CanvasDirectionLeftToRight, + CanvasDirection::RightToLeft => sys::CanvasDirection_CanvasDirectionRightToLeft, + CanvasDirection::TopToBottom => sys::CanvasDirection_CanvasDirectionTopToBottom, + } + } +} + +#[non_exhaustive] +#[derive(Copy, Clone, Debug, uDebug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub enum FromSysCanvasDirectionError { + Invalid(SysCanvasDirection), +} + +impl Display for FromSysCanvasDirectionError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let Self::Invalid(id) = self; + write!(f, "canvas direction ID {id} is invalid") + } +} + +impl uDisplay for FromSysCanvasDirectionError { + fn fmt(&self, f: &mut ufmt::Formatter<'_, W>) -> Result<(), W::Error> + where + W: uWrite + ?Sized, + { + let Self::Invalid(id) = self; + uwrite!(f, "canvas direction ID {} is invalid", id) + } +} + +impl_std_error!(FromSysCanvasDirectionError); diff --git a/crates/flipperzero/src/gui/canvas/color.rs b/crates/flipperzero/src/gui/canvas/color.rs new file mode 100644 index 00000000..a35ced53 --- /dev/null +++ b/crates/flipperzero/src/gui/canvas/color.rs @@ -0,0 +1,59 @@ +use crate::internals::macros::impl_std_error; +use core::fmt::{self, Display, Formatter}; +use flipperzero_sys::{self as sys, Color as SysColor}; +use ufmt::{derive::uDebug, uDisplay, uWrite, uwrite}; + +#[derive(Copy, Clone, Debug, uDebug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub enum Color { + White, + Black, + Xor, +} + +impl TryFrom for Color { + type Error = FromSysColorError; + + fn try_from(value: SysColor) -> Result { + Ok(match value { + sys::Color_ColorWhite => Self::White, + sys::Color_ColorBlack => Self::Black, + sys::Color_ColorXOR => Self::Xor, + invalid => Err(Self::Error::Invalid(invalid))?, + }) + } +} + +impl From for SysColor { + fn from(value: Color) -> Self { + match value { + Color::White => sys::Color_ColorWhite, + Color::Black => sys::Color_ColorBlack, + Color::Xor => sys::Color_ColorXOR, + } + } +} + +#[non_exhaustive] +#[derive(Copy, Clone, Debug, uDebug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub enum FromSysColorError { + Invalid(SysColor), +} + +impl Display for FromSysColorError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let Self::Invalid(id) = self; + write!(f, "color ID {id} is invalid") + } +} + +impl uDisplay for FromSysColorError { + fn fmt(&self, f: &mut ufmt::Formatter<'_, W>) -> Result<(), W::Error> + where + W: uWrite + ?Sized, + { + let Self::Invalid(id) = self; + uwrite!(f, "color ID {} is invalid", id) + } +} + +impl_std_error!(FromSysColorError); diff --git a/crates/flipperzero/src/gui/canvas/font.rs b/crates/flipperzero/src/gui/canvas/font.rs new file mode 100644 index 00000000..8ce37ad4 --- /dev/null +++ b/crates/flipperzero/src/gui/canvas/font.rs @@ -0,0 +1,90 @@ +use crate::internals::macros::impl_std_error; +use core::fmt::{self, Display, Formatter}; +use flipperzero_sys::{self as sys, Font as SysFont}; +use ufmt::{derive::uDebug, uDisplay, uWrite, uwrite}; + +#[derive(Copy, Clone, Debug, uDebug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub enum Font { + Primary, + Secondary, + Keyboard, + BigNumbers, +} + +impl Font { + /// Gets the total number of available fonts. + /// + /// # Example + /// + /// ``` + /// # use flipperzero::gui::canvas::Font; + /// assert_eq!(Font::total_number(), 4); + /// ``` + pub const fn total_number() -> usize { + sys::Font_FontTotalNumber as usize + } +} + +impl TryFrom for Font { + type Error = FromSysFontError; + + fn try_from(value: SysFont) -> Result { + Ok(match value { + sys::Font_FontPrimary => Self::Primary, + sys::Font_FontSecondary => Self::Secondary, + sys::Font_FontKeyboard => Self::Keyboard, + sys::Font_FontBigNumbers => Self::BigNumbers, + sys::Font_FontTotalNumber => Err(Self::Error::TotalNumber)?, + invalid => Err(Self::Error::Invalid(invalid))?, + }) + } +} + +impl From for SysFont { + fn from(value: Font) -> Self { + match value { + Font::Primary => sys::Font_FontPrimary, + Font::Secondary => sys::Font_FontSecondary, + Font::Keyboard => sys::Font_FontKeyboard, + Font::BigNumbers => sys::Font_FontBigNumbers, + } + } +} + +#[non_exhaustive] +#[derive(Copy, Clone, Debug, uDebug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub enum FromSysFontError { + TotalNumber, + Invalid(SysFont), +} + +impl Display for FromSysFontError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Self::TotalNumber => write!( + f, + "font ID {} (TotalNumber) is a meta-value", + sys::Font_FontTotalNumber, + ), + Self::Invalid(id) => write!(f, "font ID {id} is invalid"), + } + } +} + +impl uDisplay for FromSysFontError { + fn fmt(&self, f: &mut ufmt::Formatter<'_, W>) -> Result<(), W::Error> + where + W: uWrite + ?Sized, + { + match self { + Self::TotalNumber => uwrite!( + f, + "font ID {} (TotalNumber) is a meta-value", + sys::Font_FontTotalNumber, + ), + Self::Invalid(id) => uwrite!(f, "font ID {} is invalid", id), + } + } +} + +impl_std_error!(FromSysFontError); diff --git a/crates/flipperzero/src/gui/canvas/font_parameters.rs b/crates/flipperzero/src/gui/canvas/font_parameters.rs new file mode 100644 index 00000000..ffd1e85d --- /dev/null +++ b/crates/flipperzero/src/gui/canvas/font_parameters.rs @@ -0,0 +1,78 @@ +use crate::internals::macros::impl_std_error; +use core::{ + fmt::{self, Display, Formatter}, + num::NonZeroU8, +}; +use flipperzero_sys::CanvasFontParameters as SysCanvasFontParameters; +use ufmt::{derive::uDebug, uDisplay, uWrite}; + +#[derive(Copy, Clone, Debug, uDebug, Eq, PartialEq, Hash)] +pub struct CanvasFontParametersSnapshot { + pub leading_default: NonZeroU8, + pub leading_min: NonZeroU8, + pub height: NonZeroU8, + pub descender: u8, +} + +impl TryFrom for CanvasFontParametersSnapshot { + type Error = FromSysCanvasFontParameters; + + fn try_from(value: SysCanvasFontParameters) -> Result { + Ok(Self { + leading_default: value + .leading_default + .try_into() + .or(Err(Self::Error::ZeroLeadingDefault))?, + leading_min: value + .leading_min + .try_into() + .or(Err(Self::Error::ZeroLeadingMin))?, + height: value.height.try_into().or(Err(Self::Error::ZeroHeight))?, + descender: value.descender, + }) + } +} + +impl From for SysCanvasFontParameters { + fn from(value: CanvasFontParametersSnapshot) -> Self { + Self { + leading_default: value.leading_default.into(), + leading_min: value.leading_min.into(), + height: value.height.into(), + descender: value.descender, + } + } +} + +#[non_exhaustive] +#[derive(Copy, Clone, Debug, uDebug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub enum FromSysCanvasFontParameters { + ZeroLeadingDefault, + ZeroLeadingMin, + ZeroHeight, +} + +impl Display for FromSysCanvasFontParameters { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_str(match self { + FromSysCanvasFontParameters::ZeroLeadingDefault => "leading_default is zero", + FromSysCanvasFontParameters::ZeroLeadingMin => "leading_min is zero", + FromSysCanvasFontParameters::ZeroHeight => "height is zero", + }) + } +} + +impl uDisplay for FromSysCanvasFontParameters { + fn fmt(&self, f: &mut ufmt::Formatter<'_, W>) -> Result<(), W::Error> + where + W: uWrite + ?Sized, + { + f.write_str(match self { + FromSysCanvasFontParameters::ZeroLeadingDefault => "leading_default is zero", + FromSysCanvasFontParameters::ZeroLeadingMin => "leading_min is zero", + FromSysCanvasFontParameters::ZeroHeight => "height is zero", + }) + } +} + +impl_std_error!(FromSysCanvasFontParameters); diff --git a/crates/flipperzero/src/gui/gui_layer.rs b/crates/flipperzero/src/gui/gui_layer.rs new file mode 100644 index 00000000..bbe8ea69 --- /dev/null +++ b/crates/flipperzero/src/gui/gui_layer.rs @@ -0,0 +1,78 @@ +use crate::internals::macros::impl_std_error; +use core::fmt::{self, Display, Formatter}; +use flipperzero_sys::{self as sys, GuiLayer as SysGuiLayer}; +use ufmt::{derive::uDebug, uDisplay, uWrite, uwrite}; + +#[derive(Copy, Clone, Debug, uDebug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub enum GuiLayer { + Desktop, + Window, + StatusBarLeft, + StatusBarRight, + Fullscreen, +} + +impl TryFrom for GuiLayer { + type Error = FromSysGuiLayerError; + + fn try_from(value: SysGuiLayer) -> Result { + Ok(match value { + sys::GuiLayer_GuiLayerDesktop => Self::Desktop, + sys::GuiLayer_GuiLayerWindow => Self::Window, + sys::GuiLayer_GuiLayerStatusBarLeft => Self::StatusBarLeft, + sys::GuiLayer_GuiLayerStatusBarRight => Self::StatusBarRight, + sys::GuiLayer_GuiLayerFullscreen => Self::Fullscreen, + sys::GuiLayer_GuiLayerMAX => Err(Self::Error::Max)?, + invalid => Err(Self::Error::Invalid(invalid))?, + }) + } +} + +impl From for SysGuiLayer { + fn from(value: GuiLayer) -> Self { + match value { + GuiLayer::Desktop => sys::GuiLayer_GuiLayerDesktop, + GuiLayer::Window => sys::GuiLayer_GuiLayerWindow, + GuiLayer::StatusBarLeft => sys::GuiLayer_GuiLayerStatusBarLeft, + GuiLayer::StatusBarRight => sys::GuiLayer_GuiLayerStatusBarRight, + GuiLayer::Fullscreen => sys::GuiLayer_GuiLayerFullscreen, + } + } +} + +#[derive(Copy, Clone, Debug, uDebug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub enum FromSysGuiLayerError { + Max, + Invalid(SysGuiLayer), +} + +impl Display for FromSysGuiLayerError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Self::Max => write!( + f, + "gui layer ID {} (MAX) is a meta-value", + sys::GuiLayer_GuiLayerMAX, + ), + Self::Invalid(id) => write!(f, "gui layer ID {id} is invalid"), + } + } +} + +impl uDisplay for FromSysGuiLayerError { + fn fmt(&self, f: &mut ufmt::Formatter<'_, W>) -> Result<(), W::Error> + where + W: uWrite + ?Sized, + { + match self { + Self::Max => uwrite!( + f, + "gui layer ID {} (MAX) is a meta-value", + sys::GuiLayer_GuiLayerMAX, + ), + Self::Invalid(id) => uwrite!(f, "gui layer ID {} is invalid", id), + } + } +} + +impl_std_error!(FromSysGuiLayerError); diff --git a/crates/flipperzero/src/gui/mod.rs b/crates/flipperzero/src/gui/mod.rs index 47185093..f7be385a 100644 --- a/crates/flipperzero/src/gui/mod.rs +++ b/crates/flipperzero/src/gui/mod.rs @@ -1,5 +1,7 @@ //! GUI service. +mod gui_layer; + pub mod canvas; pub mod icon; pub mod icon_animation; @@ -15,12 +17,11 @@ use crate::{ }; use core::{ ffi::c_char, - fmt::Debug, ops::{Deref, DerefMut}, }; -use flipperzero_sys::{ - self as sys, furi::UnsafeRecord, Canvas as SysCanvas, Gui as SysGui, GuiLayer as SysGuiLayer, -}; +use flipperzero_sys::{self as sys, furi::UnsafeRecord, Canvas as SysCanvas, Gui as SysGui}; + +pub use gui_layer::*; /// System Gui wrapper. pub struct Gui { @@ -148,49 +149,6 @@ impl Drop for GuiViewPort<'_, VPC> { } } -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] -pub enum GuiLayer { - Desktop, - Window, - StatusBarLeft, - StatusBarRight, - Fullscreen, -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] -pub enum FromSysGuiLayerError { - Max, - Invalid(SysGuiLayer), -} - -impl TryFrom for GuiLayer { - type Error = FromSysGuiLayerError; - - fn try_from(value: SysGuiLayer) -> Result { - Ok(match value { - sys::GuiLayer_GuiLayerDesktop => Self::Desktop, - sys::GuiLayer_GuiLayerWindow => Self::Window, - sys::GuiLayer_GuiLayerStatusBarLeft => Self::StatusBarLeft, - sys::GuiLayer_GuiLayerStatusBarRight => Self::StatusBarRight, - sys::GuiLayer_GuiLayerFullscreen => Self::Fullscreen, - sys::GuiLayer_GuiLayerMAX => Err(Self::Error::Max)?, - invalid => Err(Self::Error::Invalid(invalid))?, - }) - } -} - -impl From for SysGuiLayer { - fn from(value: GuiLayer) -> Self { - match value { - GuiLayer::Desktop => sys::GuiLayer_GuiLayerDesktop, - GuiLayer::Window => sys::GuiLayer_GuiLayerWindow, - GuiLayer::StatusBarLeft => sys::GuiLayer_GuiLayerStatusBarLeft, - GuiLayer::StatusBarRight => sys::GuiLayer_GuiLayerStatusBarRight, - GuiLayer::Fullscreen => sys::GuiLayer_GuiLayerFullscreen, - } - } -} - pub trait GuiCallbacks { fn on_draw(&mut self, _canvas: *mut SysCanvas) {} fn on_input(&mut self, _event: InputEvent) {} diff --git a/crates/flipperzero/src/gui/view_port.rs b/crates/flipperzero/src/gui/view_port.rs index 19f5cb4c..feee3692 100644 --- a/crates/flipperzero/src/gui/view_port.rs +++ b/crates/flipperzero/src/gui/view_port.rs @@ -1,5 +1,7 @@ //! ViewPort APIs +mod orientation; + use crate::{gui::canvas::CanvasView, input::InputEvent}; use alloc::boxed::Box; use core::{ @@ -12,6 +14,8 @@ use flipperzero_sys::{ ViewPortOrientation as SysViewPortOrientation, }; +pub use orientation::*; + /// System ViewPort. pub struct ViewPort { raw: NonNull, @@ -352,52 +356,6 @@ impl Drop for ViewPort { } } -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] -pub enum ViewPortOrientation { - Horizontal, - HorizontalFlip, - Vertical, - VerticalFlip, -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] -pub enum FromSysViewPortOrientationError { - Max, - Invalid(SysViewPortOrientation), -} - -impl TryFrom for ViewPortOrientation { - type Error = FromSysViewPortOrientationError; - - fn try_from(value: SysViewPortOrientation) -> Result { - Ok(match value { - sys::ViewPortOrientation_ViewPortOrientationHorizontal => Self::Horizontal, - sys::ViewPortOrientation_ViewPortOrientationHorizontalFlip => Self::HorizontalFlip, - sys::ViewPortOrientation_ViewPortOrientationVertical => Self::Vertical, - sys::ViewPortOrientation_ViewPortOrientationVerticalFlip => Self::VerticalFlip, - sys::ViewPortOrientation_ViewPortOrientationMAX => Err(Self::Error::Max)?, - invalid => Err(Self::Error::Invalid(invalid))?, - }) - } -} - -impl From for SysViewPortOrientation { - fn from(value: ViewPortOrientation) -> Self { - match value { - ViewPortOrientation::Horizontal => { - sys::ViewPortOrientation_ViewPortOrientationHorizontal - } - ViewPortOrientation::HorizontalFlip => { - sys::ViewPortOrientation_ViewPortOrientationHorizontalFlip - } - ViewPortOrientation::Vertical => sys::ViewPortOrientation_ViewPortOrientationVertical, - ViewPortOrientation::VerticalFlip => { - sys::ViewPortOrientation_ViewPortOrientationVerticalFlip - } - } - } -} - pub trait ViewPortCallbacks { fn on_draw(&mut self, _canvas: CanvasView) {} fn on_input(&mut self, _event: InputEvent) {} diff --git a/crates/flipperzero/src/gui/view_port/orientation.rs b/crates/flipperzero/src/gui/view_port/orientation.rs new file mode 100644 index 00000000..f06a6233 --- /dev/null +++ b/crates/flipperzero/src/gui/view_port/orientation.rs @@ -0,0 +1,82 @@ +use crate::internals::macros::impl_std_error; +use core::fmt::{self, Display, Formatter}; +use flipperzero_sys::{self as sys, ViewPortOrientation as SysViewPortOrientation}; +use ufmt::{derive::uDebug, uDisplay, uWrite, uwrite}; + +#[derive(Copy, Clone, Debug, uDebug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub enum ViewPortOrientation { + Horizontal, + HorizontalFlip, + Vertical, + VerticalFlip, +} + +impl TryFrom for ViewPortOrientation { + type Error = FromSysViewPortOrientationError; + + fn try_from(value: SysViewPortOrientation) -> Result { + Ok(match value { + sys::ViewPortOrientation_ViewPortOrientationHorizontal => Self::Horizontal, + sys::ViewPortOrientation_ViewPortOrientationHorizontalFlip => Self::HorizontalFlip, + sys::ViewPortOrientation_ViewPortOrientationVertical => Self::Vertical, + sys::ViewPortOrientation_ViewPortOrientationVerticalFlip => Self::VerticalFlip, + sys::ViewPortOrientation_ViewPortOrientationMAX => Err(Self::Error::Max)?, + invalid => Err(Self::Error::Invalid(invalid))?, + }) + } +} + +impl From for SysViewPortOrientation { + fn from(value: ViewPortOrientation) -> Self { + match value { + ViewPortOrientation::Horizontal => { + sys::ViewPortOrientation_ViewPortOrientationHorizontal + } + ViewPortOrientation::HorizontalFlip => { + sys::ViewPortOrientation_ViewPortOrientationHorizontalFlip + } + ViewPortOrientation::Vertical => sys::ViewPortOrientation_ViewPortOrientationVertical, + ViewPortOrientation::VerticalFlip => { + sys::ViewPortOrientation_ViewPortOrientationVerticalFlip + } + } + } +} + +#[non_exhaustive] +#[derive(Copy, Clone, Debug, uDebug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub enum FromSysViewPortOrientationError { + Max, + Invalid(SysViewPortOrientation), +} + +impl Display for FromSysViewPortOrientationError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Self::Max => write!( + f, + "view port orientation ID {} (MAX) is a meta-value", + sys::GuiLayer_GuiLayerMAX, + ), + Self::Invalid(id) => write!(f, "view port orientation ID {id} is invalid"), + } + } +} + +impl uDisplay for FromSysViewPortOrientationError { + fn fmt(&self, f: &mut ufmt::Formatter<'_, W>) -> Result<(), W::Error> + where + W: uWrite + ?Sized, + { + match self { + Self::Max => uwrite!( + f, + "view port orientation ID {} (MAX) is a meta-value", + sys::GuiLayer_GuiLayerMAX, + ), + Self::Invalid(id) => uwrite!(f, "view port orientation ID {} is invalid", id), + } + } +} + +impl_std_error!(FromSysViewPortOrientationError); diff --git a/crates/flipperzero/src/input/key.rs b/crates/flipperzero/src/input/key.rs new file mode 100644 index 00000000..83ef59f4 --- /dev/null +++ b/crates/flipperzero/src/input/key.rs @@ -0,0 +1,91 @@ +use crate::internals::macros::impl_std_error; +use core::ffi::CStr; +use core::fmt::{self, Display, Formatter}; +use flipperzero_sys::{self as sys, InputKey as SysInputKey}; +use ufmt::{derive::uDebug, uDisplay, uWrite, uwrite}; + +#[derive(Copy, Clone, Debug, uDebug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub enum InputKey { + Up, + Down, + Right, + Left, + Ok, + Back, +} + +impl InputKey { + pub fn name(self) -> &'static CStr { + let this = SysInputKey::from(self); + // SAFETY: `this` is always a valid enum value + // and the returned string is a static string + unsafe { CStr::from_ptr(sys::input_get_key_name(this)) } + } +} + +impl TryFrom for InputKey { + type Error = FromSysInputKeyError; + + fn try_from(value: SysInputKey) -> Result { + Ok(match value { + sys::InputKey_InputKeyUp => Self::Up, + sys::InputKey_InputKeyDown => Self::Down, + sys::InputKey_InputKeyRight => Self::Right, + sys::InputKey_InputKeyLeft => Self::Left, + sys::InputKey_InputKeyOk => Self::Ok, + sys::InputKey_InputKeyBack => Self::Back, + sys::InputKey_InputKeyMAX => Err(Self::Error::Max)?, + invalid => Err(Self::Error::Invalid(invalid))?, + }) + } +} + +impl From for SysInputKey { + fn from(value: InputKey) -> Self { + match value { + InputKey::Up => sys::InputKey_InputKeyUp, + InputKey::Down => sys::InputKey_InputKeyDown, + InputKey::Right => sys::InputKey_InputKeyRight, + InputKey::Left => sys::InputKey_InputKeyLeft, + InputKey::Ok => sys::InputKey_InputKeyOk, + InputKey::Back => sys::InputKey_InputKeyBack, + } + } +} + +#[derive(Copy, Clone, Debug, uDebug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub enum FromSysInputKeyError { + Max, + Invalid(SysInputKey), +} + +impl Display for FromSysInputKeyError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Self::Max => write!( + f, + "input key ID {} (Max) is a meta-value", + sys::Font_FontTotalNumber, + ), + Self::Invalid(id) => write!(f, "input key ID {id} is invalid"), + } + } +} + +impl uDisplay for FromSysInputKeyError { + fn fmt(&self, f: &mut ufmt::Formatter<'_, W>) -> Result<(), W::Error> + where + W: uWrite + ?Sized, + { + match self { + Self::Max => uwrite!( + f, + "input key ID {} (Max) is a meta-value", + sys::Font_FontTotalNumber, + ), + Self::Invalid(id) => uwrite!(f, "input key ID {} is invalid", id), + } + } +} + +impl_std_error!(FromSysInputKeyError); diff --git a/crates/flipperzero/src/input/mod.rs b/crates/flipperzero/src/input/mod.rs index 0acc4fdf..3042cbf2 100644 --- a/crates/flipperzero/src/input/mod.rs +++ b/crates/flipperzero/src/input/mod.rs @@ -1,10 +1,13 @@ -use core::ffi::CStr; -use flipperzero_sys::{ - self as sys, InputEvent as SysInputEvent, InputKey as SysInputKey, InputType as SysInputType, -}; +mod key; +mod r#type; + +use flipperzero_sys::{self as sys, InputEvent as SysInputEvent}; // public type alias for an anonymous union pub use sys::InputEvent__bindgen_ty_1 as SysInputEventSequence; +pub use key::*; +pub use r#type::*; + #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] pub struct InputEvent { pub sequence: InputEventSequence, @@ -91,110 +94,3 @@ impl From for FromSysInputEventError { Self::InvalidType(value) } } - -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] -pub enum InputType { - Press, - Release, - Short, - Long, - Repeat, -} - -impl InputType { - pub fn name(self) -> &'static CStr { - let this = SysInputType::from(self); - // SAFETY: `this` is always a valid enum value - // and the returned string is a static string - unsafe { CStr::from_ptr(sys::input_get_type_name(this)) } - } -} - -impl TryFrom for InputType { - type Error = FromSysInputTypeError; - - fn try_from(value: SysInputType) -> Result { - Ok(match value { - sys::InputType_InputTypePress => Self::Press, - sys::InputType_InputTypeRelease => Self::Release, - sys::InputType_InputTypeShort => Self::Short, - sys::InputType_InputTypeLong => Self::Long, - sys::InputType_InputTypeRepeat => Self::Repeat, - sys::InputType_InputTypeMAX => Err(Self::Error::Max)?, - invalid => Err(Self::Error::Invalid(invalid))?, - }) - } -} - -impl From for SysInputType { - fn from(value: InputType) -> Self { - match value { - InputType::Press => sys::InputType_InputTypePress, - InputType::Release => sys::InputType_InputTypeRelease, - InputType::Short => sys::InputType_InputTypeShort, - InputType::Long => sys::InputType_InputTypeLong, - InputType::Repeat => sys::InputType_InputTypeRepeat, - } - } -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] -pub enum FromSysInputTypeError { - Max, - Invalid(SysInputType), -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] -pub enum InputKey { - Up, - Down, - Right, - Left, - Ok, - Back, -} - -impl InputKey { - pub fn name(self) -> &'static CStr { - let this = SysInputKey::from(self); - // SAFETY: `this` is always a valid enum value - // and the returned string is a static string - unsafe { CStr::from_ptr(sys::input_get_key_name(this)) } - } -} - -impl TryFrom for InputKey { - type Error = FromSysInputKeyError; - - fn try_from(value: SysInputKey) -> Result { - Ok(match value { - sys::InputKey_InputKeyUp => Self::Up, - sys::InputKey_InputKeyDown => Self::Down, - sys::InputKey_InputKeyRight => Self::Right, - sys::InputKey_InputKeyLeft => Self::Left, - sys::InputKey_InputKeyOk => Self::Ok, - sys::InputKey_InputKeyBack => Self::Back, - sys::InputKey_InputKeyMAX => Err(Self::Error::Max)?, - invalid => Err(Self::Error::Invalid(invalid))?, - }) - } -} - -impl From for SysInputKey { - fn from(value: InputKey) -> Self { - match value { - InputKey::Up => sys::InputKey_InputKeyUp, - InputKey::Down => sys::InputKey_InputKeyDown, - InputKey::Right => sys::InputKey_InputKeyRight, - InputKey::Left => sys::InputKey_InputKeyLeft, - InputKey::Ok => sys::InputKey_InputKeyOk, - InputKey::Back => sys::InputKey_InputKeyBack, - } - } -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] -pub enum FromSysInputKeyError { - Max, - Invalid(SysInputKey), -} diff --git a/crates/flipperzero/src/input/type.rs b/crates/flipperzero/src/input/type.rs new file mode 100644 index 00000000..70b544a3 --- /dev/null +++ b/crates/flipperzero/src/input/type.rs @@ -0,0 +1,88 @@ +use crate::internals::macros::impl_std_error; +use core::ffi::CStr; +use core::fmt::{self, Display, Formatter}; +use flipperzero_sys::{self as sys, InputType as SysInputType}; +use ufmt::{derive::uDebug, uDisplay, uWrite, uwrite}; + +#[derive(Copy, Clone, Debug, uDebug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub enum InputType { + Press, + Release, + Short, + Long, + Repeat, +} + +impl InputType { + pub fn name(self) -> &'static CStr { + let this = SysInputType::from(self); + // SAFETY: `this` is always a valid enum value + // and the returned string is a static string + unsafe { CStr::from_ptr(sys::input_get_type_name(this)) } + } +} + +impl TryFrom for InputType { + type Error = FromSysInputTypeError; + + fn try_from(value: SysInputType) -> Result { + Ok(match value { + sys::InputType_InputTypePress => Self::Press, + sys::InputType_InputTypeRelease => Self::Release, + sys::InputType_InputTypeShort => Self::Short, + sys::InputType_InputTypeLong => Self::Long, + sys::InputType_InputTypeRepeat => Self::Repeat, + sys::InputType_InputTypeMAX => Err(Self::Error::Max)?, + invalid => Err(Self::Error::Invalid(invalid))?, + }) + } +} + +impl From for SysInputType { + fn from(value: InputType) -> Self { + match value { + InputType::Press => sys::InputType_InputTypePress, + InputType::Release => sys::InputType_InputTypeRelease, + InputType::Short => sys::InputType_InputTypeShort, + InputType::Long => sys::InputType_InputTypeLong, + InputType::Repeat => sys::InputType_InputTypeRepeat, + } + } +} + +#[derive(Copy, Clone, Debug, uDebug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub enum FromSysInputTypeError { + Max, + Invalid(SysInputType), +} + +impl Display for FromSysInputTypeError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Self::Max => write!( + f, + "input key ID {} (Max) is a meta-value", + sys::Font_FontTotalNumber, + ), + Self::Invalid(id) => write!(f, "input key ID {id} is invalid"), + } + } +} + +impl uDisplay for FromSysInputTypeError { + fn fmt(&self, f: &mut ufmt::Formatter<'_, W>) -> Result<(), W::Error> + where + W: uWrite + ?Sized, + { + match self { + Self::Max => uwrite!( + f, + "input key ID {} (Max) is a meta-value", + sys::Font_FontTotalNumber, + ), + Self::Invalid(id) => uwrite!(f, "input key ID {} is invalid", id), + } + } +} + +impl_std_error!(FromSysInputTypeError); diff --git a/crates/flipperzero/src/internals.rs b/crates/flipperzero/src/internals.rs index 842286ae..fb8c5aca 100644 --- a/crates/flipperzero/src/internals.rs +++ b/crates/flipperzero/src/internals.rs @@ -85,3 +85,15 @@ pub(crate) mod ops { } } } + +pub(crate) mod macros { + macro_rules! impl_std_error { + ($error_type:ident) => { + #[cfg(feature = "std")] + #[cfg_attr(docsrs, doc(cfg(feature = "std")))] + impl std::error::Error for $error_type {} + }; + } + + pub(crate) use impl_std_error; +} diff --git a/crates/flipperzero/src/io.rs b/crates/flipperzero/src/io.rs index b702fc72..86978743 100644 --- a/crates/flipperzero/src/io.rs +++ b/crates/flipperzero/src/io.rs @@ -1,6 +1,7 @@ use core::ffi::CStr; use core::fmt; +use crate::internals::macros::impl_std_error; use flipperzero_sys as sys; /// Stream and file system related error kinds. @@ -97,6 +98,8 @@ impl ufmt::uDisplay for Error { } } +impl_std_error!(LockError); + /// Trait comparable to `std::Read` for the Flipper Zero API pub trait Read { /// Reads some bytes from this source into the given buffer, returning how many bytes diff --git a/crates/flipperzero/src/kernel.rs b/crates/flipperzero/src/kernel.rs index 2789c07c..2bcf2fd3 100644 --- a/crates/flipperzero/src/kernel.rs +++ b/crates/flipperzero/src/kernel.rs @@ -1,4 +1,4 @@ -use crate::internals::Unsend; +use crate::internals::{macros::impl_std_error, Unsend}; use core::{ fmt::{self, Display, Formatter}, marker::PhantomData, @@ -72,3 +72,5 @@ impl Display for LockError { } } } + +impl_std_error!(LockError); diff --git a/crates/flipperzero/src/xbm.rs b/crates/flipperzero/src/xbm.rs index b776cd2c..283c5034 100644 --- a/crates/flipperzero/src/xbm.rs +++ b/crates/flipperzero/src/xbm.rs @@ -182,7 +182,7 @@ impl XbmImage<&'static [u8]> { /// Basic usage: /// /// ```rust - /// # use flipperzero::gui::xbm::XbmImage; + /// # use flipperzero::xbm::XbmImage; /// const IMAGE: XbmImage<&'static [u8]> = XbmImage::new_from_static(4, 4, &[0xFE, 0x12]); /// ``` pub const fn new_from_static(width: u8, height: u8, data: &'static [u8]) -> Self { @@ -209,7 +209,7 @@ impl XbmImage> { /// Basic usage: /// /// ```rust - /// # use flipperzero::gui::xbm::{XbmImage, ByteArray}; + /// # use flipperzero::xbm::{XbmImage, ByteArray}; /// const IMAGE: XbmImage> = XbmImage::new_from_array::<4, 4>([0xFE, 0x12]); /// ``` pub const fn new_from_array(data: [u8; SIZE]) -> Self { From 6b48e68deae1a6ccd32ad126bc2d417484b619ae Mon Sep 17 00:00:00 2001 From: Petr Portnov Date: Mon, 12 Jun 2023 23:51:43 +0300 Subject: [PATCH 43/49] docs: add documentation to error types --- crates/flipperzero/examples/gui.rs | 1 - crates/flipperzero/examples/images.rs | 2 +- .../flipperzero/examples/view_dispatcher.rs | 3 +- crates/flipperzero/src/gui/canvas/align.rs | 11 +++ .../src/gui/canvas/canvas_direction.rs | 10 +++ crates/flipperzero/src/gui/canvas/color.rs | 9 +++ crates/flipperzero/src/gui/canvas/font.rs | 13 +++ .../src/gui/canvas/font_parameters.rs | 18 ++++- .../src/gui/{canvas.rs => canvas/mod.rs} | 75 ++++++++++++----- crates/flipperzero/src/gui/gui_layer.rs | 15 ++++ crates/flipperzero/src/gui/icon.rs | 5 +- crates/flipperzero/src/gui/mod.rs | 15 +++- crates/flipperzero/src/gui/view.rs | 1 + .../gui/{view_port.rs => view_port/mod.rs} | 2 +- .../src/gui/view_port/orientation.rs | 81 +++++++++++++++++++ crates/flipperzero/src/input/key.rs | 33 +++++++- crates/flipperzero/src/input/mod.rs | 75 +++++++++++------ crates/flipperzero/src/input/type.rs | 34 +++++++- crates/flipperzero/src/internals.rs | 2 +- crates/flipperzero/src/notification/mod.rs | 3 +- 20 files changed, 346 insertions(+), 62 deletions(-) rename crates/flipperzero/src/gui/{canvas.rs => canvas/mod.rs} (86%) rename crates/flipperzero/src/gui/{view_port.rs => view_port/mod.rs} (99%) diff --git a/crates/flipperzero/examples/gui.rs b/crates/flipperzero/examples/gui.rs index a1678322..009f00b0 100644 --- a/crates/flipperzero/examples/gui.rs +++ b/crates/flipperzero/examples/gui.rs @@ -16,7 +16,6 @@ extern crate flipperzero_alloc as alloc; use alloc::{ffi::CString, string::ToString}; use core::{ffi::CStr, time::Duration}; - use flipperzero::{ furi::message_queue::MessageQueue, gui::{ diff --git a/crates/flipperzero/examples/images.rs b/crates/flipperzero/examples/images.rs index 2343ac36..74786d79 100644 --- a/crates/flipperzero/examples/images.rs +++ b/crates/flipperzero/examples/images.rs @@ -25,7 +25,7 @@ rt::manifest!(name = "Example: Images"); rt::entry!(main); // NOTE: `*mut`s are required to enforce `unsafe` since there are raw pointers involved -static mut TARGET_FRAMES: [*const u8; 1] = [include_bytes!("icons/rustacean-48x32.icon").as_ptr()]; +static TARGET_FRAMES: [*const u8; 1] = [include_bytes!("icons/rustacean-48x32.icon").as_ptr()]; static mut SYS_ICON: sys::Icon = sys::Icon { width: 48, height: 32, diff --git a/crates/flipperzero/examples/view_dispatcher.rs b/crates/flipperzero/examples/view_dispatcher.rs index 5343881e..69acb7ca 100644 --- a/crates/flipperzero/examples/view_dispatcher.rs +++ b/crates/flipperzero/examples/view_dispatcher.rs @@ -15,6 +15,7 @@ use flipperzero::furi::string::FuriString; use flipperzero::gui::Gui; use flipperzero_rt::{entry, manifest}; use flipperzero_sys as sys; +use flipperzero_sys::ViewDispatcher; manifest!(name = "Rust ViewDispatcher example"); entry!(main); @@ -26,7 +27,7 @@ enum AppView { struct App { name: [c_char; 16], - view_dispatcher: NonNull, + view_dispatcher: NonNull, widget: NonNull, text_input: NonNull, } diff --git a/crates/flipperzero/src/gui/canvas/align.rs b/crates/flipperzero/src/gui/canvas/align.rs index 98e80c55..b41fc1fa 100644 --- a/crates/flipperzero/src/gui/canvas/align.rs +++ b/crates/flipperzero/src/gui/canvas/align.rs @@ -3,12 +3,20 @@ use core::fmt::{self, Display, Formatter}; use flipperzero_sys::{self as sys, Align as SysAlign}; use ufmt::{derive::uDebug, uDisplay, uWrite, uwrite}; +/// Alignment of an object on the canvas. +/// +/// Corresponds to raw [`SysAlign`]. #[derive(Copy, Clone, Debug, uDebug, Eq, PartialEq, Hash, Ord, PartialOrd)] pub enum Align { + /// The values are aligned relative to the right. Left, + /// The values are aligned relative to the left. Right, + /// The values are aligned relative to the top. Top, + /// The values are aligned relative to the bottom. Bottom, + /// The values are aligned relative to the center. Center, } @@ -39,9 +47,12 @@ impl From for SysAlign { } } +/// An error which may occur while trying +/// to convert raw [`SysAlign`] to [`Align`]. #[non_exhaustive] #[derive(Copy, Clone, Debug, uDebug, Eq, PartialEq, Hash, Ord, PartialOrd)] pub enum FromSysAlignError { + /// The [`SysAlign`] is an invalid value. Invalid(SysAlign), } diff --git a/crates/flipperzero/src/gui/canvas/canvas_direction.rs b/crates/flipperzero/src/gui/canvas/canvas_direction.rs index 62ecc9a9..310ac52a 100644 --- a/crates/flipperzero/src/gui/canvas/canvas_direction.rs +++ b/crates/flipperzero/src/gui/canvas/canvas_direction.rs @@ -3,11 +3,18 @@ use core::fmt::{self, Display, Formatter}; use flipperzero_sys::{self as sys, CanvasDirection as SysCanvasDirection}; use ufmt::{derive::uDebug, uDisplay, uWrite, uwrite}; +/// Direction of an element on the canvas. +/// +/// Corresponds to raw [`SysCanvasDirection`]. #[derive(Copy, Clone, Debug, uDebug, Eq, PartialEq, Hash, Ord, PartialOrd)] pub enum CanvasDirection { + /// The direction is from left to right. LeftToRight, + /// The direction is from top to bottom. TopToBottom, + /// The direction is from right to left. RightToLeft, + /// The direction is from bottom to top. BottomToTop, } @@ -36,9 +43,12 @@ impl From for SysCanvasDirection { } } +/// An error which may occur while trying +/// to convert raw [`SysCanvasDirection`] to [`CanvasDirection`]. #[non_exhaustive] #[derive(Copy, Clone, Debug, uDebug, Eq, PartialEq, Hash, Ord, PartialOrd)] pub enum FromSysCanvasDirectionError { + /// The [`SysCanvasDirection`] is an invalid value. Invalid(SysCanvasDirection), } diff --git a/crates/flipperzero/src/gui/canvas/color.rs b/crates/flipperzero/src/gui/canvas/color.rs index a35ced53..f08530ed 100644 --- a/crates/flipperzero/src/gui/canvas/color.rs +++ b/crates/flipperzero/src/gui/canvas/color.rs @@ -3,10 +3,16 @@ use core::fmt::{self, Display, Formatter}; use flipperzero_sys::{self as sys, Color as SysColor}; use ufmt::{derive::uDebug, uDisplay, uWrite, uwrite}; +/// Color on the canvas. +/// +/// Corresponds to raw [`SysColor`]. #[derive(Copy, Clone, Debug, uDebug, Eq, PartialEq, Hash, Ord, PartialOrd)] pub enum Color { + /// White color is used. White, + /// Black color is used. Black, + /// The color is inverted. Xor, } @@ -33,9 +39,12 @@ impl From for SysColor { } } +/// An error which may occur while trying +/// to convert raw [`SysColor`] to [`Color`]. #[non_exhaustive] #[derive(Copy, Clone, Debug, uDebug, Eq, PartialEq, Hash, Ord, PartialOrd)] pub enum FromSysColorError { + /// The [`SysColor`] is an invalid value. Invalid(SysColor), } diff --git a/crates/flipperzero/src/gui/canvas/font.rs b/crates/flipperzero/src/gui/canvas/font.rs index 8ce37ad4..b9ac878a 100644 --- a/crates/flipperzero/src/gui/canvas/font.rs +++ b/crates/flipperzero/src/gui/canvas/font.rs @@ -3,11 +3,18 @@ use core::fmt::{self, Display, Formatter}; use flipperzero_sys::{self as sys, Font as SysFont}; use ufmt::{derive::uDebug, uDisplay, uWrite, uwrite}; +/// The font used to draw text. +/// +/// Corresponds to raw [`SysFont`]. #[derive(Copy, Clone, Debug, uDebug, Eq, PartialEq, Hash, Ord, PartialOrd)] pub enum Font { + /// The primary font. Primary, + /// The secondary font. Secondary, + /// The keyboard font. Keyboard, + /// The font with big numbers. BigNumbers, } @@ -51,10 +58,16 @@ impl From for SysFont { } } +/// An error which may occur while trying +/// to convert raw [`SysFont`] to [`Font`]. #[non_exhaustive] #[derive(Copy, Clone, Debug, uDebug, Eq, PartialEq, Hash, Ord, PartialOrd)] pub enum FromSysFontError { + /// The [`SysFont`] is [`TotalNumber`][sys::Font_FontTotalNumber] + /// which is a meta-value used to track enum size. TotalNumber, + /// The [`SysFont`] is an invalid value + /// other than [`TotalNumber`][sys::Font_FontTotalNumber]. Invalid(SysFont), } diff --git a/crates/flipperzero/src/gui/canvas/font_parameters.rs b/crates/flipperzero/src/gui/canvas/font_parameters.rs index ffd1e85d..f5f4e4f7 100644 --- a/crates/flipperzero/src/gui/canvas/font_parameters.rs +++ b/crates/flipperzero/src/gui/canvas/font_parameters.rs @@ -6,15 +6,18 @@ use core::{ use flipperzero_sys::CanvasFontParameters as SysCanvasFontParameters; use ufmt::{derive::uDebug, uDisplay, uWrite}; +/// Font parameters on a canvas. +/// +/// Corresponds to raw [`SysCanvasFontParameters`]. #[derive(Copy, Clone, Debug, uDebug, Eq, PartialEq, Hash)] -pub struct CanvasFontParametersSnapshot { +pub struct CanvasFontParameters { pub leading_default: NonZeroU8, pub leading_min: NonZeroU8, pub height: NonZeroU8, pub descender: u8, } -impl TryFrom for CanvasFontParametersSnapshot { +impl TryFrom for CanvasFontParameters { type Error = FromSysCanvasFontParameters; fn try_from(value: SysCanvasFontParameters) -> Result { @@ -33,8 +36,8 @@ impl TryFrom for CanvasFontParametersSnapshot { } } -impl From for SysCanvasFontParameters { - fn from(value: CanvasFontParametersSnapshot) -> Self { +impl From for SysCanvasFontParameters { + fn from(value: CanvasFontParameters) -> Self { Self { leading_default: value.leading_default.into(), leading_min: value.leading_min.into(), @@ -44,11 +47,18 @@ impl From for SysCanvasFontParameters { } } +/// An error which may occur while trying +/// to convert raw [`SysCanvasFontParameters`] to [`CanvasFontParameters`]. +/// +/// All of these correspond to errors in individual parameters. #[non_exhaustive] #[derive(Copy, Clone, Debug, uDebug, Eq, PartialEq, Hash, Ord, PartialOrd)] pub enum FromSysCanvasFontParameters { + /// [`leading_default`] field is set to `0`. ZeroLeadingDefault, + /// [`leading_min`] field is set to `0`. ZeroLeadingMin, + /// [`height`] field is set to `0`. ZeroHeight, } diff --git a/crates/flipperzero/src/gui/canvas.rs b/crates/flipperzero/src/gui/canvas/mod.rs similarity index 86% rename from crates/flipperzero/src/gui/canvas.rs rename to crates/flipperzero/src/gui/canvas/mod.rs index 9a4df78d..c9d6a3db 100644 --- a/crates/flipperzero/src/gui/canvas.rs +++ b/crates/flipperzero/src/gui/canvas/mod.rs @@ -1,4 +1,4 @@ -//! Canvases. +//! Canvas-related APIs allowing to draw on it. mod align; mod canvas_direction; @@ -11,7 +11,6 @@ use crate::{ icon::Icon, icon_animation::{IconAnimation, IconAnimationCallbacks}, }, - warn, xbm::XbmImage, }; use core::{ @@ -50,10 +49,10 @@ impl CanvasView<'_> { /// Basic usage: /// /// ``` - /// use flipperzero::gui::canvas::CanvasView; - /// - /// let ptr = todo!(); - /// let canvas = unsafe { CanvasView::from_raw(ptr) }; + /// # use flipperzero::gui::canvas::CanvasView; + /// # let canvas_ptr: *mut flipperzero_sys::Canvas = todo!(); + /// // wrap a raw pointer to a canvas + /// let canvas = unsafe { CanvasView::from_raw(canvas_ptr) }; /// ``` pub unsafe fn from_raw(raw: *mut SysCanvas) -> Self { Self { @@ -63,6 +62,49 @@ impl CanvasView<'_> { } } + /// Resets canvas drawing tools configuration. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// # use flipperzero::gui::canvas::{CanvasView, Color}; + /// # let mut canvas: CanvasView<'static> = todo!(); + /// // change canvas color and use it for drawing + /// canvas.set_color(Color::Xor); + /// canvas.draw_circle(10, 10, 5); + /// // reset canvas options and use defaults for drawing + /// canvas.reset(); + /// canvas.draw_circle(20, 20, 5); + /// ``` + pub fn reset(&mut self) { + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is always valid + unsafe { sys::canvas_reset(raw) }; + } + + /// Commits canvas sending its buffer to display. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// # use flipperzero::gui::canvas::{CanvasView, Color}; + /// # let mut canvas: CanvasView<'static> = todo!(); + /// // perform some draw operations on the canvas + /// canvas.draw_frame(0, 0, 51, 51); + /// canvas.draw_circle(25, 25, 10); + /// // commit changes + /// canvas.commit(); + /// ``` + pub fn commit(&mut self) { + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is always valid + unsafe { sys::canvas_commit(raw) }; + } + // FIXME: // - canvas_reset // - canvas_commit @@ -92,7 +134,7 @@ impl CanvasView<'_> { .expect("`canvas_current_font_height` should produce a positive value") } - pub fn get_font_params(&self, font: Font) -> CanvasFontParameters<'_> { + pub fn get_font_params(&self, font: Font) -> OwnedCanvasFontParameters<'_> { let raw = self.raw.as_ptr(); // SAFETY: `raw` is always valid let font = font.into(); @@ -102,7 +144,7 @@ impl CanvasView<'_> { let raw = unsafe { sys::canvas_get_font_params(raw, font) }.cast_mut(); // SAFETY: `raw` is always a valid pointer let raw = unsafe { NonNull::new_unchecked(raw) }; - CanvasFontParameters { + OwnedCanvasFontParameters { raw, _parent: PhantomData, } @@ -213,6 +255,7 @@ impl CanvasView<'_> { } // TODO: do we need other range checks? + #[cfg(feature = "xbm")] pub fn draw_xbm(&mut self, x: u8, y: u8, xbm: &XbmImage>) { let raw = self.raw.as_ptr(); let width = xbm.width(); @@ -223,16 +266,6 @@ impl CanvasView<'_> { // SAFETY: `raw` is always valid // and `data` is always valid and does not have to outlive the view // as it is copied - if x == 2 && y == 2 { - warn!( - "Printing image at {}:{} of dims {}:{}: {:?}", - x, - y, - width, - height, - xbm.data() - ); - } unsafe { sys::canvas_draw_xbm(raw, x, y, width, height, data) }; } @@ -326,13 +359,13 @@ impl CanvasView<'_> { } } -pub struct CanvasFontParameters<'a> { +pub struct OwnedCanvasFontParameters<'a> { // this wraps an effectively const pointer thus it should never be used for weiting raw: NonNull, _parent: PhantomData<&'a CanvasView<'a>>, } -impl<'a> CanvasFontParameters<'a> { +impl<'a> OwnedCanvasFontParameters<'a> { pub fn leading_default(&self) -> NonZeroU8 { let raw = self.raw.as_ptr(); // SAFETY: `raw` is always valid and this allways outlives its parent @@ -366,7 +399,7 @@ impl<'a> CanvasFontParameters<'a> { unsafe { *raw }.descender } - pub fn snapshot(&self) -> CanvasFontParametersSnapshot { + pub fn snapshot(&self) -> CanvasFontParameters { let raw = self.raw.as_ptr(); // SAFETY: `raw` is always valid and this allways outlives its parent unsafe { *raw } diff --git a/crates/flipperzero/src/gui/gui_layer.rs b/crates/flipperzero/src/gui/gui_layer.rs index bbe8ea69..bc09607b 100644 --- a/crates/flipperzero/src/gui/gui_layer.rs +++ b/crates/flipperzero/src/gui/gui_layer.rs @@ -3,12 +3,20 @@ use core::fmt::{self, Display, Formatter}; use flipperzero_sys::{self as sys, GuiLayer as SysGuiLayer}; use ufmt::{derive::uDebug, uDisplay, uWrite, uwrite}; +/// The font used to draw text. +/// +/// Corresponds to raw [`SysGuiLayer`]. #[derive(Copy, Clone, Debug, uDebug, Eq, PartialEq, Hash, Ord, PartialOrd)] pub enum GuiLayer { + /// Desktop layer for internal use. Like fullscreen but with status bar. Desktop, + /// Window layer, status bar is shown. Window, + /// Status bar left-side layer, auto-layout. StatusBarLeft, + /// Status bar right-side layer, auto-layout StatusBarRight, + /// Fullscreen layer, no status bar. Fullscreen, } @@ -40,9 +48,16 @@ impl From for SysGuiLayer { } } +/// An error which may occur while trying +/// to convert raw [`SysGuiLayer`] to [`GuiLayer`]. +#[non_exhaustive] #[derive(Copy, Clone, Debug, uDebug, Eq, PartialEq, Hash, Ord, PartialOrd)] pub enum FromSysGuiLayerError { + /// The [`SysGuiLayer`] is [`MAX`][sys::GuiLayer_GuiLayerMAX] + /// which is a meta-value used to track enum size. Max, + /// The [`SysGuiLayer`] is an invalid value + /// other than [`MAX`][sys::GuiLayer_GuiLayerMAX]. Invalid(SysGuiLayer), } diff --git a/crates/flipperzero/src/gui/icon.rs b/crates/flipperzero/src/gui/icon.rs index aa804a77..f7316882 100644 --- a/crates/flipperzero/src/gui/icon.rs +++ b/crates/flipperzero/src/gui/icon.rs @@ -56,8 +56,9 @@ impl Icon { let (width, height) = self.get_dimensions(); let raw = self.raw.as_ptr(); - // SAFETY: `raw` is always valid - // and `width` and `height` are always in sync with data + // SAFETY: `raw` is always valid, + // `width` and `height` are always in sync with data + // and the lifetime is based on `&self`'s unsafe { XbmImage::from_raw(width, height, sys::icon_get_data(raw)) } } } diff --git a/crates/flipperzero/src/gui/mod.rs b/crates/flipperzero/src/gui/mod.rs index f7be385a..e07745e8 100644 --- a/crates/flipperzero/src/gui/mod.rs +++ b/crates/flipperzero/src/gui/mod.rs @@ -23,7 +23,7 @@ use flipperzero_sys::{self as sys, furi::UnsafeRecord, Canvas as SysCanvas, Gui pub use gui_layer::*; -/// System Gui wrapper. +/// System GUI wrapper. pub struct Gui { raw: UnsafeRecord, } @@ -32,6 +32,19 @@ impl Gui { /// Furi record corresponding to GUI. pub const RECORD: *const c_char = sys::c_string!("gui"); + /// Creates a new GUI. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// # use flipperzero::gui::{view_port::ViewPort, Gui, GuiLayer}; + /// let view_port = ViewPort::new(()); + /// // create a GUI with a view port added to it + /// let mut gui = Gui::new(); + /// let view_port = gui.add_view_port(view_port, GuiLayer::Desktop); + /// ``` pub fn new() -> Self { // SAFETY: `RECORD` is a constant let gui = unsafe { UnsafeRecord::open(Self::RECORD) }; diff --git a/crates/flipperzero/src/gui/view.rs b/crates/flipperzero/src/gui/view.rs index 501479fe..50dfc2d1 100644 --- a/crates/flipperzero/src/gui/view.rs +++ b/crates/flipperzero/src/gui/view.rs @@ -35,6 +35,7 @@ impl Drop for View { pub trait ViewCallbacks { fn on_draw(&mut self, _canvas: CanvasView) {} fn on_input(&mut self, _event: InputEvent) {} + // TODO: the remaining callbacks and actual usage of callbacks } impl ViewCallbacks for () {} diff --git a/crates/flipperzero/src/gui/view_port.rs b/crates/flipperzero/src/gui/view_port/mod.rs similarity index 99% rename from crates/flipperzero/src/gui/view_port.rs rename to crates/flipperzero/src/gui/view_port/mod.rs index feee3692..d673929c 100644 --- a/crates/flipperzero/src/gui/view_port.rs +++ b/crates/flipperzero/src/gui/view_port/mod.rs @@ -73,7 +73,7 @@ impl ViewPort { input_event: *mut sys::InputEvent, context: *mut c_void, ) { - let input_event: InputEvent = (&unsafe { *input_event }) + let input_event: InputEvent = (unsafe { *input_event }) .try_into() .expect("`input_event` should be a valid event"); diff --git a/crates/flipperzero/src/gui/view_port/orientation.rs b/crates/flipperzero/src/gui/view_port/orientation.rs index f06a6233..d9aa79a5 100644 --- a/crates/flipperzero/src/gui/view_port/orientation.rs +++ b/crates/flipperzero/src/gui/view_port/orientation.rs @@ -3,14 +3,88 @@ use core::fmt::{self, Display, Formatter}; use flipperzero_sys::{self as sys, ViewPortOrientation as SysViewPortOrientation}; use ufmt::{derive::uDebug, uDisplay, uWrite, uwrite}; +/// Orientation of a view port. +/// +/// Corresponds to raw [`SysViewPortOrientation`]. +/// +/// # Examples +/// +/// Basic +/// +/// ``` +/// # use flipperzero::gui::view_port::ViewPort; +/// # use flipperzero::log; +/// let view_port = ViewPort::new(()); +/// let orientation = view_port.get_orientation(); +/// if matches!(orientation, ViewPortOrientation::Horizontal) { +/// log!("Currently in horizontal orientation") +/// } +/// ``` #[derive(Copy, Clone, Debug, uDebug, Eq, PartialEq, Hash, Ord, PartialOrd)] pub enum ViewPortOrientation { + /// Horizontal orientation. Horizontal, + /// Flipped horizontal orientation. HorizontalFlip, + /// Vertical orientation. Vertical, + /// Flipped vertical orientation. VerticalFlip, } +impl ViewPortOrientation { + /// Checks that this orientation is horizontal. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// # use flipperzero::gui::view_port::ViewPortOrientation; + /// assert!(ViewPortOrientation::Horizontal.is_horizontal()); + /// assert!(ViewPortOrientation::HorizontalFlip.is_horizontal()); + /// assert!(!ViewPortOrientation::Vertical.is_horizontal()); + /// assert!(!ViewPortOrientation::VerticalFlip.is_horizontal()); + /// ``` + pub const fn is_horizontal(self) -> bool { + matches!(self, Self::Horizontal | Self::HorizontalFlip) + } + + /// Checks that this orientation is vertical. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// # use flipperzero::gui::view_port::ViewPortOrientation; + /// assert!(ViewPortOrientation::Vertical.is_vertical()); + /// assert!(ViewPortOrientation::VerticalFlip.is_vertical()); + /// assert!(!ViewPortOrientation::Horizontal.is_vertical()); + /// assert!(!ViewPortOrientation::HorizontalFlip.is_vertical()); + /// ``` + pub const fn is_vertical(self) -> bool { + matches!(self, Self::Vertical | Self::VerticalFlip) + } + + /// Checks that this orientation is flipped. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// # use flipperzero::gui::view_port::ViewPortOrientation; + /// assert!(ViewPortOrientation::HorizontalFlip.is_flipped()); + /// assert!(ViewPortOrientation::VerticalFlip.is_flipped()); + /// assert!(!ViewPortOrientation::Horizontal.is_flipped()); + /// assert!(!ViewPortOrientation::Vertical.is_flipped()); + /// ``` + pub const fn is_flipped(self) -> bool { + matches!(self, Self::HorizontalFlip | Self::VerticalFlip) + } +} + impl TryFrom for ViewPortOrientation { type Error = FromSysViewPortOrientationError; @@ -43,10 +117,17 @@ impl From for SysViewPortOrientation { } } +/// An error which may occur while trying +/// to convert raw [`SysViewPortOrientation`] to [`ViewPortOrientation`]. #[non_exhaustive] #[derive(Copy, Clone, Debug, uDebug, Eq, PartialEq, Hash, Ord, PartialOrd)] pub enum FromSysViewPortOrientationError { + /// The [`SysViewPortOrientation`] + /// is [`MAX`][sys::ViewPortOrientation_ViewPortOrientationMAX] + /// which is a meta-value used to track enum size. Max, + /// The [`SysViewPortOrientation`] is an invalid value + /// other than [`MAX`][sys::ViewPortOrientation_ViewPortOrientationMAX]. Invalid(SysViewPortOrientation), } diff --git a/crates/flipperzero/src/input/key.rs b/crates/flipperzero/src/input/key.rs index 83ef59f4..34b15b20 100644 --- a/crates/flipperzero/src/input/key.rs +++ b/crates/flipperzero/src/input/key.rs @@ -4,17 +4,38 @@ use core::fmt::{self, Display, Formatter}; use flipperzero_sys::{self as sys, InputKey as SysInputKey}; use ufmt::{derive::uDebug, uDisplay, uWrite, uwrite}; +/// Input key of a Flipper, i.e. its button. +/// +/// Corresponds to raw [`SysInputKey`]. #[derive(Copy, Clone, Debug, uDebug, Eq, PartialEq, Hash, Ord, PartialOrd)] pub enum InputKey { + /// **Up** key (top triangle). Up, + /// **Down** key (bottom triangle). Down, + /// **Right** key (right triangle). Right, + /// **Left** key (left triangle). Left, + /// **Ok** key (central round). Ok, + /// **Back** key (right bottom backward arrow). Back, } impl InputKey { + /// Gets the name of this input key. + /// Unlike `Debug` and `uDebug` which use Rust enu name, + /// this relies on Flipper's API intended for this purpose. + /// + /// # Example + /// + /// Basic usage: + /// + /// ``` + /// # use flipperzero::input::InputKey; + /// assert_eq!(InputKey::Up.name(), "Up"); + /// ``` pub fn name(self) -> &'static CStr { let this = SysInputKey::from(self); // SAFETY: `this` is always a valid enum value @@ -53,9 +74,15 @@ impl From for SysInputKey { } } +/// An error which may occur while trying +/// to convert raw [`SysInputKey`] to [`InputKey`]. #[derive(Copy, Clone, Debug, uDebug, Eq, PartialEq, Hash, Ord, PartialOrd)] pub enum FromSysInputKeyError { + /// The [`SysInputKey`] is [`MAX`][sys::InputKey_InputKeyMAX] + /// which is a meta-value used to track enum size. Max, + /// The [`SysInputKey`] is an invalid value + /// other than [`MAX`][sys::InputKey_InputKeyMAX]. Invalid(SysInputKey), } @@ -64,8 +91,8 @@ impl Display for FromSysInputKeyError { match self { Self::Max => write!( f, - "input key ID {} (Max) is a meta-value", - sys::Font_FontTotalNumber, + "input key ID {} (MAX) is a meta-value", + sys::InputKey_InputKeyMAX, ), Self::Invalid(id) => write!(f, "input key ID {id} is invalid"), } @@ -81,7 +108,7 @@ impl uDisplay for FromSysInputKeyError { Self::Max => uwrite!( f, "input key ID {} (Max) is a meta-value", - sys::Font_FontTotalNumber, + sys::InputKey_InputKeyMAX, ), Self::Invalid(id) => uwrite!(f, "input key ID {} is invalid", id), } diff --git a/crates/flipperzero/src/input/mod.rs b/crates/flipperzero/src/input/mod.rs index 3042cbf2..ebf56abc 100644 --- a/crates/flipperzero/src/input/mod.rs +++ b/crates/flipperzero/src/input/mod.rs @@ -2,23 +2,30 @@ mod key; mod r#type; use flipperzero_sys::{self as sys, InputEvent as SysInputEvent}; +use ufmt::derive::uDebug; // public type alias for an anonymous union pub use sys::InputEvent__bindgen_ty_1 as SysInputEventSequence; pub use key::*; pub use r#type::*; +/// Input event occurring on user actions. +/// +/// Corresponds to raw [`SysInputEvent`]. #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] pub struct InputEvent { + /// Sequence qualifying the event. pub sequence: InputEventSequence, + /// Physical key causing the event. pub key: InputKey, + /// The type of the event. pub r#type: InputType, } -impl<'a> TryFrom<&'a SysInputEvent> for InputEvent { +impl TryFrom for InputEvent { type Error = FromSysInputEventError; - fn try_from(value: &'a SysInputEvent) -> Result { + fn try_from(value: SysInputEvent) -> Result { Ok(Self { sequence: value.__bindgen_anon_1.into(), key: value.key.try_into()?, @@ -37,16 +44,56 @@ impl From for SysInputEvent { } } +/// An error which may occur while trying +/// to convert raw [`SysInputEvent`] to [`InputEvent`]. +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub enum FromSysInputEventError { + InvalidKey(FromSysInputKeyError), + InvalidType(FromSysInputTypeError), +} + +impl From for FromSysInputEventError { + fn from(value: FromSysInputKeyError) -> Self { + Self::InvalidKey(value) + } +} + +impl From for FromSysInputEventError { + fn from(value: FromSysInputTypeError) -> Self { + Self::InvalidType(value) + } +} + +/// [`InputEvent`] sequence. +/// +/// This is a transparent view over [`u32`](prim@u32) with the following representation: +/// +/// | Bits | 31..30 | 29..0 | +/// |---------|--------|--------| +/// | Payload | Source | Counter| +/// +/// Corresponds to raw [`SysInputEventSequence`]. +/// +/// # Example usage +/// +/// Decoding a raw `u32` value: +/// +/// ``` +/// use flipperzero::input::InputEventSequence; +/// let sequence = InputEventSequence::from(0b10__000000_10101010_11110000_11111111u32); +/// assert_eq!(0b10, sequence.source()); +/// assert_eq!(0b10101010_11110000_11111111, sequence.counter()); +/// ``` #[repr(transparent)] -#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)] +#[derive(Copy, Clone, Debug, uDebug, Hash, Eq, PartialEq, PartialOrd, Ord)] pub struct InputEventSequence(u32); impl InputEventSequence { - const fn source(&self) -> u8 { + pub const fn source(self) -> u8 { ((self.0 >> 30) & 0b11) as u8 } - const fn counter(&self) -> u32 { + pub const fn counter(self) -> u32 { self.0 & !(0b11 << 30) } } @@ -76,21 +123,3 @@ impl From for SysInputEventSequence { Self { sequence: value.0 } } } - -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] -pub enum FromSysInputEventError { - InvalidKey(FromSysInputKeyError), - InvalidType(FromSysInputTypeError), -} - -impl From for FromSysInputEventError { - fn from(value: FromSysInputKeyError) -> Self { - Self::InvalidKey(value) - } -} - -impl From for FromSysInputEventError { - fn from(value: FromSysInputTypeError) -> Self { - Self::InvalidType(value) - } -} diff --git a/crates/flipperzero/src/input/type.rs b/crates/flipperzero/src/input/type.rs index 70b544a3..0af6cacb 100644 --- a/crates/flipperzero/src/input/type.rs +++ b/crates/flipperzero/src/input/type.rs @@ -4,16 +4,40 @@ use core::fmt::{self, Display, Formatter}; use flipperzero_sys::{self as sys, InputType as SysInputType}; use ufmt::{derive::uDebug, uDisplay, uWrite, uwrite}; +/// Input type of a Flipper's button describing +/// the kind of action on it (physical or logical). +/// +/// Corresponds to raw [`SysInputType`]. #[derive(Copy, Clone, Debug, uDebug, Eq, PartialEq, Hash, Ord, PartialOrd)] pub enum InputType { + /// Press event, emitted after debounce. Press, + /// Release event, emitted after debounce. Release, + /// Short event, emitted after [`InputType::Release`] + /// done within `INPUT_LONG_PRESS` interval. Short, + /// Long event, emitted after `INPUT_LONG_PRESS_COUNTS` interval, + /// asynchronous to [`InputTypeRelease`]. Long, + /// Repeat event, emitted with `INPUT_LONG_PRESS_COUNTS` period + /// after [InputType::Long] event. Repeat, } impl InputType { + /// Gets the name of this input type. + /// Unlike `Debug` and `uDebug` which use Rust enu name, + /// this relies on Flipper's API intended for this purpose. + /// + /// # Example + /// + /// Basic usage: + /// + /// ``` + /// # use flipperzero::input::InputType; + /// assert_eq!(InputType::Release.name(), "Release"); + /// ``` pub fn name(self) -> &'static CStr { let this = SysInputType::from(self); // SAFETY: `this` is always a valid enum value @@ -50,9 +74,15 @@ impl From for SysInputType { } } +/// An error which may occur while trying +/// to convert raw [`SysInputType`] to [`InputType`]. #[derive(Copy, Clone, Debug, uDebug, Eq, PartialEq, Hash, Ord, PartialOrd)] pub enum FromSysInputTypeError { + /// The [`SysInputType`] is [`MAX`][sys::InputType_InputTypeMAX] + /// which is a meta-value used to track enum size. Max, + /// The [`SysInputType`] is an invalid value + /// other than [`MAX`][sys::InputType_InputTypeMAX]. Invalid(SysInputType), } @@ -62,7 +92,7 @@ impl Display for FromSysInputTypeError { Self::Max => write!( f, "input key ID {} (Max) is a meta-value", - sys::Font_FontTotalNumber, + sys::InputType_InputTypeMAX, ), Self::Invalid(id) => write!(f, "input key ID {id} is invalid"), } @@ -78,7 +108,7 @@ impl uDisplay for FromSysInputTypeError { Self::Max => uwrite!( f, "input key ID {} (Max) is a meta-value", - sys::Font_FontTotalNumber, + sys::InputType_InputTypeMAX, ), Self::Invalid(id) => uwrite!(f, "input key ID {} is invalid", id), } diff --git a/crates/flipperzero/src/internals.rs b/crates/flipperzero/src/internals.rs index fb8c5aca..cc145375 100644 --- a/crates/flipperzero/src/internals.rs +++ b/crates/flipperzero/src/internals.rs @@ -68,7 +68,7 @@ const _: () = { /// falling back to ad-hoc implementations otherwise. #[allow(dead_code)] // this functions may be unused if a specific feature set does not require them pub(crate) mod ops { - pub const fn div_ceil_u8(divident: u8, divisor: u8) -> u8 { + pub(crate) const fn div_ceil_u8(divident: u8, divisor: u8) -> u8 { #[cfg(feature = "unstable_intrinsics")] { divident.div_ceil(divisor) diff --git a/crates/flipperzero/src/notification/mod.rs b/crates/flipperzero/src/notification/mod.rs index 93e19fa8..e3e71877 100644 --- a/crates/flipperzero/src/notification/mod.rs +++ b/crates/flipperzero/src/notification/mod.rs @@ -37,7 +37,8 @@ impl NotificationService { /// Runs a notification sequence. /// - /// #Safety + /// # Safety + /// /// Due to how rust interacts with the firmware this function is not safe to use at any time /// where the application might exit directly afterwards as the rust runtime will free the /// sequence before the firmware has finished reading it. At any time where this is an issue From c06ac5afd8aec74c2eadf2ff92c750710b157156 Mon Sep 17 00:00:00 2001 From: Petr Portnov Date: Tue, 13 Jun 2023 19:02:10 +0300 Subject: [PATCH 44/49] fix: do not store `Box`es when their pointers are shared --- .../flipperzero/examples/view_dispatcher.rs | 2 +- crates/flipperzero/src/gui/canvas/mod.rs | 15 ++++++------ crates/flipperzero/src/gui/icon_animation.rs | 23 ++++++++++++------- crates/flipperzero/src/gui/mod.rs | 6 ++--- crates/flipperzero/src/gui/view.rs | 10 ++++++-- crates/flipperzero/src/gui/view_port/mod.rs | 18 +++++++++++---- crates/flipperzero/src/input/key.rs | 2 +- crates/flipperzero/src/input/type.rs | 2 +- crates/sys/src/furi.rs | 6 +---- 9 files changed, 50 insertions(+), 34 deletions(-) diff --git a/crates/flipperzero/examples/view_dispatcher.rs b/crates/flipperzero/examples/view_dispatcher.rs index 69acb7ca..3509d16a 100644 --- a/crates/flipperzero/examples/view_dispatcher.rs +++ b/crates/flipperzero/examples/view_dispatcher.rs @@ -70,7 +70,7 @@ pub unsafe extern "C" fn text_input_callback(context: *mut c_void) { } pub unsafe extern "C" fn navigation_event_callback(context: *mut c_void) -> bool { - let view_dispatcher = context as *mut sys::ViewDispatcher; + let view_dispatcher = context as *mut ViewDispatcher; sys::view_dispatcher_stop(view_dispatcher); sys::view_dispatcher_remove_view(view_dispatcher, AppView::Widget as u32); sys::view_dispatcher_remove_view(view_dispatcher, AppView::TextInput as u32); diff --git a/crates/flipperzero/src/gui/canvas/mod.rs b/crates/flipperzero/src/gui/canvas/mod.rs index c9d6a3db..7fbaf0aa 100644 --- a/crates/flipperzero/src/gui/canvas/mod.rs +++ b/crates/flipperzero/src/gui/canvas/mod.rs @@ -6,13 +6,12 @@ mod color; mod font; mod font_parameters; -use crate::{ - gui::{ - icon::Icon, - icon_animation::{IconAnimation, IconAnimationCallbacks}, - }, - xbm::XbmImage, +use crate::gui::{ + icon::Icon, + icon_animation::{IconAnimation, IconAnimationCallbacks}, }; +#[cfg(feature = "xbm")] +use crate::xbm::XbmImage; use core::{ ffi::{c_char, CStr}, marker::PhantomData, @@ -138,11 +137,11 @@ impl CanvasView<'_> { let raw = self.raw.as_ptr(); // SAFETY: `raw` is always valid let font = font.into(); - // SAFETY: `raw` is always a valid pointer + // SAFETY: `raw` is a valid pointer // and `font` is guaranteed to be a valid value by `From` implementation // `cast_mut` is required since `NonNull` can only be created froma mut-pointer let raw = unsafe { sys::canvas_get_font_params(raw, font) }.cast_mut(); - // SAFETY: `raw` is always a valid pointer + // SAFETY: `raw` is a valid pointer let raw = unsafe { NonNull::new_unchecked(raw) }; OwnedCanvasFontParameters { raw, diff --git a/crates/flipperzero/src/gui/icon_animation.rs b/crates/flipperzero/src/gui/icon_animation.rs index c9417a1f..bb718b26 100644 --- a/crates/flipperzero/src/gui/icon_animation.rs +++ b/crates/flipperzero/src/gui/icon_animation.rs @@ -10,7 +10,7 @@ use flipperzero_sys::{self as sys, IconAnimation as SysIconAnimation}; /// System Icon Animation wrapper. pub struct IconAnimation<'a, C: IconAnimationCallbacks> { raw: NonNull, - callbacks: Box, + callbacks: NonNull, _parent_lifetime: PhantomData<&'a ()>, } @@ -21,12 +21,15 @@ impl<'a, C: IconAnimationCallbacks> IconAnimation<'a, C> { // or stops the system on OOM, // `icon` is a valid pointer and `icon` outlives this animation let raw = unsafe { NonNull::new_unchecked(sys::icon_animation_alloc(icon)) }; - let callbacks = Box::new(callbacks); - - let icon_animation = Self { - raw, - callbacks, - _parent_lifetime: PhantomData, + let callbacks = Box::into_raw(Box::new(callbacks)); + + let icon_animation = { + let callbacks = unsafe { NonNull::new_unchecked(callbacks) }; + Self { + raw, + callbacks, + _parent_lifetime: PhantomData, + } }; { @@ -49,7 +52,7 @@ impl<'a, C: IconAnimationCallbacks> IconAnimation<'a, C> { ) { let raw = raw.as_ptr(); let callback = Some(dispatch_update:: as _); - let context = (&*icon_animation.callbacks as *const C).cast_mut().cast(); + let context = callbacks.cast(); // SAFETY: `raw` and `callback` are valid // and `context` is valid as the box lives with this struct @@ -105,6 +108,10 @@ impl Drop for IconAnimation<'_, C> { // SAFETY: `raw` is a valid pointer // which should have been created via `icon_animation_alloc` unsafe { sys::icon_animation_free(raw) } + + let callbacks = self.callbacks.as_ptr(); + // SAFETY: `callbacks` has been created via `Box` + let _ = unsafe { Box::from_raw(callbacks) }; } } diff --git a/crates/flipperzero/src/gui/mod.rs b/crates/flipperzero/src/gui/mod.rs index e07745e8..044c6cba 100644 --- a/crates/flipperzero/src/gui/mod.rs +++ b/crates/flipperzero/src/gui/mod.rs @@ -76,13 +76,13 @@ impl Gui { pub fn get_frame_buffer_size(&self) -> usize { let raw = self.as_raw(); - // SAFETY: `raw` is always a valid pointer + // SAFETY: `raw` is a valid pointer unsafe { sys::gui_get_framebuffer_size(raw) } } pub fn set_lockdown(&self, lockdown: bool) { let raw = self.raw.as_raw(); - // SAFETY: `raw` is always a valid pointer + // SAFETY: `raw` is a valid pointer unsafe { sys::gui_set_lockdown(raw, lockdown) } } @@ -91,7 +91,7 @@ impl Gui { pub fn direct_draw_acquire(&mut self) -> ExclusiveCanvas<'_> { let raw = self.as_raw(); - // SAFETY: `raw` is always a valid pointer + // SAFETY: `raw` is a valid pointer let canvas = unsafe { CanvasView::from_raw(sys::gui_direct_draw_acquire(raw)) }; ExclusiveCanvas { gui: self, canvas } diff --git a/crates/flipperzero/src/gui/view.rs b/crates/flipperzero/src/gui/view.rs index 50dfc2d1..133246b5 100644 --- a/crates/flipperzero/src/gui/view.rs +++ b/crates/flipperzero/src/gui/view.rs @@ -5,7 +5,7 @@ use flipperzero_sys::{self as sys, View as SysView}; pub struct View { raw: NonNull, - callbacks: Box, + callbacks: NonNull, } impl View { @@ -13,7 +13,9 @@ impl View { // SAFETY: allocation either succeeds producing a valid non-null pointer // or stops the system on OOM let raw = unsafe { NonNull::new_unchecked(sys::view_alloc()) }; - let callbacks = Box::new(callbacks); + let callbacks = Box::into_raw(Box::new(callbacks)); + // SAFETY: callbacks is a valid non-null pointer + let callbacks = unsafe { NonNull::new_unchecked(callbacks) }; Self { raw, callbacks } } @@ -29,6 +31,10 @@ impl Drop for View { let raw = self.raw.as_ptr(); // SAFETY: `raw` is always valid unsafe { sys::view_free(raw) } + + let callbacks = self.callbacks.as_ptr(); + // SAFETY: `callbacks` has been created via `Box` + let _ = unsafe { Box::from_raw(callbacks) }; } } diff --git a/crates/flipperzero/src/gui/view_port/mod.rs b/crates/flipperzero/src/gui/view_port/mod.rs index d673929c..66c51143 100644 --- a/crates/flipperzero/src/gui/view_port/mod.rs +++ b/crates/flipperzero/src/gui/view_port/mod.rs @@ -19,7 +19,7 @@ pub use orientation::*; /// System ViewPort. pub struct ViewPort { raw: NonNull, - callbacks: Box, + callbacks: NonNull, } impl ViewPort { @@ -38,9 +38,13 @@ impl ViewPort { // SAFETY: allocation either succeeds producing the valid pointer // or stops the system on OOM let raw = unsafe { NonNull::new_unchecked(sys::view_port_alloc()) }; - let callbacks = Box::new(callbacks); + let callbacks = Box::into_raw(Box::new(callbacks)); - let view_port = Self { raw, callbacks }; + let view_port = { + // SAFETY: `callbacks` has been created via `Box` + let callbacks = unsafe { NonNull::new_unchecked(callbacks) }; + Self { raw, callbacks } + }; { pub unsafe extern "C" fn dispatch_draw( @@ -60,7 +64,7 @@ impl ViewPort { C::on_draw as *const c_void, <() as ViewPortCallbacks>::on_draw as *const c_void, ) { - let context = (&*view_port.callbacks as *const C).cast_mut().cast(); + let context = callbacks.cast(); let raw = raw.as_ptr(); let callback = Some(dispatch_draw:: as _); // SAFETY: `raw` is valid @@ -87,7 +91,7 @@ impl ViewPort { C::on_input as *const c_void, <() as ViewPortCallbacks>::on_input as *const c_void, ) { - let context = (&*view_port.callbacks as *const C).cast_mut().cast(); + let context = callbacks.cast(); let raw = raw.as_ptr(); let callback = Some(dispatch_input:: as _); @@ -353,6 +357,10 @@ impl Drop for ViewPort { sys::view_port_enabled_set(raw, false); sys::view_port_free(raw); } + + let callbacks = self.callbacks.as_ptr(); + // SAFETY: `callbacks` has been created via `Box` + let _ = unsafe { Box::from_raw(callbacks) }; } } diff --git a/crates/flipperzero/src/input/key.rs b/crates/flipperzero/src/input/key.rs index 34b15b20..113974bf 100644 --- a/crates/flipperzero/src/input/key.rs +++ b/crates/flipperzero/src/input/key.rs @@ -38,7 +38,7 @@ impl InputKey { /// ``` pub fn name(self) -> &'static CStr { let this = SysInputKey::from(self); - // SAFETY: `this` is always a valid enum value + // SAFETY: `this` is a valid enum value // and the returned string is a static string unsafe { CStr::from_ptr(sys::input_get_key_name(this)) } } diff --git a/crates/flipperzero/src/input/type.rs b/crates/flipperzero/src/input/type.rs index 0af6cacb..2f72eddb 100644 --- a/crates/flipperzero/src/input/type.rs +++ b/crates/flipperzero/src/input/type.rs @@ -40,7 +40,7 @@ impl InputType { /// ``` pub fn name(self) -> &'static CStr { let this = SysInputType::from(self); - // SAFETY: `this` is always a valid enum value + // SAFETY: `this` is a valid enum value // and the returned string is a static string unsafe { CStr::from_ptr(sys::input_get_type_name(this)) } } diff --git a/crates/sys/src/furi.rs b/crates/sys/src/furi.rs index 3ddf68ad..4777cd90 100644 --- a/crates/sys/src/furi.rs +++ b/crates/sys/src/furi.rs @@ -104,11 +104,7 @@ impl UnsafeRecord { /// # Safety /// /// The caller must ensure that `record_name` lives for the - /// duration of the object lifetime. - /// - /// # Safety - /// - /// The caller must provide a valid C-string `name`. + /// duration of the object lifetime and that it is a valid C-string. pub unsafe fn open(name: *const c_char) -> Self { // SAFETY: the created pointer is guaranteed to be valid let data = unsafe { crate::furi_record_open(name) } as *mut T; From 20a6d0ec476bcde7371d6e02489cf157a4302af6 Mon Sep 17 00:00:00 2001 From: Petr Portnov Date: Fri, 23 Jun 2023 20:10:13 +0300 Subject: [PATCH 45/49] chore(#29): address initial PR review by @dcoles --- crates/Cargo.toml | 1 + crates/flipperzero/Cargo.toml | 5 +- crates/flipperzero/examples/gui.rs | 17 +- crates/flipperzero/examples/images.rs | 2 +- .../examples/{xbms => xbm_images}/ferris.rs | 0 .../examples/{xbms => xbm_images}/mod.rs | 0 crates/flipperzero/src/furi/message_queue.rs | 6 +- .../src/gui/canvas/font_parameters.rs | 6 +- crates/flipperzero/src/gui/canvas/mod.rs | 6 - crates/flipperzero/src/gui/icon_animation.rs | 21 +-- crates/flipperzero/src/gui/mod.rs | 1 + crates/flipperzero/src/gui/view.rs | 11 +- .../src/gui/view_dispatcher/mod.rs | 172 ++++++++++++++++++ crates/flipperzero/src/input/mod.rs | 8 +- crates/flipperzero/src/input/type.rs | 2 +- crates/flipperzero/src/internals.rs | 4 +- crates/flipperzero/src/kernel.rs | 29 ++- crates/flipperzero/src/lib.rs | 2 - crates/flipperzero/src/xbm.rs | 9 - 19 files changed, 244 insertions(+), 58 deletions(-) rename crates/flipperzero/examples/{xbms => xbm_images}/ferris.rs (100%) rename crates/flipperzero/examples/{xbms => xbm_images}/mod.rs (100%) create mode 100644 crates/flipperzero/src/gui/view_dispatcher/mod.rs diff --git a/crates/Cargo.toml b/crates/Cargo.toml index d3ad99e9..713ee383 100644 --- a/crates/Cargo.toml +++ b/crates/Cargo.toml @@ -23,6 +23,7 @@ flipperzero-sys = { path = "sys", version = "0.10.0" } flipperzero-rt = { path = "rt", version = "0.10.0" } flipperzero-alloc = { path = "alloc", version = "0.10.0" } flipperzero-test = { path = "test", version = "0.10.0" } +cfg-if = "1.0.0" ufmt = "0.2.0" document-features = "0.2.0" diff --git a/crates/flipperzero/Cargo.toml b/crates/flipperzero/Cargo.toml index 9e41654b..077a049f 100644 --- a/crates/flipperzero/Cargo.toml +++ b/crates/flipperzero/Cargo.toml @@ -23,6 +23,8 @@ harness = false [dependencies] flipperzero-sys.workspace = true flipperzero-test.workspace = true + +# Formatting ufmt.workspace = true # HAL wrappers @@ -71,7 +73,6 @@ all-services = [ "service-storage", "service-notification", "service-input", - "service-toolbox", "service-gui", "service-dialogs", "service-dolphin", @@ -82,8 +83,6 @@ service-storage = [] service-notification = [] ## Enables Input Service APIs. service-input = [] -## Enables Toolbox APIs. -service-toolbox = [] ## Enables GUI APIs of Flipper. service-gui = ["alloc", "service-input"] ## Enables Dialogs APIs of Flipper. diff --git a/crates/flipperzero/examples/gui.rs b/crates/flipperzero/examples/gui.rs index 009f00b0..7595dc39 100644 --- a/crates/flipperzero/examples/gui.rs +++ b/crates/flipperzero/examples/gui.rs @@ -6,7 +6,7 @@ #![no_std] #![forbid(unsafe_code)] -mod xbms; +mod xbm_images; // Required for panic handler extern crate flipperzero_rt; @@ -33,6 +33,11 @@ use flipperzero_sys::furi::Status; manifest!(name = "Rust GUI example"); entry!(main); +/// An image of an 8x8 plus. +/// +/// It is important to note that byte bits are read in reverse order +/// but since this image is symmetric we don't need to reverse the bytes +/// unlike in [`RS_IMAGE`]. const PLUS_IMAGE: XbmImage> = XbmImage::new_from_array::<8, 8>([ 0b00_11_11_00, 0b00_11_11_00, @@ -44,6 +49,7 @@ const PLUS_IMAGE: XbmImage> = XbmImage::new_from_array::<8, 8>([ 0b10_11_11_01, ]); +/// An image of an 8x8 R and S letters. const RS_IMAGE: XbmImage> = XbmImage::new_from_array::<8, 8>([ 0b11100000u8.reverse_bits(), 0b10010000u8.reverse_bits(), @@ -72,7 +78,7 @@ fn main(_args: *mut u8) -> i32 { .expect("should be a valid string"); canvas.draw_str(80, 10, bottom_text); canvas.draw_xbm(100, 50, &RS_IMAGE); - canvas.draw_xbm(0, 32, &xbms::ferris::IMAGE); + canvas.draw_xbm(0, 32, &xbm_images::ferris::IMAGE); } fn on_input(&mut self, event: InputEvent) { @@ -113,11 +119,10 @@ fn main(_args: *mut u8) -> i32 { println!("Exit pressed"); break 0; } + Err(Status::ERR_TIMEOUT) => {} // it's okay to continue polling Err(e) => { - if e != Status::ERR_TIMEOUT { - println!("ERROR while receiving event: {:?}", e); - break 1; - } + println!("ERROR while receiving event: {:?}", e); + break 1; } } }; diff --git a/crates/flipperzero/examples/images.rs b/crates/flipperzero/examples/images.rs index 74786d79..2343ac36 100644 --- a/crates/flipperzero/examples/images.rs +++ b/crates/flipperzero/examples/images.rs @@ -25,7 +25,7 @@ rt::manifest!(name = "Example: Images"); rt::entry!(main); // NOTE: `*mut`s are required to enforce `unsafe` since there are raw pointers involved -static TARGET_FRAMES: [*const u8; 1] = [include_bytes!("icons/rustacean-48x32.icon").as_ptr()]; +static mut TARGET_FRAMES: [*const u8; 1] = [include_bytes!("icons/rustacean-48x32.icon").as_ptr()]; static mut SYS_ICON: sys::Icon = sys::Icon { width: 48, height: 32, diff --git a/crates/flipperzero/examples/xbms/ferris.rs b/crates/flipperzero/examples/xbm_images/ferris.rs similarity index 100% rename from crates/flipperzero/examples/xbms/ferris.rs rename to crates/flipperzero/examples/xbm_images/ferris.rs diff --git a/crates/flipperzero/examples/xbms/mod.rs b/crates/flipperzero/examples/xbm_images/mod.rs similarity index 100% rename from crates/flipperzero/examples/xbms/mod.rs rename to crates/flipperzero/examples/xbm_images/mod.rs diff --git a/crates/flipperzero/src/furi/message_queue.rs b/crates/flipperzero/src/furi/message_queue.rs index 41d146a1..581d61f9 100644 --- a/crates/flipperzero/src/furi/message_queue.rs +++ b/crates/flipperzero/src/furi/message_queue.rs @@ -27,7 +27,7 @@ impl MessageQueue { } } - // Attempts to add the message to the end of the queue, waiting up to timeout ticks. + /// Attempts to add the message to the end of the queue, waiting up to timeout ticks. pub fn put(&self, msg: M, timeout: Duration) -> furi::Result<()> { let mut msg = core::mem::ManuallyDrop::new(msg); let timeout_ticks = duration_to_ticks(timeout); @@ -44,12 +44,13 @@ impl MessageQueue { status.err_or(()) } + /// Attempts to instantly [`put`](Self::put) the message to the end of the queue. #[inline] pub fn put_now(&self, msg: M) -> furi::Result<()> { self.put(msg, Duration::ZERO) } - // Attempts to read a message from the front of the queue within timeout ticks. + /// Attempts to read a message from the front of the queue within timeout ticks. pub fn get(&self, timeout: Duration) -> furi::Result { let timeout_ticks = duration_to_ticks(timeout); let mut out = core::mem::MaybeUninit::::uninit(); @@ -69,6 +70,7 @@ impl MessageQueue { } } + /// Attempts to instantly [`get`](Self::get) the message from the front of the queue. #[inline] pub fn get_now(&self) -> furi::Result { self.get(Duration::ZERO) diff --git a/crates/flipperzero/src/gui/canvas/font_parameters.rs b/crates/flipperzero/src/gui/canvas/font_parameters.rs index f5f4e4f7..77ff4016 100644 --- a/crates/flipperzero/src/gui/canvas/font_parameters.rs +++ b/crates/flipperzero/src/gui/canvas/font_parameters.rs @@ -54,11 +54,11 @@ impl From for SysCanvasFontParameters { #[non_exhaustive] #[derive(Copy, Clone, Debug, uDebug, Eq, PartialEq, Hash, Ord, PartialOrd)] pub enum FromSysCanvasFontParameters { - /// [`leading_default`] field is set to `0`. + /// [`SysCanvasFontParameters::leading_default`] field is set to `0`. ZeroLeadingDefault, - /// [`leading_min`] field is set to `0`. + /// [`SysCanvasFontParameters::leading_min`] field is set to `0`. ZeroLeadingMin, - /// [`height`] field is set to `0`. + /// [`SysCanvasFontParameters::height`] field is set to `0`. ZeroHeight, } diff --git a/crates/flipperzero/src/gui/canvas/mod.rs b/crates/flipperzero/src/gui/canvas/mod.rs index 7fbaf0aa..70409f6a 100644 --- a/crates/flipperzero/src/gui/canvas/mod.rs +++ b/crates/flipperzero/src/gui/canvas/mod.rs @@ -104,11 +104,6 @@ impl CanvasView<'_> { unsafe { sys::canvas_commit(raw) }; } - // FIXME: - // - canvas_reset - // - canvas_commit - // This are currently not available in bindings - pub fn width(&self) -> NonZeroU8 { let raw = self.raw.as_ptr(); // SAFETY: `raw` is always valid @@ -253,7 +248,6 @@ impl CanvasView<'_> { unsafe { sys::canvas_draw_icon(raw, x, y, icon) } } - // TODO: do we need other range checks? #[cfg(feature = "xbm")] pub fn draw_xbm(&mut self, x: u8, y: u8, xbm: &XbmImage>) { let raw = self.raw.as_ptr(); diff --git a/crates/flipperzero/src/gui/icon_animation.rs b/crates/flipperzero/src/gui/icon_animation.rs index bb718b26..dda838f5 100644 --- a/crates/flipperzero/src/gui/icon_animation.rs +++ b/crates/flipperzero/src/gui/icon_animation.rs @@ -1,4 +1,4 @@ -use crate::gui::icon::Icon; +use crate::{gui::icon::Icon, internals::alloc::BoxNonNull}; use alloc::boxed::Box; use core::{ ffi::c_void, @@ -10,7 +10,7 @@ use flipperzero_sys::{self as sys, IconAnimation as SysIconAnimation}; /// System Icon Animation wrapper. pub struct IconAnimation<'a, C: IconAnimationCallbacks> { raw: NonNull, - callbacks: NonNull, + callbacks: BoxNonNull, _parent_lifetime: PhantomData<&'a ()>, } @@ -21,15 +21,12 @@ impl<'a, C: IconAnimationCallbacks> IconAnimation<'a, C> { // or stops the system on OOM, // `icon` is a valid pointer and `icon` outlives this animation let raw = unsafe { NonNull::new_unchecked(sys::icon_animation_alloc(icon)) }; - let callbacks = Box::into_raw(Box::new(callbacks)); - - let icon_animation = { - let callbacks = unsafe { NonNull::new_unchecked(callbacks) }; - Self { - raw, - callbacks, - _parent_lifetime: PhantomData, - } + let callbacks = BoxNonNull::new(callbacks); + + let icon_animation = Self { + raw, + callbacks, + _parent_lifetime: PhantomData, }; { @@ -52,7 +49,7 @@ impl<'a, C: IconAnimationCallbacks> IconAnimation<'a, C> { ) { let raw = raw.as_ptr(); let callback = Some(dispatch_update:: as _); - let context = callbacks.cast(); + let context = icon_animation.callbacks.as_ptr().cast(); // SAFETY: `raw` and `callback` are valid // and `context` is valid as the box lives with this struct diff --git a/crates/flipperzero/src/gui/mod.rs b/crates/flipperzero/src/gui/mod.rs index 044c6cba..84195b05 100644 --- a/crates/flipperzero/src/gui/mod.rs +++ b/crates/flipperzero/src/gui/mod.rs @@ -6,6 +6,7 @@ pub mod canvas; pub mod icon; pub mod icon_animation; pub mod view; +pub mod view_dispatcher; pub mod view_port; use crate::{ diff --git a/crates/flipperzero/src/gui/view.rs b/crates/flipperzero/src/gui/view.rs index 133246b5..190ff9d5 100644 --- a/crates/flipperzero/src/gui/view.rs +++ b/crates/flipperzero/src/gui/view.rs @@ -1,3 +1,4 @@ +use crate::internals::alloc::BoxNonNull; use crate::{gui::canvas::CanvasView, input::InputEvent}; use alloc::boxed::Box; use core::ptr::NonNull; @@ -5,7 +6,7 @@ use flipperzero_sys::{self as sys, View as SysView}; pub struct View { raw: NonNull, - callbacks: NonNull, + callbacks: BoxNonNull, } impl View { @@ -13,9 +14,7 @@ impl View { // SAFETY: allocation either succeeds producing a valid non-null pointer // or stops the system on OOM let raw = unsafe { NonNull::new_unchecked(sys::view_alloc()) }; - let callbacks = Box::into_raw(Box::new(callbacks)); - // SAFETY: callbacks is a valid non-null pointer - let callbacks = unsafe { NonNull::new_unchecked(callbacks) }; + let callbacks = BoxNonNull::new(callbacks); Self { raw, callbacks } } @@ -31,10 +30,6 @@ impl Drop for View { let raw = self.raw.as_ptr(); // SAFETY: `raw` is always valid unsafe { sys::view_free(raw) } - - let callbacks = self.callbacks.as_ptr(); - // SAFETY: `callbacks` has been created via `Box` - let _ = unsafe { Box::from_raw(callbacks) }; } } diff --git a/crates/flipperzero/src/gui/view_dispatcher/mod.rs b/crates/flipperzero/src/gui/view_dispatcher/mod.rs new file mode 100644 index 00000000..c78987e1 --- /dev/null +++ b/crates/flipperzero/src/gui/view_dispatcher/mod.rs @@ -0,0 +1,172 @@ +use crate::internals::alloc::BoxNonNull; +use alloc::boxed::Box; +use core::num::NonZeroU32; +use core::{ + ffi::c_void, + ptr::{self, NonNull}, +}; +use flipperzero_sys::{self as sys, ViewDispatcher as SysViewDispatcher}; + +pub struct ViewDispatcher { + raw: NonNull, + callbacks: BoxNonNull, +} + +impl ViewDispatcher { + pub fn new(callbacks: C) -> Self { + // discover which callbacks should be registered + let register_custom_event = !ptr::eq( + C::on_custom as *const c_void, + <() as ViewDispatcherCallbacks>::on_custom as *const c_void, + ); + let register_navigation_callback = !ptr::eq( + C::on_navigation as *const c_void, + <() as ViewDispatcherCallbacks>::on_navigation as *const c_void, + ); + let tick_period = (!ptr::eq( + C::on_tick as *const c_void, + <() as ViewDispatcherCallbacks>::on_tick as *const c_void, + )) + .then(|| callbacks.tick_period()); + + // SAFETY: allocation either succeeds producing the valid pointer + // or stops the system on OOM, + let raw = unsafe { sys::view_dispatcher_alloc() }; + let callbacks = BoxNonNull::new(callbacks); + + // SAFETY: both pointers are guaranteed to be non-null + let view_dispatcher = { + let raw = unsafe { NonNull::new_unchecked(raw) }; + Self { raw, callbacks } + }; + + if QUEUE { + // SAFETY: `raw` is a valid pointer + // and corresponds to a newly created `ViewPort` + // which does not have a queue yet + unsafe { sys::view_dispatcher_enable_queue(raw) }; + } + + // and store context if at least one event should be registered + if register_custom_event || register_navigation_callback || tick_period.is_some() { + let context = view_dispatcher.callbacks.as_ptr().cast(); + // SAFETY: `raw` is valid + // and `callbacks` is valid and lives with this struct + unsafe { sys::view_dispatcher_set_event_callback_context(raw, context) }; + } + + if register_custom_event { + pub unsafe extern "C" fn dispatch_custom( + context: *mut c_void, + event: u32, + ) -> bool { + let context: *mut C = context.cast(); + // SAFETY: `context` is stored in a `Box` which is a member of `ViewPort` + // and the callback is accessed exclusively by this function + unsafe { &mut *context }.on_custom(event) + } + + let callback = Some(dispatch_custom:: as _); + // SAFETY: `raw` is valid + // and `callbacks` is valid and lives with this struct + unsafe { sys::view_dispatcher_set_custom_event_callback(raw, callback) }; + } + if register_navigation_callback { + pub unsafe extern "C" fn dispatch_navigation( + context: *mut c_void, + ) -> bool { + let context: *mut C = context.cast(); + // SAFETY: `context` is stored in a `Box` which is a member of `ViewPort` + // and the callback is accessed exclusively by this function + unsafe { &mut *context }.on_navigation() + } + + let callback = Some(dispatch_navigation:: as _); + // SAFETY: `raw` is valid + // and `callbacks` is valid and lives with this struct + unsafe { sys::view_dispatcher_set_navigation_event_callback(raw, callback) }; + } + if let Some(tick_period) = tick_period { + pub unsafe extern "C" fn dispatch_tick( + context: *mut c_void, + ) { + let context: *mut C = context.cast(); + // SAFETY: `context` is stored in a `Box` which is a member of `ViewPort` + // and the callback is accessed exclusively by this function + unsafe { &mut *context }.on_tick(); + } + + let tick_period = tick_period.get(); + let callback = Some(dispatch_tick:: as _); + // SAFETY: `raw` is valid + // and `callbacks` is valid and lives with this struct + unsafe { sys::view_dispatcher_set_tick_event_callback(raw, callback, tick_period) }; + } + + view_dispatcher + } + + pub fn as_raw(&self) -> *mut SysViewDispatcher { + self.raw.as_ptr() + } + + // pub fn add_view(&mut self,) +} + +impl ViewDispatcher { + pub fn run(&mut self) { + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is a valid pointer + // and this is a `ViewDispatcher` with a queue + unsafe { sys::view_dispatcher_run(raw) }; + } + + pub fn stop(&mut self) { + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is a valid pointer + // and this is a `ViewDispatcher` with a queue + unsafe { sys::view_dispatcher_stop(raw) }; + } + + pub fn send_custom_event(&mut self, event: u32) { + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is a valid pointer + // and this is a `ViewDispatcher` with a queue + unsafe { sys::view_dispatcher_send_custom_event(raw, event) }; + } +} + +impl Drop for ViewDispatcher { + fn drop(&mut self) { + let raw = self.raw.as_ptr(); + // SAFETY: `raw` is a valid pointer + unsafe { sys::view_dispatcher_free(raw) }; + + let callbacks = self.callbacks.as_ptr(); + // SAFETY: `callbacks` has been created via `Box` + let _ = unsafe { Box::from_raw(callbacks) }; + } +} + +pub trait ViewDispatcherCallbacks { + fn on_custom(&mut self, _event: u32) -> bool { + true + } + fn on_navigation(&mut self) -> bool { + true + } + fn on_tick(&mut self) {} + + #[must_use] + fn tick_period(&self) -> NonZeroU32 { + // Some arbitrary default + NonZeroU32::new(100).unwrap() + } +} + +impl ViewDispatcherCallbacks for () { + // use MAX value since this should never be used normally + fn tick_period(&self) -> NonZeroU32 { + NonZeroU32::MAX + } +} diff --git a/crates/flipperzero/src/input/mod.rs b/crates/flipperzero/src/input/mod.rs index ebf56abc..2159d142 100644 --- a/crates/flipperzero/src/input/mod.rs +++ b/crates/flipperzero/src/input/mod.rs @@ -89,12 +89,16 @@ impl From for FromSysInputEventError { pub struct InputEventSequence(u32); impl InputEventSequence { + const SOURCE_SHIFT: u32 = 30; + const SOURCE_MASK: u32 = (u32::MAX) >> Self::SOURCE_SHIFT; + const COUNTER_MASK: u32 = !(Self::SOURCE_MASK << Self::SOURCE_SHIFT); + pub const fn source(self) -> u8 { - ((self.0 >> 30) & 0b11) as u8 + ((self.0 >> Self::SOURCE_SHIFT) & Self::SOURCE_MASK) as u8 } pub const fn counter(self) -> u32 { - self.0 & !(0b11 << 30) + self.0 & Self::COUNTER_MASK } } diff --git a/crates/flipperzero/src/input/type.rs b/crates/flipperzero/src/input/type.rs index 2f72eddb..0186633d 100644 --- a/crates/flipperzero/src/input/type.rs +++ b/crates/flipperzero/src/input/type.rs @@ -18,7 +18,7 @@ pub enum InputType { /// done within `INPUT_LONG_PRESS` interval. Short, /// Long event, emitted after `INPUT_LONG_PRESS_COUNTS` interval, - /// asynchronous to [`InputTypeRelease`]. + /// asynchronous to [`InputType::Release`]. Long, /// Repeat event, emitted with `INPUT_LONG_PRESS_COUNTS` period /// after [InputType::Long] event. diff --git a/crates/flipperzero/src/internals.rs b/crates/flipperzero/src/internals.rs index cc145375..90833131 100644 --- a/crates/flipperzero/src/internals.rs +++ b/crates/flipperzero/src/internals.rs @@ -87,11 +87,13 @@ pub(crate) mod ops { } pub(crate) mod macros { + /// Generates an implementation of `std::error::Error` for the passed type + /// hidden behind an `std` feature flag. macro_rules! impl_std_error { ($error_type:ident) => { #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] - impl std::error::Error for $error_type {} + impl ::std::error::Error for $error_type {} }; } diff --git a/crates/flipperzero/src/kernel.rs b/crates/flipperzero/src/kernel.rs index 2bcf2fd3..a14ceb8c 100644 --- a/crates/flipperzero/src/kernel.rs +++ b/crates/flipperzero/src/kernel.rs @@ -4,6 +4,7 @@ use core::{ marker::PhantomData, }; use flipperzero_sys::{self as sys, furi::Status}; +use ufmt::{derive::uDebug, uDebug, uDisplay, uWrite, uwrite}; #[inline(always)] fn is_irq_or_masked() -> bool { @@ -32,7 +33,7 @@ pub fn lock() -> Result { } } -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug)] #[cfg_attr( feature = "unstable_lints", must_not_suspend = "holding a MutexGuard across suspend \ @@ -57,8 +58,20 @@ impl Drop for LockGuard { } } +impl uDebug for LockGuard { + fn fmt(&self, f: &mut ufmt::Formatter<'_, W>) -> Result<(), W::Error> + where + W: uWrite + ?Sized, + { + // TODO: use `derive` once `ufmt` supports `PhantomReference` + f.debug_struct("LockGuard")? + .field("was_locked", &self.was_locked)? + .finish() + } +} + /// A type of error which can be returned whenever a lock is acquired. -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] +#[derive(Copy, Clone, Debug, uDebug, Eq, PartialEq, Hash, Ord, PartialOrd)] pub enum LockError { Interrupted, ErrorStatus(Status), @@ -73,4 +86,16 @@ impl Display for LockError { } } +impl uDisplay for LockError { + fn fmt(&self, f: &mut ufmt::Formatter<'_, W>) -> Result<(), W::Error> + where + W: uWrite + ?Sized, + { + match self { + Self::Interrupted => uwrite!(f, "context is in interruption state"), + Self::ErrorStatus(status) => uwrite!(f, "error status: {}", status), + } + } +} + impl_std_error!(LockError); diff --git a/crates/flipperzero/src/lib.rs b/crates/flipperzero/src/lib.rs index 01859893..fc40e2a3 100644 --- a/crates/flipperzero/src/lib.rs +++ b/crates/flipperzero/src/lib.rs @@ -38,8 +38,6 @@ pub mod notification; #[cfg(feature = "service-storage")] #[cfg_attr(docsrs, doc(cfg(feature = "service-storage")))] pub mod storage; -#[cfg(feature = "service-toolbox")] -#[cfg_attr(docsrs, doc(cfg(feature = "service-toolbox")))] pub mod toolbox; #[cfg(feature = "xbm")] #[cfg_attr(docsrs, doc(cfg(feature = "xbm")))] diff --git a/crates/flipperzero/src/xbm.rs b/crates/flipperzero/src/xbm.rs index 283c5034..1dabbedd 100644 --- a/crates/flipperzero/src/xbm.rs +++ b/crates/flipperzero/src/xbm.rs @@ -333,15 +333,6 @@ macro_rules! xbm { "bits identifier and y-hotspot identifier should have the same prefix" ); )? - - // assert!(::core::matches!( - // width_ident.get(width_ident.len() - 5), - // ::core::option::Option::Some(b'w') - // ), "sad"); - // match width_ident.get(width_ident.len() - 5..) { - // ::core::option::Option::Some(b"width") => {}, - // _ => panic!("the first identifier should end with `_width") - // }; } $crate::xbm!(unsafe { From d587aec450b803db3c0adfddb337cf276a4e0a08 Mon Sep 17 00:00:00 2001 From: Petr Portnov Date: Sat, 24 Jun 2023 04:09:56 +0300 Subject: [PATCH 46/49] feat: start implementing sound `ViewDispatcher` Co-authored-by: Mike Mogenson <@mogenson> --- crates/flipperzero/src/gui/icon.rs | 2 + crates/flipperzero/src/gui/icon_animation.rs | 95 ++++++++------ crates/flipperzero/src/gui/mod.rs | 2 + crates/flipperzero/src/gui/view.rs | 38 ++++-- .../src/gui/view_dispatcher/mod.rs | 63 ++++----- .../src/gui/view_dispatcher/type.rs | 67 ++++++++++ crates/flipperzero/src/gui/view_port/mod.rs | 60 ++++----- crates/flipperzero/src/internals.rs | 122 ++++++++++++++++++ 8 files changed, 341 insertions(+), 108 deletions(-) create mode 100644 crates/flipperzero/src/gui/view_dispatcher/type.rs diff --git a/crates/flipperzero/src/gui/icon.rs b/crates/flipperzero/src/gui/icon.rs index f7316882..e28f4bef 100644 --- a/crates/flipperzero/src/gui/icon.rs +++ b/crates/flipperzero/src/gui/icon.rs @@ -30,6 +30,8 @@ impl Icon { Self { raw } } + #[inline] + #[must_use] pub fn as_raw(&self) -> *mut SysIcon { self.raw.as_ptr() } diff --git a/crates/flipperzero/src/gui/icon_animation.rs b/crates/flipperzero/src/gui/icon_animation.rs index dda838f5..04de4869 100644 --- a/crates/flipperzero/src/gui/icon_animation.rs +++ b/crates/flipperzero/src/gui/icon_animation.rs @@ -1,30 +1,29 @@ -use crate::{gui::icon::Icon, internals::alloc::BoxNonNull}; -use alloc::boxed::Box; +use crate::{gui::icon::Icon, internals::alloc::NonUniqueBox}; use core::{ ffi::c_void, marker::PhantomData, ptr::{self, NonNull}, }; -use flipperzero_sys::{self as sys, IconAnimation as SysIconAnimation}; +use flipperzero_sys::{self as sys, Icon as SysIcon, IconAnimation as SysIconAnimation}; -/// System Icon Animation wrapper. +/// Icon Animation +/// which can be [started](IconAnimation::start) and [stopped](IconAnimation::stop). pub struct IconAnimation<'a, C: IconAnimationCallbacks> { - raw: NonNull, - callbacks: BoxNonNull, + inner: IconAnimationInner, + callbacks: NonUniqueBox, _parent_lifetime: PhantomData<&'a ()>, } impl<'a, C: IconAnimationCallbacks> IconAnimation<'a, C> { + /// Creates a new icon animation from the specified [icon](`Icon`). pub fn new<'b: 'a>(icon: &'b Icon, callbacks: C) -> Self { - let icon = icon.as_raw(); - // SAFETY: allocation either succeeds producing the valid pointer - // or stops the system on OOM, - // `icon` is a valid pointer and `icon` outlives this animation - let raw = unsafe { NonNull::new_unchecked(sys::icon_animation_alloc(icon)) }; - let callbacks = BoxNonNull::new(callbacks); + let icon = icon.as_raw().cast_const(); + // SAFETY: `icon` is a valid pointer and will outlive `inner` while remaining const + let inner = unsafe { IconAnimationInner::new(icon) }; + let callbacks = NonUniqueBox::new(callbacks); let icon_animation = Self { - raw, + inner, callbacks, _parent_lifetime: PhantomData, }; @@ -47,7 +46,7 @@ impl<'a, C: IconAnimationCallbacks> IconAnimation<'a, C> { C::on_update as *const c_void, <() as IconAnimationCallbacks>::on_update as *const c_void, ) { - let raw = raw.as_ptr(); + let raw = icon_animation.as_raw(); let callback = Some(dispatch_update:: as _); let context = icon_animation.callbacks.as_ptr().cast(); @@ -60,55 +59,77 @@ impl<'a, C: IconAnimationCallbacks> IconAnimation<'a, C> { icon_animation } + #[inline] + #[must_use] pub fn as_raw(&self) -> *mut SysIconAnimation { - self.raw.as_ptr() + self.inner.0.as_ptr() } + /// Gets the width of this icon animation. pub fn get_width(&self) -> u8 { - let raw = self.raw.as_ptr(); - // SAFETY: `raw` is always valid + let raw = self.as_raw(); + // SAFETY: `raw` is valid unsafe { sys::icon_animation_get_width(raw) } } + /// Gets the height of this icon animation. pub fn get_height(&self) -> u8 { - let raw = self.raw.as_ptr(); - // SAFETY: `raw` is always valid + let raw = self.as_raw(); + // SAFETY: `raw` is valid unsafe { sys::icon_animation_get_height(raw) } } + /// Gets the dimensions of this icon animation. pub fn get_dimensions(&self) -> (u8, u8) { (self.get_width(), self.get_height()) } + /// Starts this icon animation. pub fn start(&mut self) { - let raw = self.raw.as_ptr(); - // SAFETY: `raw` is always valid + let raw = self.as_raw(); + // SAFETY: `raw` is valid unsafe { sys::icon_animation_start(raw) } } + /// Stops this icon animation. pub fn stop(&mut self) { - let raw = self.raw.as_ptr(); - // SAFETY: `raw` is always valid + let raw = self.as_raw(); + // SAFETY: `raw` is valid unsafe { sys::icon_animation_stop(raw) } } + /// Checks if the current frame is the last one. pub fn is_last_frame(&self) -> bool { - let raw = self.raw.as_ptr(); - // SAFETY: `raw` is always valid + let raw = self.as_raw(); + // SAFETY: `raw` is valid unsafe { sys::icon_animation_is_last_frame(raw) } } } -impl Drop for IconAnimation<'_, C> { +/// Plain alloc-free wrapper over a [`SysIconAnimation`]. +struct IconAnimationInner(NonNull); + +impl IconAnimationInner { + /// Creates a new icon animation wrapper for the specified icon. + /// + /// # Safety + /// + /// `icon` should outlive the created wrapper + /// and should not mutate during this wrapper's existence. + unsafe fn new(icon: *const SysIcon) -> Self { + // SAFETY: allocation either succeeds producing the valid pointer + // or stops the system on OOM, + // `icon` is a valid pointer and `icon` outlives this animation + Self(unsafe { NonNull::new_unchecked(sys::icon_animation_alloc(icon)) }) + } +} + +impl Drop for IconAnimationInner { fn drop(&mut self) { - let raw = self.raw.as_ptr(); + let raw = self.0.as_ptr(); // SAFETY: `raw` is a valid pointer // which should have been created via `icon_animation_alloc` unsafe { sys::icon_animation_free(raw) } - - let callbacks = self.callbacks.as_ptr(); - // SAFETY: `callbacks` has been created via `Box` - let _ = unsafe { Box::from_raw(callbacks) }; } } @@ -148,13 +169,13 @@ impl IconAnimationView<'_> { pub fn get_width(&self) -> u8 { let raw = self.raw.as_ptr(); - // SAFETY: `raw` is always valid + // SAFETY: `raw` is valid unsafe { sys::icon_animation_get_width(raw) } } pub fn get_height(&self) -> u8 { let raw = self.raw.as_ptr(); - // SAFETY: `raw` is always valid + // SAFETY: `raw` is valid unsafe { sys::icon_animation_get_height(raw) } } @@ -166,25 +187,27 @@ impl IconAnimationView<'_> { // i.e. if it is sound to call start/stop from callbacks // pub fn start(&mut self) { // let raw = self.raw.as_ptr(); - // // SAFETY: `raw` is always valid + // // SAFETY: `raw` is valid // unsafe { sys::icon_animation_start(raw) } // } // // pub fn stop(&mut self) { // let raw = self.raw.as_ptr(); - // // SAFETY: `raw` is always valid + // // SAFETY: `raw` is valid // unsafe { sys::icon_animation_stop(raw) } // } pub fn is_last_frame(&self) -> bool { let raw = self.raw.as_ptr(); - // SAFETY: `raw` is always valid + // SAFETY: `raw` is valid unsafe { sys::icon_animation_is_last_frame(raw) } } } +/// Callbacks of the [`IconAnimation`]. pub trait IconAnimationCallbacks { fn on_update(&mut self, _icon_animation: IconAnimationView) {} } +/// Stub implementation, use it whenever callbacks are not needed. impl IconAnimationCallbacks for () {} diff --git a/crates/flipperzero/src/gui/mod.rs b/crates/flipperzero/src/gui/mod.rs index 84195b05..ea2867d1 100644 --- a/crates/flipperzero/src/gui/mod.rs +++ b/crates/flipperzero/src/gui/mod.rs @@ -53,6 +53,8 @@ impl Gui { Self { raw: gui } } + #[inline] + #[must_use] pub fn as_raw(&self) -> *mut SysGui { self.raw.as_raw() } diff --git a/crates/flipperzero/src/gui/view.rs b/crates/flipperzero/src/gui/view.rs index 190ff9d5..31356bc4 100644 --- a/crates/flipperzero/src/gui/view.rs +++ b/crates/flipperzero/src/gui/view.rs @@ -1,34 +1,44 @@ -use crate::internals::alloc::BoxNonNull; -use crate::{gui::canvas::CanvasView, input::InputEvent}; -use alloc::boxed::Box; +use crate::{gui::canvas::CanvasView, input::InputEvent, internals::alloc::NonUniqueBox}; use core::ptr::NonNull; use flipperzero_sys::{self as sys, View as SysView}; +/// UI view. pub struct View { - raw: NonNull, - callbacks: BoxNonNull, + inner: ViewInner, + callbacks: NonUniqueBox, } impl View { pub fn new(callbacks: C) -> Self { - // SAFETY: allocation either succeeds producing a valid non-null pointer - // or stops the system on OOM - let raw = unsafe { NonNull::new_unchecked(sys::view_alloc()) }; - let callbacks = BoxNonNull::new(callbacks); + let inner = ViewInner::new(); + let callbacks = NonUniqueBox::new(callbacks); - Self { raw, callbacks } + Self { inner, callbacks } } /// Creates a copy of raw pointer to the [`sys::View`]. + #[inline] + #[must_use] pub fn as_raw(&self) -> *mut SysView { - self.raw.as_ptr() + self.inner.0.as_ptr() + } +} + +/// Plain alloc-free wrapper over a [`SysView`]. +struct ViewInner(NonNull); + +impl ViewInner { + fn new() -> Self { + // SAFETY: allocation either succeeds producing a valid non-null pointer + // or stops the system on OOM + Self(unsafe { NonNull::new_unchecked(sys::view_alloc()) }) } } -impl Drop for View { +impl Drop for ViewInner { fn drop(&mut self) { - let raw = self.raw.as_ptr(); - // SAFETY: `raw` is always valid + let raw = self.0.as_ptr(); + // SAFETY: `raw` is valid unsafe { sys::view_free(raw) } } } diff --git a/crates/flipperzero/src/gui/view_dispatcher/mod.rs b/crates/flipperzero/src/gui/view_dispatcher/mod.rs index c78987e1..44d4a49c 100644 --- a/crates/flipperzero/src/gui/view_dispatcher/mod.rs +++ b/crates/flipperzero/src/gui/view_dispatcher/mod.rs @@ -1,15 +1,18 @@ -use crate::internals::alloc::BoxNonNull; -use alloc::boxed::Box; -use core::num::NonZeroU32; +mod r#type; + +use crate::internals::alloc::NonUniqueBox; use core::{ ffi::c_void, + num::NonZeroU32, ptr::{self, NonNull}, }; use flipperzero_sys::{self as sys, ViewDispatcher as SysViewDispatcher}; +pub use r#type::*; + pub struct ViewDispatcher { - raw: NonNull, - callbacks: BoxNonNull, + inner: ViewDispatcherInner, + callbacks: NonUniqueBox, } impl ViewDispatcher { @@ -29,17 +32,13 @@ impl ViewDispatcher { )) .then(|| callbacks.tick_period()); - // SAFETY: allocation either succeeds producing the valid pointer - // or stops the system on OOM, - let raw = unsafe { sys::view_dispatcher_alloc() }; - let callbacks = BoxNonNull::new(callbacks); + let inner = ViewDispatcherInner::new(); + let callbacks = NonUniqueBox::new(callbacks); // SAFETY: both pointers are guaranteed to be non-null - let view_dispatcher = { - let raw = unsafe { NonNull::new_unchecked(raw) }; - Self { raw, callbacks } - }; + let view_dispatcher = Self { inner, callbacks }; + let raw = view_dispatcher.as_raw(); if QUEUE { // SAFETY: `raw` is a valid pointer // and corresponds to a newly created `ViewPort` @@ -106,45 +105,51 @@ impl ViewDispatcher { view_dispatcher } + #[inline] + #[must_use] pub fn as_raw(&self) -> *mut SysViewDispatcher { - self.raw.as_ptr() + self.inner.0.as_ptr() } - - // pub fn add_view(&mut self,) } impl ViewDispatcher { pub fn run(&mut self) { - let raw = self.raw.as_ptr(); - // SAFETY: `raw` is a valid pointer + let raw = self.as_raw(); + // SAFETY: `raw` is valid // and this is a `ViewDispatcher` with a queue unsafe { sys::view_dispatcher_run(raw) }; } pub fn stop(&mut self) { - let raw = self.raw.as_ptr(); - // SAFETY: `raw` is a valid pointer + let raw = self.as_raw(); + // SAFETY: `raw` is valid // and this is a `ViewDispatcher` with a queue unsafe { sys::view_dispatcher_stop(raw) }; } pub fn send_custom_event(&mut self, event: u32) { - let raw = self.raw.as_ptr(); - // SAFETY: `raw` is a valid pointer + let raw = self.as_raw(); + // SAFETY: `raw` is valid // and this is a `ViewDispatcher` with a queue unsafe { sys::view_dispatcher_send_custom_event(raw, event) }; } } -impl Drop for ViewDispatcher { +struct ViewDispatcherInner(NonNull); + +impl ViewDispatcherInner { + fn new() -> Self { + // SAFETY: allocation either succeeds producing the valid pointer + // or stops the system on OOM, + Self(unsafe { NonNull::new_unchecked(sys::view_dispatcher_alloc()) }) + } +} + +impl Drop for ViewDispatcherInner { fn drop(&mut self) { - let raw = self.raw.as_ptr(); - // SAFETY: `raw` is a valid pointer + let raw = self.0.as_ptr(); + // SAFETY: `raw` is valid unsafe { sys::view_dispatcher_free(raw) }; - - let callbacks = self.callbacks.as_ptr(); - // SAFETY: `callbacks` has been created via `Box` - let _ = unsafe { Box::from_raw(callbacks) }; } } diff --git a/crates/flipperzero/src/gui/view_dispatcher/type.rs b/crates/flipperzero/src/gui/view_dispatcher/type.rs new file mode 100644 index 00000000..cb155616 --- /dev/null +++ b/crates/flipperzero/src/gui/view_dispatcher/type.rs @@ -0,0 +1,67 @@ +use crate::internals::macros::impl_std_error; +use core::fmt::{self, Display, Formatter}; +use flipperzero_sys::{self as sys, ViewDispatcherType as SysViewDispatcherType}; +use ufmt::{derive::uDebug, uDisplay, uWrite, uwrite}; + +/// View dispatcher view port placement. +/// +/// Corresponds to raw [`SysViewDispatcherType`]. +#[derive(Copy, Clone, Debug, uDebug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub enum ViewDispatcherType { + /// Desktop layer: fullscreen with status bar on top of it. For internal usage. + Desktop, + /// Window layer: with status bar. + Window, + /// Fullscreen layer: without status bar. + Fullscreen, +} + +impl TryFrom for ViewDispatcherType { + type Error = FromSysViewDispatcherTypeError; + + fn try_from(value: SysViewDispatcherType) -> Result { + Ok(match value { + sys::ViewDispatcherType_ViewDispatcherTypeDesktop => ViewDispatcherType::Desktop, + sys::ViewDispatcherType_ViewDispatcherTypeWindow => ViewDispatcherType::Window, + sys::ViewDispatcherType_ViewDispatcherTypeFullscreen => ViewDispatcherType::Fullscreen, + invalid => Err(Self::Error::Invalid(invalid))?, + }) + } +} + +impl From for SysViewDispatcherType { + fn from(value: ViewDispatcherType) -> Self { + match value { + ViewDispatcherType::Desktop => sys::ViewDispatcherType_ViewDispatcherTypeDesktop, + ViewDispatcherType::Window => sys::ViewDispatcherType_ViewDispatcherTypeWindow, + ViewDispatcherType::Fullscreen => sys::ViewDispatcherType_ViewDispatcherTypeFullscreen, + } + } +} + +/// An error which may occur while trying +/// to convert raw [`SysViewDispatcherType`] to [`ViewDispatcherType`]. +#[derive(Copy, Clone, Debug, uDebug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub enum FromSysViewDispatcherTypeError { + /// The [`SysViewDispatcherType`] is an invalid value. + Invalid(SysViewDispatcherType), +} + +impl Display for FromSysViewDispatcherTypeError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let Self::Invalid(id) = self; + write!(f, "view dispatcher type ID {id} is invalid") + } +} + +impl uDisplay for FromSysViewDispatcherTypeError { + fn fmt(&self, f: &mut ufmt::Formatter<'_, W>) -> Result<(), W::Error> + where + W: uWrite + ?Sized, + { + let Self::Invalid(id) = self; + uwrite!(f, "view dispatcher type ID {} is invalid", id) + } +} + +impl_std_error!(FromSysInputTypeError); diff --git a/crates/flipperzero/src/gui/view_port/mod.rs b/crates/flipperzero/src/gui/view_port/mod.rs index 66c51143..8099f70e 100644 --- a/crates/flipperzero/src/gui/view_port/mod.rs +++ b/crates/flipperzero/src/gui/view_port/mod.rs @@ -2,8 +2,7 @@ mod orientation; -use crate::{gui::canvas::CanvasView, input::InputEvent}; -use alloc::boxed::Box; +use crate::{gui::canvas::CanvasView, input::InputEvent, internals::alloc::NonUniqueBox}; use core::{ ffi::c_void, num::NonZeroU8, @@ -18,8 +17,8 @@ pub use orientation::*; /// System ViewPort. pub struct ViewPort { - raw: NonNull, - callbacks: NonNull, + inner: ViewPortInner, + callbacks: NonUniqueBox, } impl ViewPort { @@ -35,16 +34,11 @@ impl ViewPort { /// let view_port = ViewPort::new(()); /// ``` pub fn new(callbacks: C) -> Self { - // SAFETY: allocation either succeeds producing the valid pointer - // or stops the system on OOM - let raw = unsafe { NonNull::new_unchecked(sys::view_port_alloc()) }; - let callbacks = Box::into_raw(Box::new(callbacks)); + let inner = ViewPortInner::new(); + let callbacks = NonUniqueBox::new(callbacks); - let view_port = { - // SAFETY: `callbacks` has been created via `Box` - let callbacks = unsafe { NonNull::new_unchecked(callbacks) }; - Self { raw, callbacks } - }; + let view_port = Self { inner, callbacks }; + let raw = view_port.as_raw(); { pub unsafe extern "C" fn dispatch_draw( @@ -64,8 +58,7 @@ impl ViewPort { C::on_draw as *const c_void, <() as ViewPortCallbacks>::on_draw as *const c_void, ) { - let context = callbacks.cast(); - let raw = raw.as_ptr(); + let context = view_port.callbacks.as_ptr().cast(); let callback = Some(dispatch_draw:: as _); // SAFETY: `raw` is valid // and `callbacks` is valid and lives with this struct @@ -91,8 +84,7 @@ impl ViewPort { C::on_input as *const c_void, <() as ViewPortCallbacks>::on_input as *const c_void, ) { - let context = callbacks.cast(); - let raw = raw.as_ptr(); + let context = view_port.callbacks.as_ptr().cast(); let callback = Some(dispatch_input:: as _); // SAFETY: `raw` is valid @@ -105,8 +97,10 @@ impl ViewPort { } /// Creates a copy of the raw pointer to the [`sys::ViewPort`]. + #[inline] + #[must_use] pub fn as_raw(&self) -> *mut SysViewPort { - self.raw.as_ptr() + self.inner.0.as_ptr() } /// Sets the width of this `ViewPort`. @@ -349,23 +343,31 @@ impl ViewPort { impl Drop for ViewPort { fn drop(&mut self) { // FIXME: unregister from system (whatever this means) + self.set_enabled(false); + } +} - let raw = self.raw.as_ptr(); - // SAFETY: `self.raw` is always valid - // and it should have been unregistered from the system by now - unsafe { - sys::view_port_enabled_set(raw, false); - sys::view_port_free(raw); - } +/// Plain alloc-free wrapper over a [`SysViewPort`]. +struct ViewPortInner(NonNull); - let callbacks = self.callbacks.as_ptr(); - // SAFETY: `callbacks` has been created via `Box` - let _ = unsafe { Box::from_raw(callbacks) }; +impl ViewPortInner { + fn new() -> Self { + // SAFETY: allocation either succeeds producing the valid non-null pointer + // or stops the system on OOM + Self(unsafe { NonNull::new_unchecked(sys::view_port_alloc()) }) + } +} + +impl Drop for ViewPortInner { + fn drop(&mut self) { + let raw = self.0.as_ptr(); + // SAFETY: `raw` is a valid pointer + unsafe { sys::view_port_free(raw) }; } } pub trait ViewPortCallbacks { - fn on_draw(&mut self, _canvas: CanvasView) {} + fn on_draw(&mut self, _canvas: CanvasView<'_>) {} fn on_input(&mut self, _event: InputEvent) {} } diff --git a/crates/flipperzero/src/internals.rs b/crates/flipperzero/src/internals.rs index 90833131..2a3408a8 100644 --- a/crates/flipperzero/src/internals.rs +++ b/crates/flipperzero/src/internals.rs @@ -61,6 +61,128 @@ const _: () = { ); }; +#[cfg(feature = "alloc")] +pub(crate) mod alloc { + use alloc::boxed::Box; + use core::{mem, ptr::NonNull}; + + /// Wrapper for a [`NonNull`] created from [`Box`] + /// which does not imply uniqueness which the box does. + /// + /// # Intended use + /// + /// This is intended to be used instead of [`Box`] whenever + /// an allocation occurs on creation of a wrapper which needs + /// to store extra information on the heap, such as FFI-callback contexts, + /// in this case this struct has to be stored as a field + /// and the raw pointer provided by it should be passed to the FFI. + /// + /// The caller must guarantee that by the moment this structure is dropped + /// no one continues using the pointers. + /// + /// # Safety + /// + /// While there are no `unsafe` methods in this struct, + /// it is easy to misuse the pointers provided by its methods, namely: + /// * [`NonUniqueBox::as_ptr`] + /// * [`NonUniqueBox::as_non_null`] + /// so it should be used with extra care, i.e. all uses of the pointers + /// should follow the rules such as stacked borrows + /// and should never be used after the drop of this structure. + /// + /// As a rule of thumb, it should only be stored in private fields + /// of the `struct`s to help with holding a pointer to an owned allocation + /// without upholding `Box`s uniqueness guarantees. + /// + /// # Examples + /// + /// Wrapper structure for some callback: + /// ```no_run + /// # struct FfiFoo; + /// # struct Context { + /// # bar: i32, + /// # baz: u8, + /// # } + /// # extern "C" { + /// # fn foo_alloc() -> *mut FfiFoo; + /// # fn foo_set_callback(foo: *mut FfiFoo, ctx: Context); + /// # fn foo_free(foo: *mut FfiFoo); + /// # } + /// # use std::ptr::NonNull; + /// # use crate::internals::alloc::NonUniqueBox; + /// pub struct Foo { + /// inner: FooInner, + /// context: NonUniqueBox, + /// } + /// struct FooInner(NonNull); + /// impl Drop for FooInner { + /// fn drop(&mut self) { + /// let raw = self.0.as_ptr(); + /// // SAFETY: `raw` should be a valid pointer + /// unsafe { foo_free(raw) }; + /// } + /// } + /// impl Foo { + /// fn new() -> Foo { + /// let inner = FooInner( + /// // SAFETY: we uphold `foo_alloc` invariant + /// // and it is never null + /// unsafe { NonNull::new_unchecked(foo_alloc()) } + /// ); + /// let context = NonUniqueBox::new(Context { bar: 123, baz: 456 }); + /// Self { inner, context } + /// } + /// } + ///``` + #[repr(transparent)] + pub(crate) struct NonUniqueBox(NonNull); + + impl NonUniqueBox { + #[inline(always)] + pub(crate) fn new(value: T) -> Self { + let value = Box::into_raw(Box::new(value)); + // SAFETY: `value` has just been allocated via `Box` + Self(unsafe { NonNull::new_unchecked(value) }) + } + } + + impl NonUniqueBox { + #[inline(always)] + pub(crate) fn as_non_null(&self) -> NonNull { + self.0 + } + + #[inline(always)] + pub(crate) fn as_ptr(&self) -> *mut T { + self.0.as_ptr() + } + + /// Converts this back into a [`Box`]. + /// + /// # Safety + /// + /// This methods is safe since it user's responsibility + /// to correctly use the pointers created from this wrapper, + /// but it still is important to keep in mind that this is easy to misuse. + pub(crate) fn to_box(self) -> Box { + let raw = self.0.as_ptr(); + mem::forget(self); + // SAFETY: `raw` should have been created from `Box` + // and it's user's responsibility to correctly use the exposed pointer + unsafe { Box::from_raw(raw) } + } + } + + impl Drop for NonUniqueBox { + fn drop(&mut self) { + let raw = self.0.as_ptr(); + // SAFETY: `raw` should have been created from `Box` + // and it's user's responsibility to correctly use the exposed pointer + let _ = unsafe { Box::from_raw(raw) }; + } + } +} + /// Operations which have unstable implementations /// but still may be implemented manually on `stable` channel. /// From 0a5822fd9305067ab7dd566b6193712ce45cb187 Mon Sep 17 00:00:00 2001 From: Petr Portnov Date: Sat, 15 Jul 2023 22:42:11 +0300 Subject: [PATCH 47/49] chore: fix invalid mentions of `ViewPort` with `ViewDispatcher` --- crates/flipperzero/src/gui/view_dispatcher/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/flipperzero/src/gui/view_dispatcher/mod.rs b/crates/flipperzero/src/gui/view_dispatcher/mod.rs index 44d4a49c..c445b36e 100644 --- a/crates/flipperzero/src/gui/view_dispatcher/mod.rs +++ b/crates/flipperzero/src/gui/view_dispatcher/mod.rs @@ -41,7 +41,7 @@ impl ViewDispatcher { let raw = view_dispatcher.as_raw(); if QUEUE { // SAFETY: `raw` is a valid pointer - // and corresponds to a newly created `ViewPort` + // and corresponds to a newly created `ViewDispatcher` // which does not have a queue yet unsafe { sys::view_dispatcher_enable_queue(raw) }; } @@ -60,7 +60,7 @@ impl ViewDispatcher { event: u32, ) -> bool { let context: *mut C = context.cast(); - // SAFETY: `context` is stored in a `Box` which is a member of `ViewPort` + // SAFETY: `context` is stored in a `Box` which is a member of `ViewDispatcher` // and the callback is accessed exclusively by this function unsafe { &mut *context }.on_custom(event) } @@ -75,7 +75,7 @@ impl ViewDispatcher { context: *mut c_void, ) -> bool { let context: *mut C = context.cast(); - // SAFETY: `context` is stored in a `Box` which is a member of `ViewPort` + // SAFETY: `context` is stored in a `Box` which is a member of `ViewDispatcher` // and the callback is accessed exclusively by this function unsafe { &mut *context }.on_navigation() } @@ -90,7 +90,7 @@ impl ViewDispatcher { context: *mut c_void, ) { let context: *mut C = context.cast(); - // SAFETY: `context` is stored in a `Box` which is a member of `ViewPort` + // SAFETY: `context` is stored in a `Box` which is a member of `ViewDispatcher` // and the callback is accessed exclusively by this function unsafe { &mut *context }.on_tick(); } From 9ede8a9dffe0df9b99c1c70f4698f1d57a889232 Mon Sep 17 00:00:00 2001 From: Petr Portnov Date: Sun, 16 Jul 2023 13:39:01 +0300 Subject: [PATCH 48/49] chore: continue experimenting with `ViewDispatcher` APIs --- crates/alloc/src/lib.rs | 6 +- crates/flipperzero/src/gui/canvas/align.rs | 4 +- .../src/gui/canvas/canvas_direction.rs | 4 +- crates/flipperzero/src/gui/canvas/color.rs | 4 +- crates/flipperzero/src/gui/canvas/font.rs | 4 +- .../src/gui/canvas/font_parameters.rs | 4 +- crates/flipperzero/src/gui/canvas/mod.rs | 21 +- crates/flipperzero/src/gui/gui_layer.rs | 4 +- crates/flipperzero/src/gui/icon.rs | 6 +- crates/flipperzero/src/gui/icon_animation.rs | 11 +- crates/flipperzero/src/gui/mod.rs | 15 +- crates/flipperzero/src/gui/view.rs | 9 +- .../src/gui/view_dispatcher/mod.rs | 301 ++++++++++++++++-- .../src/gui/view_dispatcher/type.rs | 4 +- crates/flipperzero/src/gui/view_port/mod.rs | 10 +- .../src/gui/view_port/orientation.rs | 4 +- crates/flipperzero/src/internals.rs | 2 + 17 files changed, 341 insertions(+), 72 deletions(-) diff --git a/crates/alloc/src/lib.rs b/crates/alloc/src/lib.rs index 7f97818f..e9752753 100644 --- a/crates/alloc/src/lib.rs +++ b/crates/alloc/src/lib.rs @@ -4,8 +4,10 @@ #![no_std] #![deny(rustdoc::broken_intra_doc_links)] -use core::alloc::{GlobalAlloc, Layout}; -use core::ffi::c_void; +use core::{ + alloc::{GlobalAlloc, Layout}, + ffi::c_void, +}; extern crate alloc; // re-export all items from `alloc` so that the API user can only extern this crate diff --git a/crates/flipperzero/src/gui/canvas/align.rs b/crates/flipperzero/src/gui/canvas/align.rs index b41fc1fa..33d580fc 100644 --- a/crates/flipperzero/src/gui/canvas/align.rs +++ b/crates/flipperzero/src/gui/canvas/align.rs @@ -1,8 +1,10 @@ -use crate::internals::macros::impl_std_error; use core::fmt::{self, Display, Formatter}; + use flipperzero_sys::{self as sys, Align as SysAlign}; use ufmt::{derive::uDebug, uDisplay, uWrite, uwrite}; +use crate::internals::macros::impl_std_error; + /// Alignment of an object on the canvas. /// /// Corresponds to raw [`SysAlign`]. diff --git a/crates/flipperzero/src/gui/canvas/canvas_direction.rs b/crates/flipperzero/src/gui/canvas/canvas_direction.rs index 310ac52a..0ca61f3e 100644 --- a/crates/flipperzero/src/gui/canvas/canvas_direction.rs +++ b/crates/flipperzero/src/gui/canvas/canvas_direction.rs @@ -1,8 +1,10 @@ -use crate::internals::macros::impl_std_error; use core::fmt::{self, Display, Formatter}; + use flipperzero_sys::{self as sys, CanvasDirection as SysCanvasDirection}; use ufmt::{derive::uDebug, uDisplay, uWrite, uwrite}; +use crate::internals::macros::impl_std_error; + /// Direction of an element on the canvas. /// /// Corresponds to raw [`SysCanvasDirection`]. diff --git a/crates/flipperzero/src/gui/canvas/color.rs b/crates/flipperzero/src/gui/canvas/color.rs index f08530ed..61131687 100644 --- a/crates/flipperzero/src/gui/canvas/color.rs +++ b/crates/flipperzero/src/gui/canvas/color.rs @@ -1,8 +1,10 @@ -use crate::internals::macros::impl_std_error; use core::fmt::{self, Display, Formatter}; + use flipperzero_sys::{self as sys, Color as SysColor}; use ufmt::{derive::uDebug, uDisplay, uWrite, uwrite}; +use crate::internals::macros::impl_std_error; + /// Color on the canvas. /// /// Corresponds to raw [`SysColor`]. diff --git a/crates/flipperzero/src/gui/canvas/font.rs b/crates/flipperzero/src/gui/canvas/font.rs index b9ac878a..7cd9a11e 100644 --- a/crates/flipperzero/src/gui/canvas/font.rs +++ b/crates/flipperzero/src/gui/canvas/font.rs @@ -1,8 +1,10 @@ -use crate::internals::macros::impl_std_error; use core::fmt::{self, Display, Formatter}; + use flipperzero_sys::{self as sys, Font as SysFont}; use ufmt::{derive::uDebug, uDisplay, uWrite, uwrite}; +use crate::internals::macros::impl_std_error; + /// The font used to draw text. /// /// Corresponds to raw [`SysFont`]. diff --git a/crates/flipperzero/src/gui/canvas/font_parameters.rs b/crates/flipperzero/src/gui/canvas/font_parameters.rs index 77ff4016..d9078b75 100644 --- a/crates/flipperzero/src/gui/canvas/font_parameters.rs +++ b/crates/flipperzero/src/gui/canvas/font_parameters.rs @@ -1,11 +1,13 @@ -use crate::internals::macros::impl_std_error; use core::{ fmt::{self, Display, Formatter}, num::NonZeroU8, }; + use flipperzero_sys::CanvasFontParameters as SysCanvasFontParameters; use ufmt::{derive::uDebug, uDisplay, uWrite}; +use crate::internals::macros::impl_std_error; + /// Font parameters on a canvas. /// /// Corresponds to raw [`SysCanvasFontParameters`]. diff --git a/crates/flipperzero/src/gui/canvas/mod.rs b/crates/flipperzero/src/gui/canvas/mod.rs index 70409f6a..e18dc9bb 100644 --- a/crates/flipperzero/src/gui/canvas/mod.rs +++ b/crates/flipperzero/src/gui/canvas/mod.rs @@ -6,12 +6,6 @@ mod color; mod font; mod font_parameters; -use crate::gui::{ - icon::Icon, - icon_animation::{IconAnimation, IconAnimationCallbacks}, -}; -#[cfg(feature = "xbm")] -use crate::xbm::XbmImage; use core::{ ffi::{c_char, CStr}, marker::PhantomData, @@ -19,20 +13,27 @@ use core::{ ops::Deref, ptr::NonNull, }; -use flipperzero_sys::{ - self as sys, Canvas as SysCanvas, CanvasFontParameters as SysCanvasFontParameters, -}; pub use align::*; pub use canvas_direction::*; pub use color::*; +use flipperzero_sys::{ + self as sys, Canvas as SysCanvas, CanvasFontParameters as SysCanvasFontParameters, +}; pub use font::*; pub use font_parameters::*; +use crate::gui::{ + icon::Icon, + icon_animation::{IconAnimation, IconAnimationCallbacks}, +}; +#[cfg(feature = "xbm")] +use crate::xbm::XbmImage; + /// System Canvas view. pub struct CanvasView<'a> { raw: NonNull, - _lifetime: PhantomData<&'a ()>, + _lifetime: PhantomData<&'a SysCanvas>, } impl CanvasView<'_> { diff --git a/crates/flipperzero/src/gui/gui_layer.rs b/crates/flipperzero/src/gui/gui_layer.rs index bc09607b..e1cd15bb 100644 --- a/crates/flipperzero/src/gui/gui_layer.rs +++ b/crates/flipperzero/src/gui/gui_layer.rs @@ -1,8 +1,10 @@ -use crate::internals::macros::impl_std_error; use core::fmt::{self, Display, Formatter}; + use flipperzero_sys::{self as sys, GuiLayer as SysGuiLayer}; use ufmt::{derive::uDebug, uDisplay, uWrite, uwrite}; +use crate::internals::macros::impl_std_error; + /// The font used to draw text. /// /// Corresponds to raw [`SysGuiLayer`]. diff --git a/crates/flipperzero/src/gui/icon.rs b/crates/flipperzero/src/gui/icon.rs index e28f4bef..1b0b65eb 100644 --- a/crates/flipperzero/src/gui/icon.rs +++ b/crates/flipperzero/src/gui/icon.rs @@ -1,8 +1,10 @@ -#[cfg(feature = "xbm")] -use crate::xbm::XbmImage; use core::ptr::NonNull; + use flipperzero_sys::{self as sys, Icon as SysIcon}; +#[cfg(feature = "xbm")] +use crate::xbm::XbmImage; + pub struct Icon { raw: NonNull, } diff --git a/crates/flipperzero/src/gui/icon_animation.rs b/crates/flipperzero/src/gui/icon_animation.rs index 04de4869..b967c4ce 100644 --- a/crates/flipperzero/src/gui/icon_animation.rs +++ b/crates/flipperzero/src/gui/icon_animation.rs @@ -1,17 +1,19 @@ -use crate::{gui::icon::Icon, internals::alloc::NonUniqueBox}; use core::{ ffi::c_void, marker::PhantomData, ptr::{self, NonNull}, }; + use flipperzero_sys::{self as sys, Icon as SysIcon, IconAnimation as SysIconAnimation}; +use crate::{gui::icon::Icon, internals::alloc::NonUniqueBox}; + /// Icon Animation /// which can be [started](IconAnimation::start) and [stopped](IconAnimation::stop). pub struct IconAnimation<'a, C: IconAnimationCallbacks> { inner: IconAnimationInner, callbacks: NonUniqueBox, - _parent_lifetime: PhantomData<&'a ()>, + _parent_lifetime: PhantomData<&'a mut (IconAnimationInner, C)>, } impl<'a, C: IconAnimationCallbacks> IconAnimation<'a, C> { @@ -138,7 +140,7 @@ impl Drop for IconAnimationInner { /// This is passed to [callbacks](IconAnimationCallbacks) of [`IconAnimation`]. pub struct IconAnimationView<'a> { raw: NonNull, - _lifetime: PhantomData<&'a ()>, + _lifetime: PhantomData<&'a SysIconAnimation>, } impl IconAnimationView<'_> { @@ -205,8 +207,9 @@ impl IconAnimationView<'_> { } /// Callbacks of the [`IconAnimation`]. +#[allow(unused_variables)] pub trait IconAnimationCallbacks { - fn on_update(&mut self, _icon_animation: IconAnimationView) {} + fn on_update(&mut self, icon_animation: IconAnimationView) {} } /// Stub implementation, use it whenever callbacks are not needed. diff --git a/crates/flipperzero/src/gui/mod.rs b/crates/flipperzero/src/gui/mod.rs index ea2867d1..31c3fef4 100644 --- a/crates/flipperzero/src/gui/mod.rs +++ b/crates/flipperzero/src/gui/mod.rs @@ -9,6 +9,14 @@ pub mod view; pub mod view_dispatcher; pub mod view_port; +use core::{ + ffi::c_char, + ops::{Deref, DerefMut}, +}; + +use flipperzero_sys::{self as sys, furi::UnsafeRecord, Canvas as SysCanvas, Gui as SysGui}; +pub use gui_layer::*; + use crate::{ gui::{ canvas::CanvasView, @@ -16,13 +24,6 @@ use crate::{ }, input::InputEvent, }; -use core::{ - ffi::c_char, - ops::{Deref, DerefMut}, -}; -use flipperzero_sys::{self as sys, furi::UnsafeRecord, Canvas as SysCanvas, Gui as SysGui}; - -pub use gui_layer::*; /// System GUI wrapper. pub struct Gui { diff --git a/crates/flipperzero/src/gui/view.rs b/crates/flipperzero/src/gui/view.rs index 31356bc4..c896d82a 100644 --- a/crates/flipperzero/src/gui/view.rs +++ b/crates/flipperzero/src/gui/view.rs @@ -1,7 +1,9 @@ -use crate::{gui::canvas::CanvasView, input::InputEvent, internals::alloc::NonUniqueBox}; use core::ptr::NonNull; + use flipperzero_sys::{self as sys, View as SysView}; +use crate::{gui::canvas::CanvasView, input::InputEvent, internals::alloc::NonUniqueBox}; + /// UI view. pub struct View { inner: ViewInner, @@ -43,9 +45,10 @@ impl Drop for ViewInner { } } +#[allow(unused_variables)] pub trait ViewCallbacks { - fn on_draw(&mut self, _canvas: CanvasView) {} - fn on_input(&mut self, _event: InputEvent) {} + fn on_draw(&mut self, canvas: CanvasView) {} + fn on_input(&mut self, event: InputEvent) {} // TODO: the remaining callbacks and actual usage of callbacks } diff --git a/crates/flipperzero/src/gui/view_dispatcher/mod.rs b/crates/flipperzero/src/gui/view_dispatcher/mod.rs index c445b36e..3b8ae2e3 100644 --- a/crates/flipperzero/src/gui/view_dispatcher/mod.rs +++ b/crates/flipperzero/src/gui/view_dispatcher/mod.rs @@ -1,22 +1,80 @@ mod r#type; -use crate::internals::alloc::NonUniqueBox; +use alloc::collections::BTreeSet; use core::{ ffi::c_void, + marker::PhantomData, num::NonZeroU32, ptr::{self, NonNull}, }; -use flipperzero_sys::{self as sys, ViewDispatcher as SysViewDispatcher}; +use flipperzero_sys::{self as sys, ViewDispatcher as SysViewDispatcher}; pub use r#type::*; -pub struct ViewDispatcher { +use crate::{ + gui::{view::View, Gui}, + internals::alloc::NonUniqueBox, +}; + +type ViewSet = BTreeSet; + +pub mod view_id { + + /// Special view ID which hides drawing view_port. + const NONE: u32 = 0xFFFFFFFF; + + /// Special view ID which ignores navigation event. + const IGNORE: u32 = 0xFFFFFFFE; +} + +pub struct ViewDispatcher<'a, C: ViewDispatcherCallbacks, const QUEUE: bool = true> { inner: ViewDispatcherInner, - callbacks: NonUniqueBox, + context: NonUniqueBox>, + _phantom: PhantomData<&'a mut Gui>, +} + +struct Context { + view_dispatcher: NonNull, + callbacks: C, + // TODO: propose API to Flipper for checked view addition/removal + views: ViewSet, } -impl ViewDispatcher { - pub fn new(callbacks: C) -> Self { +impl<'a, C: ViewDispatcherCallbacks, const QUEUE: bool> ViewDispatcher<'a, C, QUEUE> { + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// # use flipperzero::{ + /// # gui::{ + /// # view_dispatcher::{ + /// # ViewDispatcher, ViewDispatcherCallbacks, + /// # ViewDispatcherRef, ViewDispatcherOps, ViewDispatcherType, + /// # }, + /// # Gui, + /// # }, + /// # log, + /// # }; + /// struct MyCallbacks { + /// value: u32, + /// } + /// impl ViewDispatcherCallbacks for MyCallbacks { + /// fn on_custom(&mut self, view_dispatcher: ViewDispatcherRef<'_>, event: u32) -> bool { + /// log!("{} + {} = {}", self.value, event, self.value + event); + /// true + /// } + /// } + /// let mut gui = Gui::new(); + /// let mut view_dispatcher = ViewDispatcher::new(MyCallbacks { + /// value: 10 + /// }, &mut gui, ViewDispatcherType::Fullscreen); + /// + /// view_dispatcher.send_custom_event(20); + /// // should print `10 + 20 = 30` + /// ``` + pub fn new(callbacks: C, gui: &'a Gui, kind: ViewDispatcherType) -> Self { // discover which callbacks should be registered let register_custom_event = !ptr::eq( C::on_custom as *const c_void, @@ -33,10 +91,27 @@ impl ViewDispatcher { .then(|| callbacks.tick_period()); let inner = ViewDispatcherInner::new(); - let callbacks = NonUniqueBox::new(callbacks); + let context = NonUniqueBox::new(Context { + view_dispatcher: inner.0, + callbacks, + views: BTreeSet::new(), + }); + + { + let raw = inner.0.as_ptr(); + let gui = gui.as_raw(); + let kind = kind.into(); + // SAFETY: both pointers are valid and `kind` is a valid numeric value + // and the newly created view dispatcher does not have a Gui yet + unsafe { sys::view_dispatcher_attach_to_gui(raw, gui, kind) }; + } // SAFETY: both pointers are guaranteed to be non-null - let view_dispatcher = Self { inner, callbacks }; + let view_dispatcher = Self { + inner, + context, + _phantom: PhantomData, + }; let raw = view_dispatcher.as_raw(); if QUEUE { @@ -48,7 +123,7 @@ impl ViewDispatcher { // and store context if at least one event should be registered if register_custom_event || register_navigation_callback || tick_period.is_some() { - let context = view_dispatcher.callbacks.as_ptr().cast(); + let context = view_dispatcher.context.as_ptr().cast(); // SAFETY: `raw` is valid // and `callbacks` is valid and lives with this struct unsafe { sys::view_dispatcher_set_event_callback_context(raw, context) }; @@ -59,10 +134,18 @@ impl ViewDispatcher { context: *mut c_void, event: u32, ) -> bool { - let context: *mut C = context.cast(); + let context: *mut Context = context.cast(); // SAFETY: `context` is stored in a `Box` which is a member of `ViewDispatcher` // and the callback is accessed exclusively by this function - unsafe { &mut *context }.on_custom(event) + let context = unsafe { &mut *context }; + context.callbacks.on_custom( + ViewDispatcherRef { + raw: context.view_dispatcher, + views: &mut context.views, + _phantom: PhantomData, + }, + event, + ) } let callback = Some(dispatch_custom:: as _); @@ -74,10 +157,15 @@ impl ViewDispatcher { pub unsafe extern "C" fn dispatch_navigation( context: *mut c_void, ) -> bool { - let context: *mut C = context.cast(); + let context: *mut Context = context.cast(); // SAFETY: `context` is stored in a `Box` which is a member of `ViewDispatcher` // and the callback is accessed exclusively by this function - unsafe { &mut *context }.on_navigation() + let context = unsafe { &mut *context }; + context.callbacks.on_navigation(ViewDispatcherRef { + raw: context.view_dispatcher, + views: &mut context.views, + _phantom: PhantomData, + }) } let callback = Some(dispatch_navigation:: as _); @@ -89,10 +177,15 @@ impl ViewDispatcher { pub unsafe extern "C" fn dispatch_tick( context: *mut c_void, ) { - let context: *mut C = context.cast(); + let context: *mut Context = context.cast(); // SAFETY: `context` is stored in a `Box` which is a member of `ViewDispatcher` // and the callback is accessed exclusively by this function - unsafe { &mut *context }.on_tick(); + let context = unsafe { &mut *context }; + context.callbacks.on_tick(ViewDispatcherRef { + raw: context.view_dispatcher, + views: &mut context.views, + _phantom: PhantomData, + }); } let tick_period = tick_period.get(); @@ -107,34 +200,154 @@ impl ViewDispatcher { #[inline] #[must_use] - pub fn as_raw(&self) -> *mut SysViewDispatcher { + pub const fn as_raw(&self) -> *mut SysViewDispatcher { self.inner.0.as_ptr() } } -impl ViewDispatcher { - pub fn run(&mut self) { +impl<'a, C: ViewDispatcherCallbacks> ViewDispatcher<'a, C, false> { + // /// Creates a new view dispatcher without a queue. + // /// + // /// This is equivalent to calling: [`ViewDispatcher::new`] with `QUEUE` set to `false`. + // pub fn with_no_queue(callbacks: C) -> Self { + // Self::new(callbacks) + // } + + /// Enables the queue for this view dispatcher. + /// + /// # Examples + /// + /// ``` + /// # use flipperzero::gui::view_dispatcher::ViewDispatcher; + /// // create a view dispatcher with no queue + /// let view_dispatcher = ViewDispatcher::with_no_queue(()); + /// // ... do something ... + /// // and now enable the queue for the view dispatcher + /// let view_dispatcher = view_dispatcher.enable_queue(); + /// ``` + pub fn enable_queue(self) -> ViewDispatcher<'a, C, true> { + // SAFETY: `raw` is a valid pointer + // and corresponds to a `ViewDispatcher` + // which does not have a queue yet let raw = self.as_raw(); - // SAFETY: `raw` is valid - // and this is a `ViewDispatcher` with a queue - unsafe { sys::view_dispatcher_run(raw) }; + unsafe { sys::view_dispatcher_enable_queue(raw) }; + + ViewDispatcher { + inner: self.inner, + context: self.context, + _phantom: self._phantom, + } } +} - pub fn stop(&mut self) { +impl<'a, C: ViewDispatcherCallbacks> ViewDispatcher<'a, C, true> { + /// Runs this view dispatcher. + /// + /// This will block until the view dispatcher gets stopped. + pub fn run(self) -> Self { let raw = self.as_raw(); // SAFETY: `raw` is valid // and this is a `ViewDispatcher` with a queue - unsafe { sys::view_dispatcher_stop(raw) }; + unsafe { sys::view_dispatcher_run(raw) }; + self } +} - pub fn send_custom_event(&mut self, event: u32) { - let raw = self.as_raw(); - // SAFETY: `raw` is valid - // and this is a `ViewDispatcher` with a queue +/// Reference to a ViewDispatcher. +pub struct ViewDispatcherRef<'a> { + raw: NonNull, + views: &'a mut ViewSet, + _phantom: PhantomData<&'a mut SysViewDispatcher>, +} + +/// Operations on an initialized view dispatcher which has a queue associated with it. +pub trait ViewDispatcherOps: internals::InitViewDispatcherRaw { + fn send_custom_event(&mut self, event: u32) { + let raw = self.raw(); + // SAFETY: `raw` should be valid and point to a ViewDispatcher with a queue unsafe { sys::view_dispatcher_send_custom_event(raw, event) }; } + + /// Stops this view dispatcher. + /// + /// This will make the [ViewDispatcher::<_, true>::run] caller unfreeze. + fn stop(&mut self) { + let raw = self.raw(); + // SAFETY: `raw` should be valid and point to a ViewDispatcher with a queue + unsafe { sys::view_dispatcher_stop(raw) }; + } + + // fn add_view(&mut self, id: u32, view: &mut View<'_>) { + // if self.views().insert(id) { + // let raw = self.raw(); + // unsafe { sys::view_dispatcher_add_view(raw, id) }; + // } + // } + + fn switch_to_view(&mut self, id: u32) { + if self.views().contains(&id) { + let raw = self.raw(); + unsafe { sys::view_dispatcher_switch_to_view(raw, id) }; + } + } + + fn remove_view(&mut self, id: u32) -> Option<()> { + if self.views_mut().remove(&id) { + let raw = self.raw(); + unsafe { sys::view_dispatcher_remove_view(raw, id) } + Some(()) + } else { + None + } + } +} +impl ViewDispatcherOps for T {} + +unsafe impl internals::InitViewDispatcherRaw + for ViewDispatcher<'_, C, true> +{ + #[inline(always)] + fn raw(&self) -> *mut SysViewDispatcher { + self.inner.0.as_ptr() + } + + #[inline(always)] + fn views(&self) -> &ViewSet { + let context = self.context.as_ptr(); + // SAFETY: if this method is accessed through `ViewDispatcher` + // then no one else should be able to use it + &unsafe { &*context }.views + } + + #[inline(always)] + fn views_mut(&mut self) -> &mut ViewSet { + let context = self.context.as_ptr(); + // SAFETY: if this method is accessed through `ViewDispatcher` + // then no one else should be able to use it + &mut unsafe { &mut *context }.views + } } +unsafe impl internals::InitViewDispatcherRaw for ViewDispatcherRef<'_> { + #[inline(always)] + fn raw(&self) -> *mut SysViewDispatcher { + self.raw.as_ptr() + } + + #[inline(always)] + fn views(&self) -> &ViewSet { + self.views + } + + #[inline(always)] + fn views_mut(&mut self) -> &mut ViewSet { + self.views + } +} + +/// Internal representation of view dispatcher. +/// This is a thin non-null pointer to [`SysViewDispatcher`] +/// which performs its automatic [allocation][Self::new] and [deallocation](Self::drop). struct ViewDispatcherInner(NonNull); impl ViewDispatcherInner { @@ -153,14 +366,20 @@ impl Drop for ViewDispatcherInner { } } +#[allow(unused_variables)] pub trait ViewDispatcherCallbacks { - fn on_custom(&mut self, _event: u32) -> bool { - true + /// Handles a custom event, + /// + /// + fn on_custom(&mut self, view_dispatcher: ViewDispatcherRef<'_>, event: u32) -> bool { + false } - fn on_navigation(&mut self) -> bool { - true + + fn on_navigation(&mut self, view_dispatcher: ViewDispatcherRef<'_>) -> bool { + false } - fn on_tick(&mut self) {} + + fn on_tick(&mut self, view_dispatcher: ViewDispatcherRef<'_>) {} #[must_use] fn tick_period(&self) -> NonZeroU32 { @@ -175,3 +394,21 @@ impl ViewDispatcherCallbacks for () { NonZeroU32::MAX } } + +mod internals { + use super::{SysViewDispatcher, ViewSet}; + + /// A structure wrapping a raw [`SysViewDispatcher`] with an initialized queue. + /// + /// # Safety + /// + /// This trait should be implemented so that the provided pointer is always valid + /// and points to the [`SysViewDispatcher`] which has a queue. + pub unsafe trait InitViewDispatcherRaw { + fn raw(&self) -> *mut SysViewDispatcher; + + fn views(&self) -> &ViewSet; + + fn views_mut(&mut self) -> &mut ViewSet; + } +} diff --git a/crates/flipperzero/src/gui/view_dispatcher/type.rs b/crates/flipperzero/src/gui/view_dispatcher/type.rs index cb155616..919b4405 100644 --- a/crates/flipperzero/src/gui/view_dispatcher/type.rs +++ b/crates/flipperzero/src/gui/view_dispatcher/type.rs @@ -1,8 +1,10 @@ -use crate::internals::macros::impl_std_error; use core::fmt::{self, Display, Formatter}; + use flipperzero_sys::{self as sys, ViewDispatcherType as SysViewDispatcherType}; use ufmt::{derive::uDebug, uDisplay, uWrite, uwrite}; +use crate::internals::macros::impl_std_error; + /// View dispatcher view port placement. /// /// Corresponds to raw [`SysViewDispatcherType`]. diff --git a/crates/flipperzero/src/gui/view_port/mod.rs b/crates/flipperzero/src/gui/view_port/mod.rs index 8099f70e..bb7e7935 100644 --- a/crates/flipperzero/src/gui/view_port/mod.rs +++ b/crates/flipperzero/src/gui/view_port/mod.rs @@ -2,19 +2,20 @@ mod orientation; -use crate::{gui::canvas::CanvasView, input::InputEvent, internals::alloc::NonUniqueBox}; use core::{ ffi::c_void, num::NonZeroU8, ptr::{self, NonNull}, }; + use flipperzero_sys::{ self as sys, Canvas as SysCanvas, ViewPort as SysViewPort, ViewPortOrientation as SysViewPortOrientation, }; - pub use orientation::*; +use crate::{gui::canvas::CanvasView, input::InputEvent, internals::alloc::NonUniqueBox}; + /// System ViewPort. pub struct ViewPort { inner: ViewPortInner, @@ -366,9 +367,10 @@ impl Drop for ViewPortInner { } } +#[allow(unused_variables)] pub trait ViewPortCallbacks { - fn on_draw(&mut self, _canvas: CanvasView<'_>) {} - fn on_input(&mut self, _event: InputEvent) {} + fn on_draw(&mut self, canvas: CanvasView<'_>) {} + fn on_input(&mut self, event: InputEvent) {} } impl ViewPortCallbacks for () {} diff --git a/crates/flipperzero/src/gui/view_port/orientation.rs b/crates/flipperzero/src/gui/view_port/orientation.rs index d9aa79a5..79bbb39e 100644 --- a/crates/flipperzero/src/gui/view_port/orientation.rs +++ b/crates/flipperzero/src/gui/view_port/orientation.rs @@ -1,8 +1,10 @@ -use crate::internals::macros::impl_std_error; use core::fmt::{self, Display, Formatter}; + use flipperzero_sys::{self as sys, ViewPortOrientation as SysViewPortOrientation}; use ufmt::{derive::uDebug, uDisplay, uWrite, uwrite}; +use crate::internals::macros::impl_std_error; + /// Orientation of a view port. /// /// Corresponds to raw [`SysViewPortOrientation`]. diff --git a/crates/flipperzero/src/internals.rs b/crates/flipperzero/src/internals.rs index 2a3408a8..9f8bf477 100644 --- a/crates/flipperzero/src/internals.rs +++ b/crates/flipperzero/src/internals.rs @@ -84,8 +84,10 @@ pub(crate) mod alloc { /// /// While there are no `unsafe` methods in this struct, /// it is easy to misuse the pointers provided by its methods, namely: + /// /// * [`NonUniqueBox::as_ptr`] /// * [`NonUniqueBox::as_non_null`] + /// /// so it should be used with extra care, i.e. all uses of the pointers /// should follow the rules such as stacked borrows /// and should never be used after the drop of this structure. From 6a1c1c72562b31477ff914bfb9bbe17c5779e6b1 Mon Sep 17 00:00:00 2001 From: Petr Portnov Date: Sun, 16 Jul 2023 13:56:31 +0300 Subject: [PATCH 49/49] chore: simplify `div_ceil_u8` --- crates/flipperzero/src/internals.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/flipperzero/src/internals.rs b/crates/flipperzero/src/internals.rs index 9f8bf477..6113b328 100644 --- a/crates/flipperzero/src/internals.rs +++ b/crates/flipperzero/src/internals.rs @@ -201,7 +201,7 @@ pub(crate) mod ops { { let quotient = divident / divisor; let remainder = divident % divisor; - if remainder > 0 && divisor > 0 { + if remainder != 0 { quotient + 1 } else { quotient