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 +}