semtext/layout/
cells.rs

1// cells.rs
2//
3// Copyright (c) 2020-2022  Douglas P Lau
4//
5use crate::layout::{BBox, Pos};
6use crate::text::{Glyph, TextStyle, Theme};
7use crate::{Result, Screen};
8use textwrap::wrap;
9
10/// Cells of text on a [Screen]
11///
12/// The cells are in a rectangular area of the screen.
13pub struct Cells<'a> {
14    /// Screen containing cells
15    screen: &'a mut Screen,
16    /// Bounding box of cells
17    bbox: BBox,
18    /// Bounding box of clip area
19    clip: BBox,
20}
21
22impl<'a> Cells<'a> {
23    /// Create cells
24    pub fn new(screen: &'a mut Screen, bbox: BBox) -> Self {
25        let clip = bbox;
26        Self { screen, bbox, clip }
27    }
28
29    /// Get the width
30    pub fn width(&self) -> u16 {
31        self.clip.width()
32    }
33
34    /// Get the height
35    pub fn height(&self) -> u16 {
36        self.clip.height()
37    }
38
39    /// Clip to bounding box
40    pub fn clip(&mut self, inset: Option<BBox>) {
41        if let Some(inset) = inset {
42            let col = self.bbox.left() + inset.left();
43            let row = self.bbox.top() + inset.top();
44            let width = inset.width();
45            let height = inset.height();
46            self.clip = self.bbox.clip(BBox::new(col, row, width, height));
47        } else {
48            self.clip = self.bbox;
49        }
50    }
51
52    /// Fill the cells with a glyph
53    pub fn fill(&mut self, glyph: &Glyph) -> Result<()> {
54        let bbox = self.clip;
55        let fill_width = bbox.width() / glyph.width() as u16;
56        if bbox.height() > 0 && fill_width > 0 {
57            self.move_to(0, 0)?;
58            for row in 0..bbox.height() {
59                self.move_to(0, row)?;
60                for _ in 0..fill_width {
61                    glyph.print(self.screen)?;
62                }
63            }
64        }
65        Ok(())
66    }
67
68    /// Get the screen theme
69    pub fn theme(&self) -> &Theme {
70        self.screen.theme()
71    }
72
73    /// Set the text style
74    pub fn set_style(&mut self, st: TextStyle) -> Result<()> {
75        self.screen.set_style(st)
76    }
77
78    /// Move cursor to a cell
79    pub fn move_to(&mut self, col: u16, row: u16) -> Result<()> {
80        let col = self.clip.left() + col;
81        let row = self.clip.top() + row;
82        self.screen.move_to(col, row)
83    }
84
85    /// Move cursor right by a number of columns
86    pub fn move_right(&mut self, col: u16) -> Result<()> {
87        self.screen.move_right(col)
88    }
89
90    /// Print a char at the cursor location
91    pub fn print_char(&mut self, ch: char) -> Result<()> {
92        // FIXME: check width first
93        self.screen.print_char(ch)
94    }
95
96    /// Print a str at the cursor location
97    pub fn print_str(&mut self, st: &str) -> Result<()> {
98        // FIXME: check width first
99        self.screen.print_str(st)
100    }
101
102    /// Print some text
103    ///
104    /// Inline styling using Markdown:
105    ///
106    /// Text Style        | Markdown
107    /// ------------------|---------
108    /// Normal            | `Normal`
109    /// _Italic_          | `*Italic*` or `_Italic_`
110    /// **Bold**          | `**Bold**` or `__Bold__`
111    /// ~~Strikethrough~~ | `~~Strikethrough~~`
112    /// <u>Underline</u>  | `<u>Underline</u>`
113    /// `Reverse`         | `` `Reverse` ``
114    pub fn print_text(&mut self, text: &str, offset: Pos) -> Result<()> {
115        assert_eq!(offset.col, 0, "FIXME");
116        let top = usize::from(offset.row);
117        let width = usize::from(self.width());
118        let height = usize::from(self.height());
119        for (row, txt) in
120            wrap(text, width).iter().skip(top).take(height).enumerate()
121        {
122            let row = row as u16; // limited to u16 by take(height)
123            self.move_to(0, row)?;
124            self.print_str(txt)?;
125        }
126        Ok(())
127    }
128}