1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
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) }
}