diff options
Diffstat (limited to 'src/platform_win')
-rw-r--r-- | src/platform_win/CMakeLists.txt | 10 | ||||
-rw-r--r-- | src/platform_win/debug.cpp | 10 | ||||
-rw-r--r-- | src/platform_win/exception.cpp | 50 | ||||
-rw-r--r-- | src/platform_win/god_window.cpp | 70 | ||||
-rw-r--r-- | src/platform_win/god_window_message.hpp | 6 | ||||
-rw-r--r-- | src/platform_win/timer.cpp | 28 | ||||
-rw-r--r-- | src/platform_win/timer.hpp | 34 | ||||
-rw-r--r-- | src/platform_win/win_application.cpp | 70 | ||||
-rw-r--r-- | src/platform_win/window_class.cpp | 28 |
9 files changed, 306 insertions, 0 deletions
diff --git a/src/platform_win/CMakeLists.txt b/src/platform_win/CMakeLists.txt new file mode 100644 index 00000000..fbcf1c00 --- /dev/null +++ b/src/platform_win/CMakeLists.txt @@ -0,0 +1,10 @@ +add_library(cru_platform_win STATIC + debug.cpp + exception.cpp + god_window.cpp + timer.cpp + win_application.cpp + window_class.cpp) +target_include_directories(cru_platform_win PUBLIC ${PROJECT_SOURCE_DIR}/include .) +target_link_libraries(cru_platform_win PRIVATE D3D11 D2d1 DWrite) +target_compile_definitions(cru_platform_win PUBLIC UNICODE _UNICODE) # use unicode diff --git a/src/platform_win/debug.cpp b/src/platform_win/debug.cpp new file mode 100644 index 00000000..cdff7963 --- /dev/null +++ b/src/platform_win/debug.cpp @@ -0,0 +1,10 @@ +#include "cru/platform/win/win_pre_config.hpp" + +#include "cru/platform/debug.hpp" + + +namespace cru::debug { +void DebugMessage(const std::wstring_view& message) { + ::OutputDebugStringW(message.data()); +} +} // namespace cru::debug diff --git a/src/platform_win/exception.cpp b/src/platform_win/exception.cpp new file mode 100644 index 00000000..3db88b8b --- /dev/null +++ b/src/platform_win/exception.cpp @@ -0,0 +1,50 @@ +#include "cru/platform/win/exception.hpp" + +#include "cru/common/format.hpp" + +namespace cru::platform::win { +using util::Format; + +inline std::string HResultMakeMessage(HRESULT h_result, + const std::string_view* message) { + char buffer[10]; + sprintf_s(buffer, "%#08x", h_result); + + if (message) + return Format( + "An HResultError is thrown. HRESULT: {}.\nAdditional message: {}\n", + buffer, *message); + else + return Format("An HResultError is thrown. HRESULT: {}.\n", buffer); +} + +HResultError::HResultError(HRESULT h_result) + : runtime_error(HResultMakeMessage(h_result, nullptr)), + h_result_(h_result) {} + +HResultError::HResultError(HRESULT h_result, + const std::string_view& additional_message) + : runtime_error(HResultMakeMessage(h_result, &additional_message)), + h_result_(h_result) {} + +inline std::string Win32MakeMessage(DWORD error_code, + const std::string_view* message) { + char buffer[10]; + sprintf_s(buffer, "%#04x", error_code); + + if (message) + return Format("Last error code: {}.\nAdditional message: {}\n", buffer, + *message); + else + return Format("Last error code: {}.\n", buffer); +} + +Win32Error::Win32Error(DWORD error_code) + : runtime_error(Win32MakeMessage(error_code, nullptr)), + error_code_(error_code) {} + +Win32Error::Win32Error(DWORD error_code, + const std::string_view& additional_message) + : runtime_error(Win32MakeMessage(error_code, &additional_message)), + error_code_(error_code) {} +} // namespace cru::platform::win diff --git a/src/platform_win/god_window.cpp b/src/platform_win/god_window.cpp new file mode 100644 index 00000000..2b4fbe48 --- /dev/null +++ b/src/platform_win/god_window.cpp @@ -0,0 +1,70 @@ +#include "cru/platform/win/god_window.hpp" + +#include "cru/platform/win/exception.hpp" +#include "cru/platform/win/win_application.hpp" +#include "cru/platform/win/window_class.hpp" +#include "god_window_message.hpp" +#include "timer.hpp" + +namespace cru::platform::win { +constexpr auto god_window_class_name = L"GodWindowClass"; + +LRESULT CALLBACK GodWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, + LPARAM lParam) { + const auto app = WinApplication::GetInstance(); + + if (app) { + const auto result = + app->GetGodWindow()->HandleGodWindowMessage(hWnd, uMsg, wParam, lParam); + if (result.has_value()) + return result.value(); + else + return DefWindowProcW(hWnd, uMsg, wParam, lParam); + } else + return DefWindowProcW(hWnd, uMsg, wParam, lParam); +} + +GodWindow::GodWindow(WinApplication* application) { + application_ = application; + + const auto h_instance = application->GetInstanceHandle(); + + god_window_class_ = std::make_shared<WindowClass>(god_window_class_name, + GodWndProc, h_instance); + + hwnd_ = CreateWindowEx(0, god_window_class_name, L"", 0, CW_USEDEFAULT, + CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, + HWND_MESSAGE, nullptr, h_instance, nullptr); + + if (hwnd_ == nullptr) throw Win32Error(::GetLastError(), "Failed to create god window."); +} + +GodWindow::~GodWindow() { ::DestroyWindow(hwnd_); } + +std::optional<LRESULT> GodWindow::HandleGodWindowMessage(HWND hwnd, int msg, + WPARAM w_param, + LPARAM l_param) { + switch (msg) { + case invoke_later_message_id: { + const auto p_action = reinterpret_cast<std::function<void()>*>(w_param); + (*p_action)(); + delete p_action; + return 0; + } + case WM_TIMER: { + const auto id = static_cast<UINT_PTR>(w_param); + const auto action = application_->GetTimerManager()->GetAction(id); + if (action.has_value()) { + (action.value().second)(); + if (!action.value().first) + application_->GetTimerManager()->KillTimer(id); + return 0; + } + break; + } + default: + return std::nullopt; + } + return std::nullopt; +} +} // namespace cru::platform::win
\ No newline at end of file diff --git a/src/platform_win/god_window_message.hpp b/src/platform_win/god_window_message.hpp new file mode 100644 index 00000000..a906a3b7 --- /dev/null +++ b/src/platform_win/god_window_message.hpp @@ -0,0 +1,6 @@ +#pragma once +#include "cru/platform/win/win_pre_config.hpp" + +namespace cru::platform::win { +constexpr int invoke_later_message_id = WM_USER + 2000; +} diff --git a/src/platform_win/timer.cpp b/src/platform_win/timer.cpp new file mode 100644 index 00000000..280d1aed --- /dev/null +++ b/src/platform_win/timer.cpp @@ -0,0 +1,28 @@ +#include "timer.hpp" + +namespace cru::platform::win { +TimerManager::TimerManager(GodWindow* god_window) { god_window_ = god_window; } + +UINT_PTR TimerManager::CreateTimer(const UINT milliseconds, const bool loop, + const TimerAction& action) { + const auto id = current_count_++; + ::SetTimer(god_window_->GetHandle(), id, milliseconds, nullptr); + map_.emplace(id, std::make_pair(loop, action)); + return id; +} + +void TimerManager::KillTimer(const UINT_PTR id) { + const auto find_result = map_.find(id); + if (find_result != map_.cend()) { + ::KillTimer(god_window_->GetHandle(), id); + map_.erase(find_result); + } +} + +std::optional<std::pair<bool, TimerAction>> TimerManager::GetAction( + const UINT_PTR id) { + const auto find_result = map_.find(id); + if (find_result == map_.cend()) return std::nullopt; + return find_result->second; +} +} // namespace cru::platform::win diff --git a/src/platform_win/timer.hpp b/src/platform_win/timer.hpp new file mode 100644 index 00000000..95468b8d --- /dev/null +++ b/src/platform_win/timer.hpp @@ -0,0 +1,34 @@ +#pragma once +#include "cru/platform/win/win_pre_config.hpp" + +#include <chrono> +#include <functional> +#include <map> +#include <optional> + +#include "cru/common/base.hpp" +#include "cru/platform/win/god_window.hpp" + +namespace cru::platform::win { +using TimerAction = std::function<void()>; + +class TimerManager : public Object { + public: + TimerManager(GodWindow* god_window); + TimerManager(const TimerManager& other) = delete; + TimerManager(TimerManager&& other) = delete; + TimerManager& operator=(const TimerManager& other) = delete; + TimerManager& operator=(TimerManager&& other) = delete; + ~TimerManager() override = default; + + UINT_PTR CreateTimer(UINT milliseconds, bool loop, const TimerAction& action); + void KillTimer(UINT_PTR id); + std::optional<std::pair<bool, TimerAction>> GetAction(UINT_PTR id); + + private: + GodWindow* god_window_; + + std::map<UINT_PTR, std::pair<bool, TimerAction>> map_{}; + UINT_PTR current_count_ = 0; +}; +} // namespace cru::platform::win diff --git a/src/platform_win/win_application.cpp b/src/platform_win/win_application.cpp new file mode 100644 index 00000000..a90f509c --- /dev/null +++ b/src/platform_win/win_application.cpp @@ -0,0 +1,70 @@ +#include "cru/platform/win/win_application.hpp" + +#include <VersionHelpers.h> + +#include "cru/platform/win/exception.hpp" +#include "cru/platform/win/god_window.hpp" +#include "god_window_message.hpp" +#include "timer.hpp" + +namespace cru::platform::win { +WinApplication* WinApplication::instance_ = nullptr; + +WinApplication* WinApplication::GetInstance() { + if (instance_ == nullptr) + instance_ = new WinApplication(::GetModuleHandleW(nullptr)); + return instance_; +} + +WinApplication::WinApplication(HINSTANCE h_instance) : h_instance_(h_instance) { + if (instance_) + throw std::runtime_error("A application instance already exists."); + + instance_ = this; + + if (!::IsWindows8OrGreater()) + throw std::runtime_error("Must run on Windows 8 or later."); + + god_window_ = std::make_shared<GodWindow>(this); + timer_manager_ = std::make_shared<TimerManager>(god_window_.get()); +} + +WinApplication::~WinApplication() { instance_ = nullptr; } + +int WinApplication::Run() { + MSG msg; + while (GetMessageW(&msg, nullptr, 0, 0)) { + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + return static_cast<int>(msg.wParam); +} + +void WinApplication::Quit(const int quit_code) { ::PostQuitMessage(quit_code); } + +void WinApplication::InvokeLater(const std::function<void()>& action) { + // copy the action to a safe place + auto p_action_copy = new std::function<void()>(action); + + if (PostMessageW(GetGodWindow()->GetHandle(), invoke_later_message_id, + reinterpret_cast<WPARAM>(p_action_copy), 0) == 0) + throw Win32Error(::GetLastError(), "InvokeLater failed to post message."); +} + +unsigned long WinApplication::SetTimeout(std::chrono::milliseconds milliseconds, + const std::function<void()>& action) { + return static_cast<unsigned long>(timer_manager_->CreateTimer( + static_cast<UINT>(milliseconds.count()), false, action)); +} + +unsigned long WinApplication::SetInterval( + std::chrono::milliseconds milliseconds, + const std::function<void()>& action) { + return static_cast<unsigned long>(timer_manager_->CreateTimer( + static_cast<UINT>(milliseconds.count()), true, action)); +} + +void WinApplication::CancelTimer(unsigned long id) { + timer_manager_->KillTimer(static_cast<UINT_PTR>(id)); +} +} // namespace cru::platform::win diff --git a/src/platform_win/window_class.cpp b/src/platform_win/window_class.cpp new file mode 100644 index 00000000..b58f53b2 --- /dev/null +++ b/src/platform_win/window_class.cpp @@ -0,0 +1,28 @@ +#include "cru/platform/win/window_class.hpp" + +#include "cru/platform/win/exception.hpp" + +namespace cru::platform::win { +WindowClass::WindowClass(const std::wstring& name, WNDPROC window_proc, + HINSTANCE h_instance) + : name_(name) { + WNDCLASSEX window_class; + window_class.cbSize = sizeof(WNDCLASSEX); + + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.lpfnWndProc = window_proc; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = h_instance; + window_class.hIcon = LoadIcon(NULL, IDI_APPLICATION); + window_class.hCursor = LoadCursor(NULL, IDC_ARROW); + window_class.hbrBackground = GetSysColorBrush(COLOR_BTNFACE); + window_class.lpszMenuName = NULL; + window_class.lpszClassName = name.c_str(); + window_class.hIconSm = NULL; + + atom_ = ::RegisterClassExW(&window_class); + if (atom_ == 0) + throw Win32Error(::GetLastError(), "Failed to create window class."); +} +} // namespace cru::ui |