use crate::{Context, Node, windowing::WindowHandle};
use std::future::Future;
type SyncAction<Msg> = Box<dyn FnOnce(&mut Context) -> Option<Msg> + Send>;
type AsyncAction<Msg> = Box<dyn FnOnce(WindowHandle<Msg>) + Send>;
pub struct Command<Msg: 'static + Send> {
pub(crate) sync_actions: Vec<SyncAction<Msg>>,
pub(crate) async_actions: Vec<AsyncAction<Msg>>,
}
impl<Msg: 'static + Send> Command<Msg> {
pub fn none() -> Self {
Self {
sync_actions: Vec::new(),
async_actions: Vec::new(),
}
}
pub fn perform<F>(action: F) -> Self
where
F: FnOnce(&mut Context) -> Option<Msg> + Send + 'static,
{
Self {
sync_actions: vec![Box::new(action)],
async_actions: Vec::new(),
}
}
pub fn perform_async<Fut, T, M>(future: Fut, mapper: M) -> Self
where
Fut: Future<Output = T> + Send + 'static,
T: Send + 'static,
M: FnOnce(T) -> Msg + Send + 'static,
{
Self {
sync_actions: Vec::new(),
async_actions: vec![Box::new(move |handle: WindowHandle<Msg>| {
std::thread::spawn(move || {
let result = pollster::block_on(future);
let msg = mapper(result);
let _ = handle.send(msg);
});
})],
}
}
pub fn batch(commands: impl IntoIterator<Item = Command<Msg>>) -> Self {
let mut combined = Self::none();
for cmd in commands {
combined.sync_actions.extend(cmd.sync_actions);
combined.async_actions.extend(cmd.async_actions);
}
combined
}
pub fn clipboard_set(text: impl Into<String>) -> Self {
let text = text.into();
Self::perform(move |ctx| {
ctx.clipboard_copy(crate::ClipboardData::Text(text));
None
})
}
pub fn focus(node: Node) -> Self {
Self::perform(move |ctx| {
ctx.request_focus(node);
None
})
}
pub fn clear_focus() -> Self {
Self::perform(|ctx| {
ctx.clear_focus();
None
})
}
pub fn into_actions(self) -> (Vec<SyncAction<Msg>>, Vec<AsyncAction<Msg>>) {
(self.sync_actions, self.async_actions)
}
pub fn is_empty(&self) -> bool {
self.sync_actions.is_empty() && self.async_actions.is_empty()
}
}
impl<Msg: 'static + Send> Default for Command<Msg> {
fn default() -> Self {
Self::none()
}
}
pub trait IntoCommand<Msg: 'static + Send> {
fn into_command(self) -> Command<Msg>;
}
impl<Msg: 'static + Send> IntoCommand<Msg> for () {
fn into_command(self) -> Command<Msg> {
Command::none()
}
}
impl<Msg: 'static + Send> IntoCommand<Msg> for Command<Msg> {
fn into_command(self) -> Command<Msg> {
self
}
}
impl<Msg: 'static + Send> IntoCommand<Msg> for Option<Command<Msg>> {
fn into_command(self) -> Command<Msg> {
self.unwrap_or_else(Command::none)
}
}
impl<Msg: 'static + Send> IntoCommand<Msg> for Vec<Command<Msg>> {
fn into_command(self) -> Command<Msg> {
Command::batch(self)
}
}
impl<Msg: 'static + Send, const N: usize> IntoCommand<Msg> for [Command<Msg>; N] {
fn into_command(self) -> Command<Msg> {
Command::batch(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_command_batch_and_into() {
let cmd1: Command<i32> = Command::none();
let cmd2: Command<i32> = Command::perform(|_| Some(42));
let batched = Command::batch([cmd1, cmd2]);
assert_eq!(batched.sync_actions.len(), 1);
assert_eq!(batched.async_actions.len(), 0);
let from_unit: Command<i32> = ().into_command();
assert!(from_unit.is_empty());
let from_option: Command<i32> = Some(Command::none()).into_command();
assert!(from_option.is_empty());
let from_vec: Command<i32> = vec![Command::none(), Command::none()].into_command();
assert!(from_vec.is_empty());
}
}