#[cfg(feature = "alloc")]
use alloc::ffi::CString;
use core::ffi::{CStr, c_void};
use core::marker::PhantomData;
use core::mem::MaybeUninit;
use core::ptr::{self, NonNull};
use flipperzero_sys as sys;
use sys::furi::UnsafeRecord;
use crate::furi::string::FuriString;
use crate::gui::canvas::Align;
#[derive(Clone)]
pub struct DialogsApp {
record: UnsafeRecord<sys::DialogsApp>,
}
pub struct DialogMessage<'a> {
data: NonNull<sys::DialogMessage>,
_phantom: PhantomData<&'a CStr>,
}
#[repr(transparent)]
pub struct DialogFileBrowserOptions<'a> {
data: sys::DialogsFileBrowserOptions,
_phantom: PhantomData<&'a ()>,
}
pub enum DialogMessageButton {
Back,
Left,
Right,
Center,
}
impl DialogsApp {
pub const NAME: &CStr = c"dialogs";
pub fn open() -> Self {
Self {
record: unsafe { UnsafeRecord::open(Self::NAME) },
}
}
#[inline]
pub fn as_ptr(&self) -> *mut sys::DialogsApp {
self.record.as_ptr()
}
pub fn show_message(&mut self, message: &DialogMessage) -> DialogMessageButton {
let button_sys = unsafe { sys::dialog_message_show(self.as_ptr(), message.data.as_ptr()) };
DialogMessageButton::from_sys(button_sys).expect("Invalid button")
}
pub fn show_file_browser(
&mut self,
path: Option<&mut FuriString>,
options: Option<&DialogFileBrowserOptions>,
) -> Option<FuriString> {
let mut result_path = FuriString::new();
let path = path.unwrap_or(&mut result_path).as_mut_ptr();
let options = options
.map(|opts| &opts.data as *const sys::DialogsFileBrowserOptions)
.unwrap_or(ptr::null());
unsafe {
sys::dialog_file_browser_show(self.as_ptr(), result_path.as_mut_ptr(), path, options)
}
.then_some(result_path)
}
}
impl<'a> DialogMessage<'a> {
pub fn new() -> Self {
let data = unsafe { NonNull::new_unchecked(sys::dialog_message_alloc()) };
Self {
data,
_phantom: PhantomData,
}
}
pub fn set_buttons(
&mut self,
left: Option<&'a CStr>,
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());
unsafe {
sys::dialog_message_set_buttons(self.data.as_ptr(), left, center, right);
}
}
pub fn set_header(
&mut self,
header: &'a CStr,
x: u8,
y: u8,
horizontal: Align,
vertical: Align,
) {
unsafe {
sys::dialog_message_set_header(
self.data.as_ptr(),
header.as_ptr(),
x,
y,
horizontal.to_sys(),
vertical.to_sys(),
);
}
}
pub fn set_text(
&mut self,
text: &'a CStr,
x: u8,
y: u8,
horizontal: Align,
vertical: Align,
) {
unsafe {
sys::dialog_message_set_text(
self.data.as_ptr(),
text.as_ptr(),
x,
y,
horizontal.to_sys(),
vertical.to_sys(),
);
}
}
pub fn clear_header(&mut self) {
unsafe {
sys::dialog_message_set_header(
self.data.as_ptr(),
ptr::null(),
0,
0,
sys::AlignLeft,
sys::AlignTop,
);
}
}
pub fn clear_text(&mut self) {
unsafe {
sys::dialog_message_set_text(
self.data.as_ptr(),
ptr::null(),
0,
0,
sys::AlignLeft,
sys::AlignTop,
);
}
}
}
impl Drop for DialogMessage<'_> {
fn drop(&mut self) {
unsafe {
sys::dialog_message_free(self.data.as_ptr());
}
}
}
impl Default for DialogMessage<'_> {
fn default() -> Self {
Self::new()
}
}
impl DialogMessageButton {
fn from_sys(sys: sys::DialogMessageButton) -> Option<Self> {
match sys {
sys::DialogMessageButtonBack => Some(Self::Back),
sys::DialogMessageButtonLeft => Some(Self::Left),
sys::DialogMessageButtonCenter => Some(Self::Center),
sys::DialogMessageButtonRight => Some(Self::Right),
_ => None,
}
}
}
impl Default for DialogFileBrowserOptions<'_> {
fn default() -> Self {
Self::new()
}
}
impl<'a> DialogFileBrowserOptions<'a> {
pub fn new() -> Self {
unsafe { Self::with_extension(c"*") }
}
pub unsafe fn with_extension(extension: &'a CStr) -> Self {
let mut options = MaybeUninit::<sys::DialogsFileBrowserOptions>::uninit();
let uninit_options = options.as_mut_ptr();
let extension = extension.as_ptr();
let icon = ptr::null();
unsafe { sys::dialog_file_browser_set_basic_options(uninit_options, extension, icon) };
Self {
data: unsafe { options.assume_init() },
_phantom: PhantomData,
}
}
pub unsafe fn set_extension(mut self, extension: &'a CStr) -> Self {
self.data.extension = extension.as_ptr();
self
}
pub unsafe fn set_base_path(mut self, base_path: &'a CStr) -> Self {
self.data.base_path = base_path.as_ptr();
self
}
pub fn set_icon(mut self, icon: &'a sys::Icon) -> Self {
self.data.icon = icon as *const sys::Icon;
self
}
pub fn set_skip_assets(mut self, skip_assets: bool) -> Self {
self.data.skip_assets = skip_assets;
self
}
pub fn set_hide_dot_files(mut self, hide_dot_files: bool) -> Self {
self.data.hide_dot_files = hide_dot_files;
self
}
pub fn set_hide_ext(mut self, hide_ext: bool) -> Self {
self.data.hide_ext = hide_ext;
self
}
pub fn set_item_loader_callback(
mut self,
callback: sys::FileBrowserLoadItemCallback,
context: *mut c_void,
) -> Self {
self.data.item_loader_callback = callback;
self.data.item_loader_context = context;
self
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
pub fn alert(text: &str) {
let text = CString::new(text.as_bytes()).unwrap();
let mut dialogs = DialogsApp::open();
let mut message = DialogMessage::new();
message.set_text(&text, 0, 0, Align::Left, Align::Top);
message.set_buttons(None, Some(c"OK"), None);
dialogs.show_message(&message);
}