psemek/libs/app/source/app.cpp

255 lines
5.4 KiB
C++

#include <psemek/app/app.hpp>
#include <psemek/gfx/gl.hpp>
#include <psemek/log/log.hpp>
#include <psemek/sdl2/init.hpp>
#include <SDL2/SDL.h>
namespace psemek::app
{
using clock = std::chrono::high_resolution_clock;
struct app::impl
{
app * parent;
std::shared_ptr<void> sdl_init;
SDL_Window * window = nullptr;
SDL_GLContext gl_context = nullptr;
int width, height;
scene * current_scene = nullptr;
bool running = false;
bool had_initial_resize = false;
clock::time_point start_time;
impl(app * parent)
: parent(parent)
, sdl_init(sdl2::init(SDL_INIT_VIDEO))
{}
~impl()
{
if (gl_context) SDL_GL_DeleteContext(gl_context);
if (window) SDL_DestroyWindow(window);
}
scene * get_scene()
{
if (current_scene)
return current_scene;
return parent;
}
};
app::app(std::string const & name)
: app(name, 0)
{}
app::app(std::string const & name, int multisampling)
: pimpl_{std::make_unique<struct impl>(this)}
{
impl().start_time = clock::now();
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, gl::sys::GetLeastMajorVersion());
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, gl::sys::GetLeastMinorVersion());
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
if (multisampling == 0)
{
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0);
}
else
{
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, multisampling);
}
impl().window = SDL_CreateWindow(name.data(), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 800, 600, SDL_WINDOW_RESIZABLE | SDL_WINDOW_OPENGL | SDL_WINDOW_MAXIMIZED);
if (!impl().window)
sdl2::fail("Failed to create window: ");
impl().gl_context = SDL_GL_CreateContext(impl().window);
if (!impl().gl_context)
sdl2::fail("Failed to create OpenGL context: ");
SDL_GL_MakeCurrent(impl().window, impl().gl_context);
if (!gl::sys::LoadFunctions())
throw std::runtime_error("Failed to load OpenGL functions");
auto vendor = gl::GetString(gl::VENDOR);
auto renderer = gl::GetString(gl::RENDERER);
log::info() << "Initialized OpenGL " << gl::sys::GetMajorVersion() << '.' << gl::sys::GetMinorVersion() << ", " << vendor << ", " << renderer;
SDL_GetWindowSize(impl().window, &impl().width, &impl().height);
}
app::~app()
{}
bool app::running() const
{
return impl().running;
}
void app::stop()
{
impl().running = false;
}
void app::on_resize(int width, int height)
{
gl::Viewport(0, 0, width, height);
impl().width = width;
impl().height = height;
}
void app::on_quit()
{
impl().running = false;
}
void app::poll_events()
{
auto handler = impl().get_scene();
for (SDL_Event e; SDL_PollEvent(&e);) switch (e.type)
{
case SDL_QUIT:
on_quit();
break;
case SDL_WINDOWEVENT: switch (e.window.event)
{
case SDL_WINDOWEVENT_RESIZED:
impl().had_initial_resize = true;
handler->on_resize(e.window.data1, e.window.data2);
break;
case SDL_WINDOWEVENT_FOCUS_GAINED:
handler->on_focus_gained();
break;
case SDL_WINDOWEVENT_FOCUS_LOST:
handler->on_focus_lost();
break;
}
break;
case SDL_MOUSEMOTION:
handler->on_mouse_move(e.motion.x, e.motion.y, e.motion.xrel, e.motion.yrel);
break;
case SDL_MOUSEWHEEL:
handler->on_mouse_wheel(e.wheel.y);
break;
case SDL_MOUSEBUTTONDOWN:
switch (e.button.button)
{
case SDL_BUTTON_LEFT:
handler->on_left_button_down();
break;
case SDL_BUTTON_MIDDLE:
handler->on_middle_button_down();
break;
case SDL_BUTTON_RIGHT:
handler->on_right_button_down();
break;
}
break;
case SDL_MOUSEBUTTONUP:
switch (e.button.button)
{
case SDL_BUTTON_LEFT:
handler->on_left_button_up();
break;
case SDL_BUTTON_MIDDLE:
handler->on_middle_button_up();
break;
case SDL_BUTTON_RIGHT:
handler->on_right_button_up();
break;
}
break;
case SDL_KEYDOWN:
handler->on_key_down(e.key.keysym.sym);
break;
case SDL_KEYUP:
handler->on_key_up(e.key.keysym.sym);
break;
}
}
void app::render()
{
gl::ClearColor(0.7f, 0.7f, 1.f, 1.f);
gl::Clear(gl::COLOR_BUFFER_BIT);
}
void app::run()
{
impl().running = true;
if (impl().current_scene == nullptr)
impl().get_scene()->on_scene_enter();
while (running())
{
poll_events();
auto handler = impl().get_scene();
if (!impl().had_initial_resize)
{
int w, h;
SDL_GetWindowSize(impl().window, &w, &h);
impl().had_initial_resize = true;
handler->on_resize(w, h);
}
if (!running()) break;
handler->update();
handler->render();
SDL_GL_SwapWindow(impl().window);
}
}
scene * app::set_scene(scene * s)
{
impl().get_scene()->on_scene_exit();
impl().had_initial_resize = false;
std::swap(s, impl().current_scene);
impl().get_scene()->on_scene_enter();
return s;
}
void app::show_cursor(bool show)
{
SDL_ShowCursor(show ? SDL_TRUE : SDL_FALSE);
SDL_SetRelativeMouseMode(show ? SDL_FALSE : SDL_TRUE);
}
float app::time() const
{
return std::chrono::duration_cast<std::chrono::duration<float>>(clock::now() - impl().start_time).count();
}
int app::width() const
{
return impl().width;
}
int app::height() const
{
return impl().height;
}
}