use crate::color::Color;
use crate::geometry::*;
use std::fmt::Display;
use unicode_segmentation::{Graphemes, UnicodeSegmentation};
#[derive(Default, Debug, Clone, PartialEq, Eq, Hash)]
pub struct Spot {
pub symbol: String,
pub style: Style,
}
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct Style {
pub bold: Option<bool>,
pub underline: Option<bool>,
pub foreground: Option<Color>,
pub background: Option<Color>,
}
impl Style {
pub fn or(&self, other: &Style) -> Style {
Style {
bold: self.bold.or(other.bold),
underline: self.underline.or(other.underline),
foreground: self.foreground.or(other.foreground),
background: self.background.or(other.background),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Drawing {
pub text: String,
pub scope: Window,
pub style: Style,
}
impl Default for Drawing {
fn default() -> Self {
Drawing {
text: Default::default(),
style: Default::default(),
scope: window(
Point::default(),
point(x(u16::max_value()), y(u16::max_value())),
),
}
}
}
pub trait Drawable {
fn as_drawing(&self) -> Drawing;
}
impl<T: Display> Drawable for T {
fn as_drawing(&self) -> Drawing {
Drawing::new().with_text(self)
}
}
impl Drawable for Spot {
fn as_drawing(&self) -> Drawing {
Drawing::new()
.with_text(&self.symbol)
.with_style(self.style)
}
}
impl Drawing {
pub fn new() -> Drawing {
Drawing::default()
}
pub fn with_style(mut self, style: Style) -> Self {
self.style = style;
self
}
pub fn append(mut self, text: impl Display) -> Self {
self.text.push_str(&format!("{}", text));
self
}
pub fn with_text(mut self, text: impl Display) -> Self {
self.text = format!("{}", text);
self
}
pub fn with_pos(mut self, position: Point) -> Self {
self.scope = window(position, self.scope.size);
self
}
pub fn scope(mut self, scope: Window) -> Self {
self.scope = scope;
self
}
pub fn size(mut self, size: Point) -> Self {
self.scope = window(self.scope.position, size);
self
}
pub fn left(mut self, left: u16) -> Self {
self.scope = window(point(x(left), self.scope.position.y), self.scope.size);
self
}
pub fn top(mut self, top: u16) -> Self {
self.scope = window(point(self.scope.position.x, y(top)), self.scope.size);
self
}
pub fn width(mut self, width: u16) -> Self {
self.scope = window(self.scope.position, point(x(width), self.scope.size.y));
self
}
pub fn height(mut self, height: u16) -> Self {
self.scope = window(self.scope.position, point(self.scope.size.x, y(height)));
self
}
pub fn fg(mut self, foreground: impl Into<Option<Color>>) -> Self {
self.style.foreground = foreground.into();
self
}
pub fn bg(mut self, background: impl Into<Option<Color>>) -> Self {
self.style.background = background.into();
self
}
pub fn lines<'a>(&'a self) -> DrawingLines<'a> {
DrawingLines(self)
}
pub fn spots<'a>(&'a self) -> DrawingSpots<'a> {
DrawingSpots {
style: self.style,
size: self.scope.size,
graphemes: self.text.graphemes(true),
pos: point(x(0), y(0)),
}
}
pub fn actual_width(&self) -> X {
self.lines()
.into_iter()
.map(|l| x(l.graphemes(true).count() as u16))
.max()
.unwrap_or(x(0))
}
}
pub struct DrawingSpots<'a> {
size: Point,
style: Style,
graphemes: Graphemes<'a>,
pos: Point,
}
impl<'a> Iterator for DrawingSpots<'a> {
type Item = (Point, Spot);
fn next(&mut self) -> Option<Self::Item> {
while let (Some(g), true) = (self.graphemes.next(), self.size.contains(&self.pos)) {
match g {
"\r\n" | "\n\r" => {
self.pos.x = x(0);
self.pos.y += y(1);
}
"\n" => self.pos.y += y(1),
"\r" => self.pos.x = x(0),
g => {
let p = self.pos.clone();
if let Some(idx) = self.size.idx_at_point(p) {
if let Some(next) = self.size.x.point_at_idx(idx + 1) {
self.pos = next;
} else {
return None;
}
} else {
return None;
}
return Some((
p,
Spot {
symbol: g.to_owned(),
style: self.style,
},
));
}
}
}
None
}
}
pub struct DrawingLines<'a>(&'a Drawing);
pub struct DrawingLinesIterator<'a> {
size: Point,
graphemes: Graphemes<'a>,
lines: u16,
}
impl<'a> IntoIterator for DrawingLines<'a> {
type Item = String;
type IntoIter = DrawingLinesIterator<'a>;
fn into_iter(self) -> Self::IntoIter {
DrawingLinesIterator {
size: self.0.scope.size,
graphemes: self.0.text.graphemes(true),
lines: 0,
}
}
}
impl<'a> Iterator for DrawingLinesIterator<'a> {
type Item = String;
fn next(&mut self) -> Option<Self::Item> {
let mut line = vec![];
let mut can_be_empty = false;
let max_lines = self.size.y.to_primitive();
let max_cols = self.size.x.to_primitive();
while self.lines < max_lines && line.len() < max_cols as usize {
if let Some(c) = self.graphemes.next() {
can_be_empty = true;
match c {
"\r\n" | "\n\r" | "\n" | "\r" => break,
c => line.push(c),
}
} else {
break;
};
}
if can_be_empty || !line.is_empty() {
self.lines += 1;
Some(line.concat())
} else {
None
}
}
}
#[test]
fn test_drawing_lines_break_on_size() {
let lines: Vec<String> = "123456"
.as_drawing()
.width(2)
.height(2)
.lines()
.into_iter()
.collect();
assert_eq!(lines, ["12", "34"])
}
#[test]
fn test_drawing_default_size() {
let size = "".as_drawing().scope.size;
assert_eq!(size.x.to_primitive(), u16::max_value());
assert_eq!(size.y.to_primitive(), u16::max_value());
}
#[test]
fn test_drawing_actual_width() {
let width = "12\n3".as_drawing().actual_width();
assert_eq!(width, x(2));
let width = "12\r3".as_drawing().actual_width();
assert_eq!(width, x(2));
let width = "12\r\n3".as_drawing().actual_width();
assert_eq!(width, x(2));
}
#[test]
fn test_drawing_spots() {
let count = "12\r\n4567".as_drawing().width(3).height(2).spots().count();
assert_eq!(count, 5);
}