//! Makes drawing on ncurses windows easier. use backend::Backend; use enumset::EnumSet; use std::cell::Cell; use std::cmp::min; use std::rc::Rc; use theme::{BorderStyle, ColorStyle, Effect, PaletteColor, Style, Theme}; use unicode_segmentation::UnicodeSegmentation; use utils::lines::simple::prefix; use vec::Vec2; use with::With; /// Convenient interface to draw on a subset of the screen. pub struct Printer<'a> { /// Offset into the window this printer should start drawing at. pub offset: Vec2, /// Offset into the view for this printer. pub content_offset: Vec2, /// Size of the area we are allowed to draw on. /// /// Anything outside of this should be discarded. pub size: Vec2, /// Whether the view to draw is currently focused or not. pub focused: bool, /// Currently used theme pub theme: &'a Theme, /// `true` if nothing has been drawn yet. new: Rc>, /// Backend used to actually draw things backend: &'a Backend, } impl<'a> Clone for Printer<'a> { fn clone(&self) -> Self { Printer { offset: self.offset, content_offset: self.content_offset, size: self.size, focused: self.focused, theme: self.theme, backend: self.backend, new: Rc::clone(&self.new), } } } impl<'a> Printer<'a> { /// Creates a new printer on the given window. /// /// But nobody needs to know that. #[doc(hidden)] pub fn new>( size: T, theme: &'a Theme, backend: &'a Backend ) -> Self { Printer { offset: Vec2::zero(), content_offset: Vec2::zero(), size: size.into(), focused: true, theme, new: Rc::new(Cell::new(true)), backend, } } /// Clear the screen. /// /// It will discard anything drawn before. /// /// Users rarely need to call this directly. pub fn clear(&self) { self.backend .clear(self.theme.palette[PaletteColor::Background]); } /// Returns `true` if nothing has been printed yet. pub fn is_new(&self) -> bool { self.new.get() } // 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? /// Prints some text at the given position relative to the window. pub fn print>(&self, pos: S, text: &str) { self.new.set(false); let p = pos.into(); if p.y >= self.size.y || p.x >= self.size.x { return; } // Do we have enough room for the entire line? let room = self.size.x - p.x; // We want the number of CHARACTERS, not bytes. // (Actually we want the "width" of the string, see unicode-width) let prefix_len = prefix(text.graphemes(true), room, "").length; let text = &text[..prefix_len]; let p = p + self.offset; self.backend.print_at(p, text); } /// Prints a vertical line using the given character. pub fn print_vline>(&self, start: T, len: usize, c: &str) { self.new.set(false); let p = start.into(); if p.y > self.size.y || p.x > self.size.x { return; } let len = min(len, self.size.y - p.y); let p = p + self.offset; for y in 0..len { self.backend.print_at(p + (0,y), c); } } /// Prints a horizontal line using the given character. pub fn print_hline>(&self, start: T, len: usize, c: &str) { self.new.set(false); let p = start.into(); if p.y > self.size.y || p.x > self.size.x { return; } let len = min(len, self.size.x - p.x); let text: String = ::std::iter::repeat(c).take(len).collect(); let p = p + self.offset; self.backend.print_at(p, &text); } /// Call the given closure with a colored printer, /// that will apply the given color on prints. /// /// # Examples /// /// ```rust /// # use cursive::Printer; /// # use cursive::theme; /// # use cursive::backend; /// # let b = backend::dummy::Backend::init(); /// # let t = theme::load_default(); /// # let printer = Printer::new((6,4), &t, &*b); /// printer.with_color(theme::ColorStyle::highlight(), |printer| { /// printer.print((0,0), "This text is highlighted!"); /// }); /// ``` pub fn with_color(&self, c: ColorStyle, f: F) where F: FnOnce(&Printer), { let old = self.backend.set_color(c.resolve(&self.theme.palette)); f(self); self.backend.set_color(old); } /// Call the given closure with a styled printer, /// that will apply the given style on prints. pub fn with_style(&self, style: T, f: F) where F: FnOnce(&Printer), T: Into