pel 0.1.0

OpenGL backed framebuffer
Documentation
use std::{ffi::CStr, marker::PhantomData, mem};

use glutin::{
    dpi::{LogicalSize, Size},
    event::Event,
    event_loop::EventLoop,
    window::{Fullscreen, Icon, WindowBuilder},
    Api, ContextBuilder, GlProfile, GlRequest,
};

use ogl33::load_gl_with;

use crate::{wrapper::Wrapper, Context, Events, Surface};

/// Pel configuration.
pub struct Config {
    ctx_attribs: ContextAttributes,
    win_attribs: WindowAttributes,
    pel_attribs: PelAttributes,
    phantom: PhantomData<*const ()>, // !Send + !Sync
}

impl Config {
    /// Request pel window to have vsync enabled.
    ///
    /// By default, vsync is enabled.
    pub fn vsync(mut self, vsync: bool) -> Self {
        self.ctx_attribs.vsync = vsync;
        self
    }

    /// Request pel window to have sRGB enabled.
    ///
    /// By default, sRGB is enabled.
    pub fn srgb(mut self, srgb: bool) -> Self {
        self.ctx_attribs.srgb = srgb;
        self
    }

    /// Requests pel window client area to be of specific size in logical pixels.
    ///
    /// By default, platform defaults are used.
    pub fn window_size(mut self, width: u32, height: u32) -> Self {
        self.win_attribs.size = Some(LogicalSize::new(width, height).into());
        self
    }

    /// Requests minimum pel window client area to be no less than specified in logical pixels.
    ///
    /// By default, platform defaults are used.
    pub fn min_window_size(mut self, width: u32, height: u32) -> Self {
        self.win_attribs.min_size = Some(LogicalSize::new(width, height).into());
        self
    }

    /// Requests maximum pel window client area to be no more than specified in logical pixels.
    ///
    /// By default, platform defaults are used.
    pub fn max_window_size(mut self, width: u32, height: u32) -> Self {
        self.win_attribs.max_size = Some(LogicalSize::new(width, height).into());
        self
    }

    /// Requests pel window to be resizable.
    ///
    /// By default, window is resizable.
    pub fn resizable(mut self, resizable: bool) -> Self {
        self.win_attribs.resizable = resizable;
        self
    }

    /// Requests pel window to appear maximized when created.
    ///
    /// By default, window isn't maximized.
    pub fn maximized(mut self, maximized: bool) -> Self {
        self.win_attribs.maximized = maximized;
        self
    }

    /// Requests pel window to have border.
    ///
    /// By default, window does have a border.
    pub fn decorations(mut self, decorations: bool) -> Self {
        self.win_attribs.decorations = decorations;
        self
    }

    /// Requests pel window to use given icon.
    ///
    /// By default, window has no icon.
    pub fn icon<S: Into<Surface>>(mut self: Self, icon: S) -> Self {
        let (surface, w, h) = icon.into().into_raw_parts();
        let buf = Vec::<u8>::with_capacity(surface.len() * 4);
        let buf = surface.into_iter().fold(buf, |mut vec, color| {
            let bytes: [u8; 4] = color.into();
            vec.extend_from_slice(&[bytes[1], bytes[2], bytes[3], bytes[0]]);
            vec
        });
        self.win_attribs.icon = Some(Icon::from_rgba(buf, w as u32, h as u32).expect("malformed surface?"));
        self
    }

    /// Requests pel window to have a specific title.
    ///
    /// By default, window title is `"pel window"`.
    pub fn title<T: Into<String>>(mut self, title: T) -> Self {
        self.win_attribs.title = Some(title.into());
        self
    }

    /// Requests pel window to be created in fullscreen mode.
    ///
    /// By default, window is not in fullscreen mode.
    pub fn fullscreen(mut self, fullscreen: bool) -> Self {
        self.win_attribs.fullscreen = fullscreen;
        self
    }

    /// Requests pel window to be in always on top mode.
    ///
    /// By default, window isn't in always on top mode.
    pub fn always_on_top(mut self, always_on_top: bool) -> Self {
        self.win_attribs.always_on_top = always_on_top;
        self
    }

    /// Requests window pel texture to be of specific size in logical pixels.
    ///
    /// This is the "resolution" of your application, in which you will be working in, and it is
    /// independent of physical window size.
    ///
    /// By default, texture size is `None` and it must be set manually or builder will panic.
    pub fn texture_size(mut self, width: usize, height: usize) -> Self {
        self.pel_attribs.texture_size = Some((width, height));
        self
    }

    /// Starts pel event loop.
    ///
    /// Due to cross-platform limitations, this method should only be called in the main thread.
    ///
    /// See documentation of [`Events`][1] for more information.
    ///
    /// [1]: trait.Events.html
    pub fn run<E>(self, event_handler: E) -> !
    where
        E: Events + 'static,
    {
        let mut wb = WindowBuilder::new();
        wb.window.inner_size = self.win_attribs.size;
        wb.window.min_inner_size = self.win_attribs.min_size;
        wb.window.max_inner_size = self.win_attribs.max_size;
        wb.window.resizable = self.win_attribs.resizable;
        wb.window.maximized = self.win_attribs.maximized;
        wb.window.visible = self.win_attribs.visible;
        wb.window.decorations = self.win_attribs.decorations;
        wb.window.always_on_top = self.win_attribs.always_on_top;
        wb.window.window_icon = self.win_attribs.icon.clone();
        wb.window.title = self
            .win_attribs
            .title
            .clone()
            .unwrap_or_else(|| String::from("pel window"));

        let el = EventLoop::new();
        let context = ContextBuilder::new()
            .with_gl(GlRequest::Specific(Api::OpenGl, (3, 3)))
            .with_gl_profile(GlProfile::Core)
            .with_vsync(self.ctx_attribs.vsync)
            .with_srgb(self.ctx_attribs.srgb)
            .with_double_buffer(Some(true))
            .with_hardware_acceleration(Some(true))
            .build_windowed(wb, &el)
            .expect("could not create a window");

        let window = unsafe {
            context
                .make_current()
                .expect("could not make OpenGL 3.3 the current context")
        };
        unsafe {
            load_gl_with(|ptr| {
                window
                    .context()
                    .get_proc_address(CStr::from_ptr(ptr).to_str().expect("invalid c_str")) as *const _
            });
        }

        if self.win_attribs.fullscreen {
            let window_handle = window.window();
            let monitor_handle = window_handle.current_monitor();
            window_handle.set_fullscreen(Some(Fullscreen::Borderless(monitor_handle)));
        }

        let mut pel = Wrapper::build(event_handler);
        let mut ctx = Context::new(window, &self.pel_attribs);

        mem::drop(self);

        el.run(move |event, _, flow| match event {
            Event::WindowEvent { window_id, event } => pel.window_event(&mut ctx, window_id, event, flow),
            Event::DeviceEvent { device_id, event } => pel.device_event(&mut ctx, device_id, event),
            Event::NewEvents(cause) => pel.start(&mut ctx, cause),
            Event::MainEventsCleared => pel.update(&mut ctx),
            Event::RedrawRequested(window_id) => pel.draw(&mut ctx, window_id),
            Event::LoopDestroyed => pel.loop_destroyed(),
            _ => (),
        })
    }

    pub(crate) fn new() -> Self {
        Self::default()
    }
}

impl Default for Config {
    fn default() -> Self {
        Self {
            ctx_attribs: ContextAttributes::default(),
            win_attribs: WindowAttributes::default(),
            pel_attribs: PelAttributes::default(),
            phantom: PhantomData,
        }
    }
}

struct ContextAttributes {
    vsync: bool,
    srgb: bool,
}

impl Default for ContextAttributes {
    fn default() -> Self {
        Self {
            vsync: true,
            srgb: true,
        }
    }
}

struct WindowAttributes {
    size: Option<Size>,
    min_size: Option<Size>,
    max_size: Option<Size>,
    resizable: bool,
    maximized: bool,
    visible: bool,
    decorations: bool,
    always_on_top: bool,
    icon: Option<Icon>,
    title: Option<String>,
    fullscreen: bool,
}

impl Default for WindowAttributes {
    fn default() -> Self {
        Self {
            size: None,
            min_size: None,
            max_size: None,
            resizable: true,
            maximized: false,
            visible: true,
            decorations: true,
            always_on_top: false,
            icon: None,
            title: None,
            fullscreen: false,
        }
    }
}

pub struct PelAttributes {
    pub texture_size: Option<(usize, usize)>,
}

impl Default for PelAttributes {
    fn default() -> Self {
        Self { texture_size: None }
    }
}