2015-05-15 19:16:58 +00:00
|
|
|
//! Makes drawing on ncurses windows easier.
|
|
|
|
|
2015-05-22 06:29:49 +00:00
|
|
|
|
2016-10-10 00:16:35 +00:00
|
|
|
use backend::{self, Backend};
|
2016-10-09 22:47:06 +00:00
|
|
|
use std::cell::Cell;
|
2016-10-02 22:22:29 +00:00
|
|
|
use std::cmp::min;
|
2016-10-09 22:47:06 +00:00
|
|
|
use std::rc::Rc;
|
2016-06-30 00:36:20 +00:00
|
|
|
|
2016-08-05 00:27:04 +00:00
|
|
|
use theme::{BorderStyle, ColorStyle, Effect, Theme};
|
2016-10-02 22:22:29 +00:00
|
|
|
use unicode_segmentation::UnicodeSegmentation;
|
|
|
|
|
2017-02-01 19:22:36 +00:00
|
|
|
use utils::prefix;
|
2016-07-12 02:24:00 +00:00
|
|
|
use vec::Vec2;
|
2015-05-15 18:58:47 +00:00
|
|
|
|
2015-05-23 22:58:06 +00:00
|
|
|
/// Convenient interface to draw on a subset of the screen.
|
2016-09-23 05:00:58 +00:00
|
|
|
pub struct Printer<'a> {
|
2015-05-15 18:58:47 +00:00
|
|
|
/// Offset into the window this printer should start drawing at.
|
|
|
|
pub offset: Vec2,
|
|
|
|
/// Size of the area we are allowed to draw on.
|
|
|
|
pub size: Vec2,
|
2015-05-31 04:05:34 +00:00
|
|
|
/// Whether the view to draw is currently focused or not.
|
|
|
|
pub focused: bool,
|
2015-06-06 01:08:05 +00:00
|
|
|
/// Currently used theme
|
2017-06-12 18:59:33 +00:00
|
|
|
pub theme: &'a Theme,
|
2016-09-23 05:00:58 +00:00
|
|
|
|
2016-10-09 22:47:06 +00:00
|
|
|
/// `true` if nothing has been drawn yet.
|
|
|
|
new: Rc<Cell<bool>>,
|
|
|
|
/// Backend used to actually draw things
|
2016-10-10 00:16:35 +00:00
|
|
|
backend: &'a backend::Concrete,
|
2015-05-15 18:58:47 +00:00
|
|
|
}
|
|
|
|
|
2016-09-23 05:10:14 +00:00
|
|
|
impl<'a> Printer<'a> {
|
2015-05-22 06:29:49 +00:00
|
|
|
/// Creates a new printer on the given window.
|
2016-09-23 05:00:58 +00:00
|
|
|
///
|
|
|
|
/// But nobody needs to know that.
|
|
|
|
#[doc(hidden)]
|
2017-06-12 18:59:33 +00:00
|
|
|
pub fn new<T: Into<Vec2>>(size: T, theme: &'a Theme,
|
2017-02-07 02:18:17 +00:00
|
|
|
backend: &'a backend::Concrete)
|
|
|
|
-> Self {
|
2015-05-22 06:29:49 +00:00
|
|
|
Printer {
|
|
|
|
offset: Vec2::zero(),
|
2016-07-12 02:24:00 +00:00
|
|
|
size: size.into(),
|
2015-05-31 04:05:34 +00:00
|
|
|
focused: true,
|
2015-06-06 01:08:05 +00:00
|
|
|
theme: theme,
|
2016-10-09 22:47:06 +00:00
|
|
|
new: Rc::new(Cell::new(true)),
|
2016-09-23 05:00:58 +00:00
|
|
|
backend: backend,
|
2015-05-22 06:29:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-10-09 22:47:06 +00:00
|
|
|
/// Clear the screen.
|
|
|
|
///
|
2016-10-15 01:18:19 +00:00
|
|
|
/// It will discard anything drawn before.
|
|
|
|
///
|
|
|
|
/// Users rarely need to call this directly.
|
2016-10-09 22:47:06 +00:00
|
|
|
pub fn clear(&self) {
|
2017-06-13 06:29:26 +00:00
|
|
|
self.backend.clear(self.theme.colors.background);
|
2016-10-09 22:47:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns `true` if nothing has been printed yet.
|
|
|
|
pub fn is_new(&self) -> bool {
|
|
|
|
self.new.get()
|
|
|
|
}
|
|
|
|
|
2016-06-28 05:10:59 +00:00
|
|
|
// TODO: use &mut self? We don't *need* it, but it may make sense.
|
|
|
|
// We don't want people to start calling prints in parallel?
|
2015-05-15 18:58:47 +00:00
|
|
|
/// Prints some text at the given position relative to the window.
|
2016-07-12 02:24:00 +00:00
|
|
|
pub fn print<S: Into<Vec2>>(&self, pos: S, text: &str) {
|
2016-10-09 22:47:06 +00:00
|
|
|
self.new.set(false);
|
|
|
|
|
2016-07-12 02:24:00 +00:00
|
|
|
let p = pos.into();
|
2016-03-15 22:37:57 +00:00
|
|
|
if p.y >= self.size.y || p.x >= self.size.x {
|
|
|
|
return;
|
|
|
|
}
|
2015-05-24 08:49:50 +00:00
|
|
|
// Do we have enough room for the entire line?
|
2015-05-25 21:46:29 +00:00
|
|
|
let room = self.size.x - p.x;
|
2015-05-24 08:49:50 +00:00
|
|
|
// We want the number of CHARACTERS, not bytes.
|
2016-06-28 05:10:59 +00:00
|
|
|
// (Actually we want the "width" of the string, see unicode-width)
|
2017-02-01 19:22:36 +00:00
|
|
|
let prefix_len = prefix(text.graphemes(true), room, "").length;
|
2016-07-26 06:54:33 +00:00
|
|
|
let text = &text[..prefix_len];
|
2015-05-29 00:37:00 +00:00
|
|
|
|
2015-05-24 08:49:50 +00:00
|
|
|
let p = p + self.offset;
|
2016-09-24 23:51:42 +00:00
|
|
|
self.backend.print_at((p.x, p.y), text);
|
2015-05-15 18:58:47 +00:00
|
|
|
}
|
|
|
|
|
2015-05-20 17:31:38 +00:00
|
|
|
/// Prints a vertical line using the given character.
|
2016-07-12 02:24:00 +00:00
|
|
|
pub fn print_vline<T: Into<Vec2>>(&self, start: T, len: usize, c: &str) {
|
2016-10-09 22:47:06 +00:00
|
|
|
self.new.set(false);
|
|
|
|
|
2016-07-12 02:24:00 +00:00
|
|
|
let p = start.into();
|
2016-03-15 22:37:57 +00:00
|
|
|
if p.y > self.size.y || p.x > self.size.x {
|
|
|
|
return;
|
|
|
|
}
|
2015-05-24 08:49:50 +00:00
|
|
|
let len = min(len, self.size.y - p.y);
|
|
|
|
|
|
|
|
let p = p + self.offset;
|
2016-06-25 22:52:19 +00:00
|
|
|
for y in 0..len {
|
2016-09-24 23:51:42 +00:00
|
|
|
self.backend.print_at((p.x, (p.y + y)), c);
|
2016-06-25 22:52:19 +00:00
|
|
|
}
|
2015-05-20 17:31:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Prints a horizontal line using the given character.
|
2016-07-12 02:24:00 +00:00
|
|
|
pub fn print_hline<T: Into<Vec2>>(&self, start: T, len: usize, c: &str) {
|
2016-10-09 22:47:06 +00:00
|
|
|
self.new.set(false);
|
|
|
|
|
2016-07-12 02:24:00 +00:00
|
|
|
let p = start.into();
|
2016-03-15 22:37:57 +00:00
|
|
|
if p.y > self.size.y || p.x > self.size.x {
|
|
|
|
return;
|
|
|
|
}
|
2015-05-24 08:49:50 +00:00
|
|
|
let len = min(len, self.size.x - p.x);
|
2016-10-08 23:47:15 +00:00
|
|
|
let text: String = ::std::iter::repeat(c).take(len).collect();
|
2015-05-24 08:49:50 +00:00
|
|
|
|
|
|
|
let p = p + self.offset;
|
2016-10-10 00:42:57 +00:00
|
|
|
self.backend.print_at((p.x, p.y), &text);
|
2015-05-22 06:29:49 +00:00
|
|
|
}
|
|
|
|
|
2015-05-27 07:09:22 +00:00
|
|
|
/// Call the given closure with a colored printer,
|
|
|
|
/// that will apply the given color on prints.
|
2015-05-26 23:48:27 +00:00
|
|
|
///
|
|
|
|
/// # Examples
|
|
|
|
///
|
2016-06-30 00:46:39 +00:00
|
|
|
/// ```no_run
|
2016-07-14 06:25:54 +00:00
|
|
|
/// # use cursive::Printer;
|
2015-06-06 01:08:05 +00:00
|
|
|
/// # use cursive::theme;
|
2016-10-10 21:08:07 +00:00
|
|
|
/// # use cursive::backend::{self, Backend};
|
2016-10-10 18:23:00 +00:00
|
|
|
/// # let b = backend::Concrete::init();
|
2017-06-13 01:03:52 +00:00
|
|
|
/// # let t = theme::load_default();
|
|
|
|
/// # let printer = Printer::new((6,4), &t, &b);
|
2016-07-01 06:38:01 +00:00
|
|
|
/// printer.with_color(theme::ColorStyle::Highlight, |printer| {
|
2015-05-26 23:48:27 +00:00
|
|
|
/// printer.print((0,0), "This text is highlighted!");
|
|
|
|
/// });
|
|
|
|
/// ```
|
2016-07-01 06:38:01 +00:00
|
|
|
pub fn with_color<F>(&self, c: ColorStyle, f: F)
|
2016-06-30 00:36:20 +00:00
|
|
|
where F: FnOnce(&Printer)
|
2015-05-23 22:58:06 +00:00
|
|
|
{
|
2017-06-12 18:59:33 +00:00
|
|
|
self.backend.with_color(c.resolve(self.theme), || f(self));
|
2015-05-20 17:31:38 +00:00
|
|
|
}
|
|
|
|
|
2015-05-27 07:09:22 +00:00
|
|
|
/// Same as `with_color`, but apply a ncurses style instead,
|
|
|
|
/// like `ncurses::A_BOLD()` or `ncurses::A_REVERSE()`.
|
|
|
|
///
|
|
|
|
/// Will probably use a cursive enum some day.
|
2016-06-30 00:36:20 +00:00
|
|
|
pub fn with_effect<F>(&self, effect: Effect, f: F)
|
|
|
|
where F: FnOnce(&Printer)
|
2015-05-27 04:45:00 +00:00
|
|
|
{
|
2016-09-24 23:51:42 +00:00
|
|
|
self.backend.with_effect(effect, || f(self));
|
2015-05-27 04:45:00 +00:00
|
|
|
}
|
|
|
|
|
2015-05-20 17:36:35 +00:00
|
|
|
/// Prints a rectangular box.
|
|
|
|
///
|
2016-08-05 00:38:26 +00:00
|
|
|
/// If `invert` is `true`, and the theme uses `Outset` borders, then the
|
|
|
|
/// box will use an "inset" style instead.
|
|
|
|
///
|
2015-05-20 17:36:35 +00:00
|
|
|
/// # Examples
|
|
|
|
///
|
2016-08-05 01:01:46 +00:00
|
|
|
/// ```no_run
|
2016-07-14 06:25:54 +00:00
|
|
|
/// # use cursive::Printer;
|
2015-06-06 01:08:05 +00:00
|
|
|
/// # use cursive::theme;
|
2016-10-10 21:08:07 +00:00
|
|
|
/// # use cursive::backend::{self, Backend};
|
2016-10-10 18:23:00 +00:00
|
|
|
/// # let b = backend::Concrete::init();
|
2017-06-13 01:03:52 +00:00
|
|
|
/// # let t = theme::load_default();
|
|
|
|
/// # let printer = Printer::new((6,4), &t, &b);
|
2016-08-05 00:38:26 +00:00
|
|
|
/// printer.print_box((0,0), (6,4), false);
|
2015-05-20 17:36:35 +00:00
|
|
|
/// ```
|
2016-08-05 00:27:04 +00:00
|
|
|
pub fn print_box<T: Into<Vec2>, S: Into<Vec2>>(&self, start: T, size: S,
|
|
|
|
invert: bool) {
|
2016-10-09 22:47:06 +00:00
|
|
|
self.new.set(false);
|
|
|
|
|
2016-07-17 08:20:41 +00:00
|
|
|
let start = start.into();
|
|
|
|
let size = size.into();
|
|
|
|
if size.x < 2 || size.y < 2 {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
let size = size - (1, 1);
|
|
|
|
|
2016-08-05 17:49:16 +00:00
|
|
|
self.with_high_border(invert, |s| {
|
|
|
|
s.print(start, "┌");
|
|
|
|
s.print(start + size.keep_y(), "└");
|
|
|
|
s.print_hline(start + (1, 0), size.x - 1, "─");
|
|
|
|
s.print_vline(start + (0, 1), size.y - 1, "│");
|
|
|
|
});
|
2016-07-17 08:20:41 +00:00
|
|
|
|
2016-08-05 17:49:16 +00:00
|
|
|
self.with_low_border(invert, |s| {
|
2016-08-05 00:27:04 +00:00
|
|
|
s.print(start + size.keep_x(), "┐");
|
|
|
|
s.print(start + size, "┘");
|
|
|
|
s.print_hline(start + (1, 0) + size.keep_y(), size.x - 1, "─");
|
|
|
|
s.print_vline(start + (0, 1) + size.keep_x(), size.y - 1, "│");
|
|
|
|
});
|
2016-06-28 05:10:59 +00:00
|
|
|
}
|
|
|
|
|
2016-08-05 17:49:16 +00:00
|
|
|
/// Runs the given function using a color depending on the theme.
|
|
|
|
///
|
|
|
|
/// * If the theme's borders is `None`, return without calling `f`.
|
|
|
|
/// * If the theme's borders is "outset" and `invert` is `false`,
|
|
|
|
/// use `ColorStyle::Tertiary`.
|
|
|
|
/// * Otherwise, use `ColorStyle::Primary`.
|
|
|
|
pub fn with_high_border<F>(&self, invert: bool, f: F)
|
|
|
|
where F: FnOnce(&Printer)
|
|
|
|
{
|
|
|
|
let color = match self.theme.borders {
|
2017-06-12 18:59:33 +00:00
|
|
|
BorderStyle::None => return,
|
|
|
|
BorderStyle::Outset if !invert => ColorStyle::Tertiary,
|
2016-08-05 17:49:16 +00:00
|
|
|
_ => ColorStyle::Primary,
|
|
|
|
};
|
|
|
|
|
|
|
|
self.with_color(color, f);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Runs the given function using a color depending on the theme.
|
|
|
|
///
|
|
|
|
/// * If the theme's borders is `None`, return without calling `f`.
|
|
|
|
/// * If the theme's borders is "outset" and `invert` is `true`,
|
|
|
|
/// use `ColorStyle::Tertiary`.
|
|
|
|
/// * Otherwise, use `ColorStyle::Primary`.
|
|
|
|
pub fn with_low_border<F>(&self, invert: bool, f: F)
|
|
|
|
where F: FnOnce(&Printer)
|
|
|
|
{
|
|
|
|
let color = match self.theme.borders {
|
2017-06-12 18:59:33 +00:00
|
|
|
BorderStyle::None => return,
|
|
|
|
BorderStyle::Outset if invert => ColorStyle::Tertiary,
|
2016-08-05 17:49:16 +00:00
|
|
|
_ => ColorStyle::Primary,
|
|
|
|
};
|
|
|
|
|
|
|
|
self.with_color(color, f);
|
|
|
|
}
|
|
|
|
|
2016-07-12 03:26:33 +00:00
|
|
|
/// Apply a selection style and call the given function.
|
|
|
|
///
|
|
|
|
/// * If `selection` is `false`, simply uses `ColorStyle::Primary`.
|
|
|
|
/// * If `selection` is `true`:
|
|
|
|
/// * If the printer currently has the focus,
|
|
|
|
/// uses `ColorStyle::Highlight`.
|
|
|
|
/// * Otherwise, uses `ColorStyle::HighlightInactive`.
|
2016-07-02 08:01:09 +00:00
|
|
|
pub fn with_selection<F: FnOnce(&Printer)>(&self, selection: bool, f: F) {
|
|
|
|
self.with_color(if selection {
|
2016-07-10 02:05:51 +00:00
|
|
|
if self.focused {
|
|
|
|
ColorStyle::Highlight
|
|
|
|
} else {
|
|
|
|
ColorStyle::HighlightInactive
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
ColorStyle::Primary
|
|
|
|
},
|
|
|
|
f);
|
2016-07-02 08:01:09 +00:00
|
|
|
}
|
|
|
|
|
2016-07-12 03:26:33 +00:00
|
|
|
/// Prints a horizontal delimiter with side border `├` and `┤`.
|
2016-07-12 02:24:00 +00:00
|
|
|
pub fn print_hdelim<T: Into<Vec2>>(&self, start: T, len: usize) {
|
|
|
|
let start = start.into();
|
2016-06-28 05:10:59 +00:00
|
|
|
self.print(start, "├");
|
|
|
|
self.print_hline(start + (1, 0), len - 2, "─");
|
|
|
|
self.print(start + (len - 1, 0), "┤");
|
2015-05-20 17:31:38 +00:00
|
|
|
}
|
|
|
|
|
2015-05-15 18:58:47 +00:00
|
|
|
/// Returns a printer on a subset of this one's area.
|
2016-09-23 05:00:58 +00:00
|
|
|
pub fn sub_printer<S: Into<Vec2>, T: Into<Vec2>>(&'a self, offset: S,
|
2016-07-16 20:52:28 +00:00
|
|
|
size: T, focused: bool)
|
2016-09-23 05:00:58 +00:00
|
|
|
-> Printer<'a> {
|
2016-07-17 08:20:41 +00:00
|
|
|
let size = size.into();
|
2016-07-13 08:19:05 +00:00
|
|
|
let offset = offset.into().or_min(self.size);
|
2015-05-15 18:58:47 +00:00
|
|
|
Printer {
|
2016-07-13 08:19:05 +00:00
|
|
|
offset: self.offset + offset,
|
2015-05-15 18:58:47 +00:00
|
|
|
// We can't be larger than what remains
|
2016-07-17 08:20:41 +00:00
|
|
|
size: Vec2::min(self.size - offset, size),
|
2015-05-31 04:05:34 +00:00
|
|
|
focused: self.focused && focused,
|
2017-06-12 18:59:33 +00:00
|
|
|
theme: self.theme,
|
2016-09-23 05:00:58 +00:00
|
|
|
backend: self.backend,
|
2016-10-09 22:47:06 +00:00
|
|
|
new: self.new.clone(),
|
2015-05-15 18:58:47 +00:00
|
|
|
}
|
|
|
|
}
|
2016-07-16 06:44:38 +00:00
|
|
|
|
|
|
|
/// Returns a sub-printer with the given offset.
|
|
|
|
pub fn offset<S: Into<Vec2>>(&self, offset: S, focused: bool) -> Printer {
|
|
|
|
self.sub_printer(offset, self.size, focused)
|
|
|
|
}
|
2015-05-15 18:58:47 +00:00
|
|
|
}
|