rustty_oxide/
dialog.rs

1use std::collections::HashMap;
2use std::boxed::Box;
3
4use rustty::{CellAccessor, Size, HasSize};
5
6use core::{
7    Alignable,
8    HorizontalAlign,
9    VerticalAlign,
10    Widget,
11    Frame,
12    Button,
13    ButtonResult,
14    Layout,
15    Painter
16};
17
18use label::Label;
19
20/// Pack labels, buttons and other widgets into dialogs
21///
22/// # Examples
23///
24/// ```
25/// use oxide::core::{VerticalAlign, HorizontalAlign, ButtonResult, Widget};
26/// use oxide::{Dialog, StdButton};
27///
28/// let mut maindlg = Dialog::new(60, 10);
29///
30/// let mut b1 = StdButton::new("Quit", 'q', ButtonResult::Ok);
31/// b1.pack(&maindlg, HorizontalAlign::Left, VerticalAlign::Middle, (1,1));
32///
33/// maindlg.draw_box();
34/// // draw to terminal
35/// // maindlg.draw(&mut term);
36/// ```
37///
38pub struct Dialog {
39    frame: Frame,
40    buttons: Vec<Box<Button>>,
41    layouts: Vec<Box<Layout>>,
42    accel2result: HashMap<char, ButtonResult>,
43}
44
45
46impl Dialog {
47    /// Construct a new Dialog widget `cols` wide by `rows` high.
48    /// 
49    /// # Examples
50    ///
51    /// ```
52    /// use oxide::Dialog;
53    ///
54    /// let mut maindlg = Dialog::new(60, 10);
55    /// ```
56    pub fn new(cols: usize, rows: usize) -> Dialog {
57        Dialog {
58            frame: Frame::new(cols, rows),
59            buttons: Vec::new(),
60            layouts: Vec::new(),
61            accel2result: HashMap::new(),
62        }
63    }
64
65    /// Add an existing widget that implements the [Button](core/button/trait.Button.html)
66    /// trait.
67    ///
68    /// # Examples
69    ///
70    /// ```
71    /// use oxide::core::{Widget, ButtonResult, HorizontalAlign, VerticalAlign};
72    /// use oxide::{Dialog, StdButton};
73    /// let mut maindlg = Dialog::new(60, 10);
74    ///
75    /// let mut b1 = StdButton::new("Quit", 'q', ButtonResult::Ok);
76    /// b1.pack(&maindlg, HorizontalAlign::Middle, VerticalAlign::Middle, (1,1));
77    /// maindlg.add_button(b1);
78    /// ```
79    pub fn add_button<T: Button + 'static>(&mut self, button: T) {
80        self.accel2result.insert(button.accel(), button.result());
81        self.buttons.push(Box::new(button));
82
83        self.buttons.last_mut().unwrap().draw(&mut self.frame);
84    }
85
86    /// Add an existing widget that implements the [Layout](core/layout/trait.Layout.html)
87    /// trait. **NEEDS A REWORK**, the way of passing in a vector of buttons is ugly and a 
88    /// very bad API.
89    ///
90    /// # Examples
91    ///
92    /// ```
93    /// use oxide::core::{HorizontalAlign, VerticalAlign, ButtonResult, Button, Widget};
94    /// use oxide::{Dialog, StdButton, VerticalLayout};
95    ///
96    /// let mut maindlg = Dialog::new(60, 10);
97    /// let b1 = StdButton::new("Foo", 'f', ButtonResult::Ok);
98    /// let b2 = StdButton::new("Bar", 'b', ButtonResult::Cancel);
99    ///
100    /// let vec = vec![b1, b2].into_iter().map(Box::new).map(|x| x as Box<Button>).collect();
101    /// let mut vlayout = VerticalLayout::from_vec(vec, 0);
102    /// vlayout.pack(&maindlg, HorizontalAlign::Middle, VerticalAlign::Middle, (0,0));
103    ///
104    /// maindlg.add_layout(vlayout);
105    /// ```
106    ///
107    pub fn add_layout<T: Layout + 'static>(&mut self, layout: T) {
108        self.layouts.push(Box::new(layout));
109        
110        self.layouts.last_mut().unwrap().align_elems();
111        self.layouts.last_mut().unwrap().frame().draw_into(&mut self.frame);
112        self.layouts.last_mut().unwrap().forward_keys(&mut self.accel2result);
113    }
114
115    /// Add an existing label that contains some text.
116    ///
117    /// # Examples
118    ///
119    /// ```
120    /// use oxide::core::{HorizontalAlign, VerticalAlign, Widget};
121    /// use oxide::{Dialog, Label};
122    ///
123    /// let mut maindlg = Dialog::new(60, 10);
124    /// let mut lbl = Label::from_str("Foo");
125    /// lbl.pack(&maindlg, HorizontalAlign::Middle, VerticalAlign::Middle, (0,0));
126    ///
127    /// maindlg.add_label(lbl);
128    /// ```
129    ///
130    pub fn add_label(&mut self, mut label: Label) {
131        label.draw(&mut self.frame);
132    }
133
134    /// Change the state of an existing CheckButton, if any exists, 
135    /// within the dialog. If an invalid button result is given, the
136    /// function will panic. *Note* StdButtons are still valid handles
137    /// for this function, however they will not actually do anything.
138    /// This function is for buttons that perform some action upon being
139    /// pressed.
140    ///
141    /// # Examples
142    /// 
143    /// ```ignore
144    /// // match character for dialog
145    /// match dialog.result_for_key(ch) {
146    ///     Some(ButtonResult::Ok)  => {
147    ///         dialog.button_pressed(ButtonResult::Custom(i));
148    ///         // do stuff ...
149    ///     },
150    ///     _                       => { }
151    /// }
152    /// ```
153    ///
154    pub fn button_pressed(&mut self, res: ButtonResult) {
155        match self.buttons.iter_mut().find(|x| x.result() == res) {
156            Some(i) => { i.pressed(); i.draw(&mut self.frame)}
157            _       => { panic!("Not a valid button result for\
158                                Dialog::button_checked()"); }
159        }
160    }
161
162    /// For buttons that have a state manager, this function will return
163    /// the current state of the button. *CheckButton* for example uses 
164    /// a state to manage whether the button is checked, different actions
165    /// can be taken depending on the state once read.
166    ///
167    /// # Examples
168    ///
169    /// ```ignore
170    /// // match character for dialog
171    /// match dialog.result_for_key(ch) {
172    ///     Some(ButtonResult::Ok)  => {
173    ///         dialog.button_pressed(ButtonResult::Custom(i));
174    ///         if dialog.is_button_pressed(ButtonResult::Custom(i)) {
175    ///             // do ...
176    ///         } else {
177    ///             // do else ...
178    ///         }
179    ///     },
180    ///     _                       => { }
181    /// }
182    /// ```
183    ///
184    pub fn is_button_pressed(&self, res: ButtonResult) -> bool {
185        match self.buttons.iter().find(|x| x.result() == res) {
186            Some(i) => i.state(),
187            _       => panic!("Not a valid button result for\
188                               Dialog::is_button_checked()")
189        }
190    }
191
192    /// Checks whether the char passed is a valid key for any buttons currently
193    /// drawn within the dialog, if so the corresponding `ButtonResult` is returned
194    pub fn result_for_key(&self, key: char) -> Option<ButtonResult> {
195        match self.accel2result.get(&key.to_lowercase().next().unwrap_or(key)) {
196            Some(r) => Some(*r),
197            None => None,
198        }
199    }
200}
201
202impl Widget for Dialog {
203    fn draw(&mut self, parent: &mut CellAccessor) {
204        self.frame.draw_into(parent);
205    }
206    
207    fn pack(&mut self, parent: &HasSize, halign: HorizontalAlign, valign: VerticalAlign,
208                margin: (usize, usize)) {
209        self.frame.align(parent, halign, valign, margin);
210    }
211
212    fn draw_box(&mut self) {
213        self.frame.draw_box();
214    }
215
216    fn resize(&mut self, new_size: Size) {
217        self.frame.resize(new_size);
218    }
219
220    fn frame(&self) -> &Frame {
221        &self.frame
222    }
223
224    fn frame_mut(&mut self) -> &mut Frame {
225        &mut self.frame
226    }
227}
228
229impl HasSize for Dialog {
230    fn size(&self) -> Size {
231        self.frame.size()
232    }
233}