cursive-extras 0.13.4

Extra views for the Cursive TUI library as well some helper functions and macros
Documentation
use super::ButtonContent;
use cursive_core::{
    Cursive, impl_enabled,
    Printer, Vec2, View, Rect,
    direction::Direction,
    view::CannotFocus,
    event::{
        Callback,
        Event,
        EventResult,
        Key,
        MouseButton,
        MouseEvent
    },
    utils::markup::StyledString
};
use rust_utils::encapsulated;

/// A special button view that can be multiple lines, have data attached to it, and also accepts `StyledStrings`
#[derive(Clone)]
#[encapsulated]
pub struct AdvancedButton<D: Send + Sync + 'static = ()> {
    // the content of this button (the "title")
    title: ButtonContent,

    // callback when Enter is pressed
    callback: Callback,

    // is it enabled
    enabled: bool,

    // does this button have brackets
    has_brackets: bool,

    // the greatest row width
    width: usize,

    // the data of this button
    #[getter(mutable, doc = "Return a reference to the data")]
    #[setter(doc = "Set the data")]
    data: D,

    // the view's current size if it has already been calculated
    size_cache: Option<Vec2>
}

impl AdvancedButton {
    /// Create a new `AdvancedButton` without data
    #[must_use]
    pub fn new<T: Into<StyledString>, F: Fn(&mut Cursive) + Send + Sync + 'static>(title: T, callback: F) -> AdvancedButton {
        Self::new_with_data(title, (), callback)
    }
}

impl<D: Send + Sync + 'static> AdvancedButton<D> {
    impl_enabled!(self.enabled);

    /// Create a new `AdvancedButton` with data
    #[must_use]
    pub fn new_with_data<T: Into<StyledString>, F: Fn(&mut Cursive) + Send + Sync + 'static>(title: T, data: D, callback: F) -> AdvancedButton<D> {
        AdvancedButton {
            title: ButtonContent::new(title),
            width: 0,
            callback: Callback::from_fn(callback),
            has_brackets: false,
            enabled: true,
            data,
            size_cache: None
        }
    }

    /// Show or hide the brackets on this button
    pub fn show_brackets(&mut self, show: bool) {
        self.size_cache = None;
        self.has_brackets = show;
    }

    /// Show the brackets on this button
    #[must_use]
    pub fn brackets(mut self) -> Self {
        self.has_brackets = true;
        self
    }

    /// Get the styled title of this button
    pub fn title(&self) -> &StyledString { self.title.get_content() }

    /// Set the title of this button
    pub fn set_title<T: Into<StyledString>>(&mut self, title: T) {
        self.size_cache = None;
        self.title.set_content(title);
    }

    /// Set the callback for when Enter is pressed
    pub fn set_callback<F: Fn(&mut Cursive) + Send + Sync + 'static>(&mut self, callback: F) { self.callback = Callback::from_fn(callback); }
}

impl<D: Send + Sync + 'static> View for AdvancedButton<D> {
    fn draw(&self, printer: &Printer) {
        self.title.draw(printer, (0, 0).into(), self.enabled, printer.focused, self.has_brackets);
    }

    fn required_size(&mut self, bound: Vec2) -> Vec2 {
        if let Some(size) = self.size_cache {
            if self.width > 0 { return size; }
        }
        self.title.fit_to_width(bound.x);
        let size = self.title.size(self.has_brackets);
        self.width = size.x;
        size
    }

    fn layout(&mut self, size: Vec2) {
        self.size_cache = Some(size);
        self.title.fit_to_width(size.x);
    }

    fn on_event(&mut self, event: Event) -> EventResult {
        if !self.enabled {
            return EventResult::Ignored;
        }

        match event {
            Event::Mouse {
                event: MouseEvent::Release(MouseButton::Left),
                position,
                offset
            } => {
                let b_rect = Rect::from_size((0, 0),self.title.size(self.has_brackets));

                if let Some(new_pos) = position.checked_sub(offset) {
                    if b_rect.contains(new_pos) {
                        EventResult::Consumed(Some(self.callback.clone()))
                    }
                    else { EventResult::Ignored }
                }
                else { EventResult::Ignored }
            }

            Event::Key(Key::Enter) => EventResult::Consumed(Some(self.callback.clone())),

            Event::WindowResize => {
                self.size_cache = None;
                self.width = 0;
                EventResult::Ignored
            }

            _ => EventResult::Ignored,
        }
    }

    fn take_focus(&mut self, _: Direction) -> Result<EventResult, CannotFocus> { self.enabled.then(EventResult::consumed).ok_or(CannotFocus) }
}