#pragma once // Easy window creation in C++. // // This header-only library uses SDL; therefore, on a Linux system, you need to // add something to the effect of the following to your build system: // CXXFLAGS += $(shell pkg-config --cflags sdl2) // LDFLAGS += $(shell pkg-config --libs sdl2) #include #include #include #include #include #include class Window { public: class Opts { public: inline Opts() = default; inline Opts& fullscreen(bool f) { _fullscreen = f; return *this; } inline Opts& resizable(bool r) { _resizable = r; return *this; } inline bool fullscreen() const { return _fullscreen; } inline bool resizable() const { return _resizable; } private: bool _fullscreen = false; bool _resizable = false; }; /// The width and height passed to this function are _suggestions_. /// The window manager may decide to use some other size instead, and this /// size may also change during execution (especially if resizable, but /// perhaps also otherwise). inline Window(const std::string &title, int wid_sugg, int hei_sugg); inline Window(const std::string &title, int wid_sugg, int hei_sugg, Opts opts); inline ~Window(); class Buffer { public: using Clr = std::array; inline Buffer(int width, int height, std::vector &arr); inline std::vector& raw() { return arr; } inline int width() { return wid; } inline int height() { return hei; } inline void clear(Clr clr); inline void plot(int x, int y, Clr clr, float alpha = 1.0); inline void plotf(float x, float y, Clr clr, float alpha = 1.0); private: int wid, hei; std::vector &arr; }; enum Action { ACT_REDRAW, ACT_STOP, ACT_OK, }; /// The draw buffer will be of size 3 * width * height; row-major order, /// RGB pixels. The draw handler will be invoked if the event handler /// returns ACT_REDRAW, but also possibly in other circumstances (e.g. /// window resize). using DrawHandler = std::function; using EventHandler = std::function; inline void event_loop(EventHandler eventfunc, DrawHandler drawfunc); private: SDL_Window *win = nullptr; std::vector drawbuf; SDL_Surface *surface = nullptr; int surf_wid = -1, surf_hei = -1; }; inline Window::Window(const std::string &title, int wid_sugg, int hei_sugg) : Window(title, wid_sugg, hei_sugg, Opts{}) {} inline Window::Window(const std::string &title, int wid_sugg, int hei_sugg, Opts opts) { if (SDL_Init(SDL_INIT_VIDEO) < 0) { throw std::runtime_error(std::string{"SDL init error: "} + SDL_GetError()); } Uint32 flags = 0; if (opts.fullscreen()) flags |= SDL_WINDOW_FULLSCREEN_DESKTOP; if (opts.resizable()) flags |= SDL_WINDOW_RESIZABLE; win = SDL_CreateWindow( title.data(), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, wid_sugg, hei_sugg, flags ); if (!win) { throw std::runtime_error(std::string{"SDL window creation error: "} + SDL_GetError()); } SDL_GetWindowSize(win, &surf_wid, &surf_hei); drawbuf.resize(3 * surf_wid * surf_hei); surface = SDL_CreateRGBSurfaceFrom( drawbuf.data(), surf_wid, surf_hei, 24, 3 * surf_wid, 255, 255 << 8, 255 << 16, 0 ); SDL_BlitSurface(surface, nullptr, SDL_GetWindowSurface(win), nullptr); SDL_UpdateWindowSurface(win); } inline Window::~Window() { if (surface) SDL_FreeSurface(surface); SDL_DestroyWindow(win); SDL_Quit(); } inline void Window::event_loop(EventHandler eventfunc, DrawHandler drawfunc) { SDL_Event e; while (true) { if (SDL_WaitEvent(&e) == 0) { throw std::runtime_error(std::string{"SDL event wait error: "} + SDL_GetError()); } if (e.type == SDL_QUIT) break; bool need_redraw = false; bool need_refresh = false; if (e.type == SDL_WINDOWEVENT) { if (e.window.event == SDL_WINDOWEVENT_FOCUS_GAINED) { need_refresh = true; } } Action act = eventfunc(e); switch (act) { case ACT_STOP: return; case ACT_REDRAW: need_redraw = true; break; case ACT_OK: break; default: throw std::logic_error("Invalid Action from EventHandler"); } int width, height; SDL_GetWindowSize(win, &width, &height); if (width != surf_wid || height != surf_hei) { SDL_FreeSurface(surface); surf_wid = width; surf_hei = height; drawbuf.resize(3 * surf_wid * surf_hei); surface = SDL_CreateRGBSurfaceFrom( drawbuf.data(), surf_wid, surf_hei, 24, 3 * surf_wid, 255, 255 << 8, 255 << 16, 0 ); need_redraw = true; } if (need_redraw) { Buffer buffer{width, height, drawbuf}; drawfunc(buffer); } if (need_redraw || need_refresh) { SDL_Surface *winsurf = SDL_GetWindowSurface(win); if (SDL_MUSTLOCK(winsurf)) SDL_LockSurface(winsurf); SDL_BlitSurface(surface, nullptr, winsurf, nullptr); if (SDL_MUSTLOCK(winsurf)) SDL_UnlockSurface(winsurf); SDL_UpdateWindowSurface(win); } } } inline Window::Buffer::Buffer(int width, int height, std::vector &arr) : wid{width}, hei{height}, arr{arr} {} inline void Window::Buffer::clear(Clr clr) { if (clr[0] == clr[1] && clr[1] == clr[2]) { memset(arr.data(), clr[0], 3 * wid * hei); } else { for (int i = 0; i < wid * hei; i++) { for (int j = 0; j < 3; j++) { arr[3 * i + j] = clr[j]; } } } } inline void Window::Buffer::plot(int x, int y, Clr clr, float alpha) { for (int i = 0; i < 3; i++) { uint8_t &orig = arr[3 * (wid * y + x) + i]; orig = alpha * clr[i] + (1 - alpha) * orig; } } inline void Window::Buffer::plotf(float x, float y, Clr clr, float alpha) { const int x0 = x, y0 = y; const float xal = x - x0, yal = y - y0; plot(x0 , y0 , clr, (1 - xal) * (1 - yal) * alpha); plot(x0 + 1, y0 , clr, xal * (1 - yal) * alpha); plot(x0 , y0 + 1, clr, (1 - xal) * yal * alpha); plot(x0 + 1, y0 + 1, clr, xal * yal * alpha); }