src/glbackend_glfw.cpp
b3b5c285
 /// Guards
 
 
 #ifdef GLBACKEND_GLFW
 
 
 /// Includes
 
 
 #include <glbackend_glfw.hpp>
 
 #include <array>
 #include <functional>
 #include <limits>
 #include <sstream>
 #include <string>
 #include <type_traits>
 #include <utility>
 
 #include <glbase.hpp>
 #include <glbackend.hpp>
 
 #include <GLFW/glfw3.h>
 
 // NOLINTNEXTLINE
 #define STR_EXCEPTION GLBase::Exception
 #include <str.hpp>
 
 
 /// Special member functions
 
 
 GLBackendGLFW::GLBackendGLFW(
     std::string const & title,
     std::array<int, 2>  size,
     std::array<int, 2>  version,
     int                 samples,
     bool                fullscreen,
     bool                transparent
 )
 :
     GLBackend(),
     window_{nullptr}
 {
     //// Backend errors
     glfwSetErrorCallback(debug_glfw_error_callback_);
     auto const glfw_get_error_description = []()
     {
         char const * description{};
         glfwGetError(&description);
         return description;
     };
 
     //// Backend init
     if (!glfwInit())
         STR_THROW(
             "Failed to initialize GLFW" << ":\n" <<
             glfw_get_error_description()
         );
     ++init_count_;
 
     //// Window options
     glfwDefaultWindowHints();
     glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
     GLFWmonitor * monitor = nullptr;
     if (fullscreen)
         monitor = glfwGetPrimaryMonitor();
     if (transparent)
     {
         glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, GLFW_TRUE);
         glfwWindowHint(GLFW_DECORATED, GLFW_FALSE);
         glfwWindowHint(GLFW_FLOATING, GLFW_TRUE);
     }
     if (size[0] == 0 || size[1] == 0)
     {
         auto const * mode = glfwGetVideoMode(glfwGetPrimaryMonitor());
         size[0] = mode->width;
         size[1] = mode->height;
     }
     size_ = size;
 
     //// Context options
     glfwWindowHint(GLFW_DOUBLEBUFFER, GLFW_TRUE);
     glfwWindowHint(GLFW_RED_BITS,      8); // NOLINT
     glfwWindowHint(GLFW_GREEN_BITS,    8); // NOLINT
     glfwWindowHint(GLFW_BLUE_BITS,     8); // NOLINT
     glfwWindowHint(GLFW_ALPHA_BITS,    8); // NOLINT
     glfwWindowHint(GLFW_DEPTH_BITS,   24); // NOLINT
     glfwWindowHint(GLFW_STENCIL_BITS,  8); // NOLINT
     if (samples)
         glfwWindowHint(GLFW_SAMPLES, samples);
     if (version[0])
         glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, version[0]);
     if (version[1])
         glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, version[1]);
     if ((version[0] == 3 && version[1] >= 2) || version[0] >= 4) // NOLINT
     {
         glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
         glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE);
     }
     if (debug() >= 1)
         glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GLFW_TRUE);
     else if (version[0] >= 2) // NOLINT
         glfwWindowHint(GLFW_CONTEXT_NO_ERROR, GLFW_TRUE);
 
     try
     {
         //// Window
         window_ = glfwCreateWindow(
             size[0],
             size[1],
             title.c_str(),
             monitor,
             nullptr
         );
         if (!window_)
             STR_THROW(
                 "Failed to create GLFW window" << ":\n" <<
                 glfw_get_error_description()
             );
 
         //// Context
         glfwMakeContextCurrent(window_);
         if (debug() == 0)
             glfwSwapInterval(1);
         init_();
 
         //// Lock
         if (fullscreen)
             lock(true);
     }
     catch (...)
     {
         destroy_();
         throw;
     }
 
     //// Callbacks
     // NOLINTNEXTLINE
     #define GLBACKEND_GLFW_SELF_(WINDOW) \
         (*static_cast<GLBackendGLFW *>(glfwGetWindowUserPointer(WINDOW)))
     glfwSetWindowUserPointer(window_, this);
     glfwSetKeyCallback(
         window_,
         [](
             GLFWwindow * window,
             int key,
             int scancode,
             int action,
             int mods
         )
         {
             (void)scancode;
             (void)mods;
             if (action != GLFW_PRESS)
                 return;
             auto & self = GLBACKEND_GLFW_SELF_(window);
             if (self.callback_key_)
             {
                 if(key == GLFW_KEY_LEFT)
                     self.callback_key_("Left");
                 else if (key == GLFW_KEY_RIGHT)
                     self.callback_key_("Right");
                 else if (key == GLFW_KEY_UP)
                     self.callback_key_("Up");
                 else if (key == GLFW_KEY_DOWN)
                     self.callback_key_("Down");
                 else if (key == GLFW_KEY_ENTER)
                     self.callback_key_("Enter");
                 else if (key == GLFW_KEY_ESCAPE)
                     self.callback_key_("Escape");
                 else if (key == GLFW_KEY_TAB)
                     self.callback_key_("Tab");
                 else if (key == GLFW_KEY_BACKSPACE)
                     self.callback_key_("Backspace");
                 else if (
                     key == GLFW_KEY_RIGHT_CONTROL ||
                     key == GLFW_KEY_LEFT_CONTROL
                 )
                     self.callback_key_("Control");
                 else if (
                     key == GLFW_KEY_RIGHT_SHIFT ||
                     key == GLFW_KEY_LEFT_SHIFT
                 )
                     self.callback_key_("Shift");
                 else if (
                     key == GLFW_KEY_RIGHT_ALT ||
                     key == GLFW_KEY_LEFT_ALT
                 )
                     self.callback_key_("Alt");
                 else if (
                     0 <= key && key <= std::numeric_limits<char>::max()
                 )
                     self.callback_key_(std::string(1, (char)key));
             }
         }
     );
     glfwSetMouseButtonCallback(
         window_,
         [](
             GLFWwindow * window,
             int button,
             int action,
             int mods
         )
         {
             (void)mods;
             if (action != GLFW_PRESS)
                 return;
             auto & self = GLBACKEND_GLFW_SELF_(window);
             if (self.callback_button_)
                 self.callback_button_(button);
         }
     );
     glfwSetScrollCallback(
         window_,
         [](
             GLFWwindow * window,
             double scroll_x,
             double scroll_y
         )
         {
             auto & self = GLBACKEND_GLFW_SELF_(window);
             self.scroll_ = {
                 (float)scroll_x,
                 (float)scroll_y,
             };
             if (self.callback_scroll_)
                 self.callback_scroll_(self.scroll_);
         }
     );
     glfwSetCursorPosCallback(
         window_,
         [](
             GLFWwindow * window,
             double x,
             double y
         )
         {
             auto & self = GLBACKEND_GLFW_SELF_(window);
             self.move_ = {
                 (float)x - self.position_[0],
                 (float)y - self.position_[1],
             };
             self.position_ = {
                 (float)x,
                 (float)y,
             };
             if (self.callback_position_)
                 self.callback_position_(self.position_);
             if (self.callback_move_)
                 self.callback_move_(self.move_);
         }
     );
     glfwSetFramebufferSizeCallback(
         window_,
         [](
             GLFWwindow * window,
             int width,
             int height
         )
         {
             auto & self = GLBACKEND_GLFW_SELF_(window);
             self.size_ = {
                 width,
                 height,
             };
             if (self.callback_size_)
                 self.callback_size_(self.size_);
         }
     );
 }
 
 
 GLBackendGLFW::GLBackendGLFW(GLBackendGLFW && other) noexcept
 :
     GLBackend(std::move(other)),
     window_{other.window_}
 {
     other.window_ = nullptr;
 }
 
 
 GLBackendGLFW::~GLBackendGLFW()
 {
     destroy_();
 }
 
 
 void GLBackendGLFW::destroy_()
 {
     if (window_)
     {
         lock(false);
         glfwDestroyWindow(window_);
     }
     if (--init_count_ == 0)
         glfwTerminate();
 }
 
 
 GLBASE_GLOBAL(GLBackendGLFW::init_count_, {0})
 
 
 /// Debug
 
 
 std::string GLBackendGLFW::debug_info() const
 {
     auto ostream = std::ostringstream{};
 
     auto version_major = int{};
     auto version_minor = int{};
     auto version_rev   = int{};
     glfwGetVersion(
         &version_major,
         &version_minor,
         &version_rev
     );
     debug_info_(ostream, "GLFW", {
         {
             "VERSION_COMPILED",
                 glfwGetVersionString()
         },
         {
             "VERSION_LINKED",
                 std::to_string(version_major) + "." +
                 std::to_string(version_minor) + "." +
                 std::to_string(version_rev),
         },
     });
 
     ostream << GLBackend::debug_info();
 
     return ostream.str();
 }
 
 
 void GLBackendGLFW::debug_glfw_error_callback_(
     int          error,
     char const * message
 )
 {
     auto ostream = std::ostringstream{};
     ostream << std::hex << std::showbase;
 
     // https://www.glfw.org/docs/3.3/group__errors.html
     ostream << "GLFW error ";
     // NOLINTNEXTLINE
     #define GLBACKEND_GLFW_CALLBACK_CASE_(VALUE) \
         case GLFW_ ## VALUE: \
             ostream << #VALUE; \
             break;
     switch(error)
     {
         GLBACKEND_GLFW_CALLBACK_CASE_(NOT_INITIALIZED)
         GLBACKEND_GLFW_CALLBACK_CASE_(NO_CURRENT_CONTEXT)
         GLBACKEND_GLFW_CALLBACK_CASE_(INVALID_ENUM)
         GLBACKEND_GLFW_CALLBACK_CASE_(INVALID_VALUE)
         GLBACKEND_GLFW_CALLBACK_CASE_(OUT_OF_MEMORY)
         GLBACKEND_GLFW_CALLBACK_CASE_(API_UNAVAILABLE)
         GLBACKEND_GLFW_CALLBACK_CASE_(VERSION_UNAVAILABLE)
         GLBACKEND_GLFW_CALLBACK_CASE_(PLATFORM_ERROR)
         GLBACKEND_GLFW_CALLBACK_CASE_(FORMAT_UNAVAILABLE)
         GLBACKEND_GLFW_CALLBACK_CASE_(NO_WINDOW_CONTEXT)
         default:
             ostream << error;
     }
     ostream << ":\n";
 
     ostream << message;
 
     debug_callback()(ostream.str());
 }
 
 
 /// Guards
 
 
 #endif