use unicode_segmentation::UnicodeSegmentation;
use unicode_width::UnicodeWidthStr;
use vector2d::Vector2D;
use crate::framebuffer::Framebuffer;
use crate::window::Thickness;
use crate::{common::*, framebuffer::Frame, window::BorderKind};
use std::cell::RefCell;
use std::{cell::RefMut, rc::Rc};
pub struct BorderCanvas<'a> {
canvas: &'a mut Canvas,
thickness: Thickness,
}
impl<'a> BorderCanvas<'a> {
pub(crate) fn from(canvas: &'a mut Canvas, thickness: Thickness) -> Self {
Self { canvas, thickness }
}
pub fn size(&self) -> TPoint {
self.canvas.size()
}
pub fn get_glyph(&mut self, x: TSize, y: TSize) -> Option<FixedWidthGlyph<'_>> {
if self.check_coord(x, y) {
self.canvas.get_glyph(x, y, GlyphWidth::Half)
}
else {
None
}
}
fn check_coord(&self, x: TSize, y: TSize) -> bool {
let size = self.size();
x < self.thickness.left
|| y < self.thickness.top
|| x >= size.x - self.thickness.right
|| y >= size.y - self.thickness.bottom
}
pub fn get_row(&self, row: TSize) -> Option<GlyphRow<'_>> {
if row >= self.size().y - self.thickness.bottom || row < self.thickness.top {
self.canvas.get_row(row, GlyphWidth::Half)
}
else {
None
}
}
pub fn get_column(&self, column: TSize) -> Option<GlyphColumn<'_>> {
if column >= self.size().x - self.thickness.right || column < self.thickness.left {
self.canvas.get_column(column, GlyphWidth::Half)
}
else {
None
}
}
pub fn draw_preset_border(
&mut self,
kind: BorderKind,
fg: Option<Color>,
bg: Option<Color>,
title: &str,
) -> Result<(), GraphemeError> {
let size = self.size();
let corners = kind.corner_style();
let lines = kind.line_style();
self.get_glyph(0, 0)
.unwrap()
.set_properties(corners[0], fg, bg)?;
self.get_glyph(size.x - 1, 0)
.unwrap()
.set_properties(corners[1], fg, bg)?;
self.get_glyph(0, size.y - 1)
.unwrap()
.set_properties(corners[2], fg, bg)?;
self.get_glyph(size.x - 1, size.y - 1)
.unwrap()
.set_properties(corners[3], fg, bg)?;
let mut top = self
.canvas
.get_row_variable_width(TSize::MIN)
.unwrap()
.with_custom_width(size.x - 1)
.unwrap();
top.skip(1);
if top.cursor() < top.width() {
top.add_grapheme(lines[0], fg, bg, Style::default())?;
top.add_string(
title,
fg,
bg,
Style::default(),
Some(VariableWidthGlyphRow::DOT3_REPLACEMENT),
)
.ok();
}
while top.cursor() < top.width() {
top.add_grapheme(lines[0], fg, bg, Style::default())?;
}
let mut bottom = self.get_row(size.y - 1).unwrap();
for x in 1..size.x - 1 {
bottom.get(x).unwrap().set_properties(lines[0], fg, bg)?;
}
let mut left = self.get_column(0).unwrap();
let mut right = self.get_column(size.x - 1).unwrap();
for y in 1..size.y - 1 {
left.get(y).unwrap().set_properties(lines[1], fg, bg)?;
right.get(y).unwrap().set_properties(lines[1], fg, bg)?;
}
Ok(())
}
}
impl<'a> Dyeable for BorderCanvas<'a> {
fn fill_foreground(&mut self, color: Color) {
let mut view = self.canvas.view.borrow_mut();
let size = self.size();
for y in 0..size.y {
for x in 0..size.x {
if self.check_coord(x, y) {
let idx = self.canvas.index(x, y);
view[idx].fg = Some(color);
}
}
}
}
fn fill_background(&mut self, color: Color) {
let mut view = self.canvas.view.borrow_mut();
let size = self.size();
for y in 0..size.y {
for x in 0..size.x {
if self.check_coord(x, y) {
let idx = self.canvas.index(x, y);
view[idx].bg = Some(color);
}
}
}
}
fn reset(&mut self) {
let mut view = self.canvas.view.borrow_mut();
let size = self.size();
for y in 0..size.y {
for x in 0..size.x {
if self.check_coord(x, y) {
let idx = self.canvas.index(x, y);
view[idx].bg = None;
view[idx].fg = None;
}
}
}
}
}
pub struct Canvas {
view: Rc<RefCell<Frame>>,
rect: Rect,
view_size: TPoint,
}
impl Canvas {
pub(crate) fn from_existing(canvas: &Canvas, relative: Rect) -> Result<Self, RectError> {
let rect = Rect {
start: relative.start + canvas.rect.start,
size: Vector2D::new(relative.size.x, relative.size.y),
};
let end = rect.end();
let base_end = canvas.rect.end();
if end.x > base_end.x {
return Err(RectError::HorizontalBorderExceeds(canvas.rect, rect));
}
if end.y > base_end.y {
return Err(RectError::VerticalBorderExceeds(canvas.rect, rect));
}
Ok(Self {
view: canvas.view.clone(),
rect,
view_size: canvas.view_size,
})
}
pub(crate) fn from_framebuffer(fb: &mut Framebuffer) -> Self {
let view = fb.prepare_next_frame();
let size = view.borrow().size();
Self {
view,
rect: Rect::from(size.x, size.y),
view_size: size,
}
}
pub fn size(&self) -> TPoint {
self.rect.size
}
pub fn get_glyph(
&mut self,
x: TSize,
y: TSize,
width: GlyphWidth,
) -> Option<FixedWidthGlyph<'_>> {
match x + width as TSize - 1 < self.rect.size.x && y < self.rect.size.y {
true => {
let idx = self.index(x, y);
#[cfg(feature = "safety")]
self.check_null_glyph(idx);
Some(FixedWidthGlyph::new(self.view.borrow_mut(), idx, width))
}
false => None,
}
}
pub fn get_row_variable_width(&self, row: TSize) -> Option<VariableWidthGlyphRow<'_>> {
match row < self.rect.size.y {
true => Some(VariableWidthGlyphRow::new(self, row)),
false => None,
}
}
pub fn get_row(&self, row: TSize, width: GlyphWidth) -> Option<GlyphRow<'_>> {
match row < self.rect.size.y {
true => Some(GlyphRow::new(self, row, width)),
false => None,
}
}
pub fn get_column(&self, column: TSize, width: GlyphWidth) -> Option<GlyphColumn<'_>> {
match column + width as TSize - 1 < self.rect.size.x {
true => Some(GlyphColumn::from_column(self, column, width)),
false => None,
}
}
#[cfg(feature = "safety")]
fn check_null_glyph(&self, idx: usize) {
let brw = &mut *self.view.borrow_mut();
if brw[idx].is_null() {
brw[idx].grapheme = Grapheme::PLACEHOLDER.get_string();
brw[idx - 1].grapheme = Grapheme::PLACEHOLDER.get_string();
}
}
#[inline(always)]
fn index(&self, x: TSize, y: TSize) -> usize {
(x + self.rect.start.x + (y + self.rect.start.y) * self.view_size.x) as usize
}
}
impl Dyeable for Canvas {
fn fill_foreground(&mut self, color: Color) {
let mut view = self.view.borrow_mut();
for y in 0..self.rect.size.y {
for x in 0..self.rect.size.x {
let idx = self.index(x, y);
view[idx].fg = Some(color);
}
}
}
fn fill_background(&mut self, color: Color) {
let mut view = self.view.borrow_mut();
for y in 0..self.rect.size.y {
for x in 0..self.rect.size.x {
let idx = self.index(x, y);
view[idx].bg = Some(color);
}
}
}
fn reset(&mut self) {
let mut view = self.view.borrow_mut();
for y in 0..self.rect.size.y {
for x in 0..self.rect.size.x {
let idx = self.index(x, y);
view[idx].bg = None;
view[idx].fg = None;
}
}
}
}
pub struct VariableWidthGlyphRow<'a> {
canvas: &'a Canvas,
skipped: TSize,
cursor: TSize,
row: TSize,
width: TSize,
}
impl<'a> VariableWidthGlyphRow<'a> {
pub const DOT3_REPLACEMENT: Grapheme = Grapheme::new_unchecked("…", GlyphWidth::Half);
fn new(canvas: &'a Canvas, row: TSize) -> Self {
Self {
canvas,
skipped: TSize::MIN,
cursor: TSize::MIN,
row,
width: canvas.rect.size.x,
}
}
pub fn step_back(&mut self) {
let idx = self.canvas.index(self.cursor - 1, self.row);
if self.cursor > 0 {
if self.canvas.view.borrow()[idx].is_null() {
self.cursor -= GlyphWidth::Full as TSize;
}
else {
self.cursor -= GlyphWidth::Half as TSize;
}
}
}
pub fn set_style(&mut self, style: Style) {
if style != Style::None {
let idx = self.canvas.index(self.skipped, self.row);
let end = self.canvas.index(self.width, self.row);
let mut view = self.canvas.view.borrow_mut();
view[idx].style = style;
if view[end - 1].grapheme.as_str() == Glyph::NULL {
view[end - 2].style |= Style::ResetAfter;
}
else {
view[end - 1].style |= Style::ResetAfter;
}
}
}
pub fn with_custom_width(self, width: TSize) -> Option<Self> {
match width <= self.canvas.rect.size.x {
true => Some(Self {
canvas: self.canvas,
skipped: TSize::MIN,
cursor: TSize::MIN,
row: self.row,
width,
}),
false => None,
}
}
pub fn width(&self) -> TSize {
self.width
}
pub fn skip(&mut self, width: TSize) {
if self.cursor + width < self.canvas.rect.size.x {
self.cursor += width;
self.skipped += width;
}
}
pub fn skip_until(&mut self, position: TSize) {
if position > self.cursor && position < self.canvas.rect.size.x {
self.cursor = position;
self.skipped = position;
}
}
pub fn cursor(&self) -> TSize {
self.cursor
}
pub fn fill_with_default(&mut self) {
for _ in self.cursor..self.canvas.rect.size.x {
self.add_grapheme(Grapheme::PLACEHOLDER, None, None, Style::None)
.unwrap();
}
}
pub fn add_string(
&mut self,
st: &str,
fg: Option<Color>,
bg: Option<Color>,
style: Style,
replacement: Option<Grapheme>,
) -> Result<(), GraphemeError> {
let width = st.width();
if replacement.is_none() && self.cursor + width as TSize > self.canvas.rect.size.x {
return Err(GraphemeError::TooManyGraphemes);
}
let old_cursor = self.cursor;
for grapheme in st.graphemes(true) {
match self.add_grapheme(Grapheme::from(grapheme)?, fg, bg, Style::None) {
Ok(_) => {}
Err(e) => {
if let Some(replace) = replacement {
self.step_back();
self.add_grapheme(replace, fg, bg, Style::None)?;
break;
}
else {
return Err(e);
}
}
}
}
self.apply_style_string(old_cursor, style);
Ok(())
}
pub fn add_string_lossy(
&mut self,
st: &str,
fg: Option<Color>,
bg: Option<Color>,
style: Style,
) {
let old_cursor = self.cursor;
for grapheme in st.graphemes(true) {
if self
.add_grapheme(
Grapheme::from(grapheme).unwrap_or(Grapheme::REPLACEMENT),
fg,
bg,
Style::None,
)
.is_err()
{
break;
}
}
self.apply_style_string(old_cursor, style);
}
fn apply_style_string(&mut self, old_cursor: TSize, style: Style) {
let diff = self.cursor - old_cursor;
if style != Style::None && diff > 0 {
let idx = self.canvas.index(old_cursor, self.row);
let end = self.canvas.index(old_cursor + diff, self.row);
let mut view = self.canvas.view.borrow_mut();
view[idx].style = style;
if view[end - 1].grapheme.as_str() == Glyph::NULL {
view[end - 2].style |= Style::ResetAfter;
}
else {
view[end - 1].style |= Style::ResetAfter;
}
}
}
pub fn add_grapheme(
&mut self,
grapheme: Grapheme,
fg: Option<Color>,
bg: Option<Color>,
style: Style,
) -> Result<(), GraphemeError> {
let width_raw = grapheme.width() as TSize;
if self.cursor + width_raw > self.width {
return Err(GraphemeError::InvalidGlyphWidth);
}
let idx = self.canvas.index(self.cursor, self.row);
let view = self.canvas.view.borrow_mut();
let mut glyph = FixedWidthGlyph::new(view, idx, grapheme.width());
glyph.set_grapheme(grapheme)?;
if style != Style::None {
glyph.set_style(style);
}
if let Some(background) = bg {
glyph.set_bg(background);
}
if let Some(foreground) = fg {
glyph.set_fg(foreground);
}
self.cursor += width_raw;
Ok(())
}
}
impl Dyeable for VariableWidthGlyphRow<'_> {
fn fill_foreground(&mut self, color: Color) {
let mut view = self.canvas.view.borrow_mut();
for x in self.skipped..self.width {
let idx = self.canvas.index(x, self.row);
view[idx].fg = Some(color);
}
}
fn fill_background(&mut self, color: Color) {
let mut view = self.canvas.view.borrow_mut();
for x in self.skipped..self.width {
let idx = self.canvas.index(x, self.row);
view[idx].bg = Some(color);
}
}
fn reset(&mut self) {
let mut view = self.canvas.view.borrow_mut();
for x in self.skipped..self.width {
let idx = self.canvas.index(x, self.row);
view[idx].bg = None;
view[idx].fg = None;
}
}
}
pub struct GlyphRow<'a> {
canvas: &'a Canvas,
row: TSize,
width: GlyphWidth,
}
impl<'a> GlyphRow<'a> {
fn new(canvas: &'a Canvas, row: TSize, width: GlyphWidth) -> Self {
Self { canvas, width, row }
}
#[cfg(test)]
fn all(&self, f: fn(&FixedWidthGlyph) -> bool) -> bool {
let mut res = true;
let start = self.canvas.index(0, self.row);
let end = self.canvas.index(self.length() as TSize, self.row);
for idx in start..end {
let glyph = FixedWidthGlyph::new(self.canvas.view.borrow_mut(), idx, self.width);
res &= f(&glyph);
}
res
}
}
impl GlyphArray for GlyphRow<'_> {
fn length(&self) -> TSize {
self.canvas.rect.size.x / self.width as TSize
}
fn get(&mut self, index: TSize) -> Option<FixedWidthGlyph<'_>> {
match index < self.length() {
true => {
let idx = self.canvas.index(index, self.row);
#[cfg(feature = "safety")]
self.canvas.check_null_glyph(idx);
Some(FixedWidthGlyph::new(
self.canvas.view.borrow_mut(),
idx,
self.width,
))
}
false => None,
}
}
fn orientation(&self) -> Orientation {
Orientation::Horizontal
}
fn glyph_width(&self) -> GlyphWidth {
self.width
}
}
impl Dyeable for GlyphRow<'_> {
fn fill_foreground(&mut self, color: Color) {
let mut view = self.canvas.view.borrow_mut();
for x in 0..self.canvas.rect.size.x {
let idx = self.canvas.index(x, self.row);
view[idx].fg = Some(color);
}
}
fn fill_background(&mut self, color: Color) {
let mut view = self.canvas.view.borrow_mut();
for x in 0..self.canvas.rect.size.x {
let idx = self.canvas.index(x, self.row);
view[idx].bg = Some(color);
}
}
fn reset(&mut self) {
let mut view = self.canvas.view.borrow_mut();
for x in 0..self.canvas.rect.size.x {
let idx = self.canvas.index(x, self.row);
view[idx].bg = None;
view[idx].fg = None;
}
}
}
pub struct GlyphColumn<'a> {
canvas: &'a Canvas,
column: TSize,
width: GlyphWidth,
}
impl<'a> GlyphColumn<'a> {
fn from_column(canvas: &'a Canvas, column: TSize, width: GlyphWidth) -> Self {
Self {
canvas,
column,
width,
}
}
#[cfg(test)]
fn all(&self, f: fn(&FixedWidthGlyph) -> bool) -> bool {
let mut res = true;
for n in 0..self.length() {
let idx = self.canvas.index(self.column, n);
let glyph = FixedWidthGlyph::new(self.canvas.view.borrow_mut(), idx, self.width);
res &= f(&glyph);
}
res
}
}
impl GlyphArray for GlyphColumn<'_> {
fn length(&self) -> TSize {
self.canvas.rect.size.y
}
fn get(&mut self, index: TSize) -> Option<FixedWidthGlyph<'_>> {
match index < self.length() {
true => {
let idx = self.canvas.index(self.column, index);
#[cfg(feature = "safety")]
self.canvas.check_null_glyph(idx);
Some(FixedWidthGlyph::new(
self.canvas.view.borrow_mut(),
idx,
self.width,
))
}
false => None,
}
}
fn orientation(&self) -> Orientation {
Orientation::Vertical
}
fn glyph_width(&self) -> GlyphWidth {
self.width
}
}
impl Dyeable for GlyphColumn<'_> {
fn fill_foreground(&mut self, color: Color) {
let mut view = self.canvas.view.borrow_mut();
for y in 0..self.canvas.rect.size.y {
let idx = self.canvas.index(self.column, y);
view[idx].fg = Some(color);
}
}
fn fill_background(&mut self, color: Color) {
let mut view = self.canvas.view.borrow_mut();
for y in 0..self.canvas.rect.size.y {
let idx = self.canvas.index(self.column, y);
view[idx].bg = Some(color);
}
}
fn reset(&mut self) {
let mut view = self.canvas.view.borrow_mut();
for y in 0..self.canvas.rect.size.y {
let idx = self.canvas.index(self.column, y);
view[idx].bg = None;
view[idx].fg = None;
}
}
}
pub struct FixedWidthGlyph<'a> {
view: RefMut<'a, Frame>,
index: usize,
width: GlyphWidth,
}
impl<'a> FixedWidthGlyph<'a> {
pub(crate) fn new(view: RefMut<'a, Frame>, index: usize, width: GlyphWidth) -> Self {
Self { view, index, width }
}
#[inline(always)]
pub fn width(&self) -> TSize {
self.width as TSize
}
pub fn get_grapheme(&self) -> &str {
&self.view[self.index].grapheme
}
pub fn set_grapheme(&mut self, grapheme: Grapheme) -> Result<(), GraphemeError> {
if self.width() != grapheme.width() as TSize {
return Err(GraphemeError::InvalidGlyphWidth);
}
if self.width == GlyphWidth::Full {
self.view[self.index + 1].nullify();
}
self.view[self.index].grapheme = grapheme.get_string();
Ok(())
}
#[inline(always)]
pub fn set_style(&mut self, style: Style) {
self.view[self.index].style = style;
}
#[inline(always)]
pub fn set_bg(&mut self, color: Color) {
self.view[self.index].bg = Some(color);
}
#[inline(always)]
pub fn set_fg(&mut self, color: Color) {
self.view[self.index].fg = Some(color);
}
#[inline(always)]
pub fn get_bg(&self) -> Option<Color> {
self.view[self.index].bg
}
#[inline(always)]
pub fn get_fg(&self) -> Option<Color> {
self.view[self.index].fg
}
pub(crate) fn set_properties(
mut self,
grapheme: Grapheme,
fg: Option<Color>,
bg: Option<Color>,
) -> Result<(), GraphemeError> {
self.set_grapheme(grapheme)?;
if let Some(foreground) = fg {
self.set_fg(foreground);
}
if let Some(background) = bg {
self.set_bg(background);
}
Ok(())
}
}
pub trait Dyeable {
fn fill_foreground(&mut self, color: Color);
fn fill_background(&mut self, color: Color);
fn reset(&mut self);
}
pub trait GlyphArray {
fn length(&self) -> TSize;
fn get(&mut self, index: TSize) -> Option<FixedWidthGlyph<'_>>;
fn orientation(&self) -> Orientation;
fn glyph_width(&self) -> GlyphWidth;
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn check_canvas_ctor() {
let mut fb = Framebuffer::new(Vector2D::new(32, 32));
let canvas = Canvas::from_framebuffer(&mut fb);
let mut subcanvas = Canvas::from_existing(&canvas, Rect::from(16, 16));
assert!(subcanvas.is_ok());
assert!(subcanvas.unwrap().size() == Vector2D::new(16, 16));
subcanvas = Canvas::from_existing(&canvas, Rect::from(32, 33));
assert!(subcanvas.is_err());
subcanvas = Canvas::from_existing(
&canvas,
Rect {
start: Vector2D::new(20, 20),
size: Vector2D::new(12, 13),
},
);
assert!(subcanvas.is_err());
}
#[test]
fn check_dyeable_impl() {
let mut fb = Framebuffer::new(Vector2D::new(32, 32));
let mut canvas = Canvas::from_framebuffer(&mut fb);
{
let mut row = canvas.get_row(2, GlyphWidth::Half).unwrap();
row.fill_background(Color::Red);
assert!(row.all(|x| x.get_bg() == Some(Color::Red)));
assert!(row.all(|x| x.get_fg().is_none()));
row = canvas.get_row(3, GlyphWidth::Half).unwrap();
row.fill_foreground(Color::Blue);
assert!(row.all(|x| x.get_bg().is_none()));
assert!(row.all(|x| x.get_fg() == Some(Color::Blue)));
}
canvas.reset();
{
let mut column = canvas.get_column(4, GlyphWidth::Half).unwrap();
column.fill_background(Color::Red);
assert!(column.all(|x| x.get_bg() == Some(Color::Red)));
assert!(column.all(|x| x.get_fg().is_none()));
column = canvas.get_column(3, GlyphWidth::Half).unwrap();
column.fill_foreground(Color::Blue);
assert!(column.all(|x| x.get_bg().is_none()));
assert!(column.all(|x| x.get_fg() == Some(Color::Blue)));
}
}
#[test]
fn check_canvas() {
let glyph = Grapheme::from(".").unwrap();
let mut fb = Framebuffer::new(Vector2D::new(8, 8));
let cvs = Canvas::from_framebuffer(&mut fb);
let canvas = Canvas::from_existing(
&cvs,
Rect {
start: Vector2D::new(2, 2),
size: Vector2D::new(4, 4),
},
)
.unwrap();
assert!(
canvas
.get_row(canvas.rect.size.y, GlyphWidth::Half)
.is_none()
);
assert!(
canvas
.get_column(canvas.rect.size.x, GlyphWidth::Half)
.is_none()
);
{
let mut row = canvas.get_row(0, GlyphWidth::Half).unwrap();
assert_eq!(row.get(0).unwrap().set_grapheme(glyph), Ok(()));
assert_eq!(
row.get(row.length() - 1).unwrap().set_grapheme(glyph),
Ok(())
);
let view = canvas.view.borrow();
assert_eq!(view[view.index(2, 2)].grapheme.as_str(), glyph.as_str());
assert_eq!(
view[view.index(3, 2)].grapheme.as_str(),
Grapheme::PLACEHOLDER.as_str()
);
assert_eq!(view[view.index(5, 2)].grapheme.as_str(), glyph.as_str());
}
{
let mut col = canvas.get_column(1, GlyphWidth::Half).unwrap();
assert_eq!(col.get(0).unwrap().set_grapheme(glyph), Ok(()));
assert_eq!(
col.get(col.length() - 1).unwrap().set_grapheme(glyph),
Ok(())
);
let view = canvas.view.borrow();
assert_eq!(view[view.index(3, 2)].grapheme.as_str(), glyph.as_str());
assert_eq!(
view[view.index(3, 3)].grapheme.as_str(),
Grapheme::PLACEHOLDER.as_str()
);
assert_eq!(view[view.index(3, 5)].grapheme.as_str(), glyph.as_str());
}
}
#[test]
fn check_row() {
let mut fb = Framebuffer::new(Vector2D::new(9, 9));
let canvas = Canvas::from_framebuffer(&mut fb);
{
let glyph = Grapheme::from(".").unwrap();
let mut row = canvas.get_row(0, GlyphWidth::Half).unwrap();
assert_eq!(row.get(0).unwrap().set_grapheme(glyph), Ok(()));
assert_eq!(
row.get(row.length() - 1).unwrap().set_grapheme(glyph),
Ok(())
);
assert_eq!(row.length(), canvas.size().x);
let view = canvas.view.borrow();
assert_eq!(view[view.index(0, 0)].grapheme.as_str(), glyph.as_str());
assert_eq!(
view[view.index(1, 0)].grapheme.as_str(),
Grapheme::PLACEHOLDER.as_str()
);
assert_eq!(
view[view.index(canvas.view_size.x - 1, 0)]
.grapheme
.as_str(),
glyph.as_str()
);
}
{
let glyph = Grapheme::from("å…¸").unwrap();
let mut row = canvas.get_row(0, GlyphWidth::Full).unwrap();
assert_eq!(row.length(), canvas.size().x / 2);
assert_eq!(row.get(0).unwrap().set_grapheme(glyph), Ok(()));
assert_eq!(
row.get(row.length() - 1).unwrap().set_grapheme(glyph),
Ok(())
);
let view = canvas.view.borrow();
assert_eq!(view[view.index(0, 0)].grapheme.as_str(), glyph.as_str());
assert_eq!(view[view.index(1, 0)].grapheme.as_str(), Glyph::NULL);
assert_eq!(
view[view.index(row.length() / 2 - 1, 0)].grapheme.as_str(),
Glyph::NULL
);
}
}
#[test]
fn check_column() {
let mut fb = Framebuffer::new(Vector2D::new(9, 9));
let canvas = Canvas::from_framebuffer(&mut fb);
{
let glyph = Grapheme::from(".").unwrap();
let mut col = canvas.get_column(0, GlyphWidth::Half).unwrap();
assert_eq!(col.get(0).unwrap().set_grapheme(glyph), Ok(()));
assert_eq!(
col.get(col.length() - 1).unwrap().set_grapheme(glyph),
Ok(())
);
assert_eq!(col.length(), canvas.size().y);
let view = canvas.view.borrow();
assert_eq!(view[view.index(0, 0)].grapheme.as_str(), glyph.as_str());
assert_eq!(
view[view.index(1, 0)].grapheme.as_str(),
Grapheme::PLACEHOLDER.as_str()
);
assert_eq!(
view[view.index(0, col.length() - 1)].grapheme.as_str(),
glyph.as_str()
);
assert_eq!(
view[view.index(1, col.length() - 1)].grapheme.as_str(),
Grapheme::PLACEHOLDER.as_str()
);
}
{
let glyph = Grapheme::from("å…¸").unwrap();
let mut col = canvas.get_column(0, GlyphWidth::Full).unwrap();
assert_eq!(col.length(), canvas.size().y);
assert_eq!(col.get(0).unwrap().set_grapheme(glyph), Ok(()));
assert_eq!(
col.get(col.length() - 1).unwrap().set_grapheme(glyph),
Ok(())
);
let view = canvas.view.borrow();
assert_eq!(view[view.index(0, 0)].grapheme.as_str(), glyph.as_str());
assert_eq!(view[view.index(1, 0)].grapheme.as_str(), Glyph::NULL);
assert_eq!(
view[view.index(0, col.length() - 1)].grapheme.as_str(),
glyph.as_str()
);
assert_eq!(
view[view.index(1, col.length() - 1)].grapheme.as_str(),
Glyph::NULL
);
}
}
#[test]
fn check_variable_width_row() {
let mut fb = Framebuffer::new(Vector2D::new(8, 8));
let canvas = Canvas::from_framebuffer(&mut fb);
let dot3 = VariableWidthGlyphRow::DOT3_REPLACEMENT;
{
let txt = "Lorem ipsum dolor sit amet";
let mut row = canvas.get_row_variable_width(0).unwrap();
assert!(row.add_string(txt, None, None, Style::None, None).is_err());
assert!(
row.add_string(txt, None, None, Style::None, Some(dot3))
.is_ok()
);
{
let view = canvas.view.borrow();
assert_eq!(
&view[view.index(view.size().x - 1, 0)].grapheme,
VariableWidthGlyphRow::DOT3_REPLACEMENT.as_str()
);
}
let mut row = canvas.get_row_variable_width(1).unwrap();
row.add_string_lossy(txt, None, None, Style::None);
{
let view = canvas.view.borrow();
assert_eq!(
view[view.index(view.size().x - 1, 1)].grapheme.as_str(),
txt.chars()
.nth(view.size().x as usize - 1)
.unwrap()
.to_string()
);
}
}
{
let txt = "Lorem";
let mut row = canvas.get_row_variable_width(2).unwrap();
assert!(row.add_string(txt, None, None, Style::None, None).is_ok());
assert_eq!(row.cursor(), txt.width() as TSize);
row.skip(2);
row.step_back();
row.step_back();
assert_eq!(row.cursor(), txt.width() as TSize);
row.skip(5);
assert_eq!(row.cursor(), txt.width() as TSize);
row = row.with_custom_width(5).unwrap();
assert_eq!(row.cursor(), TSize::MIN);
assert_eq!(row.width(), 5);
}
}
#[test]
fn check_border_canvas() {
let mut fb = Framebuffer::new(Vector2D::new(32, 32));
let mut cvs = Canvas::from_framebuffer(&mut fb);
let mut canvas = BorderCanvas::from(&mut cvs, Thickness::new(2, 1, 1, 2));
assert!(canvas.get_row(0).is_some());
assert!(canvas.get_row(1).is_some());
assert!(canvas.get_row(2).is_none());
assert!(canvas.get_row(29).is_none());
assert!(canvas.get_row(30).is_some());
assert!(canvas.get_row(31).is_some());
assert!(canvas.get_column(0).is_some());
assert!(canvas.get_column(1).is_none());
assert!(canvas.get_column(30).is_none());
assert!(canvas.get_column(31).is_some());
assert!(canvas.get_glyph(0, 2).is_some());
assert!(canvas.get_glyph(1, 1).is_some());
assert!(canvas.get_glyph(1, 2).is_none());
assert!(canvas.get_glyph(31, 2).is_some());
assert!(canvas.get_glyph(31 - 1, 1).is_some());
assert!(canvas.get_glyph(31 - 1, 2).is_none());
assert!(canvas.get_glyph(31, 31 - 2).is_some());
assert!(canvas.get_glyph(31 - 1, 31 - 1).is_some());
assert!(canvas.get_glyph(31 - 1, 31 - 2).is_none());
assert!(canvas.get_glyph(0, 31 - 2).is_some());
assert!(canvas.get_glyph(1, 31 - 1).is_some());
assert!(canvas.get_glyph(1, 31 - 2).is_none());
}
}