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}