From c58eb0142e706e9492275c54f63aea00c4605c6f Mon Sep 17 00:00:00 2001 From: Noel Berry Date: Tue, 22 Nov 2022 22:51:28 -0800 Subject: [PATCH] remove App::content_scale;App::get_backbuffer_size --- include/blah_app.h | 7 - src/blah_app.cpp | 15 - src/internal/blah_platform.h | 219 ++- src/internal/blah_platform_sdl2.cpp | 1283 ++++++++-------- src/internal/blah_platform_win32.cpp | 2083 +++++++++++++------------- 5 files changed, 1768 insertions(+), 1839 deletions(-) diff --git a/include/blah_app.h b/include/blah_app.h index efc39c9..2274bf1 100644 --- a/include/blah_app.h +++ b/include/blah_app.h @@ -119,13 +119,6 @@ namespace Blah // Sets the Window Size in Screen Coordinates void set_size(Point point); - // Gets the size of the BackBuffer, in pixels - Point get_backbuffer_size(); - - // Gets the content scale based on the platform. - // macOS is usually 2.0, other platforms vary. - float content_scale(); - // If the window is currently focused or has mouse input bool focused(); diff --git a/src/blah_app.cpp b/src/blah_app.cpp index 305914e..a2ca779 100644 --- a/src/blah_app.cpp +++ b/src/blah_app.cpp @@ -310,7 +310,6 @@ void Internal::app_shutdown() Renderer* Internal::app_renderer() { - BLAH_ASSERT_RUNNING(); return app_renderer_api; } @@ -379,20 +378,6 @@ void App::set_size(Point point) Platform::set_size(point.x, point.y); } -Point App::get_backbuffer_size() -{ - BLAH_ASSERT_RUNNING(); - if (app_renderer_api) - return Point(app_backbuffer->width(), app_backbuffer->height()); - return Point(0, 0); -} - -float App::content_scale() -{ - BLAH_ASSERT_RUNNING(); - return Platform::get_content_scale(); -} - bool App::focused() { BLAH_ASSERT_RUNNING(); diff --git a/src/internal/blah_platform.h b/src/internal/blah_platform.h index b636c37..ae5c28a 100644 --- a/src/internal/blah_platform.h +++ b/src/internal/blah_platform.h @@ -1,112 +1,109 @@ -#pragma once -#include -#include -#include -#include - -namespace Blah -{ - struct Config; - - namespace Platform - { - // Initialize the Graphics - bool init(const Config& config); - - // Called after the on_startup callback, but before the update loop begins - void ready(); - - // Called during shutdown - void shutdown(); - - // The time, in ticks (microseconds) since the Application was started - u64 ticks(); - - // Called every frame - void update(InputState& state); - - // Sleeps the current thread - void sleep(int milliseconds); - - // Called to present the window contents - void present(); - - // Called when the App sets flags - void set_app_flags(u32 flags); - - // Gets the Application Window Title in UTF-8 - const char* get_title(); - - // Sets the Application Window Title in UTF-8 - void set_title(const char* title); - - // Gets the Application Window Position, in Screen Coordinates - void get_position(int* x, int* y); - - // Sets the Application Window Position, in Screen Coordinates - void set_position(int x, int y); - - // Gets whether the Window has focus - bool get_focused(); - - // Gets the Application Window Size, in Screen Coordinates - void get_size(int* width, int* height); - - // Sets the Application Window Size, in Screen Coordinates - void set_size(int width, int height); - - // Gets the Application Window Drawing Size, in Pixels. This may differ from the Window Size on hi-dpi displays. - void get_draw_size(int* width, int* height); - - // Gets the Desktop Content Scale. Gui should be scaled by this value - float get_content_scale(); - - // Returns the absolute path to the directory that the application was started from - const char* app_path(); - - // Returns the absolute path to the user directory where save data and settings should be stored - const char* user_path(); - - // Opens a file and sets the handle, or returns an empty handle if it fails - FileRef file_open(const char* path, FileMode mode); - - // Returns true if a file with the given path exists - bool file_exists(const char* path); - - // Returns true if a file with the given path was deleted - bool file_delete(const char* path); - - // Returns true if a directory with the given path was successfully created - bool dir_create(const char* path); - - // Returns true if a directory with the given path exists - bool dir_exists(const char* path); - - // Returns true if a directory with the given path was deleted - bool dir_delete(const char* path); - - // enumerates a directory and appends each file to the given list - void dir_enumerate(Vector& list, const char* path, bool recursive); - - // opens a directory in the OS file explorer / finder - void dir_explore(const char* path); - - // sets the contents of the clipboard - void set_clipboard(const char* text); - - // gets the contents of the clipboard into the given string - const char* get_clipboard(); - - // Tries to open a URL in a web browser - void open_url(const char* url); - - // OpenGL Methods - void* gl_get_func(const char* name); - void* gl_context_create(); - void gl_context_make_current(void* context); - void gl_context_destroy(void* context); - - // D3D11 Methods - void* d3d11_get_hwnd(); - }; +#pragma once +#include +#include +#include +#include + +namespace Blah +{ + struct Config; + + namespace Platform + { + // Initialize the Graphics + bool init(const Config& config); + + // Called after the on_startup callback, but before the update loop begins + void ready(); + + // Called during shutdown + void shutdown(); + + // The time, in ticks (microseconds) since the Application was started + u64 ticks(); + + // Called every frame + void update(InputState& state); + + // Sleeps the current thread + void sleep(int milliseconds); + + // Called to present the window contents + void present(); + + // Called when the App sets flags + void set_app_flags(u32 flags); + + // Gets the Application Window Title in UTF-8 + const char* get_title(); + + // Sets the Application Window Title in UTF-8 + void set_title(const char* title); + + // Gets the Application Window Position, in Screen Coordinates + void get_position(int* x, int* y); + + // Sets the Application Window Position, in Screen Coordinates + void set_position(int x, int y); + + // Gets whether the Window has focus + bool get_focused(); + + // Gets the Application Window Size, in Screen Coordinates + void get_size(int* width, int* height); + + // Sets the Application Window Size, in Screen Coordinates + void set_size(int width, int height); + + // Gets the Application Window Drawing Size, in Pixels. This may differ from the Window Size on hi-dpi displays. + void get_draw_size(int* width, int* height); + + // Returns the absolute path to the directory that the application was started from + const char* app_path(); + + // Returns the absolute path to the user directory where save data and settings should be stored + const char* user_path(); + + // Opens a file and sets the handle, or returns an empty handle if it fails + FileRef file_open(const char* path, FileMode mode); + + // Returns true if a file with the given path exists + bool file_exists(const char* path); + + // Returns true if a file with the given path was deleted + bool file_delete(const char* path); + + // Returns true if a directory with the given path was successfully created + bool dir_create(const char* path); + + // Returns true if a directory with the given path exists + bool dir_exists(const char* path); + + // Returns true if a directory with the given path was deleted + bool dir_delete(const char* path); + + // enumerates a directory and appends each file to the given list + void dir_enumerate(Vector& list, const char* path, bool recursive); + + // opens a directory in the OS file explorer / finder + void dir_explore(const char* path); + + // sets the contents of the clipboard + void set_clipboard(const char* text); + + // gets the contents of the clipboard into the given string + const char* get_clipboard(); + + // Tries to open a URL in a web browser + void open_url(const char* url); + + // OpenGL Methods + void* gl_get_func(const char* name); + void* gl_context_create(); + void gl_context_make_current(void* context); + void gl_context_destroy(void* context); + + // D3D11 Methods + void* d3d11_get_hwnd(); + }; } \ No newline at end of file diff --git a/src/internal/blah_platform_sdl2.cpp b/src/internal/blah_platform_sdl2.cpp index 1160e91..bf6e59e 100644 --- a/src/internal/blah_platform_sdl2.cpp +++ b/src/internal/blah_platform_sdl2.cpp @@ -1,661 +1,622 @@ -#ifdef BLAH_PLATFORM_SDL2 - -#include "blah_platform.h" -#include "blah_renderer.h" -#include "blah_internal.h" -#include -#include -#include -#include -#include - -#include - -// for File Reading / Writing -#include - -// Windows requires a few extra includes -#if _WIN32 -#define WIN32_LEAN_AND_MEAN -#include // for the following includes -#include // for ShellExecute for dir_explore -#include // for SDL_SysWMinfo for D3D11 -#endif - -// Macro defined by X11 conflicts with MouseButton enum -#undef None - -namespace Blah -{ - void blah_sdl_log(void* userdata, int category, SDL_LogPriority priority, const char* message) - { - if (priority <= SDL_LOG_PRIORITY_INFO) - Log::info(message); - else if (priority <= SDL_LOG_PRIORITY_WARN) - Log::warn(message); - else - Log::error(message); - } - - int blah_sdl_find_joystick_index(SDL_Joystick** joysticks, SDL_JoystickID instance_id) - { - for (int i = 0; i < Input::max_controllers; i++) - if (joysticks[i] != nullptr && SDL_JoystickInstanceID(joysticks[i]) == instance_id) - return i; - return -1; - } - - int blah_sdl_find_gamepad_index(SDL_GameController** gamepads, SDL_JoystickID instance_id) - { - for (int i = 0; i < Input::max_controllers; i++) - { - if (gamepads[i] != nullptr) - { - auto joystick = SDL_GameControllerGetJoystick(gamepads[i]); - if (SDL_JoystickInstanceID(joystick) == instance_id) - return i; - } - } - return -1; - } - - struct SDL2_File : public File - { - SDL_RWops* handle; - SDL2_File(SDL_RWops* handle) : handle(handle) { } - ~SDL2_File() { if (handle) SDL_RWclose(handle); } - size_t length() override { return SDL_RWsize(handle); } - size_t position() override { return SDL_RWtell(handle); } - size_t seek(size_t position) override { return SDL_RWseek(handle, position, RW_SEEK_SET); } - size_t read(void* buffer, size_t length) override { return SDL_RWread(handle, buffer, sizeof(char), length); } - size_t write(const void* buffer, size_t length) override { return SDL_RWwrite(handle, buffer, sizeof(char), length); } - }; - - SDL_Window* sdl2_window = nullptr; - SDL_Joystick* sdl2_joysticks[Input::max_controllers]; - SDL_GameController* sdl2_gamepads[Input::max_controllers]; - char* sdl2_base_path_value = nullptr; - char* sdl2_user_path_value = nullptr; - char* sdl2_clipboard_text = nullptr; - bool sdl2_displayed = false; -} - -using namespace Blah; - -bool Platform::init(const Config& config) -{ - // TODO: - // control this via some kind of config flag -#ifndef __EMSCRIPTEN__ - // Note: Emscripten gets a Stack Overflow if this is assigned to Verbose, even when - // increasing the node stack size to +8mb. Some kind of SDL2 problem? - SDL_LogSetAllPriority(SDL_LOG_PRIORITY_VERBOSE); -#endif - SDL_LogSetOutputFunction(blah_sdl_log, nullptr); - - // Get SDL version - SDL_version version; - SDL_GetVersion(&version); - Log::info("SDL v%i.%i.%i", version.major, version.minor, version.patch); - - // Make us DPI aware on Windows - SDL_SetHint(SDL_HINT_WINDOWS_DPI_AWARENESS, "permonitorv2"); - SDL_SetHint(SDL_HINT_WINDOWS_DPI_SCALING, "1"); - - // initialize SDL - if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_EVENTS | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) != 0) - { - Log::error("Failed to initialize SDL2"); - return false; - } - - int flags = SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_HIDDEN; - - // enable OpenGL - if (config.renderer_type == RendererType::OpenGL) - { - flags |= SDL_WINDOW_OPENGL; - -#ifdef __EMSCRIPTEN__ - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); -#else - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); - SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); - - // TODO: - // This should be controlled via the gfx api somehow? - SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); - SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); - SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1); - SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 4); -#endif - } - - // create the window - sdl2_window = SDL_CreateWindow(config.name, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, config.width, config.height, flags); - if (sdl2_window == nullptr) - { - Log::error("Failed to create a Window"); - return false; - } - - for (int i = 0; i < Input::max_controllers; i++) - { - sdl2_joysticks[i] = nullptr; - sdl2_gamepads[i] = nullptr; - } - - return true; -} - -void Platform::ready() -{ - -} - -void Platform::shutdown() -{ - if (sdl2_window != nullptr) - SDL_DestroyWindow(sdl2_window); - sdl2_window = nullptr; - sdl2_displayed = false; - - if (sdl2_base_path_value != nullptr) - SDL_free(sdl2_base_path_value); - - if (sdl2_user_path_value != nullptr) - SDL_free(sdl2_user_path_value); - - if (sdl2_clipboard_text != nullptr) - SDL_free(sdl2_clipboard_text); - - SDL_Quit(); -} - -u64 Platform::ticks() -{ - auto counter = SDL_GetPerformanceCounter(); - auto per_second = (double)SDL_GetPerformanceFrequency(); - return (u64)(counter * (Time::ticks_per_second / per_second)); -} - -void Platform::update(InputState& state) -{ - // update the mouse every frame - { - int win_x, win_y, x, y; - - SDL_GetWindowPosition(sdl2_window, &win_x, &win_y); - SDL_GetGlobalMouseState(&x, &y); - - state.mouse.on_move( - Vec2f((float)(x - win_x), (float)(y - win_y)), - Vec2f((float)x, (float)y)); - } - - // poll normal events - SDL_Event event; - while (SDL_PollEvent(&event)) - { - if (event.type == SDL_QUIT) - { - auto& config = App::config(); - if (config.on_exit_request != nullptr) - config.on_exit_request(); - } - // Mouse - else if (event.type == SDL_MOUSEBUTTONDOWN) - { - MouseButton btn = MouseButton::None; - if (event.button.button == SDL_BUTTON_LEFT) - btn = MouseButton::Left; - else if (event.button.button == SDL_BUTTON_RIGHT) - btn = MouseButton::Right; - else if (event.button.button == SDL_BUTTON_MIDDLE) - btn = MouseButton::Middle; - - state.mouse.on_press(btn); - } - else if (event.type == SDL_MOUSEBUTTONUP) - { - MouseButton btn = MouseButton::None; - if (event.button.button == SDL_BUTTON_LEFT) - btn = MouseButton::Left; - else if (event.button.button == SDL_BUTTON_RIGHT) - btn = MouseButton::Right; - else if (event.button.button == SDL_BUTTON_MIDDLE) - btn = MouseButton::Middle; - - state.mouse.on_release(btn); - } - else if (event.type == SDL_MOUSEWHEEL) - { - state.mouse.wheel = Point(event.wheel.x, event.wheel.y); - } - // Keyboard - else if (event.type == SDL_KEYDOWN) - { - if (event.key.repeat == 0) - state.keyboard.on_press((Key)event.key.keysym.scancode); - } - else if (event.type == SDL_KEYUP) - { - if (event.key.repeat == 0) - state.keyboard.on_release((Key)event.key.keysym.scancode); - } - else if (event.type == SDL_TEXTINPUT) - { - state.keyboard.text += event.text.text; - } - // Joystick Controller - else if (event.type == SDL_JOYDEVICEADDED) - { - auto index = event.jdevice.which; - - if (SDL_IsGameController(index) == SDL_FALSE && index >= 0 && index < Input::max_controllers) - { - auto ptr = sdl2_joysticks[index] = SDL_JoystickOpen(index); - auto name = SDL_JoystickName(ptr); - auto button_count = SDL_JoystickNumButtons(ptr); - auto axis_count = SDL_JoystickNumAxes(ptr); - auto vendor = SDL_JoystickGetVendor(ptr); - auto product = SDL_JoystickGetProduct(ptr); - auto version = SDL_JoystickGetProductVersion(ptr); - - state.controllers[index].on_connect(name, 0, button_count, axis_count, vendor, product, version); - } - } - else if (event.type == SDL_JOYDEVICEREMOVED) - { - auto index = blah_sdl_find_joystick_index(sdl2_joysticks, event.jdevice.which); - if (index >= 0) - { - if (SDL_IsGameController(index) == SDL_FALSE) - { - state.controllers[index].on_disconnect(); - SDL_JoystickClose(sdl2_joysticks[index]); - } - } - } - else if (event.type == SDL_JOYBUTTONDOWN) - { - auto index = blah_sdl_find_joystick_index(sdl2_joysticks, event.jdevice.which); - if (index >= 0) - { - if (SDL_IsGameController(index) == SDL_FALSE) - state.controllers[index].on_press((Button)event.jbutton.button); - } - } - else if (event.type == SDL_JOYBUTTONUP) - { - auto index = blah_sdl_find_joystick_index(sdl2_joysticks, event.jdevice.which); - if (index >= 0) - { - if (SDL_IsGameController(index) == SDL_FALSE) - state.controllers[index].on_release((Button)event.jbutton.button); - } - } - else if (event.type == SDL_JOYAXISMOTION) - { - auto index = blah_sdl_find_joystick_index(sdl2_joysticks, event.jdevice.which); - if (index >= 0) - { - if (SDL_IsGameController(index) == SDL_FALSE) - { - float value; - if (event.jaxis.value >= 0) - value = event.jaxis.value / 32767.0f; - else - value = event.jaxis.value / 32768.0f; - state.controllers[index].on_axis((Axis)event.jaxis.axis, value); - } - } - } - // Gamepad Controller - else if (event.type == SDL_CONTROLLERDEVICEADDED) - { - auto index = event.cdevice.which; - if (index >= 0 && index < Input::max_controllers) - { - auto ptr = sdl2_gamepads[index] = SDL_GameControllerOpen(index); - auto name = SDL_GameControllerName(ptr); - auto vendor = SDL_GameControllerGetVendor(ptr); - auto product = SDL_GameControllerGetProduct(ptr); - auto version = SDL_GameControllerGetProductVersion(ptr); - - state.controllers[index].on_connect(name, 1, 15, 6, vendor, product, version); - } - } - else if (event.type == SDL_CONTROLLERDEVICEREMOVED) - { - auto index = blah_sdl_find_gamepad_index(sdl2_gamepads, event.cdevice.which); - if (index >= 0) - { - state.controllers[index].on_disconnect(); - SDL_GameControllerClose(sdl2_gamepads[index]); - } - } - else if (event.type == SDL_CONTROLLERBUTTONDOWN) - { - auto index = blah_sdl_find_gamepad_index(sdl2_gamepads, event.cdevice.which); - if (index >= 0) - { - Button button = Button::None; - if (event.cbutton.button >= 0 && event.cbutton.button < 15) - button = (Button)event.cbutton.button; // NOTE: These map directly to Engine Buttons enum! - - state.controllers[index].on_press(button); - } - } - else if (event.type == SDL_CONTROLLERBUTTONUP) - { - auto index = blah_sdl_find_gamepad_index(sdl2_gamepads, event.cdevice.which); - if (index >= 0) - { - Button button = Button::None; - if (event.cbutton.button >= 0 && event.cbutton.button < 15) - button = (Button)event.cbutton.button; // NOTE: These map directly to Engine Buttons enum! - - state.controllers[index].on_release(button); - } - } - else if (event.type == SDL_CONTROLLERAXISMOTION) - { - auto index = blah_sdl_find_gamepad_index(sdl2_gamepads, event.cdevice.which); - if (index >= 0) - { - Axis axis = Axis::None; - if (event.caxis.axis >= 0 && event.caxis.axis < 6) - axis = (Axis)event.caxis.axis; // NOTE: These map directly to Engine Axis enum! - - float value; - if (event.caxis.value >= 0) - value = event.caxis.value / 32767.0f; - else - value = event.caxis.value / 32768.0f; - - state.controllers[index].on_axis(axis, value); - } - } - } -} - -void Platform::sleep(int milliseconds) -{ - if (milliseconds >= 0) - SDL_Delay((u32)milliseconds); -} - -void Platform::present() -{ - if (App::renderer().type == RendererType::OpenGL) - SDL_GL_SwapWindow(sdl2_window); - - // display the window - // this avoids a short black screen on macOS - if (!sdl2_displayed) - { - SDL_ShowWindow(sdl2_window); - sdl2_displayed = true; - } -} - -void Platform::set_app_flags(u32 flags) -{ - // Toggle Fullscreen - SDL_SetWindowFullscreen(sdl2_window, ((flags & Flags::Fullscreen) != 0) ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0); - - // Toggle Resizable - SDL_SetWindowResizable(sdl2_window, ((flags & Flags::Resizable) != 0) ? SDL_TRUE : SDL_FALSE); - - // Toggle V-Sync for OpenGL -#ifndef __EMSCRIPTEN__ - if (App::renderer().type == RendererType::OpenGL) - SDL_GL_SetSwapInterval(((flags & Flags::VSync) != 0) ? 1 : 0); -#endif -} - -const char* Platform::get_title() -{ - return SDL_GetWindowTitle(sdl2_window); -} - -void Platform::set_title(const char* title) -{ - SDL_SetWindowTitle(sdl2_window, title); -} - -void Platform::get_position(int* x, int* y) -{ - SDL_GetWindowPosition(sdl2_window, x, y); -} - -void Platform::set_position(int x, int y) -{ - SDL_SetWindowPosition(sdl2_window, x, y); -} - -bool Platform::get_focused() -{ - auto flags = SDL_GetWindowFlags(sdl2_window); - return (flags & SDL_WINDOW_INPUT_FOCUS) != 0 && (flags & SDL_WINDOW_MINIMIZED) == 0; -} - -void Platform::get_size(int* width, int* height) -{ - SDL_GetWindowSize(sdl2_window, width, height); -} - -void Platform::set_size(int width, int height) -{ - SDL_SetWindowSize(sdl2_window, width, height); -} - -void Platform::get_draw_size(int* width, int* height) -{ - switch (App::renderer().type) - { - case RendererType::OpenGL: - SDL_GL_GetDrawableSize(sdl2_window, width, height); - break; - case RendererType::None: - case RendererType::D3D11: - SDL_GetWindowSize(sdl2_window, width, height); - break; - } -} - -float Platform::get_content_scale() -{ - // TODO: - // This is incorrect! but for some reason the scale - // is HUGE if I use the Display DPI on macOS :/ -#if __APPLE__ - return 2.0f; -#endif - - // TODO: - // is there a way to get this value properly? My Windows & Linux PC's both seem to thing 96 is correct - const float hidpi_res = 96; - - int index = SDL_GetWindowDisplayIndex(sdl2_window); - if (index < 0) - { - Log::error(SDL_GetError()); - return 1.0f; - } - - float ddpi, x, y; - if (SDL_GetDisplayDPI(index, &ddpi, &x, &y) != 0) - { - Log::error(SDL_GetError()); - return 1.0f; - } - - return (ddpi / hidpi_res); -} - -const char* Platform::app_path() -{ - if (sdl2_base_path_value == nullptr) - sdl2_base_path_value = SDL_GetBasePath(); - return sdl2_base_path_value; -} - -const char* Platform::user_path() -{ - if (sdl2_user_path_value == nullptr) - { - auto& config = App::config(); - sdl2_user_path_value = SDL_GetPrefPath(nullptr, config.name); - } - - return sdl2_user_path_value; -} - -FileRef Platform::file_open(const char* path, FileMode mode) -{ - const char* sdl_mode = ""; - - switch (mode) - { - case FileMode::OpenRead: - sdl_mode = "rb"; - break; - case FileMode::Open: - sdl_mode = "r+b"; - break; - case FileMode::CreateWrite: - sdl_mode = "wb"; - break; - case FileMode::Create: - sdl_mode = "w+b"; - break; - } - - auto ptr = SDL_RWFromFile(path, sdl_mode); - if (!ptr) - return FileRef(); - - return FileRef(new SDL2_File(ptr)); -} - -bool Platform::file_exists(const char* path) -{ - return std::filesystem::is_regular_file(path); -} - -bool Platform::file_delete(const char* path) -{ - return std::filesystem::remove(path); -} - -bool Platform::dir_create(const char* path) -{ - return std::filesystem::create_directories(path); -} - -bool Platform::dir_exists(const char* path) -{ - return std::filesystem::is_directory(path); -} - -bool Platform::dir_delete(const char* path) -{ - return std::filesystem::remove_all(path) > 0; -} - -void Platform::dir_enumerate(Vector& list, const char* path, bool recursive) -{ - if (std::filesystem::is_directory(path)) - { - if (recursive) - { - for (auto& p : std::filesystem::recursive_directory_iterator(path)) - list.emplace_back(p.path().string().c_str()); - } - else - { - for (auto& p : std::filesystem::directory_iterator(path)) - list.emplace_back(p.path().string().c_str()); - } - } -} - -void Platform::dir_explore(const char* path) -{ -#if _WIN32 - - ShellExecute(NULL, "open", path, NULL, NULL, SW_SHOWDEFAULT); - -#elif __linux__ - - system(String::fmt("xdg-open \"%s\"", path).cstr()); - -#else - - BLAH_ASSERT(false, "'dir_explore' not implemented"); - -#endif -} - -void Platform::set_clipboard(const char* text) -{ - SDL_SetClipboardText(text); -} - -const char* Platform::get_clipboard() -{ - // free previous clipboard text - if (sdl2_clipboard_text != nullptr) - SDL_free(sdl2_clipboard_text); - - sdl2_clipboard_text = SDL_GetClipboardText(); - return sdl2_clipboard_text; -} - -void* Platform::gl_get_func(const char* name) -{ - return SDL_GL_GetProcAddress(name); -} - -void* Platform::gl_context_create() -{ - void* pointer = SDL_GL_CreateContext(sdl2_window); - if (pointer == nullptr) - Log::error("SDL_GL_CreateContext failed: %s", SDL_GetError()); - return pointer; -} - -void Platform::gl_context_make_current(void* context) -{ - SDL_GL_MakeCurrent(sdl2_window, context); -} - -void Platform::gl_context_destroy(void* context) -{ - SDL_GL_DeleteContext(context); -} - -void* Platform::d3d11_get_hwnd() -{ -#if _WIN32 - SDL_SysWMinfo info; - SDL_VERSION(&info.version); - SDL_GetWindowWMInfo(sdl2_window, &info); - return info.info.win.window; -#else - return nullptr; -#endif -} - -void Platform::open_url(const char* url) -{ - SDL_OpenURL(url); -} - -#endif // BLAH_PLATFORM_SDL2 +#ifdef BLAH_PLATFORM_SDL2 + +#include "blah_platform.h" +#include "blah_renderer.h" +#include "blah_internal.h" +#include +#include +#include +#include +#include + +#include + +// for File Reading / Writing +#include + +// Windows requires a few extra includes +#if _WIN32 +#define WIN32_LEAN_AND_MEAN +#include // for the following includes +#include // for ShellExecute for dir_explore +#include // for SDL_SysWMinfo for D3D11 +#endif + +// Macro defined by X11 conflicts with MouseButton enum +#undef None + +namespace Blah +{ + void blah_sdl_log(void* userdata, int category, SDL_LogPriority priority, const char* message) + { + if (priority <= SDL_LOG_PRIORITY_INFO) + Log::info(message); + else if (priority <= SDL_LOG_PRIORITY_WARN) + Log::warn(message); + else + Log::error(message); + } + + int blah_sdl_find_joystick_index(SDL_Joystick** joysticks, SDL_JoystickID instance_id) + { + for (int i = 0; i < Input::max_controllers; i++) + if (joysticks[i] != nullptr && SDL_JoystickInstanceID(joysticks[i]) == instance_id) + return i; + return -1; + } + + int blah_sdl_find_gamepad_index(SDL_GameController** gamepads, SDL_JoystickID instance_id) + { + for (int i = 0; i < Input::max_controllers; i++) + { + if (gamepads[i] != nullptr) + { + auto joystick = SDL_GameControllerGetJoystick(gamepads[i]); + if (SDL_JoystickInstanceID(joystick) == instance_id) + return i; + } + } + return -1; + } + + struct SDL2_File : public File + { + SDL_RWops* handle; + SDL2_File(SDL_RWops* handle) : handle(handle) { } + ~SDL2_File() { if (handle) SDL_RWclose(handle); } + size_t length() override { return SDL_RWsize(handle); } + size_t position() override { return SDL_RWtell(handle); } + size_t seek(size_t position) override { return SDL_RWseek(handle, position, RW_SEEK_SET); } + size_t read(void* buffer, size_t length) override { return SDL_RWread(handle, buffer, sizeof(char), length); } + size_t write(const void* buffer, size_t length) override { return SDL_RWwrite(handle, buffer, sizeof(char), length); } + }; + + SDL_Window* sdl2_window = nullptr; + SDL_Joystick* sdl2_joysticks[Input::max_controllers]; + SDL_GameController* sdl2_gamepads[Input::max_controllers]; + char* sdl2_base_path_value = nullptr; + char* sdl2_user_path_value = nullptr; + char* sdl2_clipboard_text = nullptr; + bool sdl2_displayed = false; +} + +using namespace Blah; + +bool Platform::init(const Config& config) +{ + // TODO: + // control this via some kind of config flag +#ifndef __EMSCRIPTEN__ + // Note: Emscripten gets a Stack Overflow if this is assigned to Verbose, even when + // increasing the node stack size to +8mb. Some kind of SDL2 problem? + SDL_LogSetAllPriority(SDL_LOG_PRIORITY_VERBOSE); +#endif + SDL_LogSetOutputFunction(blah_sdl_log, nullptr); + + // Get SDL version + SDL_version version; + SDL_GetVersion(&version); + Log::info("SDL v%i.%i.%i", version.major, version.minor, version.patch); + + // Make us DPI aware on Windows + SDL_SetHint(SDL_HINT_WINDOWS_DPI_AWARENESS, "permonitorv2"); + SDL_SetHint(SDL_HINT_WINDOWS_DPI_SCALING, "1"); + + // initialize SDL + if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_EVENTS | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) != 0) + { + Log::error("Failed to initialize SDL2"); + return false; + } + + int flags = SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_HIDDEN; + + // enable OpenGL + if (config.renderer_type == RendererType::OpenGL) + { + flags |= SDL_WINDOW_OPENGL; + +#ifdef __EMSCRIPTEN__ + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); +#else + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + + // TODO: + // This should be controlled via the gfx api somehow? + SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); + SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1); + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 4); +#endif + } + + // create the window + sdl2_window = SDL_CreateWindow(config.name, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, config.width, config.height, flags); + if (sdl2_window == nullptr) + { + Log::error("Failed to create a Window"); + return false; + } + + for (int i = 0; i < Input::max_controllers; i++) + { + sdl2_joysticks[i] = nullptr; + sdl2_gamepads[i] = nullptr; + } + + return true; +} + +void Platform::ready() +{ + +} + +void Platform::shutdown() +{ + if (sdl2_window != nullptr) + SDL_DestroyWindow(sdl2_window); + sdl2_window = nullptr; + sdl2_displayed = false; + + if (sdl2_base_path_value != nullptr) + SDL_free(sdl2_base_path_value); + + if (sdl2_user_path_value != nullptr) + SDL_free(sdl2_user_path_value); + + if (sdl2_clipboard_text != nullptr) + SDL_free(sdl2_clipboard_text); + + SDL_Quit(); +} + +u64 Platform::ticks() +{ + auto counter = SDL_GetPerformanceCounter(); + auto per_second = (double)SDL_GetPerformanceFrequency(); + return (u64)(counter * (Time::ticks_per_second / per_second)); +} + +void Platform::update(InputState& state) +{ + // update the mouse every frame + { + int win_x, win_y, x, y; + + SDL_GetWindowPosition(sdl2_window, &win_x, &win_y); + SDL_GetGlobalMouseState(&x, &y); + + state.mouse.on_move( + Vec2f((float)(x - win_x), (float)(y - win_y)), + Vec2f((float)x, (float)y)); + } + + // poll normal events + SDL_Event event; + while (SDL_PollEvent(&event)) + { + if (event.type == SDL_QUIT) + { + auto& config = App::config(); + if (config.on_exit_request != nullptr) + config.on_exit_request(); + } + // Mouse + else if (event.type == SDL_MOUSEBUTTONDOWN) + { + MouseButton btn = MouseButton::None; + if (event.button.button == SDL_BUTTON_LEFT) + btn = MouseButton::Left; + else if (event.button.button == SDL_BUTTON_RIGHT) + btn = MouseButton::Right; + else if (event.button.button == SDL_BUTTON_MIDDLE) + btn = MouseButton::Middle; + + state.mouse.on_press(btn); + } + else if (event.type == SDL_MOUSEBUTTONUP) + { + MouseButton btn = MouseButton::None; + if (event.button.button == SDL_BUTTON_LEFT) + btn = MouseButton::Left; + else if (event.button.button == SDL_BUTTON_RIGHT) + btn = MouseButton::Right; + else if (event.button.button == SDL_BUTTON_MIDDLE) + btn = MouseButton::Middle; + + state.mouse.on_release(btn); + } + else if (event.type == SDL_MOUSEWHEEL) + { + state.mouse.wheel = Point(event.wheel.x, event.wheel.y); + } + // Keyboard + else if (event.type == SDL_KEYDOWN) + { + if (event.key.repeat == 0) + state.keyboard.on_press((Key)event.key.keysym.scancode); + } + else if (event.type == SDL_KEYUP) + { + if (event.key.repeat == 0) + state.keyboard.on_release((Key)event.key.keysym.scancode); + } + else if (event.type == SDL_TEXTINPUT) + { + state.keyboard.text += event.text.text; + } + // Joystick Controller + else if (event.type == SDL_JOYDEVICEADDED) + { + auto index = event.jdevice.which; + + if (SDL_IsGameController(index) == SDL_FALSE && index >= 0 && index < Input::max_controllers) + { + auto ptr = sdl2_joysticks[index] = SDL_JoystickOpen(index); + auto name = SDL_JoystickName(ptr); + auto button_count = SDL_JoystickNumButtons(ptr); + auto axis_count = SDL_JoystickNumAxes(ptr); + auto vendor = SDL_JoystickGetVendor(ptr); + auto product = SDL_JoystickGetProduct(ptr); + auto version = SDL_JoystickGetProductVersion(ptr); + + state.controllers[index].on_connect(name, 0, button_count, axis_count, vendor, product, version); + } + } + else if (event.type == SDL_JOYDEVICEREMOVED) + { + auto index = blah_sdl_find_joystick_index(sdl2_joysticks, event.jdevice.which); + if (index >= 0) + { + if (SDL_IsGameController(index) == SDL_FALSE) + { + state.controllers[index].on_disconnect(); + SDL_JoystickClose(sdl2_joysticks[index]); + } + } + } + else if (event.type == SDL_JOYBUTTONDOWN) + { + auto index = blah_sdl_find_joystick_index(sdl2_joysticks, event.jdevice.which); + if (index >= 0) + { + if (SDL_IsGameController(index) == SDL_FALSE) + state.controllers[index].on_press((Button)event.jbutton.button); + } + } + else if (event.type == SDL_JOYBUTTONUP) + { + auto index = blah_sdl_find_joystick_index(sdl2_joysticks, event.jdevice.which); + if (index >= 0) + { + if (SDL_IsGameController(index) == SDL_FALSE) + state.controllers[index].on_release((Button)event.jbutton.button); + } + } + else if (event.type == SDL_JOYAXISMOTION) + { + auto index = blah_sdl_find_joystick_index(sdl2_joysticks, event.jdevice.which); + if (index >= 0) + { + if (SDL_IsGameController(index) == SDL_FALSE) + { + float value; + if (event.jaxis.value >= 0) + value = event.jaxis.value / 32767.0f; + else + value = event.jaxis.value / 32768.0f; + state.controllers[index].on_axis((Axis)event.jaxis.axis, value); + } + } + } + // Gamepad Controller + else if (event.type == SDL_CONTROLLERDEVICEADDED) + { + auto index = event.cdevice.which; + if (index >= 0 && index < Input::max_controllers) + { + auto ptr = sdl2_gamepads[index] = SDL_GameControllerOpen(index); + auto name = SDL_GameControllerName(ptr); + auto vendor = SDL_GameControllerGetVendor(ptr); + auto product = SDL_GameControllerGetProduct(ptr); + auto version = SDL_GameControllerGetProductVersion(ptr); + + state.controllers[index].on_connect(name, 1, 15, 6, vendor, product, version); + } + } + else if (event.type == SDL_CONTROLLERDEVICEREMOVED) + { + auto index = blah_sdl_find_gamepad_index(sdl2_gamepads, event.cdevice.which); + if (index >= 0) + { + state.controllers[index].on_disconnect(); + SDL_GameControllerClose(sdl2_gamepads[index]); + } + } + else if (event.type == SDL_CONTROLLERBUTTONDOWN) + { + auto index = blah_sdl_find_gamepad_index(sdl2_gamepads, event.cdevice.which); + if (index >= 0) + { + Button button = Button::None; + if (event.cbutton.button >= 0 && event.cbutton.button < 15) + button = (Button)event.cbutton.button; // NOTE: These map directly to Engine Buttons enum! + + state.controllers[index].on_press(button); + } + } + else if (event.type == SDL_CONTROLLERBUTTONUP) + { + auto index = blah_sdl_find_gamepad_index(sdl2_gamepads, event.cdevice.which); + if (index >= 0) + { + Button button = Button::None; + if (event.cbutton.button >= 0 && event.cbutton.button < 15) + button = (Button)event.cbutton.button; // NOTE: These map directly to Engine Buttons enum! + + state.controllers[index].on_release(button); + } + } + else if (event.type == SDL_CONTROLLERAXISMOTION) + { + auto index = blah_sdl_find_gamepad_index(sdl2_gamepads, event.cdevice.which); + if (index >= 0) + { + Axis axis = Axis::None; + if (event.caxis.axis >= 0 && event.caxis.axis < 6) + axis = (Axis)event.caxis.axis; // NOTE: These map directly to Engine Axis enum! + + float value; + if (event.caxis.value >= 0) + value = event.caxis.value / 32767.0f; + else + value = event.caxis.value / 32768.0f; + + state.controllers[index].on_axis(axis, value); + } + } + } +} + +void Platform::sleep(int milliseconds) +{ + if (milliseconds >= 0) + SDL_Delay((u32)milliseconds); +} + +void Platform::present() +{ + if (App::renderer().type == RendererType::OpenGL) + SDL_GL_SwapWindow(sdl2_window); + + // display the window + // this avoids a short black screen on macOS + if (!sdl2_displayed) + { + SDL_ShowWindow(sdl2_window); + sdl2_displayed = true; + } +} + +void Platform::set_app_flags(u32 flags) +{ + // Toggle Fullscreen + SDL_SetWindowFullscreen(sdl2_window, ((flags & Flags::Fullscreen) != 0) ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0); + + // Toggle Resizable + SDL_SetWindowResizable(sdl2_window, ((flags & Flags::Resizable) != 0) ? SDL_TRUE : SDL_FALSE); + + // Toggle V-Sync for OpenGL +#ifndef __EMSCRIPTEN__ + if (App::renderer().type == RendererType::OpenGL) + SDL_GL_SetSwapInterval(((flags & Flags::VSync) != 0) ? 1 : 0); +#endif +} + +const char* Platform::get_title() +{ + return SDL_GetWindowTitle(sdl2_window); +} + +void Platform::set_title(const char* title) +{ + SDL_SetWindowTitle(sdl2_window, title); +} + +void Platform::get_position(int* x, int* y) +{ + SDL_GetWindowPosition(sdl2_window, x, y); +} + +void Platform::set_position(int x, int y) +{ + SDL_SetWindowPosition(sdl2_window, x, y); +} + +bool Platform::get_focused() +{ + auto flags = SDL_GetWindowFlags(sdl2_window); + return (flags & SDL_WINDOW_INPUT_FOCUS) != 0 && (flags & SDL_WINDOW_MINIMIZED) == 0; +} + +void Platform::get_size(int* width, int* height) +{ + SDL_GetWindowSize(sdl2_window, width, height); +} + +void Platform::set_size(int width, int height) +{ + SDL_SetWindowSize(sdl2_window, width, height); +} + +void Platform::get_draw_size(int* width, int* height) +{ + SDL_GetWindowSizeInPixels(sdl2_window, width, height); +} + +const char* Platform::app_path() +{ + if (sdl2_base_path_value == nullptr) + sdl2_base_path_value = SDL_GetBasePath(); + return sdl2_base_path_value; +} + +const char* Platform::user_path() +{ + if (sdl2_user_path_value == nullptr) + { + auto& config = App::config(); + sdl2_user_path_value = SDL_GetPrefPath(nullptr, config.name); + } + + return sdl2_user_path_value; +} + +FileRef Platform::file_open(const char* path, FileMode mode) +{ + const char* sdl_mode = ""; + + switch (mode) + { + case FileMode::OpenRead: + sdl_mode = "rb"; + break; + case FileMode::Open: + sdl_mode = "r+b"; + break; + case FileMode::CreateWrite: + sdl_mode = "wb"; + break; + case FileMode::Create: + sdl_mode = "w+b"; + break; + } + + auto ptr = SDL_RWFromFile(path, sdl_mode); + if (!ptr) + return FileRef(); + + return FileRef(new SDL2_File(ptr)); +} + +bool Platform::file_exists(const char* path) +{ + return std::filesystem::is_regular_file(path); +} + +bool Platform::file_delete(const char* path) +{ + return std::filesystem::remove(path); +} + +bool Platform::dir_create(const char* path) +{ + return std::filesystem::create_directories(path); +} + +bool Platform::dir_exists(const char* path) +{ + return std::filesystem::is_directory(path); +} + +bool Platform::dir_delete(const char* path) +{ + return std::filesystem::remove_all(path) > 0; +} + +void Platform::dir_enumerate(Vector& list, const char* path, bool recursive) +{ + if (std::filesystem::is_directory(path)) + { + if (recursive) + { + for (auto& p : std::filesystem::recursive_directory_iterator(path)) + list.emplace_back(p.path().string().c_str()); + } + else + { + for (auto& p : std::filesystem::directory_iterator(path)) + list.emplace_back(p.path().string().c_str()); + } + } +} + +void Platform::dir_explore(const char* path) +{ +#if _WIN32 + + ShellExecute(NULL, "open", path, NULL, NULL, SW_SHOWDEFAULT); + +#elif __linux__ + + system(String::fmt("xdg-open \"%s\"", path).cstr()); + +#else + + BLAH_ASSERT(false, "'dir_explore' not implemented"); + +#endif +} + +void Platform::set_clipboard(const char* text) +{ + SDL_SetClipboardText(text); +} + +const char* Platform::get_clipboard() +{ + // free previous clipboard text + if (sdl2_clipboard_text != nullptr) + SDL_free(sdl2_clipboard_text); + + sdl2_clipboard_text = SDL_GetClipboardText(); + return sdl2_clipboard_text; +} + +void* Platform::gl_get_func(const char* name) +{ + return SDL_GL_GetProcAddress(name); +} + +void* Platform::gl_context_create() +{ + void* pointer = SDL_GL_CreateContext(sdl2_window); + if (pointer == nullptr) + Log::error("SDL_GL_CreateContext failed: %s", SDL_GetError()); + return pointer; +} + +void Platform::gl_context_make_current(void* context) +{ + SDL_GL_MakeCurrent(sdl2_window, context); +} + +void Platform::gl_context_destroy(void* context) +{ + SDL_GL_DeleteContext(context); +} + +void* Platform::d3d11_get_hwnd() +{ +#if _WIN32 + SDL_SysWMinfo info; + SDL_VERSION(&info.version); + SDL_GetWindowWMInfo(sdl2_window, &info); + return info.info.win.window; +#else + return nullptr; +#endif +} + +void Platform::open_url(const char* url) +{ + SDL_OpenURL(url); +} + +#endif // BLAH_PLATFORM_SDL2 diff --git a/src/internal/blah_platform_win32.cpp b/src/internal/blah_platform_win32.cpp index 75ad55f..9218e94 100644 --- a/src/internal/blah_platform_win32.cpp +++ b/src/internal/blah_platform_win32.cpp @@ -1,1045 +1,1038 @@ -#ifdef BLAH_PLATFORM_WIN32 - -// Note: -// This is unfinished! It is missing Controller Support! - -#include "blah_platform.h" -#include "blah_internal.h" -#include -#include -#include -#include -#include - -#define WIN32_LEAN_AND_MEAN -#include -#include // for SetProcessDPIAware -#include // for File Reading/Writing -#include // for file explore -#include // for known folder -#include // for ticks method -#include // for XInput - -namespace Blah -{ - using Duration = std::chrono::system_clock::duration; - - typedef HRESULT(WINAPI* DirectInput8Create_fn)(HINSTANCE, DWORD, REFIID, LPVOID*, LPUNKNOWN); - typedef DWORD(WINAPI* XInputGetCapabilities_fn)(DWORD, DWORD, XINPUT_CAPABILITIES*); - typedef DWORD(WINAPI* XInputGetState_fn)(DWORD, XINPUT_STATE*); - typedef void*(WINAPI* wglGetProcAddress_fn)(const char*); - typedef HGLRC(WINAPI* wglCreateContext_fn)(HDC); - typedef BOOL(WINAPI* wglDeleteContext_fn)(HGLRC); - typedef BOOL(WINAPI* wglMakeCurrent_fn)(HDC, HGLRC); - - class Win32File : public File - { - private: - HANDLE m_handle; - LARGE_INTEGER m_size; - - public: - Win32File(HANDLE handle); - ~Win32File(); - size_t length() override; - size_t position() override; - size_t seek(size_t position) override; - size_t read(void* buffer, size_t length) override; - size_t write(const void* buffer, size_t length) override; - }; - - // Main State - HWND win32_hwnd; - FilePath win32_working_directory; - FilePath win32_user_directory; - Duration win32_start_time; - RECT win32_windowed_position; - bool win32_fullscreen = false; - InputState* win32_input_state = nullptr; - String win32_clipboard; - - // XInput - struct - { - HMODULE dll; - XInputGetCapabilities_fn get_capabilities; - XInputGetState_fn get_state; - } win32_xinput; - - struct Joystick - { - bool connected = false; - bool accounted = false; - GUID dinstance = GUID_NULL; - DWORD xindex = 0; - } win32_joysticks[Input::max_controllers]; - - // OpenGL Methods - // These are only loaded if built using the OpenGL Backend - struct - { - HMODULE dll; - wglGetProcAddress_fn get_proc_address; - wglCreateContext_fn create_context; - wglDeleteContext_fn delete_context; - wglMakeCurrent_fn make_current; - } win32_gl; - - // Main Windows Procedure callback - LRESULT CALLBACK win32_window_procedure(HWND win32_hwnd, UINT msg, WPARAM wParam, LPARAM lParam); - - // Converts Windows scancode to Blah key - Key win32_scancode_to_key(WPARAM wParam, LPARAM lParam); - - void win32_detect_joysticks(); -} - -using namespace Blah; - -Win32File::Win32File(HANDLE handle) -{ - m_handle = handle; - m_size.QuadPart = 0; - - LARGE_INTEGER file_size; - if (GetFileSizeEx(m_handle, &file_size)) - m_size = file_size; -} - -Win32File::~Win32File() -{ - CloseHandle(m_handle); -} - -size_t Win32File::length() -{ - return m_size.QuadPart; -} - -size_t Win32File::position() -{ - LARGE_INTEGER move; - LARGE_INTEGER result; - - move.QuadPart = 0; - result.QuadPart = 0; - - SetFilePointerEx(m_handle, move, &result, FILE_CURRENT); - - return result.QuadPart; -} - -size_t Win32File::seek(size_t position) -{ - LARGE_INTEGER move; - LARGE_INTEGER result; - - move.QuadPart = position; - result.QuadPart = 0; - - SetFilePointerEx(m_handle, move, &result, FILE_BEGIN); - - return result.QuadPart; -} - -size_t Win32File::read(void* buffer, size_t length) -{ - static const DWORD read_step = 65536; - - size_t read = 0; - - while (read < length) - { - DWORD to_read = read_step; - if (to_read > length - read) - to_read = (DWORD)(length - read); - - DWORD moved = 0; - if (ReadFile(m_handle, (char*)buffer + read, to_read, &moved, NULL)) - read += moved; - - if (moved < to_read) - break; - } - - return read; -} - -size_t Win32File::write(const void* buffer, size_t length) -{ - static const DWORD write_step = 65536; - - size_t written = 0; - - while (written < length) - { - DWORD to_write = write_step; - if (to_write > length - written) - to_write = (DWORD)(length - written); - - DWORD moved = 0; - if (WriteFile(m_handle, (char*)buffer + written, to_write, &moved, NULL)) - written += moved; - - if (moved < to_write) - break; - } - - return written; -} - -bool Platform::init(const Config& config) -{ - // Required to call this for Windows - SetProcessDPIAware(); - - // Get the hInstance - HINSTANCE hInstance = GetModuleHandle(NULL); - - // Create the Window Class - WNDCLASS wc = {}; - wc.lpfnWndProc = DefWindowProc; - wc.lpszClassName = "BLAH WINDOW"; - wc.hInstance = hInstance; - wc.lpfnWndProc = win32_window_procedure; - wc.hCursor = LoadCursor(NULL, IDC_ARROW); - wc.hIcon = NULL; - wc.lpszMenuName = NULL; - wc.hbrBackground = (HBRUSH)COLOR_WINDOW; - wc.cbClsExtra = 0; - wc.cbWndExtra = 0; - - // Register the Window class - RegisterClass(&wc); - - // Create the Window Instance - win32_hwnd = CreateWindow("BLAH WINDOW", config.name, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, 640, 480, NULL, NULL, hInstance, NULL); - - // Failed to create the Window - if (win32_hwnd == NULL) - { - Log::error("Window Creation Failed"); - return false; - } - - // Setup Window Size based on content scale - { - auto scale = get_content_scale(); - int sw = (int)(App::config().width * scale); - int sh = (int)(App::config().height * scale); - set_size(sw, sh); - } - - // Create the OpenGL device info - if (config.renderer_type == RendererType::OpenGL) - { - // Load the DLL - win32_gl.dll = LoadLibraryA("opengl32.dll"); - if (win32_gl.dll == NULL) - { - Log::error("OpenGL Instantiation Failed - unable to fine opengl32.dll"); - return false; - } - - // Get the Windows GL functions we need - win32_gl.get_proc_address = (wglGetProcAddress_fn)GetProcAddress(win32_gl.dll, "wglGetProcAddress"); - win32_gl.create_context = (wglCreateContext_fn)GetProcAddress(win32_gl.dll, "wglCreateContext"); - win32_gl.delete_context = (wglDeleteContext_fn)GetProcAddress(win32_gl.dll, "wglDeleteContext"); - win32_gl.make_current = (wglMakeCurrent_fn)GetProcAddress(win32_gl.dll, "wglMakeCurrent"); - - // TODO: - // Allow the user to apply (some of) these values before instantiation. - // Also applies to the SDL2 Backend - - PIXELFORMATDESCRIPTOR pfd = - { - sizeof(PIXELFORMATDESCRIPTOR), // size of this pfd - 1, // version number - PFD_DRAW_TO_WINDOW | // support window - PFD_SUPPORT_OPENGL | // support OpenGL - PFD_DOUBLEBUFFER, // double buffered - PFD_TYPE_RGBA, // RGBA type - 32, // 32-bit color depth - 0, 0, 0, 0, 0, 0, // color bits ignored - 0, // no alpha buffer - 0, // shift bit ignored - 0, // no accumulation buffer - 0, 0, 0, 0, // accum bits ignored - 24, // 24-bit z-buffer - 8, // 8-bit stencil buffer - 0, // no auxiliary buffer - PFD_MAIN_PLANE, // main layer - 0, // reserved - 0, 0, 0 // layer masks ignored - }; - - HDC hdc = GetDC(win32_hwnd); - - // get the best available match of pixel format for the device context - int pixel_format = ChoosePixelFormat(hdc, &pfd); - - // make that the pixel format of the device context - SetPixelFormat(hdc, pixel_format, &pfd); - } - - // xinput api - { - const char* dlls[] = { "xinput1_4.dll", "xinput1_3.dll", "xinput9_1_0.dll", "xinput1_2.dll", "xinput1_1.dll", NULL }; - - for (int i = 0; dlls[i]; i++) - { - win32_xinput.dll = LoadLibraryA(dlls[i]); - - if (win32_xinput.dll) - { - win32_xinput.get_capabilities = (XInputGetCapabilities_fn)GetProcAddress(win32_xinput.dll, "XInputGetCapabilities"); - win32_xinput.get_state = (XInputGetState_fn)GetProcAddress(win32_xinput.dll, "XInputGetState"); - break; - } - } - - if (!win32_xinput.dll) - Log::warn("Failed to find XInput dll; No Controller Support"); - } - - // Get Working Directory - { - TCHAR buffer[MAX_PATH]; - GetModuleFileName(NULL, buffer, MAX_PATH); - - auto normalized = Path::normalize(buffer); - auto end = normalized.last_index_of('/');; - if (end >= 0) - win32_working_directory = FilePath(normalized.begin(), normalized.begin() + end); - else - win32_working_directory = normalized; - win32_working_directory.append("/"); - } - - // Get Application User Directory - { - PWSTR path = NULL; - if (SUCCEEDED(SHGetKnownFolderPath(FOLDERID_RoamingAppData, KF_FLAG_CREATE, NULL, &path))) - { - auto end = path; - while (*end != 0) end++; - - FilePath result; - result.append((u16*)path, (u16*)end); - - win32_user_directory = Path::join(Path::normalize(result), config.name) + "/"; - } - CoTaskMemFree(path); - } - - // Reset our game timer - win32_start_time = std::chrono::system_clock::now().time_since_epoch(); - - // Not currently win32_fullscreen - win32_fullscreen = false; - - // Finished Platform Setup - return true; -} - -void Platform::ready() -{ - // Display the game window - ShowWindow(win32_hwnd, SW_SHOW); -} - -void Platform::shutdown() -{ - if (win32_xinput.dll) - FreeLibrary(win32_xinput.dll); - - if (win32_gl.dll) - FreeLibrary(win32_gl.dll); - - DestroyWindow(win32_hwnd); -} - -u64 Platform::ticks() -{ - // Todo: - // This should account for whatever Time::ticks_per_second is set to - - auto now = std::chrono::system_clock::now().time_since_epoch(); - return std::chrono::duration_cast(now - win32_start_time).count(); -} - -void Platform::update(InputState& state) -{ - // store reference to input state - bool first_update = win32_input_state == nullptr; - win32_input_state = &state; - - // if this is the first update, poll joysticks that are already connected - if (first_update) - win32_detect_joysticks(); - - // Catch & Dispatch Window Messages - MSG msg; - while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) - { - TranslateMessage(&msg); - DispatchMessage(&msg); - } -} - -void Platform::sleep(int milliseconds) -{ - if (milliseconds > 0) - Sleep(milliseconds); -} - -void Platform::present() -{ - if (App::renderer().type == RendererType::OpenGL) - { - HDC hdc = GetDC(win32_hwnd); - SwapBuffers(hdc); - } -} - -const char* Platform::get_title() -{ - return nullptr; -} - -void Platform::set_title(const char* title) -{ - SetWindowText(win32_hwnd, title); -} - -void Platform::get_position(int* x, int* y) -{ - RECT rect; - if (GetWindowRect(win32_hwnd, &rect)) - { - *x = rect.left; - *y = rect.top; - } -} - -void Platform::set_position(int x, int y) -{ - int w, h; - get_size(&w, &h); - SetWindowPos(win32_hwnd, NULL, x, y, w, h, 0); -} - -bool Platform::get_focused() -{ - Log::warn("App::focused not implemented for Win32 yet"); - return true; -} - -void Platform::set_app_flags(u32 flags) -{ - // toggle win32_fullscreen - { - bool enabled = (flags & Flags::Fullscreen) != 0; - if (win32_fullscreen == enabled) - return; - win32_fullscreen = enabled; - - if (win32_fullscreen) - { - GetWindowRect(win32_hwnd, &win32_windowed_position); - - int w = GetSystemMetrics(SM_CXSCREEN); - int h = GetSystemMetrics(SM_CYSCREEN); - SetWindowLongPtr(win32_hwnd, GWL_STYLE, WS_VISIBLE | WS_POPUP); - SetWindowPos(win32_hwnd, HWND_TOP, 0, 0, w, h, 0); - ShowWindow(win32_hwnd, SW_SHOW); - } - else - { - SetWindowLongPtr(win32_hwnd, GWL_STYLE, WS_OVERLAPPEDWINDOW); - SetWindowPos(win32_hwnd, HWND_TOP, - win32_windowed_position.left, - win32_windowed_position.top, - win32_windowed_position.right - win32_windowed_position.left, - win32_windowed_position.bottom - win32_windowed_position.top, 0); - ShowWindow(win32_hwnd, SW_SHOW); - } - } - - // toggle resizable - // TODO: ... -} - -void Platform::get_size(int* width, int* height) -{ - RECT rect; - if (GetClientRect(win32_hwnd, &rect)) - { - *width = rect.right - rect.left; - *height = rect.bottom - rect.top; - } -} - -void Platform::set_size(int width, int height) -{ - RECT client_rect; - RECT border_rect; - - GetClientRect(win32_hwnd, &client_rect); - GetWindowRect(win32_hwnd, &border_rect); - - int border_width = (border_rect.right - border_rect.left) - (client_rect.right - client_rect.left); - int border_height = (border_rect.bottom - border_rect.top) - (client_rect.bottom - client_rect.top); - - SetWindowPos(win32_hwnd, NULL, border_rect.left, border_rect.top, width + border_width, height + border_height, 0); -} - -void Platform::get_draw_size(int* width, int* height) -{ - RECT rect; - if (GetClientRect(win32_hwnd, &rect)) - { - *width = rect.right - rect.left; - *height = rect.bottom - rect.top; - } -} - -float Platform::get_content_scale() -{ - // base value of Windows DPI - // as seen here: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getdpiforwindow - constexpr float base_raw_value = 96.0f; - - UINT raw_value = GetDpiForWindow(win32_hwnd); - - return (raw_value / base_raw_value); -} - -const char* Platform::app_path() -{ - return win32_working_directory.cstr(); -} - -const char* Platform::user_path() -{ - return win32_user_directory.cstr(); -} - -bool Platform::file_exists(const char* path) -{ - return std::filesystem::is_regular_file(path); -} - -bool Platform::file_delete(const char* path) -{ - return std::filesystem::remove(path); -} - -bool Platform::dir_create(const char* path) -{ - std::error_code error; - return std::filesystem::create_directories(path, error); -} - -bool Platform::dir_exists(const char* path) -{ - return std::filesystem::is_directory(path); -} - -bool Platform::dir_delete(const char* path) -{ - return std::filesystem::remove_all(path) > 0; -} - -void Platform::dir_enumerate(Vector& list, const char* path, bool recursive) -{ - if (std::filesystem::is_directory(path)) - { - if (recursive) - { - for (auto& p : std::filesystem::recursive_directory_iterator(path)) - list.emplace_back(p.path().string().c_str()); - } - else - { - for (auto& p : std::filesystem::directory_iterator(path)) - list.emplace_back(p.path().string().c_str()); - } - } -} - -void Platform::dir_explore(const char* path) -{ - ShellExecute(NULL, "open", path, NULL, NULL, SW_SHOWDEFAULT); -} - -FileRef Platform::file_open(const char* path, FileMode mode) -{ - int access = 0; - int creation = 0; - - switch (mode) - { - case FileMode::OpenRead: - access = GENERIC_READ; - creation = OPEN_EXISTING; - break; - case FileMode::Open: - access = GENERIC_READ | GENERIC_WRITE; - creation = OPEN_EXISTING; - break; - case FileMode::CreateWrite: - access = GENERIC_WRITE; - creation = CREATE_ALWAYS; - break; - case FileMode::Create: - access = GENERIC_READ | GENERIC_WRITE; - creation = CREATE_ALWAYS; - break; - } - - auto result = CreateFile(path, access, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, creation, FILE_ATTRIBUTE_NORMAL, NULL); - - if (result == INVALID_HANDLE_VALUE) - return FileRef(); - - return FileRef(new Win32File(result)); -} - -void* Platform::gl_get_func(const char* name) -{ - // this check is taken from https://www.khronos.org/opengl/wiki/Load_OpenGL_Functions - // wglGetProcAddress doesn't always return valid pointers for some specific methods? - - void* p = (void*)win32_gl.get_proc_address(name); - if ((p == 0) || - (p == (void*)0x1) || - (p == (void*)0x2) || - (p == (void*)0x3) || - (p == (void*)-1)) - { - p = (void*)GetProcAddress(win32_gl.dll, name); - } - - return p; -} - -void* Platform::gl_context_create() -{ - HDC hdc = GetDC(win32_hwnd); - return win32_gl.create_context(hdc); -} - -void Platform::gl_context_make_current(void* context) -{ - if (context != nullptr) - { - HDC hdc = GetDC(win32_hwnd); - win32_gl.make_current(hdc, (HGLRC)context); - } - else - win32_gl.make_current(NULL, NULL); -} - -void Platform::gl_context_destroy(void* context) -{ - win32_gl.delete_context((HGLRC)context); -} - -void* Platform::d3d11_get_hwnd() -{ - return win32_hwnd; -} - -void Platform::set_clipboard(const char* text) -{ - auto len = strlen(text); - if (auto glob = GlobalAlloc(GMEM_MOVEABLE, len)) - { - if (auto data = GlobalLock(glob)) - { - memcpy(data, text, len); - GlobalUnlock(glob); - - if (OpenClipboard(nullptr)) - { - SetClipboardData(CF_TEXT, data); - CloseClipboard(); - } - } - - GlobalFree(glob); - } -} - -const char* Platform::get_clipboard() -{ - if (OpenClipboard(nullptr)) - { - HANDLE data = GetClipboardData(CF_TEXT); - if (data) - { - auto text = static_cast(GlobalLock(data)); - if (text) - win32_clipboard = text; - GlobalUnlock(data); - } - CloseClipboard(); - } - - return win32_clipboard.cstr(); -} - -void Platform::open_url(const char* url) -{ - auto cmd = String("start ") + url; - system(cmd.cstr()); -} - -void win32_detect_joysticks() -{ - // mark all joysticks as unnacounted for - for (int i = 0; i < Input::max_controllers; i++) - win32_joysticks[i].accounted = false; - - // check for xinput controllers - if (win32_xinput.dll) - { - for (DWORD index = 0; index < XUSER_MAX_COUNT; index++) - { - // can't get capabilities; not connected - XINPUT_CAPABILITIES xic; - if (win32_xinput.get_capabilities(index, 0, &xic) != ERROR_SUCCESS) - continue; - - // already connected - bool already_connected = false; - for (int i = 0; i < Input::max_controllers; i++) - { - auto& it = win32_joysticks[i]; - if (it.connected && it.dinstance == GUID_NULL && it.xindex == index) - { - it.accounted = true; - already_connected = true; - break; - } - } - - if (already_connected) - continue; - - // find an empty slot and mark connected - for (int i = 0; i < Input::max_controllers; i++) - { - auto& it = win32_joysticks[i]; - if (!it.connected) - { - it.connected = it.accounted = true; - it.dinstance = GUID_NULL; - it.xindex = index; - - Log::info("Connected XInput [%i]", i); - - // TODO: - // Get Product Info & Proper Name - win32_input_state->controllers[i].on_connect("Xbox Controller", true, 15, 6, 0, 0, 0); - - break; - } - } - } - } - - // call disconnect on joysticks that aren't accounted for - for (int i = 0; i < Input::max_controllers; i++) - { - auto& it = win32_joysticks[i]; - if (it.connected && !it.accounted) - { - Log::info("Disconnected [%i]", i); - win32_input_state->controllers[i].on_disconnect(); - it = Joystick(); - } - } -} - -LRESULT CALLBACK Blah::win32_window_procedure(HWND win32_hwnd, UINT msg, WPARAM wParam, LPARAM lParam) -{ - switch (msg) - { - case WM_CLOSE: - { - auto& config = App::config(); - if (config.on_exit_request != nullptr) - config.on_exit_request(); - return 0; - } - - case WM_DESTROY: - PostQuitMessage(0); - return 0; - - // Controller connected event - case WM_DEVICECHANGE: - { - // DBT_DEVNODES_CHANGED = 0x0007 - // https://docs.microsoft.com/en-us/windows/win32/devio/wm-devicechange - if (wParam == 0x0007) - win32_detect_joysticks(); - return 0; - } - - // Mouse Input - case WM_LBUTTONDOWN: - win32_input_state->mouse.on_press(MouseButton::Left); - return 0; - - case WM_LBUTTONUP: - win32_input_state->mouse.on_release(MouseButton::Left); - return 0; - - case WM_RBUTTONDOWN: - win32_input_state->mouse.on_press(MouseButton::Right); - return 0; - - case WM_RBUTTONUP: - win32_input_state->mouse.on_release(MouseButton::Right); - return 0; - - case WM_MBUTTONDOWN: - win32_input_state->mouse.on_press(MouseButton::Middle); - return 0; - - case WM_MBUTTONUP: - win32_input_state->mouse.on_release(MouseButton::Middle); - return 0; - - case WM_MOUSEMOVE: - win32_input_state->mouse.on_move(Vec2f((float)((u16)lParam), (float)(lParam >> 16)), Vec2f::zero); - return 0; - - case WM_MOUSEWHEEL: - win32_input_state->mouse.wheel = Point(0, GET_WHEEL_DELTA_WPARAM(wParam) / WHEEL_DELTA); - return 0; - - // Text Input - case WM_UNICHAR: - if (wParam == UNICODE_NOCHAR) - return 1; - case WM_CHAR: - { - String result; - result.append((u32)wParam); - if (result.length() > 0) - win32_input_state->keyboard.text += result.cstr(); - return 0; - } - - // Keyboard Input - case WM_KEYDOWN: - case WM_SYSKEYDOWN: - { - auto is_repeat = ((lParam & (1 << 30)) >> 30) == 1; - if (!is_repeat) - { - auto key = Blah::win32_scancode_to_key(wParam, lParam); - if (key != Key::Unknown) - win32_input_state->keyboard.on_press(key); - } - return 0; - } - - case WM_KEYUP: - case WM_SYSKEYUP: - { - auto key = Blah::win32_scancode_to_key(wParam, lParam); - if (key != Key::Unknown) - win32_input_state->keyboard.on_release(key); - return 0; - } - } - - return DefWindowProc(win32_hwnd, msg, wParam, lParam); -} - -Blah::Key Blah::win32_scancode_to_key(WPARAM wParam, LPARAM lParam) -{ - // scancodes - switch ((lParam >> 16) & 0xFF) - { - case 1: return Key::Escape; - case 2: return Key::D1; - case 3: return Key::D2; - case 4: return Key::D3; - case 5: return Key::D4; - case 6: return Key::D5; - case 7: return Key::D6; - case 8: return Key::D7; - case 9: return Key::D8; - case 10: return Key::D9; - case 11: return Key::D0; - case 12: return Key::Minus; - case 13: return Key::Equals; - case 14: return Key::Backspace; - case 15: return Key::Tab; - case 16: return Key::Q; - case 17: return Key::W; - case 18: return Key::E; - case 19: return Key::R; - case 20: return Key::T; - case 21: return Key::Y; - case 22: return Key::U; - case 23: return Key::I; - case 24: return Key::O; - case 25: return Key::P; - case 26: return Key::LeftBracket; - case 27: return Key::RightBracket; - case 28: return Key::Enter; - case 29: return Key::LeftControl; - case 30: return Key::A; - case 31: return Key::S; - case 32: return Key::D; - case 33: return Key::F; - case 34: return Key::G; - case 35: return Key::H; - case 36: return Key::J; - case 37: return Key::K; - case 38: return Key::L; - case 39: return Key::Semicolon; - case 40: return Key::Apostrophe; - case 41: return Key::Tilde; - case 42: return Key::LeftShift; - case 43: return Key::Backslash; - case 44: return Key::Z; - case 45: return Key::X; - case 46: return Key::C; - case 47: return Key::V; - case 48: return Key::B; - case 49: return Key::N; - case 50: return Key::M; - case 51: return Key::Comma; - case 52: return Key::Period; - case 53: return Key::Slash; - case 54: return Key::RightShift; - case 55: return Key::PrintScreen; - case 56: return Key::LeftAlt; - case 57: return Key::Space; - case 58: return Key::Capslock; - case 59: return Key::F1; - case 60: return Key::F2; - case 61: return Key::F3; - case 62: return Key::F4; - case 63: return Key::F5; - case 64: return Key::F6; - case 65: return Key::F7; - case 66: return Key::F8; - case 67: return Key::F9; - case 68: return Key::F10; - case 71: return Key::Home; - case 72: return Key::Up; - case 73: return Key::PageUp; - case 74: return Key::KeypadMinus; - case 75: return Key::Left; - case 76: return Key::Keypad5; - case 77: return Key::Right; - case 78: return Key::KeypadPlus; - case 79: return Key::End; - case 80: return Key::Down; - case 81: return Key::PageDown; - case 82: return Key::Insert; - case 83: return Key::Delete; - case 87: return Key::F11; - case 88: return Key::F12; - case 89: return Key::Pause; - case 91: return Key::LeftOS; - case 92: return Key::RightOS; - case 93: return Key::Application; - case 100: return Key::F13; - case 101: return Key::F14; - case 102: return Key::F15; - case 103: return Key::F16; - case 104: return Key::F17; - case 105: return Key::F18; - case 106: return Key::F19; - } - - // virtual keys - switch (wParam) - { - case VK_CANCEL: return Key::Cancel; - case VK_BACK: return Key::Backspace; - case VK_TAB: return Key::Tab; - case VK_CLEAR: return Key::Clear; - case VK_RETURN: return Key::Enter; - case VK_SHIFT: return Key::LeftShift; - case VK_CONTROL: return Key::LeftControl; - case VK_PAUSE: return Key::Pause; - case VK_CAPITAL: return Key::Capslock; - case VK_ESCAPE: return Key::Escape; - case VK_SPACE: return Key::Space; - case VK_PRIOR: return Key::Prior; - case VK_END: return Key::End; - case VK_HOME: return Key::Home; - case VK_LEFT: return Key::Left; - case VK_UP: return Key::Up; - case VK_RIGHT: return Key::Right; - case VK_DOWN: return Key::Down; - case VK_SELECT: return Key::Select; - case VK_PRINT: return Key::PrintScreen; - case VK_EXECUTE: return Key::Execute; - case VK_SNAPSHOT: return Key::PrintScreen; - case VK_INSERT: return Key::Insert; - case VK_DELETE: return Key::Delete; - case VK_HELP: return Key::Help; - case VK_LWIN: return Key::LeftOS; - case VK_RWIN: return Key::RightOS; - case VK_APPS: return Key::Application; - case VK_SLEEP: return Key::Unknown; - case VK_NUMPAD0: return Key::Keypad0; - case VK_NUMPAD1: return Key::Keypad1; - case VK_NUMPAD2: return Key::Keypad2; - case VK_NUMPAD3: return Key::Keypad3; - case VK_NUMPAD4: return Key::Keypad4; - case VK_NUMPAD5: return Key::Keypad5; - case VK_NUMPAD6: return Key::Keypad6; - case VK_NUMPAD7: return Key::Keypad7; - case VK_NUMPAD8: return Key::Keypad8; - case VK_NUMPAD9: return Key::Keypad9; - case VK_F1: return Key::F1; - case VK_F2: return Key::F2; - case VK_F3: return Key::F3; - case VK_F4: return Key::F4; - case VK_F5: return Key::F5; - case VK_F6: return Key::F6; - case VK_F7: return Key::F7; - case VK_F8: return Key::F8; - case VK_F9: return Key::F9; - case VK_F10: return Key::F10; - case VK_F11: return Key::F11; - case VK_F12: return Key::F12; - case VK_F13: return Key::F13; - case VK_F14: return Key::F14; - case VK_F15: return Key::F15; - case VK_F16: return Key::F16; - case VK_F17: return Key::F17; - case VK_F18: return Key::F18; - case VK_F19: return Key::F19; - case VK_F20: return Key::F20; - case VK_F21: return Key::F21; - case VK_F22: return Key::F22; - case VK_F23: return Key::F23; - case VK_F24: return Key::F24; - case VK_NUMLOCK: return Key::Numlock; - case VK_LSHIFT: return Key::LeftShift; - case VK_RSHIFT: return Key::RightShift; - case VK_LCONTROL: return Key::LeftControl; - case VK_RCONTROL: return Key::RightControl; - case VK_VOLUME_MUTE: return Key::Mute; - case VK_VOLUME_DOWN: return Key::VolumeDown; - case VK_VOLUME_UP: return Key::VolumeUp; - } - - return Key::Unknown; -} - -#endif // BLAH_PLATFORM_WIN32 +#ifdef BLAH_PLATFORM_WIN32 + +// Note: +// This is unfinished! It is missing Controller Support! + +#include "blah_platform.h" +#include "blah_internal.h" +#include +#include +#include +#include +#include + +#define WIN32_LEAN_AND_MEAN +#include +#include // for SetProcessDPIAware +#include // for File Reading/Writing +#include // for file explore +#include // for known folder +#include // for ticks method +#include // for XInput + +namespace Blah +{ + using Duration = std::chrono::system_clock::duration; + + typedef HRESULT(WINAPI* DirectInput8Create_fn)(HINSTANCE, DWORD, REFIID, LPVOID*, LPUNKNOWN); + typedef DWORD(WINAPI* XInputGetCapabilities_fn)(DWORD, DWORD, XINPUT_CAPABILITIES*); + typedef DWORD(WINAPI* XInputGetState_fn)(DWORD, XINPUT_STATE*); + typedef void*(WINAPI* wglGetProcAddress_fn)(const char*); + typedef HGLRC(WINAPI* wglCreateContext_fn)(HDC); + typedef BOOL(WINAPI* wglDeleteContext_fn)(HGLRC); + typedef BOOL(WINAPI* wglMakeCurrent_fn)(HDC, HGLRC); + + class Win32File : public File + { + private: + HANDLE m_handle; + LARGE_INTEGER m_size; + + public: + Win32File(HANDLE handle); + ~Win32File(); + size_t length() override; + size_t position() override; + size_t seek(size_t position) override; + size_t read(void* buffer, size_t length) override; + size_t write(const void* buffer, size_t length) override; + }; + + // Main State + HWND win32_hwnd; + FilePath win32_working_directory; + FilePath win32_user_directory; + Duration win32_start_time; + RECT win32_windowed_position; + bool win32_fullscreen = false; + InputState* win32_input_state = nullptr; + String win32_clipboard; + + // XInput + struct + { + HMODULE dll; + XInputGetCapabilities_fn get_capabilities; + XInputGetState_fn get_state; + } win32_xinput; + + struct Joystick + { + bool connected = false; + bool accounted = false; + GUID dinstance = GUID_NULL; + DWORD xindex = 0; + } win32_joysticks[Input::max_controllers]; + + // OpenGL Methods + // These are only loaded if built using the OpenGL Backend + struct + { + HMODULE dll; + wglGetProcAddress_fn get_proc_address; + wglCreateContext_fn create_context; + wglDeleteContext_fn delete_context; + wglMakeCurrent_fn make_current; + } win32_gl; + + // Main Windows Procedure callback + LRESULT CALLBACK win32_window_procedure(HWND win32_hwnd, UINT msg, WPARAM wParam, LPARAM lParam); + + // Converts Windows scancode to Blah key + Key win32_scancode_to_key(WPARAM wParam, LPARAM lParam); + + void win32_detect_joysticks(); +} + +using namespace Blah; + +Win32File::Win32File(HANDLE handle) +{ + m_handle = handle; + m_size.QuadPart = 0; + + LARGE_INTEGER file_size; + if (GetFileSizeEx(m_handle, &file_size)) + m_size = file_size; +} + +Win32File::~Win32File() +{ + CloseHandle(m_handle); +} + +size_t Win32File::length() +{ + return m_size.QuadPart; +} + +size_t Win32File::position() +{ + LARGE_INTEGER move; + LARGE_INTEGER result; + + move.QuadPart = 0; + result.QuadPart = 0; + + SetFilePointerEx(m_handle, move, &result, FILE_CURRENT); + + return result.QuadPart; +} + +size_t Win32File::seek(size_t position) +{ + LARGE_INTEGER move; + LARGE_INTEGER result; + + move.QuadPart = position; + result.QuadPart = 0; + + SetFilePointerEx(m_handle, move, &result, FILE_BEGIN); + + return result.QuadPart; +} + +size_t Win32File::read(void* buffer, size_t length) +{ + static const DWORD read_step = 65536; + + size_t read = 0; + + while (read < length) + { + DWORD to_read = read_step; + if (to_read > length - read) + to_read = (DWORD)(length - read); + + DWORD moved = 0; + if (ReadFile(m_handle, (char*)buffer + read, to_read, &moved, NULL)) + read += moved; + + if (moved < to_read) + break; + } + + return read; +} + +size_t Win32File::write(const void* buffer, size_t length) +{ + static const DWORD write_step = 65536; + + size_t written = 0; + + while (written < length) + { + DWORD to_write = write_step; + if (to_write > length - written) + to_write = (DWORD)(length - written); + + DWORD moved = 0; + if (WriteFile(m_handle, (char*)buffer + written, to_write, &moved, NULL)) + written += moved; + + if (moved < to_write) + break; + } + + return written; +} + +bool Platform::init(const Config& config) +{ + // Required to call this for Windows + SetProcessDPIAware(); + + // Get the hInstance + HINSTANCE hInstance = GetModuleHandle(NULL); + + // Create the Window Class + WNDCLASS wc = {}; + wc.lpfnWndProc = DefWindowProc; + wc.lpszClassName = "BLAH WINDOW"; + wc.hInstance = hInstance; + wc.lpfnWndProc = win32_window_procedure; + wc.hCursor = LoadCursor(NULL, IDC_ARROW); + wc.hIcon = NULL; + wc.lpszMenuName = NULL; + wc.hbrBackground = (HBRUSH)COLOR_WINDOW; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + + // Register the Window class + RegisterClass(&wc); + + // Create the Window Instance + win32_hwnd = CreateWindow("BLAH WINDOW", config.name, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, 640, 480, NULL, NULL, hInstance, NULL); + + // Failed to create the Window + if (win32_hwnd == NULL) + { + Log::error("Window Creation Failed"); + return false; + } + + // Setup Window Size based on content scale + { + // base value of Windows DPI + // as seen here: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getdpiforwindow + constexpr float base_raw_value = 96.0f; + + UINT raw_value = GetDpiForWindow(win32_hwnd); + float scale = (raw_value / base_raw_value); + int sw = (int)(App::config().width * scale); + int sh = (int)(App::config().height * scale); + set_size(sw, sh); + } + + // Create the OpenGL device info + if (config.renderer_type == RendererType::OpenGL) + { + // Load the DLL + win32_gl.dll = LoadLibraryA("opengl32.dll"); + if (win32_gl.dll == NULL) + { + Log::error("OpenGL Instantiation Failed - unable to fine opengl32.dll"); + return false; + } + + // Get the Windows GL functions we need + win32_gl.get_proc_address = (wglGetProcAddress_fn)GetProcAddress(win32_gl.dll, "wglGetProcAddress"); + win32_gl.create_context = (wglCreateContext_fn)GetProcAddress(win32_gl.dll, "wglCreateContext"); + win32_gl.delete_context = (wglDeleteContext_fn)GetProcAddress(win32_gl.dll, "wglDeleteContext"); + win32_gl.make_current = (wglMakeCurrent_fn)GetProcAddress(win32_gl.dll, "wglMakeCurrent"); + + // TODO: + // Allow the user to apply (some of) these values before instantiation. + // Also applies to the SDL2 Backend + + PIXELFORMATDESCRIPTOR pfd = + { + sizeof(PIXELFORMATDESCRIPTOR), // size of this pfd + 1, // version number + PFD_DRAW_TO_WINDOW | // support window + PFD_SUPPORT_OPENGL | // support OpenGL + PFD_DOUBLEBUFFER, // double buffered + PFD_TYPE_RGBA, // RGBA type + 32, // 32-bit color depth + 0, 0, 0, 0, 0, 0, // color bits ignored + 0, // no alpha buffer + 0, // shift bit ignored + 0, // no accumulation buffer + 0, 0, 0, 0, // accum bits ignored + 24, // 24-bit z-buffer + 8, // 8-bit stencil buffer + 0, // no auxiliary buffer + PFD_MAIN_PLANE, // main layer + 0, // reserved + 0, 0, 0 // layer masks ignored + }; + + HDC hdc = GetDC(win32_hwnd); + + // get the best available match of pixel format for the device context + int pixel_format = ChoosePixelFormat(hdc, &pfd); + + // make that the pixel format of the device context + SetPixelFormat(hdc, pixel_format, &pfd); + } + + // xinput api + { + const char* dlls[] = { "xinput1_4.dll", "xinput1_3.dll", "xinput9_1_0.dll", "xinput1_2.dll", "xinput1_1.dll", NULL }; + + for (int i = 0; dlls[i]; i++) + { + win32_xinput.dll = LoadLibraryA(dlls[i]); + + if (win32_xinput.dll) + { + win32_xinput.get_capabilities = (XInputGetCapabilities_fn)GetProcAddress(win32_xinput.dll, "XInputGetCapabilities"); + win32_xinput.get_state = (XInputGetState_fn)GetProcAddress(win32_xinput.dll, "XInputGetState"); + break; + } + } + + if (!win32_xinput.dll) + Log::warn("Failed to find XInput dll; No Controller Support"); + } + + // Get Working Directory + { + TCHAR buffer[MAX_PATH]; + GetModuleFileName(NULL, buffer, MAX_PATH); + + auto normalized = Path::normalize(buffer); + auto end = normalized.last_index_of('/');; + if (end >= 0) + win32_working_directory = FilePath(normalized.begin(), normalized.begin() + end); + else + win32_working_directory = normalized; + win32_working_directory.append("/"); + } + + // Get Application User Directory + { + PWSTR path = NULL; + if (SUCCEEDED(SHGetKnownFolderPath(FOLDERID_RoamingAppData, KF_FLAG_CREATE, NULL, &path))) + { + auto end = path; + while (*end != 0) end++; + + FilePath result; + result.append((u16*)path, (u16*)end); + + win32_user_directory = Path::join(Path::normalize(result), config.name) + "/"; + } + CoTaskMemFree(path); + } + + // Reset our game timer + win32_start_time = std::chrono::system_clock::now().time_since_epoch(); + + // Not currently win32_fullscreen + win32_fullscreen = false; + + // Finished Platform Setup + return true; +} + +void Platform::ready() +{ + // Display the game window + ShowWindow(win32_hwnd, SW_SHOW); +} + +void Platform::shutdown() +{ + if (win32_xinput.dll) + FreeLibrary(win32_xinput.dll); + + if (win32_gl.dll) + FreeLibrary(win32_gl.dll); + + DestroyWindow(win32_hwnd); +} + +u64 Platform::ticks() +{ + // Todo: + // This should account for whatever Time::ticks_per_second is set to + + auto now = std::chrono::system_clock::now().time_since_epoch(); + return std::chrono::duration_cast(now - win32_start_time).count(); +} + +void Platform::update(InputState& state) +{ + // store reference to input state + bool first_update = win32_input_state == nullptr; + win32_input_state = &state; + + // if this is the first update, poll joysticks that are already connected + if (first_update) + win32_detect_joysticks(); + + // Catch & Dispatch Window Messages + MSG msg; + while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } +} + +void Platform::sleep(int milliseconds) +{ + if (milliseconds > 0) + Sleep(milliseconds); +} + +void Platform::present() +{ + if (App::renderer().type == RendererType::OpenGL) + { + HDC hdc = GetDC(win32_hwnd); + SwapBuffers(hdc); + } +} + +const char* Platform::get_title() +{ + return nullptr; +} + +void Platform::set_title(const char* title) +{ + SetWindowText(win32_hwnd, title); +} + +void Platform::get_position(int* x, int* y) +{ + RECT rect; + if (GetWindowRect(win32_hwnd, &rect)) + { + *x = rect.left; + *y = rect.top; + } +} + +void Platform::set_position(int x, int y) +{ + int w, h; + get_size(&w, &h); + SetWindowPos(win32_hwnd, NULL, x, y, w, h, 0); +} + +bool Platform::get_focused() +{ + Log::warn("App::focused not implemented for Win32 yet"); + return true; +} + +void Platform::set_app_flags(u32 flags) +{ + // toggle win32_fullscreen + { + bool enabled = (flags & Flags::Fullscreen) != 0; + if (win32_fullscreen == enabled) + return; + win32_fullscreen = enabled; + + if (win32_fullscreen) + { + GetWindowRect(win32_hwnd, &win32_windowed_position); + + int w = GetSystemMetrics(SM_CXSCREEN); + int h = GetSystemMetrics(SM_CYSCREEN); + SetWindowLongPtr(win32_hwnd, GWL_STYLE, WS_VISIBLE | WS_POPUP); + SetWindowPos(win32_hwnd, HWND_TOP, 0, 0, w, h, 0); + ShowWindow(win32_hwnd, SW_SHOW); + } + else + { + SetWindowLongPtr(win32_hwnd, GWL_STYLE, WS_OVERLAPPEDWINDOW); + SetWindowPos(win32_hwnd, HWND_TOP, + win32_windowed_position.left, + win32_windowed_position.top, + win32_windowed_position.right - win32_windowed_position.left, + win32_windowed_position.bottom - win32_windowed_position.top, 0); + ShowWindow(win32_hwnd, SW_SHOW); + } + } + + // toggle resizable + // TODO: ... +} + +void Platform::get_size(int* width, int* height) +{ + RECT rect; + if (GetClientRect(win32_hwnd, &rect)) + { + *width = rect.right - rect.left; + *height = rect.bottom - rect.top; + } +} + +void Platform::set_size(int width, int height) +{ + RECT client_rect; + RECT border_rect; + + GetClientRect(win32_hwnd, &client_rect); + GetWindowRect(win32_hwnd, &border_rect); + + int border_width = (border_rect.right - border_rect.left) - (client_rect.right - client_rect.left); + int border_height = (border_rect.bottom - border_rect.top) - (client_rect.bottom - client_rect.top); + + SetWindowPos(win32_hwnd, NULL, border_rect.left, border_rect.top, width + border_width, height + border_height, 0); +} + +void Platform::get_draw_size(int* width, int* height) +{ + RECT rect; + if (GetClientRect(win32_hwnd, &rect)) + { + *width = rect.right - rect.left; + *height = rect.bottom - rect.top; + } +} +const char* Platform::app_path() +{ + return win32_working_directory.cstr(); +} + +const char* Platform::user_path() +{ + return win32_user_directory.cstr(); +} + +bool Platform::file_exists(const char* path) +{ + return std::filesystem::is_regular_file(path); +} + +bool Platform::file_delete(const char* path) +{ + return std::filesystem::remove(path); +} + +bool Platform::dir_create(const char* path) +{ + std::error_code error; + return std::filesystem::create_directories(path, error); +} + +bool Platform::dir_exists(const char* path) +{ + return std::filesystem::is_directory(path); +} + +bool Platform::dir_delete(const char* path) +{ + return std::filesystem::remove_all(path) > 0; +} + +void Platform::dir_enumerate(Vector& list, const char* path, bool recursive) +{ + if (std::filesystem::is_directory(path)) + { + if (recursive) + { + for (auto& p : std::filesystem::recursive_directory_iterator(path)) + list.emplace_back(p.path().string().c_str()); + } + else + { + for (auto& p : std::filesystem::directory_iterator(path)) + list.emplace_back(p.path().string().c_str()); + } + } +} + +void Platform::dir_explore(const char* path) +{ + ShellExecute(NULL, "open", path, NULL, NULL, SW_SHOWDEFAULT); +} + +FileRef Platform::file_open(const char* path, FileMode mode) +{ + int access = 0; + int creation = 0; + + switch (mode) + { + case FileMode::OpenRead: + access = GENERIC_READ; + creation = OPEN_EXISTING; + break; + case FileMode::Open: + access = GENERIC_READ | GENERIC_WRITE; + creation = OPEN_EXISTING; + break; + case FileMode::CreateWrite: + access = GENERIC_WRITE; + creation = CREATE_ALWAYS; + break; + case FileMode::Create: + access = GENERIC_READ | GENERIC_WRITE; + creation = CREATE_ALWAYS; + break; + } + + auto result = CreateFile(path, access, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, creation, FILE_ATTRIBUTE_NORMAL, NULL); + + if (result == INVALID_HANDLE_VALUE) + return FileRef(); + + return FileRef(new Win32File(result)); +} + +void* Platform::gl_get_func(const char* name) +{ + // this check is taken from https://www.khronos.org/opengl/wiki/Load_OpenGL_Functions + // wglGetProcAddress doesn't always return valid pointers for some specific methods? + + void* p = (void*)win32_gl.get_proc_address(name); + if ((p == 0) || + (p == (void*)0x1) || + (p == (void*)0x2) || + (p == (void*)0x3) || + (p == (void*)-1)) + { + p = (void*)GetProcAddress(win32_gl.dll, name); + } + + return p; +} + +void* Platform::gl_context_create() +{ + HDC hdc = GetDC(win32_hwnd); + return win32_gl.create_context(hdc); +} + +void Platform::gl_context_make_current(void* context) +{ + if (context != nullptr) + { + HDC hdc = GetDC(win32_hwnd); + win32_gl.make_current(hdc, (HGLRC)context); + } + else + win32_gl.make_current(NULL, NULL); +} + +void Platform::gl_context_destroy(void* context) +{ + win32_gl.delete_context((HGLRC)context); +} + +void* Platform::d3d11_get_hwnd() +{ + return win32_hwnd; +} + +void Platform::set_clipboard(const char* text) +{ + auto len = strlen(text); + if (auto glob = GlobalAlloc(GMEM_MOVEABLE, len)) + { + if (auto data = GlobalLock(glob)) + { + memcpy(data, text, len); + GlobalUnlock(glob); + + if (OpenClipboard(nullptr)) + { + SetClipboardData(CF_TEXT, data); + CloseClipboard(); + } + } + + GlobalFree(glob); + } +} + +const char* Platform::get_clipboard() +{ + if (OpenClipboard(nullptr)) + { + HANDLE data = GetClipboardData(CF_TEXT); + if (data) + { + auto text = static_cast(GlobalLock(data)); + if (text) + win32_clipboard = text; + GlobalUnlock(data); + } + CloseClipboard(); + } + + return win32_clipboard.cstr(); +} + +void Platform::open_url(const char* url) +{ + auto cmd = String("start ") + url; + system(cmd.cstr()); +} + +void win32_detect_joysticks() +{ + // mark all joysticks as unnacounted for + for (int i = 0; i < Input::max_controllers; i++) + win32_joysticks[i].accounted = false; + + // check for xinput controllers + if (win32_xinput.dll) + { + for (DWORD index = 0; index < XUSER_MAX_COUNT; index++) + { + // can't get capabilities; not connected + XINPUT_CAPABILITIES xic; + if (win32_xinput.get_capabilities(index, 0, &xic) != ERROR_SUCCESS) + continue; + + // already connected + bool already_connected = false; + for (int i = 0; i < Input::max_controllers; i++) + { + auto& it = win32_joysticks[i]; + if (it.connected && it.dinstance == GUID_NULL && it.xindex == index) + { + it.accounted = true; + already_connected = true; + break; + } + } + + if (already_connected) + continue; + + // find an empty slot and mark connected + for (int i = 0; i < Input::max_controllers; i++) + { + auto& it = win32_joysticks[i]; + if (!it.connected) + { + it.connected = it.accounted = true; + it.dinstance = GUID_NULL; + it.xindex = index; + + Log::info("Connected XInput [%i]", i); + + // TODO: + // Get Product Info & Proper Name + win32_input_state->controllers[i].on_connect("Xbox Controller", true, 15, 6, 0, 0, 0); + + break; + } + } + } + } + + // call disconnect on joysticks that aren't accounted for + for (int i = 0; i < Input::max_controllers; i++) + { + auto& it = win32_joysticks[i]; + if (it.connected && !it.accounted) + { + Log::info("Disconnected [%i]", i); + win32_input_state->controllers[i].on_disconnect(); + it = Joystick(); + } + } +} + +LRESULT CALLBACK Blah::win32_window_procedure(HWND win32_hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) + { + case WM_CLOSE: + { + auto& config = App::config(); + if (config.on_exit_request != nullptr) + config.on_exit_request(); + return 0; + } + + case WM_DESTROY: + PostQuitMessage(0); + return 0; + + // Controller connected event + case WM_DEVICECHANGE: + { + // DBT_DEVNODES_CHANGED = 0x0007 + // https://docs.microsoft.com/en-us/windows/win32/devio/wm-devicechange + if (wParam == 0x0007) + win32_detect_joysticks(); + return 0; + } + + // Mouse Input + case WM_LBUTTONDOWN: + win32_input_state->mouse.on_press(MouseButton::Left); + return 0; + + case WM_LBUTTONUP: + win32_input_state->mouse.on_release(MouseButton::Left); + return 0; + + case WM_RBUTTONDOWN: + win32_input_state->mouse.on_press(MouseButton::Right); + return 0; + + case WM_RBUTTONUP: + win32_input_state->mouse.on_release(MouseButton::Right); + return 0; + + case WM_MBUTTONDOWN: + win32_input_state->mouse.on_press(MouseButton::Middle); + return 0; + + case WM_MBUTTONUP: + win32_input_state->mouse.on_release(MouseButton::Middle); + return 0; + + case WM_MOUSEMOVE: + win32_input_state->mouse.on_move(Vec2f((float)((u16)lParam), (float)(lParam >> 16)), Vec2f::zero); + return 0; + + case WM_MOUSEWHEEL: + win32_input_state->mouse.wheel = Point(0, GET_WHEEL_DELTA_WPARAM(wParam) / WHEEL_DELTA); + return 0; + + // Text Input + case WM_UNICHAR: + if (wParam == UNICODE_NOCHAR) + return 1; + case WM_CHAR: + { + String result; + result.append((u32)wParam); + if (result.length() > 0) + win32_input_state->keyboard.text += result.cstr(); + return 0; + } + + // Keyboard Input + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + { + auto is_repeat = ((lParam & (1 << 30)) >> 30) == 1; + if (!is_repeat) + { + auto key = Blah::win32_scancode_to_key(wParam, lParam); + if (key != Key::Unknown) + win32_input_state->keyboard.on_press(key); + } + return 0; + } + + case WM_KEYUP: + case WM_SYSKEYUP: + { + auto key = Blah::win32_scancode_to_key(wParam, lParam); + if (key != Key::Unknown) + win32_input_state->keyboard.on_release(key); + return 0; + } + } + + return DefWindowProc(win32_hwnd, msg, wParam, lParam); +} + +Blah::Key Blah::win32_scancode_to_key(WPARAM wParam, LPARAM lParam) +{ + // scancodes + switch ((lParam >> 16) & 0xFF) + { + case 1: return Key::Escape; + case 2: return Key::D1; + case 3: return Key::D2; + case 4: return Key::D3; + case 5: return Key::D4; + case 6: return Key::D5; + case 7: return Key::D6; + case 8: return Key::D7; + case 9: return Key::D8; + case 10: return Key::D9; + case 11: return Key::D0; + case 12: return Key::Minus; + case 13: return Key::Equals; + case 14: return Key::Backspace; + case 15: return Key::Tab; + case 16: return Key::Q; + case 17: return Key::W; + case 18: return Key::E; + case 19: return Key::R; + case 20: return Key::T; + case 21: return Key::Y; + case 22: return Key::U; + case 23: return Key::I; + case 24: return Key::O; + case 25: return Key::P; + case 26: return Key::LeftBracket; + case 27: return Key::RightBracket; + case 28: return Key::Enter; + case 29: return Key::LeftControl; + case 30: return Key::A; + case 31: return Key::S; + case 32: return Key::D; + case 33: return Key::F; + case 34: return Key::G; + case 35: return Key::H; + case 36: return Key::J; + case 37: return Key::K; + case 38: return Key::L; + case 39: return Key::Semicolon; + case 40: return Key::Apostrophe; + case 41: return Key::Tilde; + case 42: return Key::LeftShift; + case 43: return Key::Backslash; + case 44: return Key::Z; + case 45: return Key::X; + case 46: return Key::C; + case 47: return Key::V; + case 48: return Key::B; + case 49: return Key::N; + case 50: return Key::M; + case 51: return Key::Comma; + case 52: return Key::Period; + case 53: return Key::Slash; + case 54: return Key::RightShift; + case 55: return Key::PrintScreen; + case 56: return Key::LeftAlt; + case 57: return Key::Space; + case 58: return Key::Capslock; + case 59: return Key::F1; + case 60: return Key::F2; + case 61: return Key::F3; + case 62: return Key::F4; + case 63: return Key::F5; + case 64: return Key::F6; + case 65: return Key::F7; + case 66: return Key::F8; + case 67: return Key::F9; + case 68: return Key::F10; + case 71: return Key::Home; + case 72: return Key::Up; + case 73: return Key::PageUp; + case 74: return Key::KeypadMinus; + case 75: return Key::Left; + case 76: return Key::Keypad5; + case 77: return Key::Right; + case 78: return Key::KeypadPlus; + case 79: return Key::End; + case 80: return Key::Down; + case 81: return Key::PageDown; + case 82: return Key::Insert; + case 83: return Key::Delete; + case 87: return Key::F11; + case 88: return Key::F12; + case 89: return Key::Pause; + case 91: return Key::LeftOS; + case 92: return Key::RightOS; + case 93: return Key::Application; + case 100: return Key::F13; + case 101: return Key::F14; + case 102: return Key::F15; + case 103: return Key::F16; + case 104: return Key::F17; + case 105: return Key::F18; + case 106: return Key::F19; + } + + // virtual keys + switch (wParam) + { + case VK_CANCEL: return Key::Cancel; + case VK_BACK: return Key::Backspace; + case VK_TAB: return Key::Tab; + case VK_CLEAR: return Key::Clear; + case VK_RETURN: return Key::Enter; + case VK_SHIFT: return Key::LeftShift; + case VK_CONTROL: return Key::LeftControl; + case VK_PAUSE: return Key::Pause; + case VK_CAPITAL: return Key::Capslock; + case VK_ESCAPE: return Key::Escape; + case VK_SPACE: return Key::Space; + case VK_PRIOR: return Key::Prior; + case VK_END: return Key::End; + case VK_HOME: return Key::Home; + case VK_LEFT: return Key::Left; + case VK_UP: return Key::Up; + case VK_RIGHT: return Key::Right; + case VK_DOWN: return Key::Down; + case VK_SELECT: return Key::Select; + case VK_PRINT: return Key::PrintScreen; + case VK_EXECUTE: return Key::Execute; + case VK_SNAPSHOT: return Key::PrintScreen; + case VK_INSERT: return Key::Insert; + case VK_DELETE: return Key::Delete; + case VK_HELP: return Key::Help; + case VK_LWIN: return Key::LeftOS; + case VK_RWIN: return Key::RightOS; + case VK_APPS: return Key::Application; + case VK_SLEEP: return Key::Unknown; + case VK_NUMPAD0: return Key::Keypad0; + case VK_NUMPAD1: return Key::Keypad1; + case VK_NUMPAD2: return Key::Keypad2; + case VK_NUMPAD3: return Key::Keypad3; + case VK_NUMPAD4: return Key::Keypad4; + case VK_NUMPAD5: return Key::Keypad5; + case VK_NUMPAD6: return Key::Keypad6; + case VK_NUMPAD7: return Key::Keypad7; + case VK_NUMPAD8: return Key::Keypad8; + case VK_NUMPAD9: return Key::Keypad9; + case VK_F1: return Key::F1; + case VK_F2: return Key::F2; + case VK_F3: return Key::F3; + case VK_F4: return Key::F4; + case VK_F5: return Key::F5; + case VK_F6: return Key::F6; + case VK_F7: return Key::F7; + case VK_F8: return Key::F8; + case VK_F9: return Key::F9; + case VK_F10: return Key::F10; + case VK_F11: return Key::F11; + case VK_F12: return Key::F12; + case VK_F13: return Key::F13; + case VK_F14: return Key::F14; + case VK_F15: return Key::F15; + case VK_F16: return Key::F16; + case VK_F17: return Key::F17; + case VK_F18: return Key::F18; + case VK_F19: return Key::F19; + case VK_F20: return Key::F20; + case VK_F21: return Key::F21; + case VK_F22: return Key::F22; + case VK_F23: return Key::F23; + case VK_F24: return Key::F24; + case VK_NUMLOCK: return Key::Numlock; + case VK_LSHIFT: return Key::LeftShift; + case VK_RSHIFT: return Key::RightShift; + case VK_LCONTROL: return Key::LeftControl; + case VK_RCONTROL: return Key::RightControl; + case VK_VOLUME_MUTE: return Key::Mute; + case VK_VOLUME_DOWN: return Key::VolumeDown; + case VK_VOLUME_UP: return Key::VolumeUp; + } + + return Key::Unknown; +} + +#endif // BLAH_PLATFORM_WIN32