pelican_ui 2.2.0

Standard UI tools and components built off Pelican UI of the RAMP stack.
Documentation
use prism::event::{Event, TickEvent, OnEvent};
use prism::{Request, Context};
use prism::drawable::{Component, SizedTree};
use prism::layout::{Area, Wrap, Stack, Column};

use ptsd::interactions;

use crate::Callback;
use crate::theme::{Theme, Icons};
use crate::components::TextInput;
use crate::components::list_item::ListItem;
use crate::components::button::SecondaryButton;

#[derive(Debug, Component, Clone)]
pub struct SearchBar(Column, TextInput, SelectedItems, SearchableItems);
impl OnEvent for SearchBar {
    fn on_event(&mut self, _ctx: &mut Context, _sized: &SizedTree, event: Box<dyn Event>) -> Vec<Box<dyn Event>> {
        if event.downcast_ref::<TickEvent>().is_some() { self.3.sort(&self.1.value()); }
        vec![event]
    }
}

impl SearchBar {
    pub fn new(theme: &Theme, items: Vec<ListItem>) -> Self {
        SearchBar(Column::start(12.0), TextInput::default(theme), SelectedItems::new(theme, vec![]), SearchableItems::new(theme, items))
    }

    pub fn results(&self) -> Vec<ListItem> {
        // println!("{:?}", self.2.1);
        self.2.1.iter().map(|p| p.3.clone()).collect::<Vec<_>>()
    }
}

#[derive(Debug, Component, Clone)]
pub struct SelectedItems(Wrap, Vec<SearchPill>, #[skip] Theme);
impl OnEvent for SelectedItems {
    fn on_event(&mut self, ctx: &mut Context, _sized: &SizedTree, event: Box<dyn Event>) -> Vec<Box<dyn Event>> {
        if let Some(event) = event.downcast_ref::<SearchbarEvent>() {
            match event {
                SearchbarEvent::Remove(i) => if let Some(pos) = self.1.iter().position(|item| item.2 == *i) {
                    let removed = self.1.remove(pos);
                    ctx.send(Request::event(SearchbarEvent::Store(removed.3)))
                },
                SearchbarEvent::Relist(item) => {
                    let i = self.1.len();
                    self.1.push(SearchPill::new(
                        SecondaryButton::medium(&self.2, Icons::Close, &item.title().clone(), None, move |ctx: &mut Context, _theme: &Theme| {
                            ctx.send(Request::event(SearchbarEvent::Remove(i)))
                        }), i, item.clone()
                    ))
                },
                _ => {}
            }
        }
        vec![event]
    }
}

impl SelectedItems {
    pub fn new(theme: &Theme, items: Vec<SearchBarListItem>) -> Self {
        SelectedItems(Wrap::start(8.0, 8.0), items.into_iter().enumerate().map(|(i, item)| 
            SearchPill::new(SecondaryButton::medium(theme, Icons::Close, &item.3.clone(), None, move |ctx: &mut Context, _theme: &Theme| {
                ctx.send(Request::event(SearchbarEvent::Remove(i)))
            }), i, item.4.clone())).collect::<Vec<_>>(), theme.clone()
        )
    }
}

#[derive(Debug, Component, Clone)]
pub struct SearchableItems(Column, Vec<SearchBarListItem>, #[skip] Theme);
impl OnEvent for SearchableItems {
    fn on_event(&mut self, ctx: &mut Context, _sized: &SizedTree, event: Box<dyn Event>) -> Vec<Box<dyn Event>> {
        if let Some(event) = event.downcast_ref::<SearchbarEvent>() {
            match event {
                SearchbarEvent::Select(i) => if let Some(pos) = self.1.iter().position(|item| item.2 == *i) {
                    let removed = self.1.remove(pos);
                    ctx.send(Request::event(SearchbarEvent::Relist(removed.4)))
                },
                SearchbarEvent::Store(item) => {
                    let i = self.1.len();
                    self.1.push(SearchBarListItem::new(&self.2, item.clone(), Box::new(move |ctx: &mut Context, _theme: &Theme| {
                        ctx.send(Request::event(SearchbarEvent::Select(i)))
                    }), i))
                },
                _ => {}
            }
        }
        vec![event]
    }
}

impl SearchableItems {
    pub fn new(theme: &Theme, items: Vec<ListItem>) -> Self {
        SearchableItems(Column::default(), items.into_iter().enumerate().map(|(i, item)| 
            SearchBarListItem::new(theme, item, Box::new(move |ctx: &mut Context, _theme: &Theme| {
                ctx.send(Request::event(SearchbarEvent::Select(i)))
            }), i)).collect::<Vec<_>>(), theme.clone()
        )
    }

    pub fn sort(&mut self, val: &str) {
        let val = &val.to_lowercase();
        self.1.sort_by_key(|item| {
            let s = &item.3.to_lowercase();

            match s == val {
                true => 0,
                false if s.starts_with(val) => 1,
                false if s.contains(val) => 2,
                false => 3
            }
        });
    }
}

#[derive(Debug, Component, Clone)]
pub struct SearchPill(Stack, SecondaryButton, #[skip] usize, #[skip] ListItem);
impl OnEvent for SearchPill {}
impl SearchPill {
    pub fn new(button: SecondaryButton, i: usize, item: ListItem) -> Self {
        SearchPill(Stack::default(), button, i, item)
    }
}

#[derive(Clone, Debug)]
pub enum SearchbarEvent {
    Remove(usize),
    Select(usize),
    Relist(ListItem),
    Store(ListItem),
}

impl Event for SearchbarEvent {
    fn pass(self: Box<Self>, _ctx: &mut Context, children: &[Area]) -> Vec<Option<Box<dyn Event>>> {
        children.iter().map(|_| Some(self.clone() as Box<dyn Event>)).collect()
    }
}

#[derive(Component, Debug, Clone)]
pub struct SearchBarListItem(Stack, pub interactions::Button, #[skip] usize, #[skip] String, #[skip] ListItem);
impl OnEvent for SearchBarListItem {}
impl SearchBarListItem {
    pub fn new(theme: &Theme, item: ListItem, mut callback: Box<dyn Callback>, i: usize) -> Self {
        let theme = theme.clone();
        let callback = Box::new(move |ctx: &mut Context| (callback)(ctx, &theme));
        let button = interactions::Button::new(item.clone(), None::<ListItem>, None::<ListItem>, None::<ListItem>, callback, false);
        SearchBarListItem(Stack::default(), button, i, item.title().to_string(), item)
    }
}