cursive_extras/views/advanced/
button.rs

1use super::ButtonContent;
2use cursive_core::{
3    Cursive, impl_enabled,
4    Printer, Vec2, View, Rect,
5    direction::Direction,
6    view::CannotFocus,
7    event::{
8        Callback,
9        Event,
10        EventResult,
11        Key,
12        MouseButton,
13        MouseEvent
14    },
15    utils::markup::StyledString
16};
17use rust_utils::encapsulated;
18
19/// A special button view that can be multiple lines, have data attached to it, and also accepts `StyledStrings`
20#[derive(Clone)]
21#[encapsulated]
22pub struct AdvancedButton<D: Send + Sync + 'static = ()> {
23    // the content of this button (the "title")
24    title: ButtonContent,
25
26    // callback when Enter is pressed
27    callback: Callback,
28
29    // is it enabled
30    enabled: bool,
31
32    // does this button have brackets
33    has_brackets: bool,
34
35    // the greatest row width
36    width: usize,
37
38    // the data of this button
39    #[getter(mutable, doc = "Return a reference to the data")]
40    #[setter(doc = "Set the data")]
41    data: D,
42
43    // the view's current size if it has already been calculated
44    size_cache: Option<Vec2>
45}
46
47impl AdvancedButton {
48    /// Create a new `AdvancedButton` without data
49    #[must_use]
50    pub fn new<T: Into<StyledString>, F: Fn(&mut Cursive) + Send + Sync + 'static>(title: T, callback: F) -> AdvancedButton {
51        Self::new_with_data(title, (), callback)
52    }
53}
54
55impl<D: Send + Sync + 'static> AdvancedButton<D> {
56    impl_enabled!(self.enabled);
57
58    /// Create a new `AdvancedButton` with data
59    #[must_use]
60    pub fn new_with_data<T: Into<StyledString>, F: Fn(&mut Cursive) + Send + Sync + 'static>(title: T, data: D, callback: F) -> AdvancedButton<D> {
61        AdvancedButton {
62            title: ButtonContent::new(title),
63            width: 0,
64            callback: Callback::from_fn(callback),
65            has_brackets: false,
66            enabled: true,
67            data,
68            size_cache: None
69        }
70    }
71
72    /// Show or hide the brackets on this button
73    pub fn show_brackets(&mut self, show: bool) {
74        self.size_cache = None;
75        self.has_brackets = show;
76    }
77
78    /// Show the brackets on this button
79    #[must_use]
80    pub fn brackets(mut self) -> Self {
81        self.has_brackets = true;
82        self
83    }
84
85    /// Get the styled title of this button
86    pub fn title(&self) -> &StyledString { self.title.get_content() }
87
88    /// Set the title of this button
89    pub fn set_title<T: Into<StyledString>>(&mut self, title: T) {
90        self.size_cache = None;
91        self.title.set_content(title);
92    }
93
94    /// Set the callback for when Enter is pressed
95    pub fn set_callback<F: Fn(&mut Cursive) + Send + Sync + 'static>(&mut self, callback: F) { self.callback = Callback::from_fn(callback); }
96}
97
98impl<D: Send + Sync + 'static> View for AdvancedButton<D> {
99    fn draw(&self, printer: &Printer) {
100        self.title.draw(printer, (0, 0).into(), self.enabled, printer.focused, self.has_brackets);
101    }
102
103    fn required_size(&mut self, bound: Vec2) -> Vec2 {
104        if let Some(size) = self.size_cache {
105            if self.width > 0 { return size; }
106        }
107        self.title.fit_to_width(bound.x);
108        let size = self.title.size(self.has_brackets);
109        self.width = size.x;
110        size
111    }
112
113    fn layout(&mut self, size: Vec2) {
114        self.size_cache = Some(size);
115        self.title.fit_to_width(size.x);
116    }
117
118    fn on_event(&mut self, event: Event) -> EventResult {
119        if !self.enabled {
120            return EventResult::Ignored;
121        }
122
123        match event {
124            Event::Mouse {
125                event: MouseEvent::Release(MouseButton::Left),
126                position,
127                offset
128            } => {
129                let b_rect = Rect::from_size((0, 0),self.title.size(self.has_brackets));
130
131                if let Some(new_pos) = position.checked_sub(offset) {
132                    if b_rect.contains(new_pos) {
133                        EventResult::Consumed(Some(self.callback.clone()))
134                    }
135                    else { EventResult::Ignored }
136                }
137                else { EventResult::Ignored }
138            }
139
140            Event::Key(Key::Enter) => EventResult::Consumed(Some(self.callback.clone())),
141
142            Event::WindowResize => {
143                self.size_cache = None;
144                self.width = 0;
145                EventResult::Ignored
146            }
147
148            _ => EventResult::Ignored,
149        }
150    }
151
152    fn take_focus(&mut self, _: Direction) -> Result<EventResult, CannotFocus> { self.enabled.then(EventResult::consumed).ok_or(CannotFocus) }
153}