pub mod components;
pub mod errors;
pub mod window;
use std::clone::Clone;
use std::fmt::Display;
use crate::window::{Dimensions, Location, Window};
pub enum RustofiResult {
Selection(String),
Action(String),
Success,
Blank,
Error,
Cancel,
Exit
}
pub trait RustofiCallback<T>: FnMut(&T) -> RustofiResult {
fn clone_boxed(&self) -> Box<dyn RustofiCallback<T>>;
}
impl<T, C> RustofiCallback<T> for C
where
C: 'static + Clone + FnMut(&T) -> RustofiResult
{
fn clone_boxed(&self) -> Box<dyn RustofiCallback<T>> {
Box::new(self.clone())
}
}
impl<T: 'static> Clone for Box<dyn RustofiCallback<T>> {
fn clone(&self) -> Self {
self.clone_boxed()
}
}
pub trait RustofiComponent<'a> {
fn create_window() -> Window<'a>;
fn action(self, acb: Box<dyn FnMut(&String) -> RustofiResult>) -> Self;
fn blank(self, bcb: Box<dyn FnMut() -> RustofiResult>) -> Self;
fn actions(self, actions: Vec<String>) -> Self;
fn window(self, window: Window<'a>) -> Self;
fn display(&mut self, prompt: String) -> RustofiResult;
}
pub struct AppPage<'a, T> {
pub items: Vec<T>,
pub item_callback: Box<dyn RustofiCallback<T>>,
pub blank_callback: Box<dyn FnMut() -> RustofiResult>,
pub actions: Vec<String>,
pub action_callback: Box<dyn FnMut(&String) -> RustofiResult>,
pub window: Window<'a>
}
impl<'a, T: Display + Clone> AppPage<'a, T> {
pub fn new(items: Vec<T>, item_callback: Box<dyn RustofiCallback<T>>) -> Self {
AppPage {
items,
item_callback,
actions: vec![" ".to_string(), "[exit]".to_string()],
blank_callback: Box::new(|| RustofiResult::Blank),
action_callback: Box::new(|_| RustofiResult::Action("".to_string())),
window: SearchPage::<T>::create_window()
}
}
pub fn message(mut self, message: &'static str) -> Self {
self.window = self.window.message(message);
self
}
}
impl<'a, T: Display + Clone> RustofiComponent<'a> for AppPage<'a, T> {
fn create_window() -> Window<'a> {
Window::new("AppList")
.format('s')
.location(Location::MiddleCentre)
.add_args(vec!["-markup-rows".to_string()])
}
fn action(mut self, acb: Box<dyn FnMut(&String) -> RustofiResult>) -> Self {
self.action_callback = acb;
self
}
fn blank(mut self, bcb: Box<dyn FnMut() -> RustofiResult>) -> Self {
self.blank_callback = bcb;
self
}
fn actions(mut self, mut actions: Vec<String>) -> Self {
actions.insert(0, " ".to_string());
actions.insert(0, "[exit]".to_string());
self.actions = actions;
self
}
fn window(mut self, window: Window<'a>) -> Self {
self.window = window.format('s'); self
}
fn display(&mut self, prompt: String) -> RustofiResult {
let mut display_options: Vec<String> = self.items.iter().map(|s| s.to_string()).collect();
display_options.append(self.actions.as_mut());
let response = self
.window
.clone()
.prompt(prompt)
.lines(display_options.len() as i32)
.show(display_options.clone());
match response {
Ok(input) => {
if input == "[exit]" || input == "" {
RustofiResult::Exit
} else if input == " " {
(self.blank_callback)()
} else {
for item in self.items.clone() {
if input == item.to_string() {
return (self.item_callback)(&item);
}
}
for item in self.actions.clone() {
if input == item.to_string() {
return (self.action_callback)(&input);
}
}
RustofiResult::Exit
}
}
Err(_) => RustofiResult::Error
}
}
}
pub struct SearchPage<'a, T> {
pub items: Vec<T>,
pub item_callback: Box<dyn RustofiCallback<T>>,
pub blank_callback: Box<dyn FnMut() -> RustofiResult>,
pub actions: Vec<String>,
pub action_callback: Box<dyn FnMut(&String) -> RustofiResult>,
pub search_callback: Box<dyn FnMut(&String) -> RustofiResult>,
pub window: Window<'a>
}
impl<'a, T: Display + Clone> SearchPage<'a, T> {
pub fn new(
items: Vec<T>, item_callback: Box<dyn RustofiCallback<T>>,
search_callback: Box<dyn FnMut(&String) -> RustofiResult>
) -> Self {
SearchPage {
items,
item_callback,
actions: vec![" ".to_string(), "[cancel]".to_string()],
blank_callback: Box::new(|| RustofiResult::Blank),
action_callback: Box::new(|_| RustofiResult::Action("".to_string())),
search_callback,
window: SearchPage::<T>::create_window()
}
}
}
impl<'a, T: Display + Clone> RustofiComponent<'a> for SearchPage<'a, T> {
fn create_window() -> Window<'a> {
Window::new("Search")
.format('s')
.location(Location::MiddleCentre)
.dimensions(Dimensions {
width: 640,
height: 480,
lines: 5,
columns: 4
})
.add_args(vec!["-markup-rows".to_string()])
}
fn action(mut self, acb: Box<dyn FnMut(&String) -> RustofiResult>) -> Self {
self.action_callback = acb;
self
}
fn blank(mut self, bcb: Box<dyn FnMut() -> RustofiResult>) -> Self {
self.blank_callback = bcb;
self
}
fn actions(mut self, mut actions: Vec<String>) -> Self {
actions.insert(0, " ".to_string());
actions.insert(0, "[exit]".to_string());
self.actions = actions;
self
}
fn window(mut self, window: Window<'a>) -> Self {
self.window = window.format('s'); self
}
fn display(&mut self, prompt: String) -> RustofiResult {
let mut display_options: Vec<String> = self.items.iter().map(|s| s.to_string()).collect();
display_options.append(self.actions.as_mut());
let response = self
.window
.clone()
.prompt(prompt)
.show(display_options.clone());
match response {
Ok(input) => {
if input == "[exit]" {
RustofiResult::Exit
} else if input == " " {
(self.blank_callback)()
} else if input == "" {
RustofiResult::Cancel
} else {
for item in self.items.clone() {
if input == item.to_string() {
return (self.item_callback)(&item);
}
}
for item in self.actions.clone() {
if input == item.to_string() {
return (self.action_callback)(&input);
}
}
(self.search_callback)(&input)
}
}
Err(_) => RustofiResult::Error
}
}
}