libcosmic/src/dialog/file_chooser/mod.rs
2023-08-16 16:59:15 +02:00

216 lines
6 KiB
Rust

// Copyright 2023 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0
//! Dialogs for opening and save files.
pub mod open;
pub mod save;
pub use ashpd::desktop::file_chooser::{Choice, FileFilter, SelectedFiles};
use iced::futures::{channel, SinkExt, StreamExt};
use iced::{Command, Subscription};
use std::sync::atomic::{AtomicBool, Ordering};
use thiserror::Error;
/// Prevents duplicate file chooser dialog requests.
static OPENED: AtomicBool = AtomicBool::new(false);
/// Whether a file chooser dialog is currently active.
fn dialog_active() -> bool {
OPENED.load(Ordering::Relaxed)
}
/// Sets the existence of a file chooser dialog.
fn dialog_active_set(value: bool) {
OPENED.store(value, Ordering::SeqCst);
}
/// Creates an [`open::Dialog`] if no other file chooser exists.
pub fn open_file() -> Option<open::Dialog> {
if dialog_active() {
None
} else {
Some(open::Dialog::new())
}
}
/// Creates a [`save::Dialog`] if no other file chooser exists.
pub fn save_file() -> Option<save::Dialog> {
if dialog_active() {
None
} else {
Some(save::Dialog::new())
}
}
/// Creates a subscription for file chooser events.
pub fn subscription<M: Send + 'static>(handle: fn(Message) -> M) -> Subscription<M> {
let type_id = std::any::TypeId::of::<Handler<M>>();
iced::subscription::channel(type_id, 1, move |output| async move {
let mut state = Handler {
active: None,
handle,
output,
};
loop {
let (sender, mut receiver) = channel::mpsc::channel(1);
state.emit(Message::Init(Sender(sender))).await;
while let Some(request) = receiver.next().await {
match request {
Request::Close => state.close().await,
Request::Open(dialog) => {
state.open(dialog).await;
dialog_active_set(false);
}
Request::Save(dialog) => {
state.save(dialog).await;
dialog_active_set(false);
}
Request::Response => state.response().await,
}
}
}
})
}
/// Errors that my occur when interacting with the file chooser subscription
#[derive(Debug, Error)]
pub enum Error {
#[error("dialog close failed")]
Close(#[source] ashpd::Error),
#[error("dialog open failed")]
Open(#[source] ashpd::Error),
#[error("dialog response failed")]
Response(#[source] ashpd::Error),
}
/// Requests for the file chooser subscription
enum Request {
Close,
Open(open::Dialog),
Save(save::Dialog),
Response,
}
/// Messages from the file chooser subscription.
pub enum Message {
Closed,
Err(Error),
Init(Sender),
Opened,
Selected(SelectedFiles),
}
/// Sends requests to the file chooser subscription.
#[derive(Clone, Debug)]
pub struct Sender(channel::mpsc::Sender<Request>);
impl Sender {
/// Creates a [`Command`] that closes a file chooser dialog.
pub fn close(&mut self) -> Command<()> {
let mut sender = self.0.clone();
crate::command::future(async move {
let _res = sender.send(Request::Close).await;
()
})
}
/// Creates a [`Command`] that opens the file chooser.
pub fn open(&mut self, dialog: open::Dialog) -> Command<()> {
dialog_active_set(true);
let mut sender = self.0.clone();
crate::command::future(async move {
let _res = sender.send(Request::Open(dialog)).await;
()
})
}
/// Creates a [`Command`] that requests the response from a file chooser dialog.
pub fn response(&mut self) -> Command<()> {
let mut sender = self.0.clone();
crate::command::future(async move {
let _res = sender.send(Request::Response).await;
()
})
}
/// Creates a [`Command`] that opens a new save file dialog.
pub fn save(&mut self, dialog: save::Dialog) -> Command<()> {
dialog_active_set(true);
let mut sender = self.0.clone();
crate::command::future(async move {
let _res = sender.send(Request::Save(dialog)).await;
()
})
}
}
struct Handler<M> {
active: Option<ashpd::desktop::Request<SelectedFiles>>,
handle: fn(Message) -> M,
output: channel::mpsc::Sender<M>,
}
impl<M> Handler<M> {
/// Emits close request if there is an active dialog request.
async fn close(&mut self) {
if let Some(request) = self.active.take() {
if let Err(why) = request.close().await {
self.emit(Message::Err(Error::Close(why))).await;
}
}
}
async fn emit(&mut self, response: Message) {
let _res = self.output.send((self.handle)(response)).await;
}
/// Creates a new dialog, and closes any prior active dialogs.
async fn open(&mut self, dialog: open::Dialog) {
let response = match open::create(dialog).await {
Ok(request) => {
self.active = Some(request);
Message::Opened
}
Err(why) => Message::Err(Error::Open(why)),
};
self.emit(response).await;
}
/// Collects selected files from the active dialog.
async fn response(&mut self) {
if let Some(request) = self.active.as_ref() {
let response = match request.response() {
Ok(selected) => Message::Selected(selected),
Err(why) => Message::Err(Error::Response(why)),
};
self.emit(response).await;
}
}
/// Creates a new dialog, and closes any prior active dialogs.
async fn save(&mut self, dialog: save::Dialog) {
let response = match save::create(dialog).await {
Ok(request) => {
self.active = Some(request);
Message::Opened
}
Err(why) => Message::Err(Error::Open(why)),
};
self.emit(response).await;
}
}