glerminal 0.3.0

A lightweight OpenGL terminal
Documentation
use gl;
use glutin::{
    ContextBuilder, ElementState, Event, EventsLoop, GlContext, GlRequest, GlWindow, WindowBuilder,
    WindowEvent,
};

use crate::events::Events;
use crate::renderer::{self, Matrix4};
use crate::TextBuffer;
use std::cell::{Cell, RefCell};
use std::collections::HashMap;

#[cfg(test)]
use glutin::VirtualKeyCode;

#[derive(Clone)]
pub struct TextBufferDisplayData {
    pub proj_matrix: Matrix4,
    pub aspect_ratio: f32,
    pub overflows: (f32, f32),
    pub relative_dimensions: (f32, f32),
}

impl TextBufferDisplayData {
    pub fn new(width: f32, height: f32, text_buffer: &TextBuffer) -> TextBufferDisplayData {
        let (overflows, relative_dimensions) =
            Display::calc_overflows_dimensions(width, height, text_buffer.aspect_ratio);
        TextBufferDisplayData {
            proj_matrix: renderer::create_proj_matrix((width, height), text_buffer.aspect_ratio),
            aspect_ratio: text_buffer.aspect_ratio,
            overflows: overflows,
            relative_dimensions: relative_dimensions,
        }
    }
}

pub struct Display {
    pub proj_matrix: Cell<Matrix4>,
    display_datas: RefCell<HashMap<u32, TextBufferDisplayData>>,
    aspect_ratio: Cell<f32>,
    window: GlWindow,
    events: RefCell<Events>,
    events_loop: RefCell<EventsLoop>,
    width: Cell<f32>,
    height: Cell<f32>,
}

impl Display {
    pub fn new<T: Into<String>>(
        title: T,
        dimensions: (u32, u32),
        clear_color: (f32, f32, f32, f32),
        visibility: bool,
        text_buffer_aspect_ratio: bool,
        vsync: bool,
    ) -> Display {
        let (width, height) = dimensions;
        let aspect_ratio = width as f32 / height as f32;
        let title = title.into();
        let events_loop = EventsLoop::new();
        let window = WindowBuilder::new()
            .with_title(title)
            .with_dimensions(width, height)
            .with_visibility(visibility);
        let context = ContextBuilder::new()
            .with_vsync(vsync)
            .with_gl(GlRequest::Latest);
        let window = match GlWindow::new(window, context, &events_loop) {
            Ok(window) => window,
            Err(err) => panic!(err),
        };

        let width = width as f32;
        let height = height as f32;

        unsafe {
            let (r, g, b, a) = clear_color;
            if let Err(err) = window.make_current() {
                panic!(err);
            }
            gl::load_with(|symbol| window.get_proc_address(symbol) as *const _);
            gl::ClearColor(r, g, b, a);
            gl::Enable(gl::BLEND);
            gl::BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA);
        };

        let gl_version = renderer::get_version();
        if !renderer::is_gl_version_compatible(&gl_version) {
            panic!("GL version too low: OpenGL {}", gl_version);
        }

        let proj_matrix = renderer::create_proj_matrix((width, height), aspect_ratio);

        let mut events = Events::new(text_buffer_aspect_ratio);
        let (display_overflows, display_relative_dimensions) =
            Display::calc_overflows_dimensions(width, height, aspect_ratio);
        events.cursor.update_display_datas(
            display_overflows,
            display_relative_dimensions,
            HashMap::new(),
        );

        Display {
            window: window,
            events: RefCell::new(events),
            events_loop: RefCell::new(events_loop),
            aspect_ratio: Cell::new(aspect_ratio),
            proj_matrix: Cell::new(proj_matrix),
            display_datas: RefCell::new(HashMap::new()),
            width: Cell::new(width),
            height: Cell::new(height),
        }
    }

    pub fn refresh(&self) -> bool {
        let mut running = true;

        let mut dimensions: Option<(f32, f32)> = None;

        self.events.borrow_mut().clear_just_lists();

        self.window.swap_buffers().ok();

        self.events_loop.borrow_mut().poll_events(|event| {
            if let Event::WindowEvent { event, .. } = event {
                match event {
                    WindowEvent::CloseRequested => {
                        running = false;
                    }
                    WindowEvent::Destroyed => {
                        running = false;
                    }
                    WindowEvent::Resized(width, height) => {
                        dimensions = Some((width as f32, height as f32));
                    }
                    WindowEvent::KeyboardInput { input, .. } => {
                        if let (state, Some(keycode)) = (input.state, input.virtual_keycode) {
                            self.events
                                .borrow_mut()
                                .keyboard
                                .update_button_press(keycode, state == ElementState::Pressed);
                        }
                    }
                    WindowEvent::MouseInput { button, state, .. } => self
                        .events
                        .borrow_mut()
                        .mouse
                        .update_button_press(button, state == ElementState::Pressed),
                    WindowEvent::CursorMoved { position, .. } => {
                        self.events.borrow_mut().cursor.update_location((
                            position.0 as f32 / self.width.get(),
                            position.1 as f32 / self.height.get(),
                        ));
                    }
                    WindowEvent::CursorLeft { .. } => self.events.borrow_mut().cursor.cursor_left(),
                    WindowEvent::ReceivedCharacter(character) => {
                        self.events.borrow_mut().chars.add_char(character);
                    }
                    _ => (),
                }
            }
        });

        if let Some((width, height)) = dimensions {
            self.width.set(width);
            self.height.set(height);
            self.update_view();
        }

        running
    }

    pub fn get_current_events(&self) -> Events {
        self.events.borrow().clone()
    }

    pub fn set_title(&mut self, title: &str) {
        self.window.set_title(title);
    }

    pub fn show(&mut self) {
        self.window.show();
    }

    pub(crate) fn get_display_data(&self, text_buffer: &TextBuffer) -> TextBufferDisplayData {
        let mut display_datas = self.display_datas.borrow_mut();
        if !display_datas.contains_key(&text_buffer.get_idx()) {
            display_datas.insert(
                text_buffer.get_idx(),
                TextBufferDisplayData::new(self.width.get(), self.height.get(), &text_buffer),
            );

            self.update_event_display_datas(display_datas.clone());
        }
        display_datas.get(&text_buffer.get_idx()).unwrap().clone()
    }

    #[cfg(test)]
    pub(crate) fn update_virtual_keycode(&mut self, keycode: VirtualKeyCode, pressed: bool) {
        self.events
            .borrow_mut()
            .keyboard
            .update_button_press(keycode, pressed);
    }

    fn update_view(&self) {
        self.proj_matrix.set(renderer::create_proj_matrix(
            (self.width.get() as f32, self.height.get() as f32),
            self.aspect_ratio.get(),
        ));

        for data in self.display_datas.borrow_mut().values_mut() {
            data.proj_matrix = renderer::create_proj_matrix(
                (self.width.get() as f32, self.height.get() as f32),
                data.aspect_ratio,
            );

            let (overflows, relative_dimensions) = Display::calc_overflows_dimensions(
                self.width.get(),
                self.height.get(),
                data.aspect_ratio,
            );
            data.overflows = overflows;
            data.relative_dimensions = relative_dimensions;
        }

        self.update_event_display_datas(self.display_datas.borrow().clone());

        renderer::update_viewport((self.width.get() as i32, self.height.get() as i32));
    }

    fn update_event_display_datas(&self, datas: HashMap<u32, TextBufferDisplayData>) {
        let (display_overflows, display_relative_dimensions) = Display::calc_overflows_dimensions(
            self.width.get(),
            self.height.get(),
            self.aspect_ratio.get(),
        );

        self.events.borrow_mut().cursor.update_display_datas(
            display_overflows,
            display_relative_dimensions,
            datas,
        );
    }

    fn calc_overflows_dimensions(
        width: f32,
        height: f32,
        aspect_ratio: f32,
    ) -> ((f32, f32), (f32, f32)) {
        let true_width = height * aspect_ratio;
        let true_height = width / aspect_ratio;

        let mut overflow_width = 0f32;
        let mut overflow_height = 0f32;
        let mut relative_width = 1.0;
        let mut relative_height = 1.0;
        if true_width < width {
            overflow_width = (width - true_width) / width;
            relative_width = width / true_width;
        } else {
            overflow_height = (height - true_height) / height;
            relative_height = height / true_height;
        }

        let overflows = (overflow_width / 2.0, overflow_height / 2.0);
        let relative_dimensions = (relative_width, relative_height);
        (overflows, relative_dimensions)
    }
}