Skip to content

Commit

Permalink
Merge pull request #80 from mogenson/dialog-file-browser
Browse files Browse the repository at this point in the history
Dialogs app file browser wrapper
  • Loading branch information
dcoles authored Jun 19, 2023
2 parents dd1ba2a + 48a7128 commit befb4a8
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 20 deletions.
2 changes: 1 addition & 1 deletion crates/flipperzero/examples/dialog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ fn main(_args: *mut u8) -> i32 {
Some(CStr::from_bytes_with_nul(b"Scissor\0").unwrap()),
);

let button = dialogs.show(&message);
let button = dialogs.show_message(&message);

// ... or use dialog::alert() to display a simple message:
match button {
Expand Down
42 changes: 27 additions & 15 deletions crates/flipperzero/examples/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ extern crate flipperzero_alloc;

use core::ffi::CStr;

use flipperzero::dialogs::{DialogFileBrowserOptions, DialogsApp};
use flipperzero::furi::string::FuriString;
use flipperzero::io::*;
use flipperzero::println;
use flipperzero::storage::*;
Expand All @@ -22,10 +24,11 @@ entry!(main);

fn main(_args: *mut u8) -> i32 {
// First, we'll create a file on the SD card and write "Hello, Rust!" to it.
let path = CStr::from_bytes_with_nul(b"/ext/hello-rust.txt\0").unwrap();
let file = OpenOptions::new()
.write(true)
.create_always(true)
.open(CStr::from_bytes_with_nul(b"/ext/hello-rust.txt\0").unwrap());
.open(path);

match file {
Ok(mut handle) => {
Expand All @@ -36,21 +39,30 @@ fn main(_args: *mut u8) -> i32 {
Err(e) => println!("couldn't open path: {}", e),
}

// Now, we'll open it and read it back.
let mut buffer: [u8; 16] = [0; 16];
let file = OpenOptions::new()
.read(true)
.open(CStr::from_bytes_with_nul(b"/ext/hello-rust.txt\0").unwrap());
// Next, we'll open a file browser dialog and let the user select the file.
let mut dialogs_app = DialogsApp::open();
let file_browser_options = DialogFileBrowserOptions::new().set_hide_ext(false);
let mut start_path = FuriString::from(path);
let result_path =
dialogs_app.show_file_browser(Some(&mut start_path), Some(&file_browser_options));
if let Some(result_path) = result_path {
println!("file selected {}", result_path);
let path = result_path.as_c_str();

match file {
Ok(mut handle) => match handle.read(&mut buffer) {
Ok(n) => println!("Read from file: {:?}", &buffer[..n]),
Err(e) => println!("couldn't read from file: {}", e),
},
Err(e) => println!("couldn't open path: {}", e),
}
// Now, we'll open it and read it back.
let mut buffer: [u8; 16] = [0; 16];
let file = OpenOptions::new().read(true).open(path);

0
match file {
Ok(mut handle) => match handle.read(&mut buffer) {
Ok(n) => println!("Read from file: {:?}", &buffer[..n]),
Err(e) => println!("couldn't read from file: {}", e),
},
Err(e) => println!("couldn't open path: {}", e),
}
} else {
println!("no file selected");
}

// File is synchronized and closed when `f` goes out of scope.
0 // File is synchronized and closed when `file` goes out of scope.
}
108 changes: 104 additions & 4 deletions crates/flipperzero/src/dialogs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@
#[cfg(feature = "alloc")]
use alloc::ffi::CString;

use core::ffi::{c_char, CStr};
use core::ffi::{c_char, c_void, CStr};
use core::marker::PhantomData;
use core::ptr::{self, NonNull};

use flipperzero_sys as sys;
use flipperzero_sys::furi::UnsafeRecord;
use sys::{c_string, furi::UnsafeRecord};

use crate::furi::string::FuriString;
use crate::gui::canvas::Align;

/// A handle to the Dialogs app.
Expand All @@ -23,6 +24,12 @@ pub struct DialogMessage<'a> {
_phantom: PhantomData<&'a CStr>,
}

/// A dialog file browser options.
pub struct DialogFileBrowserOptions<'a> {
data: sys::DialogsFileBrowserOptions,
_phantom: PhantomData<&'a ()>,
}

/// Button pressed on a dialog.
pub enum DialogMessageButton {
Back,
Expand All @@ -42,12 +49,38 @@ impl DialogsApp {
}

/// Displays a message.
pub fn show(&mut self, message: &DialogMessage) -> DialogMessageButton {
pub fn show_message(&mut self, message: &DialogMessage) -> DialogMessageButton {
let button_sys =
unsafe { sys::dialog_message_show(self.data.as_ptr(), message.data.as_ptr()) };

DialogMessageButton::from_sys(button_sys).expect("Invalid button")
}

/// Displays a file browser.
/// - path is a optional preselected file path
/// - options are optional file browser options
pub fn show_file_browser(
&mut self,
path: Option<&mut FuriString>,
options: Option<&DialogFileBrowserOptions>,
) -> Option<FuriString> {
let mut result_path = FuriString::new();
// path will be unmodified but needs to be a valid FuriString.
// We can reuse the empty result_path if path is not provided.
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.data.as_ptr(),
result_path.as_mut_ptr(),
path,
options,
)
}
.then_some(result_path)
}
}

impl<'a> DialogMessage<'a> {
Expand Down Expand Up @@ -167,6 +200,73 @@ impl DialogMessageButton {
}
}

impl<'a> DialogFileBrowserOptions<'a> {
/// Creates a new dialog file browser options and initializes to default values.
pub fn new() -> Self {
Self {
// default values from sys::dialog_file_browser_set_basic_options()
data: sys::DialogsFileBrowserOptions {
extension: c_string!("*"),
base_path: ptr::null(),
skip_assets: true,
hide_dot_files: false,
icon: ptr::null(),
hide_ext: true,
item_loader_callback: None,
item_loader_context: ptr::null_mut(),
},
_phantom: PhantomData,
}
}

/// Set file extension to be offered for selection.
pub fn set_extension(mut self, extension: &'a CStr) -> Self {
self.data.extension = extension.as_ptr();
self
}

/// Set root folder path for navigation with back key.
pub fn set_base_path(mut self, base_path: &'a CStr) -> Self {
self.data.base_path = base_path.as_ptr();
self
}

/// Set file icon.
pub fn set_icon(mut self, icon: &'a sys::Icon) -> Self {
self.data.icon = icon as *const sys::Icon;
self
}

/// Do not show assets folder if true.
pub fn set_skip_assets(mut self, skip_assets: bool) -> Self {
self.data.skip_assets = skip_assets;
self
}

/// Hide dot files if true.
pub fn set_hide_dot_files(mut self, hide_dot_files: bool) -> Self {
self.data.hide_dot_files = hide_dot_files;
self
}

/// Hide extensions for files if true.
pub fn set_hide_ext(mut self, hide_ext: bool) -> Self {
self.data.hide_ext = hide_ext;
self
}

/// Set callback function and context for providing custom icon and entry name.
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
}
}

/// Displays a simple dialog.
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
Expand All @@ -181,5 +281,5 @@ pub fn alert(text: &str) {
message.set_text(&text, 0, 0, Align::Left, Align::Top);
message.set_buttons(None, Some(BUTTON_OK), None);

dialogs.show(&message);
dialogs.show_message(&message);
}
7 changes: 7 additions & 0 deletions crates/flipperzero/src/furi/string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,13 @@ impl FuriString {
unsafe { CStr::from_ptr(self.as_c_ptr()) }
}

/// Raw pointer to the inner sys::FuriString
#[inline]
#[must_use]
pub(crate) fn as_mut_ptr(&mut self) -> *mut sys::FuriString {
self.0.as_ptr()
}

/// Appends a given `FuriString` onto the end of this `FuriString`.
#[inline]
pub fn push_string(&mut self, string: &FuriString) {
Expand Down

0 comments on commit befb4a8

Please sign in to comment.