diff --git a/src/lib.rs b/src/lib.rs index 3e6e6da..32837e6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -82,9 +82,9 @@ pub mod theme; pub mod align; pub mod menu; pub mod direction; +pub mod utils; // This probably doesn't need to be public? -mod utils; mod printer; mod xy; mod with; diff --git a/src/menu.rs b/src/menu.rs index a9b4b90..165ba3f 100644 --- a/src/menu.rs +++ b/src/menu.rs @@ -1,4 +1,4 @@ -//! Module to build menus. +//! Build menu trees. //! //! Menus are a way to arrange many actions in groups of more manageable size. //! diff --git a/src/printer.rs b/src/printer.rs index ac13304..d115f84 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -3,7 +3,7 @@ use std::cmp::min; use unicode_segmentation::UnicodeSegmentation; -use utils::head_bytes; +use utils::prefix_length; use backend::Backend; use B; @@ -47,7 +47,7 @@ impl Printer { 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 = head_bytes(text.graphemes(true), room, ""); + let prefix_len = prefix_length(text.graphemes(true), room, ""); let text = &text[..prefix_len]; let p = p + self.offset; diff --git a/src/theme.rs b/src/theme.rs index f241788..1de8e08 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -1,4 +1,4 @@ -//! Module to handle colors and themes in the UI. +//! Handle colors and themes in the UI. //! //! # Color palette //! diff --git a/src/utils.rs b/src/utils.rs index df75970..91f3f27 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,20 +1,123 @@ -use unicode_width::UnicodeWidthStr; +//! Toolbox to make text layout easier. -// Computes a sub-string length that fits in the given `width`. -// -// Takes non-breakable elements from `iter`, while keeping the -// string width under `width` (and adding the length of `delimiter` -// between each element). -// -// Example: -// -// ``` -// let my_text = "blah..."; -// // This returns the number of bytes for a prefix of `my_text` that -// // fits within 5 cells. -// head_bytes(my_text.graphemes(true), 5, ""); -// ``` -pub fn head_bytes<'a, I: Iterator>(iter: I, width: usize, +use unicode_width::UnicodeWidthStr; +use unicode_segmentation::UnicodeSegmentation; + +/// Generates rows of text in constrained width. +/// +/// Given a long text and a width constraint, it iterates over +/// substrings of the text, each within the constraint. +pub struct LinesIterator<'a> { + content: &'a str, + start: usize, + width: usize, +} + +impl<'a> LinesIterator<'a> { + /// Returns a new `LinesIterator` on `content`. + /// + /// Yields rows of `width` cells or less. + pub fn new(content: &'a str, width: usize) -> Self { + LinesIterator { + content: content, + width: width, + start: 0, + } + } +} + +/// Represents a row of text within a `String`. +/// +/// A row is made of an offset into a parent `String` and a length. +/// The corresponding substring should take `width` cells when printed. +pub struct Row { + /// Beginning of the row in the parent `String`. + pub start: usize, + /// Length of the row, in bytes. + pub end: usize, + /// Width of the row, in cells. + pub width: usize, +} + +impl<'a> Iterator for LinesIterator<'a> { + type Item = Row; + + fn next(&mut self) -> Option { + if self.start >= self.content.len() { + // This is the end. + return None; + } + + let start = self.start; + let content = &self.content[self.start..]; + + let next = content.find('\n').unwrap_or(content.len()); + let content = &content[..next]; + + let line_width = content.width(); + if line_width <= self.width { + // We found a newline before the allowed limit. + // Break early. + self.start += next + 1; + return Some(Row { + start: start, + end: next + start, + width: line_width, + }); + } + + // Keep adding indivisible tokens + let prefix_length = + match prefix_length(content.split(' '), self.width, " ") { + 0 => prefix_length(content.graphemes(true), self.width, ""), + other => { + self.start += 1; + other + } + }; + + if prefix_length == 0 { + // This mean we can't even get a single char? + // Sucks. Let's bail. + return None; + } + + self.start += prefix_length; + + Some(Row { + start: start, + end: start + prefix_length, + width: self.width, + }) + } +} + +/// Computes a sub-string length that fits in the given `width`. +/// +/// Takes non-breakable elements from `iter`, while keeping the +/// string width under `width` (and adding the length of `delimiter` +/// between each element). +/// +/// Given `total_text = iter.collect().join(delimiter)`, the result +/// is the length of the longest prefix of `width` or less cells, +/// without breaking inside an element. +/// +/// Example: +/// +/// ``` +/// # extern crate cursive; +/// extern crate unicode_segmentation; +/// use unicode_segmentation::UnicodeSegmentation; +/// +/// # use cursive::utils::prefix_length; +/// # fn main() { +/// let my_text = "blah..."; +/// // This returns the number of bytes for a prefix of `my_text` that +/// // fits within 5 cells. +/// prefix_length(my_text.graphemes(true), 5, ""); +/// # } +/// ``` +pub fn prefix_length<'a, I: Iterator>(iter: I, width: usize, delimiter: &str) -> usize { let delimiter_width = delimiter.width(); diff --git a/src/view/mod.rs b/src/view/mod.rs index 9efb516..aa3b054 100644 --- a/src/view/mod.rs +++ b/src/view/mod.rs @@ -1,4 +1,4 @@ -//! Define the base elements required to build views. +//! Base elements required to build views. //! //! Views are the main building blocks of your UI. //! diff --git a/src/views/mod.rs b/src/views/mod.rs index f2dad61..9379305 100644 --- a/src/views/mod.rs +++ b/src/views/mod.rs @@ -1,4 +1,4 @@ -//! Defines various views to use when creating the layout. +//! Various views to use when creating the layout. mod box_view; mod button; diff --git a/src/views/text_view.rs b/src/views/text_view.rs index 01c6a2d..f0e3052 100644 --- a/src/views/text_view.rs +++ b/src/views/text_view.rs @@ -2,16 +2,15 @@ use XY; use With; use direction::Direction; use vec::Vec2; -use view::{View, SizeCache}; +use view::{SizeCache, View}; use Printer; use align::*; use event::*; use view::ScrollBase; -use utils::head_bytes; +use utils::{Row, LinesIterator}; use unicode_width::UnicodeWidthStr; -use unicode_segmentation::UnicodeSegmentation; /// A simple view showing a fixed text @@ -30,13 +29,6 @@ pub struct TextView { width: Option, } -// Subset of the main content representing a row on the display. -struct Row { - start: usize, - end: usize, - width: usize, -} - // If the last character is a newline, strip it. fn strip_last_newline(content: &str) -> &str { if !content.is_empty() && content.chars().last().unwrap() == '\n' { @@ -182,77 +174,6 @@ impl TextView { } } -// Given a multiline string, and a given maximum width, -// iterates on the computed rows. -struct LinesIterator<'a> { - content: &'a str, - start: usize, - width: usize, -} - -impl<'a> LinesIterator<'a> { - // Start an iterator on the given content. - fn new(content: &'a str, width: usize) -> Self { - LinesIterator { - content: content, - width: width, - start: 0, - } - } -} - -impl<'a> Iterator for LinesIterator<'a> { - type Item = Row; - - fn next(&mut self) -> Option { - if self.start >= self.content.len() { - // This is the end. - return None; - } - - let start = self.start; - let content = &self.content[self.start..]; - - let next = content.find('\n').unwrap_or(content.len()); - let content = &content[..next]; - - let line_width = content.width(); - if line_width <= self.width { - // We found a newline before the allowed limit. - // Break early. - self.start += next + 1; - return Some(Row { - start: start, - end: next + start, - width: line_width, - }); - } - - // Keep adding indivisible tokens - let head_bytes = - match head_bytes(content.split(' '), self.width, " ") { - 0 => head_bytes(content.graphemes(true), self.width, ""), - other => { - self.start += 1; - other - } - }; - - if head_bytes == 0 { - // This mean we can't even get a single char? - // Sucks. Let's bail. - return None; - } - - self.start += head_bytes; - - Some(Row { - start: start, - end: start + head_bytes, - width: self.width, - }) - } -} impl View for TextView { fn draw(&self, printer: &Printer) {