//! Provide higher-level abstraction to draw things on backends. use backend::Backend; use direction::Orientation; use enumset::EnumSet; use std::cmp::min; use theme::{BorderStyle, ColorStyle, Effect, PaletteColor, Style, Theme}; use unicode_segmentation::UnicodeSegmentation; use unicode_width::UnicodeWidthStr; use utils::lines::simple::{prefix, suffix}; use vec::Vec2; use with::With; /// Convenient interface to draw on a subset of the screen. /// /// The area it can print on is defined by `offset` and `size`. /// /// The part of the content it will print is defined by `content_offset` /// and `size`. pub struct Printer<'a, 'b> { /// Offset into the window this printer should start drawing at. /// /// A print request at `x` will really print at `x + offset`. pub offset: Vec2, /// Size of the area we are allowed to draw on. /// /// Anything outside of this should be discarded. pub output_size: Vec2, /// Size allocated to the view. /// /// This should be the same value as the one given in the last call to /// `View::layout`. pub size: Vec2, /// Offset into the view for this printer. /// /// A print request `x`, will really print at `x - content_offset`. pub content_offset: Vec2, /// Whether the view to draw is currently focused or not. pub focused: bool, /// Currently used theme pub theme: &'a Theme, /// Backend used to actually draw things backend: &'b Backend, } impl<'a, 'b> Clone for Printer<'a, 'b> { fn clone(&self) -> Self { Printer { offset: self.offset, content_offset: self.content_offset, output_size: self.output_size, size: self.size, focused: self.focused, theme: self.theme, backend: self.backend, } } } impl<'a, 'b> Printer<'a, 'b> { /// 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: &'b Backend, ) -> Self { let size = size.into(); Printer { offset: Vec2::zero(), content_offset: Vec2::zero(), output_size: size, size, focused: true, theme, 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]); } // 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, start: S, text: &str) { // Where we are asked to start printing. Oh boy. let start = start.into(); // We accept requests between `content_offset` and // `content_offset + output_size`. if !(start < (self.output_size + self.content_offset)) { return; } // If start < content_offset, part of the text will not be visible. // This is the part of the text that's hidden: // (It should always be smaller than the content offset) let hidden_part = self.content_offset.saturating_sub(start); if hidden_part.y > 0 { // Since we are printing a single line, there's nothing we can do. return; } let text_width = text.width(); // If we're waaaay too far left, just give up. if hidden_part.x > text_width { return; } // We have to drop hidden_part.x width from the start of the string. // prefix() may be too short if there's a double-width character. // So instead, keep the suffix and drop the prefix. // TODO: use a different prefix method that is *at least* the width // (and not *at most*) let tail = suffix(text.graphemes(true), text_width - hidden_part.x, ""); let skipped_len = text.len() - tail.length; let skipped_width = text_width - tail.width; assert_eq!(text[..skipped_len].width(), skipped_width); // This should be equal most of the time, except when there's a double // character preventing us from splitting perfectly. assert!(skipped_width >= hidden_part.x); // Drop part of the text, and move the cursor correspondingly. let text = &text[skipped_len..]; let start = start + (skipped_width, 0); assert!(start.fits(self.content_offset)); // What we did before should guarantee that this won't overflow. let start = start - self.content_offset; // Do we have enough room for the entire line? let room = self.output_size.x - start.x; // Drop the end of the text if it's too long // 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]; assert!(text.width() <= room); let start = start + self.offset; self.backend.print_at(start, text); } /// Prints a vertical line using the given character. pub fn print_vline>( &self, start: T, height: usize, c: &str, ) { let start = start.into(); // Here again, we can abort if we're trying to print too far right or // too low. if !(start < (self.output_size + self.content_offset)) { return; } // hidden_part describes how far to the top left of the viewport we are. let hidden_part = self.content_offset.saturating_sub(start); if hidden_part.x > 0 || hidden_part.y >= height { // We're printing a single column, so we can't do much here. return; } // Skip `hidden_part` let start = start + hidden_part; assert!(start.fits(self.content_offset)); let height = height - hidden_part.y; // What we did before ensures this won't overflow. let start = start - self.content_offset; // Don't go overboard let height = min(height, self.output_size.y - start.y); let start = start + self.offset; for y in 0..height { self.backend.print_at(start + (0, y), c); } } /// Prints a line using the given character. pub fn print_line>( &self, orientation: Orientation, start: T, length: usize, c: &str, ) { match orientation { Orientation::Vertical => self.print_vline(start, length, c), Orientation::Horizontal => self.print_hline(start, length, c), } } /// Prints a horizontal line using the given character. pub fn print_hline>(&self, start: T, width: usize, c: &str) { let start = start.into(); // Nothing to be done if the start if too far to the bottom/right if !(start < (self.output_size + self.content_offset)) { return; } let hidden_part = self.content_offset.saturating_sub(start); if hidden_part.y > 0 || hidden_part.x >= width { // We're printing a single line, so we can't do much here. return; } // Skip `hidden_part` let start = start + hidden_part; assert!(start.fits(self.content_offset)); let width = width - hidden_part.x; // Don't go too far let start = start - self.content_offset; // Don't write too much if we're close to the end let width = min(width, (self.output_size.x - start.x) / c.width()); // Could we avoid allocating? let text: String = ::std::iter::repeat(c).take(width).collect(); let start = start + self.offset; self.backend.print_at(start, &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