cursive/src/view/dialog.rs

264 lines
8.9 KiB
Rust
Raw Normal View History

2015-05-19 02:41:35 +00:00
use std::cmp::max;
use std::any::Any;
2015-05-19 02:41:35 +00:00
2015-05-22 06:29:49 +00:00
use color;
use ::{Cursive};
2015-06-03 02:36:22 +00:00
use align::*;
use event::*;
use view::{View,SizeRequest,DimensionRequest,Selector};
2015-05-19 02:41:35 +00:00
use view::{Button,SizedView};
use vec::{Vec2,Vec4,ToVec4};
2015-05-16 21:02:15 +00:00
use printer::Printer;
#[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
///
/// ```
2015-06-04 18:40:35 +00:00
/// # use cursive::view::{Dialog,TextView};
/// let dialog = Dialog::new(TextView::new("Hello!")).button("Ok", |s| s.quit());
2015-05-20 00:31:52 +00:00
/// ```
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: Vec4,
borders: Vec4,
2015-05-19 02:41:35 +00:00
2015-05-16 21:02:15 +00:00
focus: Focus,
2015-06-03 02:36:22 +00:00
align: Align,
2015-05-16 21:02:15 +00:00
}
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(),
focus: Focus::Content,
padding: Vec4::new(1,1,0,0),
borders: Vec4::new(1,1,1,1),
2015-06-03 02:36:22 +00:00
align: Align::top_right(),
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
2015-05-24 00:07:22 +00:00
where F: Fn(&mut Cursive) + '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-06-03 22:36:51 +00:00
/// Sets the horizontal alignment for the buttons, if any.
/// Only works if the buttons are as a row at the bottom of the dialog.
2015-06-03 02:36:22 +00:00
pub fn h_align(mut self, h: HAlign) -> Self {
self.align.h = h;
self
}
2015-06-03 22:36:51 +00:00
/// Sets the vertical alignment for the buttons, if any.
/// Only works if the buttons are as a column to the right of the dialog.
2015-06-03 02:36:22 +00:00
pub fn v_align(mut self, v: VAlign) -> Self {
self.align.v = v;
self
}
/// Shortcut method to add a button that will dismiss the dialog.
pub fn dismiss_button<'a>(self, label: &'a str) -> Self {
2015-05-24 00:07:22 +00:00
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-26 23:48:27 +00:00
/// Sets the padding in the dialog (around content and buttons).
pub fn padding<T: ToVec4>(mut self, padding: T) -> Self {
self.padding = padding.to_vec4();
self
}
2015-05-16 21:02:15 +00:00
}
impl View for Dialog {
fn draw(&mut self, printer: &Printer) {
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-06-03 02:36:22 +00:00
let width = self.buttons.len() + self.buttons.iter().map(|button| button.size.x).fold(0, |a,b| a+b);
let overhead = self.padding + self.borders;
let mut offset = overhead.left + self.align.h.get_offset(width, printer.size.x - overhead.horizontal());
let y = printer.size.y - self.padding.bottom - self.borders.bottom - 1;
for (i,button) in self.buttons.iter_mut().enumerate() {
2015-05-19 02:41:35 +00:00
let size = button.size;
2015-06-03 02:36:22 +00:00
// let offset = printer.size - self.borders.bot_right() - self.padding.bot_right() - size - Vec2::new(x, 0);
// Add some special effect to the focused button
2015-06-03 02:36:22 +00:00
button.draw(&printer.sub_printer(Vec2::new(offset, y), size, self.focus == Focus::Button(i)));
2015-05-22 07:43:58 +00:00
// Keep 1 blank between two buttons
2015-06-03 02:36:22 +00:00
offset += 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();
self.content.draw(&printer.sub_printer(self.borders.top_left() + self.padding.top_left(), inner_size, 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 len = self.title.chars().count();
let x = (printer.size.x - len) / 2;
2015-05-22 06:29:49 +00:00
printer.print((x-2,0), "");
printer.print((x+len,0), "");
2015-05-22 06:29:49 +00:00
printer.with_color(color::TITLE_PRIMARY, |p| p.print((x,0), &self.title));
2015-05-22 06:29:49 +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!
inner_size.x = max(inner_size.x, self.title.chars().count() + 6);
2015-05-22 06:29:49 +00:00
}
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_event(&mut self, event: Event) -> EventResult {
2015-05-19 02:41:35 +00:00
match self.focus {
2015-05-22 07:43:58 +00:00
// If we are on the content, we can only go down.
Focus::Content => match self.content.on_event(event) {
EventResult::Ignored if !self.buttons.is_empty() => match event {
Event::KeyEvent(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);
2015-05-24 00:07:22 +00:00
EventResult::Consumed(None)
2015-05-19 02:41:35 +00:00
},
Event::KeyEvent(Key::Tab) | Event::KeyEvent(Key::ShiftTab) => {
self.focus = Focus::Button(0);
EventResult::Consumed(None)
}
2015-05-19 02:41:35 +00:00
_ => EventResult::Ignored,
},
res => res,
},
2015-05-22 07:43:58 +00:00
// If we are on a button, we have more choice
Focus::Button(i) => match self.buttons[i].on_event(event) {
EventResult::Ignored => match event {
2015-05-22 07:43:58 +00:00
// Up goes back to the content
Event::KeyEvent(Key::Up) => {
if self.content.take_focus() {
self.focus = Focus::Content;
2015-05-24 00:07:22 +00:00
EventResult::Consumed(None)
} else {
EventResult::Ignored
}
},
Event::KeyEvent(Key::Tab) | Event::KeyEvent(Key::ShiftTab) => {
if self.content.take_focus() {
self.focus = Focus::Content;
EventResult::Consumed(None)
} else {
EventResult::Ignored
}
},
2015-05-22 07:43:58 +00:00
// Left and Right move to other buttons
Event::KeyEvent(Key::Right) if i+1 < self.buttons.len() => {
self.focus = Focus::Button(i+1);
2015-05-24 00:07:22 +00:00
EventResult::Consumed(None)
},
Event::KeyEvent(Key::Right) if i > 0 => {
self.focus = Focus::Button(i-1);
2015-05-24 00:07:22 +00:00
EventResult::Consumed(None)
},
2015-05-19 02:41:35 +00:00
_ => EventResult::Ignored,
},
res => res,
},
}
}
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.
if self.content.take_focus() {
self.focus = Focus::Content;
true
} else if !self.buttons.is_empty() {
self.focus = Focus::Button(0);
true
} else {
false
2015-05-19 02:41:35 +00:00
}
2015-05-16 21:02:15 +00:00
}
fn find(&mut self, selector: &Selector) -> Option<&mut Any> {
self.content.find(selector)
}
2015-05-16 21:02:15 +00:00
}