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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
//! This module contains two of the most central structs of GLerminal; [`TextBuffer`](struct.TextBuffer.html) and [`Parser`](parser/struct.Parser.html)
//!
//! `TextBuffer` acts as a "state machine" where you can set foreground color, background color and shakiness for the cursor,
//! move the cursor around, clear the screen and write with the cursor (using the cursor's styles).
//! It's often the most efficient way to write things, especially if you have a very structured way of displaying things, but for a more simple-to-use
//! way of writing, that isn't as structured ie. for a dialogue, you might want to use the Parser.
//!
//! `Parser` is a struct that is able to take in a piece of text and then parse it and change the cursor styles easily using the `TextBuffer`.
//! The `Parser` can handle tags imilar to BBCode tags, and can change fg, bg and shake, meaning the following tags are available to use mid-text:
//! - `[fg=color]`
//! - `[bg=color]`
//! - `[shake=decimal]`
//! - optional closing/style-resetting tags: `[/fg]`, `[/bg]` and `[/shake]`
//!
//! The colors the `Parser` uses mid text must be pre-defined however with `add_color`.
//!
//! ### Example usage of `TextBuffer`:
//! ```no_run
//! use glerminal::terminal::TerminalBuilder;
//! use glerminal::text_buffer::TextBuffer;
//!
//! let terminal = TerminalBuilder::new()
//!     .with_title("Hello GLerminal!")
//!     .with_dimensions((1280, 720))
//!     .build();
//!
//! let mut text_buffer;
//! match TextBuffer::new(&terminal, (80, 24)) {
//!   Ok(buffer) => text_buffer = buffer,
//!   Err(error) => panic!(format!("Failed to initialize text buffer: {}", error)),
//! }
//!
//! // Test TextBuffer
//! text_buffer.change_cursor_fg_color([1.0, 0.0, 0.0, 1.0]);
//! text_buffer.change_cursor_bg_color([1.0, 1.0, 1.0, 1.0]);
//! text_buffer.change_cursor_shakiness(0.5);
//! text_buffer.move_cursor(0, 0);
//! text_buffer.write("This text is shaking in red in a white background!");
//!
//! // Flush to "apply changes"
//! terminal.flush(&mut text_buffer);
//! ```
//!
//! ### Example usage of `Parser`
//! ```no_run
//! use glerminal::terminal::TerminalBuilder;
//! use glerminal::text_buffer::TextBuffer;
//! use glerminal::text_buffer::parser::Parser;
//!
//! let terminal = TerminalBuilder::new()
//!     .with_title("Hello GLerminal!")
//!     .with_dimensions((1280, 720))
//!     .build();
//!
//! let mut text_buffer;
//! match TextBuffer::new(&terminal, (80, 24)) {
//!   Ok(buffer) => text_buffer = buffer,
//!   Err(error) => panic!(format!("Failed to initialize text buffer: {}", error)),
//! }
//!
//! // Test Parser
//! let mut parser = Parser::new();
//! parser.add_color("red", [1.0, 0.0, 0.0, 1.0]);
//! parser.add_color("white", [1.0, 1.0, 1.0, 1.0]);
//! parser.write(&mut text_buffer, "[fg=red][bg=white][shake=1.0]This text is also shaking in red in a white background![/fg][/bg][/shake]");
//! // Note: it is not necessary to close fg/bg/shake tags, parser will automatically revert colors in the TextBuffer.
//!
//! // Flush to "apply changes"
//! terminal.flush(&mut text_buffer);
//! ```

pub mod parser;

use renderer::textbuffermesh::TextBufferMesh;
use renderer::backgroundmesh::BackgroundMesh;
use font::Font;
use terminal::Terminal;

/// Represents a color with values from 0.0 to 1.0 (red, green, blue, alpha)
pub type Color = [f32; 4];

/// Represents a single character in a [`TextBuffer`](struct.TextBuffer.html)
#[derive(Clone, Copy)]
pub struct TermCharacter {
    character: char,
    fg_color: Color,
    bg_color: Color,
    shakiness: f32,
}

impl TermCharacter {
    pub(crate) fn new(
        character: char,
        fg_color: Color,
        bg_color: Color,
        shakiness: f32,
    ) -> TermCharacter {
        TermCharacter {
            character,
            fg_color,
            bg_color,
            shakiness,
        }
    }

    /// Gets the char in the TermCharacter
    pub fn get_char(&self) -> char {
        self.character
    }

    /// Gets the foreground Color in the TermCharacter
    pub fn get_fg_color(&self) -> Color {
        self.fg_color
    }

    /// Gets the background Color in the TermCharacter
    pub fn get_bg_color(&self) -> Color {
        self.bg_color
    }

    /// Gets the shakiness of the TermCharacter, where shakiness is a value from 0.0 to 1.0 (other values are accepted, but these are suggested).
    pub fn get_shakiness(&self) -> f32 {
        self.shakiness
    }
}

struct TermCursor {
    x: i32,
    y: i32,
    foreground_color: Color,
    background_color: Color,
    shakiness: f32,
}

/// Represents the text buffer of the terminal; contains the "grid of [`TermCharacters`](struct.TermCharacter.html)" that will be drawn.
///
/// See [text_buffer mod](index.html) for examples and more detailed documentation.
pub struct TextBuffer {
    pub(crate) chars: Vec<TermCharacter>,
    pub(crate) height: i32,
    pub(crate) width: i32,
    pub(crate) mesh: Option<TextBufferMesh>,
    pub(crate) background_mesh: Option<BackgroundMesh>,
    cursor: TermCursor,
}

impl TextBuffer {
    /// Creates a new text buffer with the given dimensions (width in characters, height in characters)
    pub fn new(terminal: &Terminal, dimensions: (i32, i32)) -> Result<TextBuffer, String> {
        let (width, height) = dimensions;

        if width <= 0 || height <= 0 {
            return Err(
                "TextBuffer dimensions are erronous; either width or height is below 1".to_owned(),
            );
        }

        let chars =
            vec![TermCharacter::new(' ', [0.0; 4], [0.0; 4], 0.0); (width * height) as usize];
        let mesh;
        let background_mesh;
        if terminal.headless {
            mesh = None;
            background_mesh = None;
        } else {
            mesh = Some(TextBufferMesh::new(terminal.get_program(), dimensions, &terminal.font));
            background_mesh = Some(BackgroundMesh::new(terminal.get_background_program(), dimensions));
        }
        Ok(TextBuffer {
            chars,
            height,
            width,
            mesh,
            background_mesh,
            cursor: TermCursor {
                x: 0,
                y: 0,
                foreground_color: [1.0; 4],
                background_color: [0.0; 4],
                shakiness: 0.0,
            },
        })
    }

    pub(crate) fn swap_buffers(&self, font: &Font) {
        if let (&Some(ref mesh), &Some(ref background_mesh)) = (&self.mesh, &self.background_mesh) {
            mesh.update(&self, font);
            background_mesh.update(&self);
        }
    }

    pub(crate) fn out_of_bounds(&self, x: i32, y: i32) -> bool {
        (x < 0 || y < 0 || x >= self.width || y >= self.height)
    }

    /// Gets the TermChaacter in the given position
    pub fn get_character(&self, x: i32, y: i32) -> TermCharacter {
        self.chars[(y * self.width + x) as usize]
    }

    /// Clears the screen (makes every character empty and resets their style)
    pub fn clear(&mut self) {
        self.chars = vec![
            TermCharacter::new(' ', [0.0; 4], [0.0; 4], 0.0);
            (self.width * self.height) as usize
        ];
    }

    /// Puts a character to the current position of the cursor with the cursor's style
    pub fn put_char(&mut self, character: char) {
        self.chars[(self.cursor.y * self.width + self.cursor.x) as usize] = TermCharacter::new(
            character,
            self.cursor.foreground_color,
            self.cursor.background_color,
            self.cursor.shakiness,
        );
        self.move_cursor_by(1);
    }

    /// Puts the given text the same way as put_char
    pub fn write<T: Into<String>>(&mut self, text: T) {
        let text = text.into();
        for c in text.chars() {
            self.put_char(c);
        }
    }

    /// Changes the foreground color for the cursor
    pub fn change_cursor_fg_color(&mut self, color: Color) {
        self.cursor.foreground_color = color;
    }

    /// Changes the background color of the cursor
    pub fn change_cursor_bg_color(&mut self, color: Color) {
        self.cursor.background_color = color;
    }

    /// Returns the current foreground color of the cursor
    pub fn get_cursor_fg_color(&mut self) -> Color {
        self.cursor.foreground_color
    }

    /// Returns the current background color of the cursor
    pub fn get_cursor_bg_color(&mut self) -> Color {
        self.cursor.background_color
    }

    /// Changes the shakiness of the cursor
    pub fn change_cursor_shakiness(&mut self, shakiness: f32) {
        self.cursor.shakiness = shakiness;
    }

    /// Gets the current shakiness of the cursor
    pub fn get_cursor_shakiness(&mut self) -> f32 {
        self.cursor.shakiness
    }

    /// Moves the cursor to a specified location in the terminal. If the location does not exist, nothing happens.
    pub fn move_cursor(&mut self, x: i32, y: i32) {
        if !self.out_of_bounds(x, y) {
            self.cursor.x = x;
            self.cursor.y = y;
        }
    }

    /// Returns the current position of the cursor
    pub fn get_cursor_position(&self) -> (i32, i32) {
        (self.cursor.x, self.cursor.y)
    }

    fn move_cursor_by(&mut self, amount: i32) {
        let new_pos = self.cursor.x + amount;
        if new_pos >= 0 {
            self.cursor.x += amount;
            if self.cursor.x >= self.width {
                self.cursor.x = 0;
                self.cursor.y += 1;
                if self.cursor.y >= self.height {
                    self.cursor.y = 0;
                }
            }
        }
    }
}