dusk-ui 0.2.0

Discord UI framework for interact message using twilight API.
Documentation
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;

use twilight_model::channel::message;
use twilight_model::channel::message::component::SelectMenuOption;
use twilight_model::channel::message::ReactionType;
use twilight_model::gateway::payload::incoming::InteractionCreate;

use rand::distributions::Alphanumeric;
use rand::Rng;

use crate::component::Component;
use crate::context::{BuildContextPrefix, Callback, Context};

pub struct SelectMenu<D> {
    phantom: std::marker::PhantomData<D>,
    pub id: String,
    pub disabled: bool,
    pub max_values: Option<u8>,
    pub min_values: Option<u8>,
    pub options: Vec<SelectMenuOption>,
    pub placeholder: Option<String>,
    pub on_change: Option<Callback<D>>,
}

impl<D> SelectMenu<D> {
    pub fn new() -> Self {
        Self {
            id: rand::thread_rng()
                .sample_iter(&Alphanumeric)
                .take(7)
                .map(char::from)
                .collect(),
            ..Default::default()
        }
    }

    pub fn id<S: Into<String>>(mut self, id: S) -> Self {
        self.id = id.into();
        self
    }

    pub fn disabled(mut self, disabled: bool) -> Self {
        self.disabled = disabled;
        self
    }

    pub fn max_values(mut self, max_values: u8) -> Self {
        self.max_values = Some(max_values);
        self
    }

    pub fn min_values(mut self, min_values: u8) -> Self {
        self.min_values = Some(min_values);
        self
    }

    pub fn options(mut self, options: Vec<SelectOption>) -> Self {
        self.options = options.into_iter().map(|e| e.into()).collect();
        self
    }

    pub fn placeholder<S: Into<String>>(mut self, placeholder: S) -> Self {
        self.placeholder = Some(placeholder.into());
        self
    }

    pub fn on_change<
        F: 'static
            + Fn(
                &Box<InteractionCreate>,
                &Arc<Context<D>>,
                D,
            ) -> Pin<Box<dyn Future<Output = D> + Send + Sync>>
            + Send
            + Sync,
    >(
        mut self,
        f: F,
    ) -> Self {
        self.on_change = Some(Box::new(f));
        self
    }
}

impl<D> Default for SelectMenu<D> {
    fn default() -> Self {
        Self {
            phantom: std::marker::PhantomData,
            id: String::new(),
            disabled: false,
            max_values: None,
            min_values: None,
            options: Vec::new(),
            placeholder: None,
            on_change: None,
        }
    }
}

impl<D> Component<D> for SelectMenu<D> {
    fn build(
        mut self: Box<Self>,
        ctx: BuildContextPrefix<D>,
    ) -> twilight_model::channel::message::Component {
        let id = format!("{}.{}", ctx.prefix, self.id);
        if let Some(on_change) = self.on_change.take() {
            ctx.parent.binding.insert(id.clone(), on_change);
        }
        let comp = message::Component::SelectMenu(message::component::SelectMenu {
            custom_id: id,
            disabled: self.disabled,
            max_values: self.max_values,
            min_values: self.min_values,
            options: self.options,
            placeholder: self.placeholder,
        });
        message::Component::ActionRow(message::component::ActionRow {
            components: vec![comp],
        })
    }
}

pub struct SelectOption {
    pub default: bool,
    pub description: Option<String>,
    pub emoji: Option<ReactionType>,
    pub label: String,
    pub value: String,
}

impl Default for SelectOption {
    fn default() -> Self {
        Self::new()
    }
}

impl SelectOption {
    pub fn new() -> Self {
        Self {
            default: false,
            description: None,
            emoji: None,
            label: String::new(),
            value: String::new(),
        }
    }

    pub fn is_default(mut self, default: bool) -> Self {
        self.default = default;
        self
    }

    pub fn description<S: Into<String>>(mut self, description: S) -> Self {
        self.description = Some(description.into());
        self
    }

    pub fn emoji(mut self, emoji: ReactionType) -> Self {
        self.emoji = Some(emoji);
        self
    }

    pub fn label<S: Into<String>>(mut self, label: S) -> Self {
        self.label = label.into();
        self
    }

    pub fn value<S: Into<String>>(mut self, value: S) -> Self {
        self.value = value.into();
        self
    }
}

impl Into<message::component::SelectMenuOption> for SelectOption {
    fn into(self) -> message::component::SelectMenuOption {
        message::component::SelectMenuOption {
            default: self.default,
            description: self.description,
            emoji: self.emoji,
            label: self.label,
            value: self.value,
        }
    }
}