1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
//! This module holds the structs related to display of ASCII characters, both text and ASCII art

use super::view::{ColChar, Modifier, Pixel, Vec2D, ViewElement};

/// An enum to set the alignment of a Text element's content
#[derive(Debug, Clone, Copy)]
pub enum TextAlign {
    /// Align to the beginning of the text
    Left,
    /// Align to the center of the text
    Centered,
    /// Align to the end of the text
    Right,
}

/// Displays text at the given position
#[non_exhaustive]
#[derive(Debug, Clone)]
pub struct Text<'a> {
    pub pos: Vec2D,
    /// The actual text content of the element
    pub content: &'a str,
    /// How the content should align to the position
    pub align: TextAlign,
    /// A raw [`Modifier`], determining the appearance of the `Sprite`
    pub modifier: Modifier,
}

impl<'a> Text<'a> {
    /// Create a new Text element with a position, content and modifier
    ///
    /// # Panics
    /// This function will panic if the content contains a newline, as Text only works with single lines. For multi-line strings, see [Sprite]
    pub fn new(pos: Vec2D, content: &str, modifier: Modifier) -> Text {
        if content.contains('\n') {
            panic!("Text was created with a content string containing a \n character")
        }

        Text {
            pos,
            content,
            align: TextAlign::Left,
            modifier,
        }
    }

    /// Create a `Text` element with an [`align`](Text::align) parameter to set the `Text`'s align (see the [TextAlign] documentation)
    ///
    /// # Panics
    /// This function will panic if the content contains a newline, as Text only works with single lines. For multi-line strings, see [Sprite]
    pub fn new_with_align(pos: Vec2D, content: &str, align: TextAlign, modifier: Modifier) -> Text {
        let mut tmp = Text::new(pos, content, modifier);
        tmp.align = align;

        tmp
    }

    /// Return a vector of Pixels to display the given content
    pub fn draw(pos: Vec2D, content: &str, modifier: Modifier) -> Vec<Pixel> {
        let mut pixels = vec![];
        for (x, text_char) in content.chars().enumerate() {
            if text_char != ' ' {
                pixels.push(Pixel::new(
                    pos + Vec2D::new(x as isize, 0),
                    ColChar {
                        text_char,
                        modifier,
                    },
                ));
            }
        }

        pixels
    }

    /// Return a vector of Pixels to display the given content, aligning the content to the position as directed by the `align` attribute
    pub fn draw_with_align(
        pos: Vec2D,
        content: &str,
        align: TextAlign,
        modifier: Modifier,
    ) -> Vec<Pixel> {
        let pos = match align {
            TextAlign::Left => pos,
            TextAlign::Centered => pos - Vec2D::new(content.len() as isize / 2, 0),
            TextAlign::Right => pos - Vec2D::new(content.len() as isize, 0),
        };

        Text::draw(pos, content, modifier)
    }
}

impl ViewElement for Text<'_> {
    fn active_pixels(&self) -> Vec<Pixel> {
        Text::draw(self.pos, self.content, self.modifier)
    }
}

/// The `Sprite` takes a multi-line string as a parameter, and can be used to put ASCII art, text and other such things on the `View`
#[non_exhaustive]
#[derive(Debug, Clone)]
pub struct Sprite {
    pub pos: Vec2D,
    /// The ACII texture (pun intended) displayed by the `Sprite`
    pub texture: String,
    /// A raw [`Modifier`], determining the appearance of the `Sprite`
    pub modifier: Modifier,
    // TODO: add x and y align
}
impl Sprite {
    /// Create a new `Sprite` struct. All newlines at the beginning of the texture will be removed
    pub fn new(pos: Vec2D, texture: &str, modifier: Modifier) -> Self {
        let mut texture: Vec<char> = texture.chars().rev().collect();

        while *texture.last().expect("Texture consists of only newlines") == '\n' {
            texture.pop();
        }

        Self {
            pos,
            texture: texture.iter().rev().collect(),
            modifier,
        }
    }
}

impl ViewElement for Sprite {
    fn active_pixels(&self) -> Vec<Pixel> {
        let mut pixels = vec![];

        let lines = self.texture.split('\n');
        for (y, line) in lines.enumerate() {
            pixels.extend(Text::draw(
                self.pos + Vec2D::new(0, y as isize),
                line,
                self.modifier,
            ));
        }

        pixels
    }
}