gl-headless 0.2.2

Easiest way to create a headless OpenGL context
Documentation
pub mod prelude {
    pub use super::{GLContext, GLContextBuilder};
}

use std::str::FromStr;

use glfw::{Callback, Context, Glfw, OpenGlProfileHint, Window, WindowHint, WindowMode};

use crate::{GLVersionError, HeadlessError};

pub struct GLContextBuilder {
    version: Option<&'static str>,
}

impl GLContextBuilder {
    pub fn new() -> Self {
        Self { version: None }
    }

    pub fn set_version_str(&mut self, version: &'static str) {
        self.version = Some(version);
    }

    pub fn build(self) -> Result<GLContext, HeadlessError> {
        match self.version {
            Some(version) => GLContext::with_version_str(version),
            None => GLContext::new(),
        }
    }
}

pub struct GLContext {
    wnd: Window,
    glfw: Glfw,
}

impl GLContext {
    pub fn new() -> Result<Self, HeadlessError> {
        Self::with_version(GLVersion::default())
    }

    pub fn with_version_str(version: &str) -> Result<Self, HeadlessError> {
        Self::with_version(version.parse()?)
    }

    pub fn with_version(version: GLVersion) -> Result<Self, HeadlessError> {
        let mut glfw = glfw::init(Some(Callback {
            f: |err, desc, _| panic!("glfw error [{}]: {}", err, desc),
            data: (),
        }))
        .map_err(HeadlessError::Uninitialized)?;

        let GLVersion(major, minor) = version;
        glfw.window_hint(WindowHint::ContextVersion(major, minor));
        glfw.window_hint(WindowHint::OpenGlProfile(OpenGlProfileHint::Core));
        glfw.window_hint(WindowHint::OpenGlForwardCompat(true));
        glfw.window_hint(WindowHint::Visible(false));

        let (mut wnd, _events) = glfw
            .create_window(1280, 720, env!("CARGO_PKG_NAME"), WindowMode::Windowed)
            .ok_or_else(|| HeadlessError::NoWindow)?;

        wnd.make_current();

        gl::load_with(|symbol| wnd.get_proc_address(symbol) as *const _);

        Ok(Self { wnd, glfw })
    }

    #[inline]
    pub fn glfw_mut(&mut self) -> &mut Glfw {
        &mut self.glfw
    }

    #[inline]
    pub fn window_mut(&mut self) -> &mut Window {
        &mut self.wnd
    }
}

#[derive(Debug)]
pub struct GLVersion(pub u32, pub u32);

impl Default for GLVersion {
    fn default() -> Self {
        Self(4, 6)
    }
}

impl FromStr for GLVersion {
    type Err = GLVersionError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let (major, minor) = s
            .split_once('.')
            .ok_or_else(|| GLVersionError::InvalidVersion(s.to_owned()))?;
        let major = major
            .parse()
            .map_err(|err| GLVersionError::InvalidMajor(major.to_owned(), err))?;
        let minor = minor
            .parse()
            .map_err(|err| GLVersionError::InvalidMinor(minor.to_owned(), err))?;
        Ok(Self(major, minor))
    }
}