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
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
#[allow(unused_imports)]
use glutin::VirtualKeyCode;
use std::cell::{Cell, RefCell};
use std::time::SystemTime;

use crate::display::Display;
use crate::events::Events;
use crate::font::Font;
use crate::renderer;
use crate::renderer::Program;
use crate::text_buffer::TextBuffer;

static IOSEVKA_SFL: &'static str = include_str!("../fonts/iosevka.sfl");
static IOSEVKA_PNG: &'static [u8] = include_bytes!("../fonts/iosevka.png");

/// A builder for the `Terminal`. Includes some settings that can be set before building.
///
/// See [terminal mod](index.html) for examples and more detailed documentation.
pub struct TerminalBuilder {
    pub(crate) title: String,
    pub(crate) dimensions: (u32, u32),
    pub(crate) clear_color: (f32, f32, f32, f32),
    pub(crate) font: Font,
    pub(crate) visibility: bool,
    pub(crate) headless: bool,
    pub(crate) text_buffer_aspect_ratio: bool,
    pub(crate) vsync: bool,
}

#[allow(dead_code)]
impl TerminalBuilder {
    /// Creates a new terminal builder with default settings.
    pub fn new() -> TerminalBuilder {
        TerminalBuilder {
            title: "Hello, World ! ".to_owned(),
            dimensions: (1280, 720),
            clear_color: (0.14, 0.19, 0.28, 1.0),
            font: Font::load_raw(IOSEVKA_SFL, IOSEVKA_PNG),
            visibility: true,
            headless: false,
            text_buffer_aspect_ratio: true,
            vsync: true,
        }
    }

    /// Sets the title for the `Terminal`.
    pub fn with_title<T: Into<String>>(mut self, title: T) -> TerminalBuilder {
        self.title = title.into();
        self
    }

    /// Sets the dimensions the `Terminal` is to be opened with.
    pub fn with_dimensions(mut self, dimensions: (u32, u32)) -> TerminalBuilder {
        self.dimensions = dimensions;
        self
    }

    /// Sets the clear color of the terminal.
    pub fn with_clear_color(mut self, clear_color: (f32, f32, f32, f32)) -> TerminalBuilder {
        self.clear_color = clear_color;
        self
    }

    /// Changes the font that the terminal uses.
    pub fn with_font(mut self, font: Font) -> TerminalBuilder {
        self.font = font;
        self
    }

    /// Changes the visibility that the terminal will be opened with. If headless, visibility will not matter.
    pub fn with_visibility(mut self, visibility: bool) -> TerminalBuilder {
        self.visibility = visibility;
        self
    }

    /// Changes the visibility that the terminal will be opened with
    pub fn with_headless(mut self, headless: bool) -> TerminalBuilder {
        self.headless = headless;
        self
    }

    /// Changes whether the aspect ratio should be retrieved from TextBuffer instead of the original resolution of the screen.
    ///
    /// If set to false, the aspect ratio used to make black bars for the screen will be fetched from the original resolution of the screen;
    /// This will cause the fonts to strech a bit though.
    ///
    /// If set to true (default), the aspect ratio will be fetched from the TextBuffer, causing almost any resolution
    /// to have black bars to make up for the missing spaces.
    pub fn with_text_buffer_aspect_ratio(mut self, tbar: bool) -> TerminalBuilder {
        self.text_buffer_aspect_ratio = tbar;
        self
    }

    /// Enable/Disable vsync. Enabled by default.
    pub fn with_vsync(mut self, vsync: bool) -> TerminalBuilder {
        self.vsync = vsync;
        self
    }

    /// Builds the actual terminal and opens the window
    pub fn build(self) -> Terminal {
        Terminal::new(self)
    }
}

/// The Terminal acts as the window and "canvas" of the terminal, handling most behind-the-sceneries
///
/// The Terminal is used to create the window and canvas for the [`TextBuffer`](text_buffer/struct.TextBuffer.html)
/// which can then draw it, close the window, reset the title of the window or handle events.
///
/// **Note** when building with debug-mode, you are able to press `F3` to toggle between debug and non-debug. see ([`set_debug`](#method.set_debug)) for more information.
///
/// ### Terminal example:
/// ```no_run
/// use glerminal::TerminalBuilder;
///
/// let terminal = TerminalBuilder::new()
///     .with_title("Hello GLerminal!")
///     .with_dimensions((1280, 720))
///     .build();
/// ```
///
/// ### `let mut terminal` vs `let terminal`
/// In most cases you might just want to initialize the terminal as immutable, but in some, you will need to initialize it as mutable,
/// allowing it to run some additional methods, such as `.show()` and `.set_title("title")`
///
/// #### Example of a mutable terminal:
/// ```no_run
/// use glerminal::TerminalBuilder;
///
/// let mut terminal = TerminalBuilder::new()
///     .with_title("Hello GLerminal!")
///     .with_dimensions((1280, 720))
///     .with_visibility(false)
///     .build();
///
/// terminal.set_title("Changed title!");
/// terminal.show();
/// ```
pub struct Terminal {
    display: Option<Display>,
    program: Program,
    background_program: Program,
    debug_program: Program,
    debug: Cell<bool>,
    running: Cell<bool>,
    pub(crate) headless: bool,
    since_start: SystemTime,
    pub(crate) font: Font,

    timer: RefCell<Timer>,
    text_buffer_aspect_ratio: bool,
}

impl Terminal {
    fn new(builder: TerminalBuilder) -> Terminal {
        let (display, program, background_program, debug_program) = if builder.headless {
            (None, Program::empty(), Program::empty(), Program::empty())
        } else {
            (
                Some(Display::new(
                    builder.title,
                    builder.dimensions,
                    builder.clear_color,
                    builder.visibility,
                    builder.text_buffer_aspect_ratio,
                    builder.vsync,
                )),
                renderer::create_program(renderer::VERT_SHADER, renderer::FRAG_SHADER),
                renderer::create_program(renderer::VERT_SHADER, renderer::BG_FRAG_SHADER),
                renderer::create_program(renderer::VERT_SHADER, renderer::DEBUG_FRAG_SHADER),
            )
        };
        Terminal {
            display,
            program,
            background_program,
            debug_program,
            debug: Cell::new(false),
            running: Cell::new(true),
            headless: builder.headless,
            since_start: SystemTime::now(),
            font: builder.font,
            timer: RefCell::new(Timer::new()),
            text_buffer_aspect_ratio: builder.text_buffer_aspect_ratio,
        }
    }

    /// Sets debug mode (changes characters and backgrounds into wireframe)
    pub fn set_debug(&self, debug: bool) {
        if !self.headless {
            renderer::set_debug(debug);
            self.debug.set(debug);
        }
    }

    /// Refreshes the screen and returns whether the while-loop should continue (is the program running)
    #[cfg(debug_assertions)]
    pub fn refresh(&self) -> bool {
        let mut timer = self.timer.borrow_mut();
        timer.update();
        drop(timer);

        let running = if let Some(ref display) = self.display {
            let events = self.get_current_events();
            if events.keyboard.was_just_pressed(VirtualKeyCode::F3) {
                self.set_debug(!self.debug.get());
            }
            display.refresh() && self.running.get()
        } else {
            self.running.get()
        };

        if running && !self.headless {
            renderer::clear();
        }
        running
    }

    /// Refreshes the screen and returns whether the while-loop should continue (is the program running)
    #[cfg(not(debug_assertions))]
    pub fn refresh(&self) -> bool {
        let mut timer = self.timer.borrow_mut();
        timer.update();
        drop(timer);

        let running = if let Some(ref display) = self.display {
            display.refresh() && self.running.get()
        } else {
            self.running.get()
        };

        if running && !self.headless {
            renderer::clear();
        }
        running
    }

    /// Flushes `TextBuffer`, taking it's character-grid and making it show for the next draw.
    ///
    /// This is quite a heavy function and it's calling should be avoided when unnecessary.
    pub fn flush(&self, text_buffer: &mut TextBuffer) {
        text_buffer.swap_buffers(&self.font);
    }

    /// Draws a `TextBuffer`. This should be called every frame for each text buffer.
    pub fn draw(&self, text_buffer: &TextBuffer) {
        if let (&Some(ref display), &Some(ref mesh), &Some(ref background_mesh)) = (
            &self.display,
            &text_buffer.mesh,
            &text_buffer.background_mesh,
        ) {
            let proj_matrix = if self.text_buffer_aspect_ratio {
                display.get_display_data(&text_buffer).proj_matrix
            } else {
                display.proj_matrix.get()
            };

            let duration = SystemTime::now().duration_since(self.since_start).unwrap();

            let time = duration.as_secs() as f32 + duration.subsec_nanos() as f32 / 1_000_000_000.0;

            renderer::draw(
                self.get_background_program(),
                proj_matrix,
                time,
                background_mesh,
            );
            renderer::draw(self.get_program(), proj_matrix, time, mesh);
        }
    }

    /// Gets the current Events, must be retrieved every time you want new events. (ie. every frame)
    pub fn get_current_events(&self) -> Events {
        if let Some(ref display) = self.display {
            display.get_current_events()
        } else {
            Events::new(self.text_buffer_aspect_ratio)
        }
    }

    /// Closes the Terminal
    pub fn close(&self) {
        self.running.set(false);
    }

    /// Sets the title for the window.
    ///
    /// **Warning:** This is a nuclear hazard (takes up a lot of performance), it might melt down your computer if called every frame (or so).
    pub fn set_title<T: Into<String>>(&mut self, title: T) {
        if let Some(ref mut display) = self.display {
            display.set_title(&title.into());
        }
    }

    /// Shows the window, if it's hidden
    pub fn show(&mut self) {
        if let Some(ref mut display) = self.display {
            display.show();
        }
    }

    /// Get the delta-time (in seconds).
    pub fn delta_time(&self) -> f32 {
        self.timer.borrow().get_delta_time()
    }

    pub(crate) fn get_program(&self) -> Program {
        if self.headless {
            panic!("Unable to get program from headless terminal");
        }
        if !self.debug.get() {
            self.program
        } else {
            self.debug_program
        }
    }

    pub(crate) fn get_background_program(&self) -> Program {
        if self.headless {
            panic!("Unable to get program from headless terminal");
        }
        if !self.debug.get() {
            self.background_program
        } else {
            self.debug_program
        }
    }

    #[cfg(test)]
    pub(crate) fn update_virtual_keycode(&mut self, keycode: VirtualKeyCode, pressed: bool) {
        if let Some(ref mut display) = self.display {
            display.update_virtual_keycode(keycode, pressed);
        }
    }
}

pub(crate) struct Timer {
    last_check: SystemTime,
    delta_time: f32,
}

impl Timer {
    pub fn new() -> Timer {
        Timer {
            last_check: SystemTime::now(),
            delta_time: 0.0,
        }
    }

    pub fn update(&mut self) {
        let current_time = SystemTime::now();
        let duration = current_time.duration_since(self.last_check).unwrap();
        self.last_check = current_time;

        self.delta_time =
            duration.as_secs() as f32 + duration.subsec_nanos() as f32 / 1_000_000_000.0;
    }

    pub fn get_delta_time(&self) -> f32 {
        self.delta_time
    }
}