cursive/src/views/dialog.rs

586 lines
18 KiB
Rust
Raw Normal View History

2016-03-15 22:37:57 +00:00
use Cursive;
2016-10-02 22:22:29 +00:00
use Printer;
use With;
2015-06-03 02:36:22 +00:00
use align::*;
2016-10-02 22:22:29 +00:00
use direction::Direction;
use event::*;
2018-03-22 22:24:27 +00:00
use rect::Rect;
2016-10-02 22:22:29 +00:00
use std::any::Any;
2017-10-11 22:07:25 +00:00
use std::cell::Cell;
2016-10-02 22:22:29 +00:00
use std::cmp::max;
use theme::ColorStyle;
2016-07-04 23:04:32 +00:00
use unicode_width::UnicodeWidthStr;
2018-03-16 23:09:47 +00:00
use vec::Vec2;
use view::{Margins, Selector, View};
2018-03-14 22:11:27 +00:00
use views::{Button, DummyView, SizedView, TextView, ViewBox};
2016-07-04 23:04:32 +00:00
/// Identifies currently focused element in [`Dialog`].
///
/// [`Dialog`]: struct.Dialog.html
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum DialogFocus {
/// Content element focused
2015-05-16 21:02:15 +00:00
Content,
/// One of buttons focused
2015-05-16 21:02:15 +00:00
Button(usize),
}
2017-10-11 22:07:25 +00:00
struct ChildButton {
button: SizedView<Button>,
offset: Cell<Vec2>,
}
impl ChildButton {
pub fn new<F, S: Into<String>>(label: S, cb: F) -> Self
2017-10-11 22:07:25 +00:00
where
F: 'static + Fn(&mut Cursive),
2017-10-11 22:07:25 +00:00
{
ChildButton {
button: SizedView::new(Button::new(label, cb)),
offset: Cell::new(Vec2::zero()),
}
}
}
2015-05-20 00:31:52 +00:00
/// Popup-like view with a main content, and optional buttons under it.
///
/// # Examples
///
/// ```
2016-07-28 23:36:01 +00:00
/// # use cursive::views::{Dialog,TextView};
/// let dialog = Dialog::around(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 {
2017-10-11 22:07:25 +00:00
// Possibly empty title.
2015-05-22 06:29:49 +00:00
title: String,
2015-05-19 02:41:35 +00:00
// Where to put the title position
title_position: HAlign,
2017-10-11 22:07:25 +00:00
// The actual inner view.
content: SizedView<ViewBox>,
2017-10-11 22:07:25 +00:00
// Optional list of buttons under the main view.
// Include the top-left corner.
buttons: Vec<ChildButton>,
2015-05-19 02:41:35 +00:00
2017-10-11 22:07:25 +00:00
// Padding around the inner view.
2018-03-16 23:06:35 +00:00
padding: Margins,
2017-10-11 22:07:25 +00:00
// Borders around everything.
2018-03-16 23:06:35 +00:00
borders: Margins,
2015-05-19 02:41:35 +00:00
2017-10-11 22:07:25 +00:00
// The current element in focus
focus: DialogFocus,
2015-06-03 02:36:22 +00:00
2017-10-11 22:07:25 +00:00
// How to align the buttons under the view.
2015-06-03 02:36:22 +00:00
align: Align,
2015-05-16 21:02:15 +00:00
}
2016-10-02 23:03:31 +00:00
new_default!(Dialog);
2015-05-16 21:02:15 +00:00
impl Dialog {
/// Creates a new `Dialog` with empty content.
///
/// You should probably call `content()` next.
pub fn new() -> Self {
Self::around(DummyView)
}
/// Creates a new `Dialog` with the given content.
pub fn around<V: View + 'static>(view: V) -> Self {
2015-05-16 21:02:15 +00:00
Dialog {
content: SizedView::new(ViewBox::boxed(view)),
2015-05-16 21:02:15 +00:00
buttons: Vec::new(),
2015-05-22 06:29:49 +00:00
title: String::new(),
title_position: HAlign::Center,
focus: DialogFocus::Content,
2018-03-16 23:06:35 +00:00
padding: Margins::new(1, 1, 0, 0),
borders: Margins::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
}
}
/// Sets the content for this dialog.
///
/// Chainable variant.
pub fn content<V: View + 'static>(self, view: V) -> Self {
self.with(|s| s.set_content(view))
}
2018-01-22 18:36:10 +00:00
/// Gets the content of this dialog.
///
/// ```
/// use cursive::views::{Dialog, TextView};
/// let dialog = Dialog::around(TextView::new("Hello!"));
2018-01-22 22:37:27 +00:00
/// let text_view: &TextView = dialog
/// .get_content()
/// .as_any()
/// .downcast_ref::<TextView>()
/// .unwrap();
2018-01-22 18:36:10 +00:00
/// assert_eq!(text_view.get_content().source(), "Hello!");
/// ```
pub fn get_content(&self) -> &View {
2018-01-22 18:36:10 +00:00
&*self.content.view
}
/// Gets mutable access to the content.
pub fn get_content_mut(&mut self) -> &mut View {
&mut *self.content.view
}
/// Sets the content for this dialog.
///
/// Previous content will be dropped.
pub fn set_content<V: View + 'static>(&mut self, view: V) {
self.content = SizedView::new(ViewBox::boxed(view));
}
/// Convenient method to create a dialog with a simple text content.
pub fn text<S: Into<String>>(text: S) -> Self {
Self::around(TextView::new(text))
}
/// Convenient method to create an infobox.
///
/// It will contain the given text and a `Ok` dismiss button.
pub fn info<S: Into<String>>(text: S) -> Self {
Dialog::text(text).dismiss_button("Ok")
2016-06-28 05:10:59 +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.
pub fn button<F, S: Into<String>>(mut self, label: S, cb: F) -> Self
2017-10-11 22:07:25 +00:00
where
F: 'static + Fn(&mut Cursive),
2015-05-16 21:02:15 +00:00
{
2017-10-11 22:07:25 +00:00
self.buttons.push(ChildButton::new(label, cb));
2015-05-19 02:41:35 +00:00
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.
2016-08-13 08:03:40 +00:00
///
2015-06-03 22:36:51 +00:00
/// 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
}
2017-10-11 22:07:25 +00:00
/*
* Commented out because currently un-implemented.
*
2015-06-03 22:36:51 +00:00
/// Sets the vertical alignment for the buttons, if any.
2016-08-13 08:03:40 +00:00
///
2015-06-03 22:36:51 +00:00
/// 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
}
2017-10-11 22:07:25 +00:00
*/
2015-06-03 02:36:22 +00:00
/// Shortcut method to add a button that will dismiss the dialog.
pub fn dismiss_button<S: Into<String>>(self, label: S) -> Self {
self.button(label, |s| {
s.pop_layer();
})
}
/// Sets the title of the dialog.
2016-08-13 08:03:40 +00:00
///
/// If not empty, it will be visible at the top.
2016-08-13 08:03:40 +00:00
pub fn title<S: Into<String>>(self, label: S) -> Self {
self.with(|s| s.set_title(label))
}
/// Sets the title of the dialog.
pub fn set_title<S: Into<String>>(&mut self, label: S) {
self.title = label.into();
2015-05-22 06:29:49 +00:00
}
/// Sets the horizontal position of the title in the dialog.
/// The default position is `HAlign::Center`
pub fn title_position(self, align: HAlign) -> Self {
self.with(|s| s.set_title_position(align))
}
/// Sets the horizontal position of the title in the dialog.
/// The default position is `HAlign::Center`
pub fn set_title_position(&mut self, align: HAlign) {
self.title_position = align;
}
2015-05-26 23:48:27 +00:00
/// Sets the padding in the dialog (around content and buttons).
2018-03-16 23:06:35 +00:00
pub fn padding<T: Into<Margins>>(mut self, padding: T) -> Self {
self.padding = padding.into();
self
}
/// Sets the top padding in the dialog (under the title).
pub fn padding_top(mut self, padding: usize) -> Self {
self.padding.top = padding;
self
}
/// Sets the bottom padding in the dialog (under buttons).
pub fn padding_bottom(mut self, padding: usize) -> Self {
self.padding.bottom = padding;
self
}
/// Sets the left padding in the dialog.
pub fn padding_left(mut self, padding: usize) -> Self {
self.padding.left = padding;
self
}
/// Sets the right padding in the dialog.
pub fn padding_right(mut self, padding: usize) -> Self {
self.padding.right = padding;
self
}
2015-05-16 21:02:15 +00:00
/// Returns an iterator on this buttons for this dialog.
pub fn buttons_mut<'a>(
2018-01-22 22:37:27 +00:00
&'a mut self
) -> Box<'a + Iterator<Item = &'a mut Button>> {
2018-03-22 18:04:10 +00:00
Box::new(
self.buttons
.iter_mut()
.map(|b| &mut b.button.view),
)
}
/// Returns currently focused element
pub fn focus(&self) -> DialogFocus {
self.focus
}
2017-10-11 22:07:25 +00:00
// Private methods
// An event is received while the content is in focus
fn on_event_content(&mut self, event: Event) -> EventResult {
match self.content.on_event(
event.relativized((self.padding + self.borders).top_left()),
) {
2017-10-11 22:07:25 +00:00
EventResult::Ignored if !self.buttons.is_empty() => {
match event {
Event::Key(Key::Down)
| Event::Key(Key::Tab)
| Event::Shift(Key::Tab) => {
2017-10-11 22:07:25 +00:00
// Default to leftmost button when going down.
self.focus = DialogFocus::Button(0);
2017-10-11 22:07:25 +00:00
EventResult::Consumed(None)
}
_ => EventResult::Ignored,
}
}
res => res,
}
}
// An event is received while a button is in focus
fn on_event_button(
2018-01-22 22:37:27 +00:00
&mut self, event: Event, button_id: usize
2017-10-11 22:07:25 +00:00
) -> EventResult {
let result = {
let button = &mut self.buttons[button_id];
button
.button
.on_event(event.relativized(button.offset.get()))
};
match result {
EventResult::Ignored => {
match event {
// Up goes back to the content
Event::Key(Key::Up) => {
if self.content.take_focus(Direction::down()) {
self.focus = DialogFocus::Content;
2017-10-11 22:07:25 +00:00
EventResult::Consumed(None)
} else {
EventResult::Ignored
}
}
Event::Shift(Key::Tab) => {
if self.content.take_focus(Direction::back()) {
self.focus = DialogFocus::Content;
2017-10-11 22:07:25 +00:00
EventResult::Consumed(None)
} else {
EventResult::Ignored
}
}
Event::Key(Key::Tab) => {
if self.content.take_focus(Direction::front()) {
self.focus = DialogFocus::Content;
2017-10-11 22:07:25 +00:00
EventResult::Consumed(None)
} else {
EventResult::Ignored
}
}
// Left and Right move to other buttons
Event::Key(Key::Right)
if button_id + 1 < self.buttons.len() =>
{
self.focus = DialogFocus::Button(button_id + 1);
2017-10-11 22:07:25 +00:00
EventResult::Consumed(None)
}
Event::Key(Key::Left) if button_id > 0 => {
self.focus = DialogFocus::Button(button_id - 1);
2017-10-11 22:07:25 +00:00
EventResult::Consumed(None)
}
_ => EventResult::Ignored,
}
}
res => res,
}
}
fn draw_buttons(&self, printer: &Printer) -> Option<usize> {
let mut buttons_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
2015-06-08 22:11:36 +00:00
// Sum of the sizes + len-1 for margins
2017-08-14 23:32:01 +00:00
let width = self.buttons
.iter()
2017-10-11 22:07:25 +00:00
.map(|button| button.button.size.x)
2018-01-22 22:47:56 +00:00
.sum::<usize>()
2017-10-11 22:07:25 +00:00
+ self.buttons.len().saturating_sub(1);
2015-06-03 02:36:22 +00:00
let overhead = self.padding + self.borders;
2016-07-17 05:05:28 +00:00
if printer.size.x < overhead.horizontal() {
2017-10-11 22:07:25 +00:00
return None;
2016-07-17 05:05:28 +00:00
}
2017-10-11 22:07:25 +00:00
let mut offset = overhead.left
+ self.align
.h
.get_offset(width, printer.size.x - overhead.horizontal());
2016-07-17 05:05:28 +00:00
let overhead_bottom = self.padding.bottom + self.borders.bottom + 1;
2017-08-14 23:32:01 +00:00
let y = match printer.size.y.checked_sub(overhead_bottom) {
Some(y) => y,
2017-10-11 22:07:25 +00:00
None => return None,
2017-08-14 23:32:01 +00:00
};
2015-06-03 02:36:22 +00:00
for (i, button) in self.buttons.iter().enumerate() {
2017-10-11 22:07:25 +00:00
let size = button.button.size;
// Add some special effect to the focused button
2017-10-11 22:07:25 +00:00
let position = Vec2::new(offset, y);
button.offset.set(position);
button.button.draw(&printer.sub_printer(
position,
size,
self.focus == DialogFocus::Button(i),
2017-10-11 22:07:25 +00:00
));
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
buttons_height = max(buttons_height, size.y + 1);
2015-05-19 02:41:35 +00:00
}
2017-10-11 22:07:25 +00:00
Some(buttons_height)
}
fn draw_content(&self, printer: &Printer, buttons_height: usize) {
2015-05-22 07:43:58 +00:00
// What do we have left?
2017-10-11 22:07:25 +00:00
let taken = Vec2::new(0, buttons_height) + self.borders.combined()
+ self.padding.combined();
2015-05-19 02:41:35 +00:00
2017-08-14 23:32:01 +00:00
let inner_size = match printer.size.checked_sub(taken) {
Some(s) => s,
None => return,
};
2017-10-11 22:07:25 +00:00
self.content.draw(&printer.sub_printer(
self.borders.top_left() + self.padding.top_left(),
inner_size,
self.focus == DialogFocus::Content,
2017-10-11 22:07:25 +00:00
));
}
2015-05-22 06:29:49 +00:00
2017-10-11 22:07:25 +00:00
fn draw_title(&self, printer: &Printer) {
2016-06-28 05:40:11 +00:00
if !self.title.is_empty() {
2016-07-04 23:04:32 +00:00
let len = self.title.width();
2016-07-17 08:20:41 +00:00
if len + 4 > printer.size.x {
return;
}
let spacing = 3; //minimum distance to borders
let x = spacing
+ self.title_position
.get_offset(len, printer.size.x - 2 * spacing);
2016-08-05 17:49:16 +00:00
printer.with_high_border(false, |printer| {
printer.print((x - 2, 0), "");
printer.print((x + len, 0), "");
});
2015-05-22 06:29:49 +00:00
printer.with_color(ColorStyle::title_primary(), |p| {
2017-12-30 22:03:42 +00:00
p.print((x, 0), &self.title)
});
2015-05-22 06:29:49 +00:00
}
2017-10-11 22:07:25 +00:00
}
2017-10-11 22:07:25 +00:00
fn check_focus_grab(&mut self, event: &Event) {
2017-10-13 18:22:02 +00:00
if let Event::Mouse {
2017-10-11 22:07:25 +00:00
offset,
position,
event,
2017-10-13 18:22:02 +00:00
} = *event
2017-10-11 22:07:25 +00:00
{
if !event.grabs_focus() {
return;
}
let position = match position.checked_sub(offset) {
None => return,
Some(pos) => pos,
};
2017-10-11 22:08:19 +00:00
// eprintln!("Rel pos: {:?}", position);
2017-10-11 22:07:25 +00:00
// Now that we have a relative position, checks for buttons?
if let Some(i) = self.buttons.iter().position(|btn| {
// If position fits there...
position.fits_in_rect(btn.offset.get(), btn.button.size)
}) {
self.focus = DialogFocus::Button(i);
} else if position.fits_in_rect(
(self.padding + self.borders).top_left(),
self.content.size,
)
2017-10-11 22:07:25 +00:00
&& self.content.take_focus(Direction::none())
{
// Or did we click the content?
self.focus = DialogFocus::Content;
2017-10-11 22:07:25 +00:00
}
}
}
}
impl View for Dialog {
fn draw(&self, printer: &Printer) {
// This will be the buttons_height used by the buttons.
let buttons_height = match self.draw_buttons(printer) {
Some(height) => height,
None => return,
};
self.draw_content(printer, buttons_height);
// Print the borders
printer.print_box(Vec2::new(0, 0), printer.size, false);
self.draw_title(printer);
2015-05-16 21:02:15 +00:00
}
fn required_size(&mut self, req: Vec2) -> Vec2 {
2015-05-22 07:43:58 +00:00
// Padding and borders are not available for kids.
let nomans_land = self.padding.combined() + self.borders.combined();
2015-05-16 21:02:15 +00:00
// Buttons are not flexible, so their size doesn't depend on ours.
2016-03-15 22:37:57 +00:00
let mut buttons_size = Vec2::new(0, 0);
2017-08-14 23:32:01 +00:00
// Start with the inter-button space.
buttons_size.x += self.buttons.len().saturating_sub(1);
for button in &mut self.buttons {
2017-10-11 22:07:25 +00:00
let s = button.button.view.required_size(req);
2015-06-09 05:24:59 +00:00
buttons_size.x += s.x;
2015-05-19 02:41:35 +00:00
buttons_size.y = max(buttons_size.y, s.y + 1);
}
// We also remove one row for the buttons.
2016-07-17 05:05:28 +00:00
let taken = nomans_land + Vec2::new(0, buttons_size.y);
2017-08-14 23:32:01 +00:00
let content_req = match req.checked_sub(taken) {
Some(r) => r,
2016-07-17 05:05:28 +00:00
// Bad!!
2017-08-14 23:32:01 +00:00
None => return taken,
};
2016-07-17 05:05:28 +00:00
let content_size = self.content.required_size(content_req);
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.
2017-10-11 22:07:25 +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();
2015-05-22 06:29:49 +00:00
2016-06-28 05:40:11 +00:00
if !self.title.is_empty() {
2015-05-22 07:43:58 +00:00
// If we have a title, we have to fit it too!
2016-07-04 23:04:32 +00:00
inner_size.x = max(inner_size.x, self.title.width() + 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.
// TODO: handle border-less themes?
2016-07-17 05:05:28 +00:00
let taken = self.borders.combined() + self.padding.combined();
2017-08-14 23:32:01 +00:00
size = size.saturating_sub(taken);
2015-05-19 02:41:35 +00:00
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() {
2017-10-11 22:07:25 +00:00
let size = button.button.required_size(size);
2016-03-15 22:37:57 +00:00
buttons_height = max(buttons_height, size.y + 1);
2017-10-11 22:07:25 +00:00
button.button.layout(size);
2015-05-19 02:41:35 +00:00
}
2015-05-22 07:43:58 +00:00
// Poor content will have to make do with what's left.
2016-07-17 05:05:28 +00:00
if buttons_height > size.y {
buttons_height = size.y;
}
2017-10-11 22:07:25 +00:00
self.content
.layout(size.saturating_sub((0, buttons_height)));
2015-05-19 02:41:35 +00:00
}
fn on_event(&mut self, event: Event) -> EventResult {
2017-10-11 22:07:25 +00:00
// First: some mouse events can instantly change the focus.
self.check_focus_grab(&event);
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.
2017-10-11 22:07:25 +00:00
// TODO: Careful if/when we add buttons elsewhere on the dialog!
DialogFocus::Content => self.on_event_content(event),
2016-06-25 23:36:22 +00:00
// If we are on a button, we have more choice
DialogFocus::Button(i) => self.on_event_button(event, i),
}
}
2016-07-15 05:32:43 +00:00
fn take_focus(&mut self, source: Direction) -> bool {
// Dialogs aren't meant to be used in layouts, so...
// Let's be super lazy and not even care about the focus source.
if self.content.take_focus(source) {
self.focus = DialogFocus::Content;
true
} else if !self.buttons.is_empty() {
self.focus = DialogFocus::Button(0);
true
} else {
false
2015-05-19 02:41:35 +00:00
}
2015-05-16 21:02:15 +00:00
}
2017-10-11 22:07:25 +00:00
fn call_on_any<'a>(
2018-01-22 22:37:27 +00:00
&mut self, selector: &Selector, callback: Box<FnMut(&mut Any) + 'a>
2017-10-11 22:07:25 +00:00
) {
self.content.call_on_any(selector, callback);
}
2017-03-25 21:50:52 +00:00
fn focus_view(&mut self, selector: &Selector) -> Result<(), ()> {
self.content.focus_view(selector)
}
2018-03-22 22:24:27 +00:00
fn important_area(&self, _: Vec2) -> Rect {
self.content.important_area(self.content.size)
+ self.borders.top_left() + self.padding.top_left()
}
2015-05-16 21:02:15 +00:00
}