2015-05-19 02:41:35 +00:00
|
|
|
use std::cmp::max;
|
|
|
|
|
|
|
|
use ncurses;
|
|
|
|
|
2015-05-22 06:29:49 +00:00
|
|
|
use color;
|
2015-05-19 02:41:35 +00:00
|
|
|
use ::{Cursive,Margins};
|
|
|
|
use event::EventResult;
|
2015-05-16 21:02:15 +00:00
|
|
|
use view::{View,ViewPath,SizeRequest,DimensionRequest};
|
2015-05-19 02:41:35 +00:00
|
|
|
use view::{Button,SizedView};
|
2015-05-18 22:31:55 +00:00
|
|
|
use vec::Vec2;
|
2015-05-16 21:02:15 +00:00
|
|
|
use printer::Printer;
|
|
|
|
|
2015-05-19 17:58:42 +00:00
|
|
|
#[derive(PartialEq)]
|
2015-05-16 21:02:15 +00:00
|
|
|
enum Focus {
|
|
|
|
Content,
|
|
|
|
Button(usize),
|
|
|
|
}
|
|
|
|
|
2015-05-20 00:31:52 +00:00
|
|
|
/// Popup-like view with a main content, and optional buttons under it.
|
|
|
|
///
|
|
|
|
/// # Examples
|
|
|
|
///
|
|
|
|
/// ```
|
|
|
|
/// let dialog = Dialog::new(TextView::new("Hello!")).button("Ok", |s,_| s.quit());
|
|
|
|
/// ```
|
2015-05-16 21:02:15 +00:00
|
|
|
pub struct Dialog {
|
2015-05-22 06:29:49 +00:00
|
|
|
title: String,
|
2015-05-16 21:02:15 +00:00
|
|
|
content: Box<View>,
|
2015-05-19 02:41:35 +00:00
|
|
|
|
|
|
|
buttons: Vec<SizedView<Button>>,
|
|
|
|
|
|
|
|
padding: Margins,
|
|
|
|
borders: Margins,
|
|
|
|
|
2015-05-16 21:02:15 +00:00
|
|
|
focus: Focus,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Dialog {
|
2015-05-20 00:31:52 +00:00
|
|
|
/// Creates a new Dialog with the given content.
|
2015-05-16 21:02:15 +00:00
|
|
|
pub fn new<V: View + 'static>(view: V) -> Self {
|
|
|
|
Dialog {
|
|
|
|
content: Box::new(view),
|
|
|
|
buttons: Vec::new(),
|
2015-05-22 06:29:49 +00:00
|
|
|
title: String::new(),
|
2015-05-19 17:58:42 +00:00
|
|
|
focus: Focus::Content,
|
2015-05-19 02:41:35 +00:00
|
|
|
padding: Margins::new(1,1,0,0),
|
|
|
|
borders: Margins::new(1,1,1,1),
|
2015-05-16 21:02:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-05-20 00:31:52 +00:00
|
|
|
/// Adds a button to the dialog with the given label and callback.
|
|
|
|
///
|
|
|
|
/// Consumes and returns self for easy chaining.
|
2015-05-19 02:41:35 +00:00
|
|
|
pub fn button<'a, F>(mut self, label: &'a str, cb: F) -> Self
|
|
|
|
where F: Fn(&mut Cursive, &ViewPath) + 'static
|
2015-05-16 21:02:15 +00:00
|
|
|
{
|
2015-05-19 02:41:35 +00:00
|
|
|
self.buttons.push(SizedView::new(Button::new(label, cb)));
|
|
|
|
|
2015-05-16 21:02:15 +00:00
|
|
|
self
|
|
|
|
}
|
2015-05-19 02:41:35 +00:00
|
|
|
|
2015-05-22 07:01:23 +00:00
|
|
|
/// Shortcut method to add a button that will dismiss the dialog.
|
|
|
|
pub fn dismiss_button<'a>(self, label: &'a str) -> Self {
|
|
|
|
self.button(label, |s, _| s.screen_mut().pop_layer())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Sets the title of the dialog.
|
|
|
|
/// If not empty, it will be visible at the top.
|
2015-05-22 06:29:49 +00:00
|
|
|
pub fn title(mut self, label: &str) -> Self {
|
|
|
|
self.title = label.to_string();
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2015-05-16 21:02:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl View for Dialog {
|
2015-05-22 23:28:05 +00:00
|
|
|
fn draw(&mut self, printer: &Printer, focused: bool) {
|
2015-05-19 02:41:35 +00:00
|
|
|
|
2015-05-22 07:43:58 +00:00
|
|
|
// This will be the height used by the buttons.
|
2015-05-19 02:41:35 +00:00
|
|
|
let mut height = 0;
|
2015-05-22 07:43:58 +00:00
|
|
|
// Current horizontal position of the next button we'll draw.
|
2015-05-19 02:41:35 +00:00
|
|
|
let mut x = 0;
|
2015-05-22 23:28:05 +00:00
|
|
|
for (i,button) in self.buttons.iter_mut().enumerate().rev() {
|
2015-05-19 02:41:35 +00:00
|
|
|
let size = button.size;
|
|
|
|
let offset = printer.size - self.borders.bot_right() - self.padding.bot_right() - size - Vec2::new(x, 0);
|
2015-05-19 22:54:11 +00:00
|
|
|
// Add some special effect to the focused button
|
|
|
|
button.draw(&printer.sub_printer(offset, size), focused && (self.focus == Focus::Button(i)));
|
2015-05-22 07:43:58 +00:00
|
|
|
// Keep 1 blank between two buttons
|
2015-05-19 02:41:35 +00:00
|
|
|
x += size.x + 1;
|
2015-05-22 07:43:58 +00:00
|
|
|
// Also keep 1 blank above the buttons
|
2015-05-19 02:41:35 +00:00
|
|
|
height = max(height, size.y+1);
|
|
|
|
}
|
|
|
|
|
2015-05-22 07:43:58 +00:00
|
|
|
// What do we have left?
|
2015-05-19 02:41:35 +00:00
|
|
|
let inner_size = printer.size
|
|
|
|
- Vec2::new(0, height)
|
|
|
|
- self.borders.combined()
|
|
|
|
- self.padding.combined();
|
|
|
|
|
2015-05-19 22:54:11 +00:00
|
|
|
self.content.draw(&printer.sub_printer(self.borders.top_left() + self.padding.top_left(), inner_size), focused && self.focus == Focus::Content);
|
2015-05-19 02:41:35 +00:00
|
|
|
|
2015-05-22 06:29:49 +00:00
|
|
|
printer.print_box(Vec2::new(0,0), printer.size);
|
|
|
|
|
|
|
|
if self.title.len() > 0 {
|
|
|
|
let x = (printer.size.x - self.title.len() as u32) / 2;
|
|
|
|
printer.print((x-2,0), "┤ ");
|
|
|
|
printer.print((x+self.title.len() as u32,0), " ├");
|
|
|
|
|
|
|
|
printer.style(color::TITLE_PRIMARY).print((x,0), &self.title);
|
|
|
|
}
|
2015-05-20 17:31:38 +00:00
|
|
|
|
2015-05-16 21:02:15 +00:00
|
|
|
}
|
|
|
|
|
2015-05-19 02:41:35 +00:00
|
|
|
fn get_min_size(&self, req: SizeRequest) -> Vec2 {
|
2015-05-22 07:43:58 +00:00
|
|
|
// Padding and borders are not available for kids.
|
2015-05-19 02:41:35 +00:00
|
|
|
let content_req = req.reduced(self.padding.combined() + self.borders.combined());
|
2015-05-16 21:02:15 +00:00
|
|
|
let content_size = self.content.get_min_size(content_req);
|
|
|
|
|
2015-05-19 02:41:35 +00:00
|
|
|
let mut buttons_size = Vec2::new(0,0);
|
|
|
|
for button in self.buttons.iter() {
|
|
|
|
let s = button.view.get_min_size(req);
|
|
|
|
buttons_size.x += s.x + 1;
|
|
|
|
buttons_size.y = max(buttons_size.y, s.y + 1);
|
|
|
|
}
|
|
|
|
|
2015-05-22 07:43:58 +00:00
|
|
|
// On the Y axis, we add buttons and content.
|
|
|
|
// On the X axis, we take the max.
|
2015-05-22 06:29:49 +00:00
|
|
|
let mut inner_size = Vec2::new(max(content_size.x, buttons_size.x),
|
|
|
|
content_size.y + buttons_size.y)
|
|
|
|
+ self.padding.combined() + self.borders.combined();
|
|
|
|
|
|
|
|
if self.title.len() > 0 {
|
2015-05-22 07:43:58 +00:00
|
|
|
// If we have a title, we have to fit it too!
|
2015-05-22 06:29:49 +00:00
|
|
|
inner_size.x = max(inner_size.x, self.title.len() as u32 + 6);
|
|
|
|
}
|
2015-05-19 02:41:35 +00:00
|
|
|
|
2015-05-22 06:29:49 +00:00
|
|
|
inner_size
|
2015-05-19 02:41:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn layout(&mut self, mut size: Vec2) {
|
2015-05-22 07:43:58 +00:00
|
|
|
// Padding and borders are taken, sorry.
|
2015-05-19 02:41:35 +00:00
|
|
|
size = size - (self.borders.combined() + self.padding.combined());
|
|
|
|
let req = SizeRequest {
|
|
|
|
w: DimensionRequest::AtMost(size.x),
|
|
|
|
h: DimensionRequest::AtMost(size.y),
|
|
|
|
};
|
|
|
|
|
2015-05-22 07:43:58 +00:00
|
|
|
// Buttons are kings, we give them everything they want.
|
2015-05-19 02:41:35 +00:00
|
|
|
let mut buttons_height = 0;
|
|
|
|
for button in self.buttons.iter_mut().rev() {
|
|
|
|
let size = button.get_min_size(req);
|
|
|
|
buttons_height = max(buttons_height, size.y+1);
|
|
|
|
button.layout(size);
|
|
|
|
}
|
|
|
|
|
2015-05-22 07:43:58 +00:00
|
|
|
// Poor content will have to make do with what's left.
|
2015-05-19 02:41:35 +00:00
|
|
|
self.content.layout(size - Vec2::new(0, buttons_height));
|
|
|
|
}
|
|
|
|
|
|
|
|
fn on_key_event(&mut self, ch: i32) -> EventResult {
|
|
|
|
match self.focus {
|
2015-05-22 07:43:58 +00:00
|
|
|
// If we are on the content, we can only go down.
|
2015-05-19 02:41:35 +00:00
|
|
|
Focus::Content => match self.content.on_key_event(ch) {
|
|
|
|
EventResult::Ignored if !self.buttons.is_empty() => match ch {
|
|
|
|
ncurses::KEY_DOWN => {
|
2015-05-22 07:43:58 +00:00
|
|
|
// Default to leftmost button when going down.
|
2015-05-19 02:41:35 +00:00
|
|
|
self.focus = Focus::Button(0);
|
|
|
|
EventResult::Consumed(None, ViewPath::new())
|
|
|
|
},
|
|
|
|
_ => EventResult::Ignored,
|
|
|
|
},
|
|
|
|
res => res,
|
|
|
|
},
|
2015-05-22 07:43:58 +00:00
|
|
|
// If we are on a button, we have more choice
|
2015-05-19 02:41:35 +00:00
|
|
|
Focus::Button(i) => match self.buttons[i].on_key_event(ch) {
|
|
|
|
EventResult::Ignored => match ch {
|
2015-05-22 07:43:58 +00:00
|
|
|
// Up goes back to the content
|
2015-05-20 17:31:38 +00:00
|
|
|
ncurses::KEY_UP => {
|
|
|
|
if self.content.take_focus() {
|
|
|
|
self.focus = Focus::Content;
|
|
|
|
EventResult::consume()
|
|
|
|
} else {
|
|
|
|
EventResult::Ignored
|
|
|
|
}
|
|
|
|
},
|
2015-05-22 07:43:58 +00:00
|
|
|
// Left and Right move to other buttons
|
2015-05-19 17:58:42 +00:00
|
|
|
ncurses::KEY_RIGHT if i+1 < self.buttons.len() => {
|
|
|
|
self.focus = Focus::Button(i+1);
|
|
|
|
EventResult::consume()
|
|
|
|
},
|
|
|
|
ncurses::KEY_LEFT if i > 0 => {
|
|
|
|
self.focus = Focus::Button(i-1);
|
|
|
|
EventResult::consume()
|
|
|
|
},
|
2015-05-19 02:41:35 +00:00
|
|
|
_ => EventResult::Ignored,
|
|
|
|
},
|
|
|
|
res => res,
|
|
|
|
},
|
2015-05-19 22:54:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn take_focus(&mut self) -> bool {
|
2015-05-22 07:43:58 +00:00
|
|
|
// TODO: add a direction to the focus. Meanwhile, takes button first.
|
2015-05-19 22:54:11 +00:00
|
|
|
if !self.buttons.is_empty() {
|
|
|
|
self.focus = Focus::Button(0);
|
|
|
|
true
|
|
|
|
} else {
|
|
|
|
self.focus = Focus::Content;
|
|
|
|
self.content.take_focus()
|
2015-05-19 02:41:35 +00:00
|
|
|
}
|
2015-05-16 21:02:15 +00:00
|
|
|
}
|
|
|
|
}
|