diff options
author | crupest <crupest@outlook.com> | 2019-03-31 17:14:47 +0800 |
---|---|---|
committer | crupest <crupest@outlook.com> | 2019-03-31 17:14:47 +0800 |
commit | fbfd90255731954fb80483f4ba7188d3611fafec (patch) | |
tree | 8e3283c911d7aec76130d6a1dc7f5d8a85512b59 | |
parent | 877f65e2e2c40eecc7cfeb194dc9d391af60711b (diff) | |
download | cru-fbfd90255731954fb80483f4ba7188d3611fafec.tar.gz cru-fbfd90255731954fb80483f4ba7188d3611fafec.tar.bz2 cru-fbfd90255731954fb80483f4ba7188d3611fafec.zip |
...
31 files changed, 595 insertions, 528 deletions
diff --git a/include/cru/common/base.hpp b/include/cru/common/base.hpp new file mode 100644 index 00000000..7dfe8240 --- /dev/null +++ b/include/cru/common/base.hpp @@ -0,0 +1,18 @@ +#pragma once +#include "pre_config.hpp" + +namespace cru { +class Object { + public: + Object() = default; + Object(const Object&) = default; + Object& operator=(const Object&) = default; + Object(Object&&) = default; + Object& operator=(Object&&) = default; + virtual ~Object() = default; +}; + +struct Interface { + virtual ~Interface() = default; +}; +} // namespace cru diff --git a/include/cru/common/format.hpp b/include/cru/common/format.hpp new file mode 100644 index 00000000..1fb6863a --- /dev/null +++ b/include/cru/common/format.hpp @@ -0,0 +1,104 @@ +#pragma once +#include "pre_config.hpp" + +#include <string> +#include <string_view> + +namespace cru::util { +namespace details { +template <typename T> +struct TypeTag {}; + +constexpr std::wstring_view PlaceHolder(TypeTag<std::wstring>) { + return std::wstring_view(L"{}"); +} + +constexpr std::string_view PlaceHolder(TypeTag<std::string>) { + return std::string_view("{}"); +} + +template <typename TString> +void FormatInternal(TString& string) { + const auto find_result = string.find(PlaceHolder(TypeTag<TString>{})); + if (find_result != TString::npos) + throw std::invalid_argument("There is more placeholders than args."); +} + +template <typename TString, typename T, typename... TRest> +void FormatInternal(TString& string, const T& arg, const TRest&... args) { + const auto find_result = string.find(PlaceHolder(TypeTag<TString>{})); + if (find_result == TString::npos) + throw std::invalid_argument("There is less placeholders than args."); + + string.replace(find_result, 2, FormatToString(arg, TypeTag<TString>{})); + FormatInternal<TString>(string, args...); +} +} // namespace details + +template <typename... T> +std::wstring Format(const std::wstring_view& format, const T&... args) { + std::wstring result(format); + details::FormatInternal<std::wstring>(result, args...); + return result; +} + +template <typename... T> +std::string Format(const std::string_view& format, const T&... args) { + std::string result(format); + details::FormatInternal<std::string>(result, args...); + return result; +} + +#define CRU_FORMAT_NUMBER(type) \ + inline std::string FormatToString(const type number, \ + details::TypeTag<std::string>) { \ + return std::to_string(number); \ + } \ + inline std::wstring FormatToString(const type number, \ + details::TypeTag<std::wstring>) { \ + return std::to_wstring(number); \ + } + +CRU_FORMAT_NUMBER(int) +CRU_FORMAT_NUMBER(short) +CRU_FORMAT_NUMBER(long) +CRU_FORMAT_NUMBER(long long) +CRU_FORMAT_NUMBER(unsigned int) +CRU_FORMAT_NUMBER(unsigned short) +CRU_FORMAT_NUMBER(unsigned long) +CRU_FORMAT_NUMBER(unsigned long long) +CRU_FORMAT_NUMBER(float) +CRU_FORMAT_NUMBER(double) + +#undef CRU_FORMAT_NUMBER + +inline std::wstring_view FormatToString(const std::wstring& string, + details::TypeTag<std::wstring>) { + return string; +} + +inline std::string_view FormatToString(const std::string& string, + details::TypeTag<std::string>) { + return string; +} + +inline std::wstring_view FormatToString(const std::wstring_view& string, + details::TypeTag<String>) { + return string; +} + +inline std::string_view FormatToString(const std::string_view& string, + details::TypeTag<std::string>) { + return string; +} + +inline std::wstring_view FormatToString(const wchar_t* string, + details::TypeTag<String>) { + return std::wstring_view(string); +} + +inline std::string_view FormatToString(const char* string, + details::TypeTag<std::string>) { + return std::string(string); +} +} // namespace cru::util diff --git a/include/cru/common/pre_config.hpp b/include/cru/common/pre_config.hpp new file mode 100644 index 00000000..aa4d680b --- /dev/null +++ b/include/cru/common/pre_config.hpp @@ -0,0 +1,5 @@ +#pragma once + +#ifdef _DEBUG +#define CRU_DEBUG +#endif diff --git a/src/ui/ui_base.hpp b/include/cru/common/ui_base.hpp index ba6c8b9a..42ed0eb4 100644 --- a/src/ui/ui_base.hpp +++ b/include/cru/common/ui_base.hpp @@ -1,12 +1,11 @@ #pragma once -#include "pre.hpp" +#include "pre_config.hpp" +#include <utility> #include <optional> namespace cru::ui { struct Point final { - constexpr static Point Zero() { return Point(0, 0); } - constexpr Point() = default; constexpr Point(const float x, const float y) : x(x), y(y) {} @@ -23,8 +22,6 @@ constexpr bool operator!=(const Point& left, const Point& right) { } struct Size final { - constexpr static Size Zero() { return Size(0, 0); } - constexpr Size() = default; constexpr Size(const float width, const float height) : width(width), height(height) {} @@ -50,8 +47,6 @@ constexpr bool operator!=(const Size& left, const Size& right) { } struct Thickness final { - constexpr static Thickness Zero() { return Thickness(0); } - constexpr Thickness() : Thickness(0) {} constexpr explicit Thickness(const float width) @@ -64,9 +59,9 @@ struct Thickness final { const float bottom) : left(left), top(top), right(right), bottom(bottom) {} - float GetHorizontalTotal() const { return left + right; } + constexpr float GetHorizontalTotal() const { return left + right; } - float GetVerticalTotal() const { return top + bottom; } + constexpr float GetVerticalTotal() const { return top + bottom; } void SetLeftRight(const float value) { left = right = value; } @@ -74,7 +69,7 @@ struct Thickness final { void SetAll(const float value) { left = top = right = bottom = value; } - float Validate() const { + constexpr float Validate() const { return left >= 0.0 && top >= 0.0 && right >= 0.0 && bottom >= 0.0; } diff --git a/include/cru/platform/debug.hpp b/include/cru/platform/debug.hpp new file mode 100644 index 00000000..24759ee1 --- /dev/null +++ b/include/cru/platform/debug.hpp @@ -0,0 +1,8 @@ +#pragma once +#include "cru/common/pre_config.hpp" + +#include <string_view> + +namespace cru::platform::debug { +void DebugMessage(const std::string_view& message); +} diff --git a/include/cru/platform/native_window.hpp b/include/cru/platform/native_window.hpp new file mode 100644 index 00000000..1ed9a25e --- /dev/null +++ b/include/cru/platform/native_window.hpp @@ -0,0 +1,31 @@ +#pragma once +#include "cru/common/base.hpp" +#include "cru/common/ui_base.hpp" + +namespace cru::platform { +struct Painter; + +struct NativeWindow : public virtual Interface { + virtual bool IsValid() = 0; + + virtual void Close() = 0; + + virtual NativeWindow* GetParent() = 0; + + virtual bool IsVisible() const = 0; + virtual void SetVisible(bool is_visible) = 0; + + virtual ui::Size GetClientSize() = 0; + virtual void SetClientSize(const ui::Size& size) = 0; + + // Get the rect of the window containing frame. + // The lefttop of the rect is relative to screen lefttop. + virtual ui::Rect GetWindowRect() = 0; + + // Set the rect of the window containing frame. + // The lefttop of the rect is relative to screen lefttop. + virtual void SetWindowRect(const ui::Rect& rect) = 0; + + virtual Painter* GetPainter() = 0; +}; +} // namespace cru::platform diff --git a/include/cru/platform/ui_applicaition.hpp b/include/cru/platform/ui_applicaition.hpp new file mode 100644 index 00000000..1345d38c --- /dev/null +++ b/include/cru/platform/ui_applicaition.hpp @@ -0,0 +1,28 @@ +#pragma once +#include "cru/common/base.hpp" + +#include <chrono> +#include <functional> + +namespace cru::platform { +struct NativeWindow; +struct GraphFactory; + +struct UiApplication : public virtual Interface { + static UiApplication* GetInstance(); + + virtual int Run() = 0; + virtual void Quit(int quite_code) = 0; + + virtual void InvokeLater(const std::function<void()>& action) = 0; + virtual unsigned long SetTimeout(std::chrono::milliseconds milliseconds, + const std::function<void()>& action) = 0; + virtual unsigned long SetInterval(std::chrono::milliseconds milliseconds, + const std::function<void()>& action) = 0; + virtual void CancelTimer(unsigned long id) = 0; + + virtual NativeWindow* CreateWindow() = 0; + + virtual GraphFactory* GetGraphFactory() = 0; +}; +} // namespace cru::platform diff --git a/src/exception.hpp b/include/cru/platform/win/exception.hpp index ade51d54..01b139b4 100644 --- a/src/exception.hpp +++ b/include/cru/platform/win/exception.hpp @@ -1,17 +1,15 @@ #pragma once -#include "pre.hpp" +#include "win_pre_config.hpp" -#include <Windows.h> -#include <optional> +#include <stdexcept> +#include <string_view> -#include "base.hpp" - -namespace cru { +namespace cru::platform::win { class HResultError : public std::runtime_error { public: - explicit HResultError( - HRESULT h_result, - std::optional<MultiByteStringView> additional_message = std::nullopt); + explicit HResultError(HRESULT h_result); + explicit HResultError(HRESULT h_result, + const std::string_view& additional_message); HResultError(const HResultError& other) = default; HResultError(HResultError&& other) = default; HResultError& operator=(const HResultError& other) = default; @@ -29,15 +27,14 @@ inline void ThrowIfFailed(const HRESULT h_result) { } inline void ThrowIfFailed(const HRESULT h_result, - const MultiByteStringView& message) { + const std::string_view& message) { if (FAILED(h_result)) throw HResultError(h_result, message); } class Win32Error : public std::runtime_error { public: - explicit Win32Error( - DWORD error_code, - std::optional<MultiByteStringView> additional_message = std::nullopt); + explicit Win32Error(DWORD error_code); + Win32Error(DWORD error_code, const std::string_view& additional_message); Win32Error(const Win32Error& other) = default; Win32Error(Win32Error&& other) = default; Win32Error& operator=(const Win32Error& other) = default; @@ -49,4 +46,4 @@ class Win32Error : public std::runtime_error { private: DWORD error_code_; }; -} // namespace cru +} // namespace cru::platform::win diff --git a/include/cru/platform/win/god_window.hpp b/include/cru/platform/win/god_window.hpp new file mode 100644 index 00000000..534dfedb --- /dev/null +++ b/include/cru/platform/win/god_window.hpp @@ -0,0 +1,33 @@ +#pragma once +#include "win_pre_config.hpp" + +#include <memory> +#include <optional> + +#include "cru/common/base.hpp" + +namespace cru::platform::win { +class WinApplication; +class WindowClass; + +class GodWindow : public Object { + public: + explicit GodWindow(WinApplication* application); + GodWindow(const GodWindow& other) = delete; + GodWindow(GodWindow&& other) = delete; + GodWindow& operator=(const GodWindow& other) = delete; + GodWindow& operator=(GodWindow&& other) = delete; + ~GodWindow() override; + + HWND GetHandle() const { return hwnd_; } + + std::optional<LRESULT> HandleGodWindowMessage(HWND hwnd, int msg, + WPARAM w_param, LPARAM l_param); + + private: + WinApplication* application_; + + std::shared_ptr<WindowClass> god_window_class_; + HWND hwnd_; +}; +} // namespace cru::platform::win
\ No newline at end of file diff --git a/include/cru/platform/win/win_application.hpp b/include/cru/platform/win/win_application.hpp new file mode 100644 index 00000000..363ae170 --- /dev/null +++ b/include/cru/platform/win/win_application.hpp @@ -0,0 +1,53 @@ +#pragma once +#include "win_pre_config.hpp" + +#include "../ui_applicaition.hpp" +#include "cru/common/base.hpp" + +#include <memory> + +namespace cru::platform::win { +class GodWindow; +class TimerManager; + +class WinApplication : public Object, public virtual UiApplication { + public: + static WinApplication* GetInstance(); + + private: + static WinApplication* instance_; + + private: + explicit WinApplication(HINSTANCE h_instance); + + public: + WinApplication(const WinApplication&) = delete; + WinApplication(WinApplication&&) = delete; + WinApplication& operator=(const WinApplication&) = delete; + WinApplication& operator=(WinApplication&&) = delete; + ~WinApplication() override; + + public: + int Run() override; + void Quit(int quit_code) override; + + void InvokeLater(const std::function<void()>& action) override; + unsigned long SetTimeout(std::chrono::milliseconds milliseconds, + const std::function<void()>& action) override; + unsigned long SetInterval(std::chrono::milliseconds milliseconds, + const std::function<void()>& action) override; + void CancelTimer(unsigned long id) override; + + HINSTANCE GetInstanceHandle() const { return h_instance_; } + + GodWindow* GetGodWindow() const { return god_window_.get(); } + + TimerManager* GetTimerManager() const; + + private: + HINSTANCE h_instance_; + + std::shared_ptr<GodWindow> god_window_; + std::shared_ptr<TimerManager> timer_manager_; +}; +} // namespace cru::platform::win diff --git a/include/cru/platform/win/win_pre_config.hpp b/include/cru/platform/win/win_pre_config.hpp new file mode 100644 index 00000000..2e8bb80e --- /dev/null +++ b/include/cru/platform/win/win_pre_config.hpp @@ -0,0 +1,6 @@ +#include "cru/common/pre_config.hpp" + +#define NOMINMAX +#define WIN32_LEAN_AND_MEAN +#include <Windows.h> +#undef CreateWindow diff --git a/src/ui/window_class.hpp b/include/cru/platform/win/window_class.hpp index 72a7c431..be79af13 100644 --- a/src/ui/window_class.hpp +++ b/include/cru/platform/win/window_class.hpp @@ -1,14 +1,14 @@ #pragma once -#include "pre.hpp" +#include "win_pre_config.hpp" -#include <Windows.h> +#include "cru/common/base.hpp" -#include "base.hpp" +#include <string> -namespace cru::ui { +namespace cru::platform::win { class WindowClass : public Object { public: - WindowClass(const String& name, WNDPROC window_proc, HINSTANCE h_instance); + WindowClass(const std::wstring& name, WNDPROC window_proc, HINSTANCE h_instance); WindowClass(const WindowClass& other) = delete; WindowClass(WindowClass&& other) = delete; WindowClass& operator=(const WindowClass& other) = delete; @@ -20,7 +20,7 @@ class WindowClass : public Object { ATOM GetAtom() const { return atom_; } private: - String name_; + std::wstring name_; ATOM atom_; }; } // namespace cru::ui diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b89e5202..5048020d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,8 +1,4 @@ add_library(cru_ui STATIC - application.cpp - cru_debug.cpp - exception.cpp - timer.cpp graph/graph_manager.cpp graph/window_render_target.cpp ui/content_control.cpp @@ -11,7 +7,6 @@ add_library(cru_ui STATIC ui/layout_control.cpp ui/no_child_control.cpp ui/ui_manager.cpp - ui/window_class.cpp ui/window.cpp ui/controls/button.cpp ui/controls/flex_layout.cpp @@ -23,14 +18,15 @@ add_library(cru_ui STATIC ui/render/window_render_object.cpp util/string_util.cpp) - -target_include_directories(cru_ui PUBLIC .) +target_include_directories(cru_ui PUBLIC ${PROJECT_SOURCE_DIR}/include .) if(WIN32) -target_link_libraries(cru_ui PRIVATE D3D11 D2d1 DWrite) -target_compile_definitions(cru_ui PUBLIC UNICODE _UNICODE) # use unicode +add_subdirectory(platform_win) +target_link_libraries(cru_ui PUBLIC cru_platform_win) endif() + + add_executable(demo WIN32 main.cpp) target_link_libraries(demo PRIVATE cru_ui) diff --git a/src/application.cpp b/src/application.cpp deleted file mode 100644 index aafca6fe..00000000 --- a/src/application.cpp +++ /dev/null @@ -1,113 +0,0 @@ -#include "application.hpp" - -#include <VersionHelpers.h> - -#include "exception.hpp" -#include "timer.hpp" -#include "ui/window_class.hpp" - -namespace cru { -constexpr auto god_window_class_name = L"GodWindowClass"; -constexpr int invoke_later_message_id = WM_USER + 2000; - -LRESULT CALLBACK GodWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, - LPARAM lParam) { - const auto app = Application::GetInstance(); - - if (app) { - const auto result = - app->GetGodWindow()->HandleGodWindowMessage(hWnd, uMsg, wParam, lParam); - if (result.has_value()) - return result.value(); - else - return DefWindowProc(hWnd, uMsg, wParam, lParam); - } else - return DefWindowProc(hWnd, uMsg, wParam, lParam); -} - -GodWindow::GodWindow(Application* application) { - const auto h_instance = application->GetInstanceHandle(); - - god_window_class_ = std::make_unique<ui::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 std::runtime_error("Failed to create 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 = TimerManager::GetInstance()->GetAction(id); - if (action.has_value()) { - (action.value().second)(); - if (!action.value().first) TimerManager::GetInstance()->KillTimer(id); - return 0; - } - break; - } - default: - return std::nullopt; - } - return std::nullopt; -} - -Application* Application::instance_ = nullptr; - -Application* Application::GetInstance() { return instance_; } - -Application::Application(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_unique<GodWindow>(this); -} - -Application::~Application() { - for (auto i = singleton_list_.crbegin(); i != singleton_list_.crend(); ++i) - delete *i; - instance_ = nullptr; -} - -int Application::Run() { - MSG msg; - - while (GetMessage(&msg, nullptr, 0, 0)) { - TranslateMessage(&msg); - DispatchMessage(&msg); - } - - return static_cast<int>(msg.wParam); -} - -void Application::Quit(const int quit_code) { ::PostQuitMessage(quit_code); } - -void InvokeLater(const std::function<void()>& action) { - // copy the action to a safe place - auto p_action_copy = new std::function<void()>(action); - - if (PostMessageW(Application::GetInstance()->GetGodWindow()->GetHandle(), - invoke_later_message_id, - reinterpret_cast<WPARAM>(p_action_copy), 0) == 0) - throw Win32Error(::GetLastError(), "InvokeLater failed to post message."); -} -} // namespace cru diff --git a/src/base.hpp b/src/base.hpp deleted file mode 100644 index e3dfc1ee..00000000 --- a/src/base.hpp +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once -#include "pre.hpp" - -#include <cassert> -#include <chrono> -#include <stdexcept> -#include <string> -#include <string_view> - -namespace cru { -// typedefs -using String = std::wstring; -using MultiByteString = std::string; - -using StringView = std::wstring_view; -using MultiByteStringView = std::string_view; - -using FloatSecond = std::chrono::duration<double, std::chrono::seconds::period>; - -enum class FlowControl { Continue, Break }; - -class Object { - public: - Object() = default; - Object(const Object&) = default; - Object& operator=(const Object&) = default; - Object(Object&&) = default; - Object& operator=(Object&&) = default; - virtual ~Object() = default; -}; - -struct Interface { - virtual ~Interface() = default; -}; - -[[noreturn]] inline void UnreachableCode() { - throw std::logic_error("Unreachable code."); -} -} // namespace cru diff --git a/src/cru_debug.cpp b/src/cru_debug.cpp deleted file mode 100644 index 81945227..00000000 --- a/src/cru_debug.cpp +++ /dev/null @@ -1,11 +0,0 @@ -#include "cru_debug.hpp" - -#include <Windows.h> - -namespace cru::debug { -#ifdef CRU_DEBUG -void DebugMessage(const StringView& message) { - ::OutputDebugStringW(message.data()); -} -#endif -} // namespace cru::debug diff --git a/src/cru_debug.hpp b/src/cru_debug.hpp deleted file mode 100644 index 58431d56..00000000 --- a/src/cru_debug.hpp +++ /dev/null @@ -1,50 +0,0 @@ -#pragma once -#include "pre.hpp" - -#include <functional> - -#include "base.hpp" -#include "util/format.hpp" - -namespace cru::debug { -#ifdef CRU_DEBUG -void DebugMessage(const StringView& message); -#else -inline void DebugMessage(const StringView& message) {} -#endif - -#ifdef CRU_DEBUG -inline void DebugTime(const std::function<void()>& action, - const StringView& hint_message) { - const auto before = std::chrono::steady_clock::now(); - action(); - const auto after = std::chrono::steady_clock::now(); - const auto duration = - std::chrono::duration_cast<std::chrono::milliseconds>(after - before); - DebugMessage(util::Format(L"{}: {}ms.\n", hint_message, duration.count())); -} - -template <typename TReturn> -TReturn DebugTime(const std::function<TReturn()>& action, - const StringView& hint_message) { - const auto before = std::chrono::steady_clock::now(); - auto&& result = action(); - const auto after = std::chrono::steady_clock::now(); - const auto duration = - std::chrono::duration_cast<std::chrono::milliseconds>(after - before); - DebugMessage(util::Format(L"{}: {}ms.\n", hint_message, duration.count())); - return std::move(result); -} -#else -inline void DebugTime(const std::function<void()>& action, - const StringView& hint_message) { - action(); -} - -template <typename TReturn> -TReturn DebugTime(const std::function<TReturn()>& action, - const StringView& hint_message) { - return action(); -} -#endif -} // namespace cru::debug diff --git a/src/exception.cpp b/src/exception.cpp deleted file mode 100644 index dbc98453..00000000 --- a/src/exception.cpp +++ /dev/null @@ -1,42 +0,0 @@ -#include "exception.hpp" - -#include "util/format.hpp" - -namespace cru { -using util::Format; - -inline std::string HResultMakeMessage( - HRESULT h_result, std::optional<MultiByteStringView> message) { - char buffer[10]; - sprintf_s(buffer, "%#08x", h_result); - - if (message.has_value()) - return Format( - "An HResultError is thrown. HRESULT: {}.\nAdditional message: {}\n", - buffer, message.value()); - else - return Format("An HResultError is thrown. HRESULT: {}.\n", buffer); -} - -HResultError::HResultError( - HRESULT h_result, std::optional<MultiByteStringView> additional_message) - : runtime_error(HResultMakeMessage(h_result, std::nullopt)), - h_result_(h_result) {} - -inline std::string Win32MakeMessage( - DWORD error_code, std::optional<MultiByteStringView> message) { - char buffer[10]; - sprintf_s(buffer, "%#04x", error_code); - - if (message.has_value()) - return Format("Last error code: {}.\nAdditional message: {}\n", buffer, - message.value()); - else - return Format("Last error code: {}.\n", buffer); -} - -Win32Error::Win32Error(DWORD error_code, - std::optional<MultiByteStringView> additional_message) - : runtime_error(Win32MakeMessage(error_code, std::nullopt)), - error_code_(error_code) {} -} // namespace cru 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/ui/window_class.cpp b/src/platform_win/window_class.cpp index 5e8b3454..b58f53b2 100644 --- a/src/ui/window_class.cpp +++ b/src/platform_win/window_class.cpp @@ -1,9 +1,9 @@ -#include "window_class.hpp" +#include "cru/platform/win/window_class.hpp" -#include "exception.hpp" +#include "cru/platform/win/exception.hpp" -namespace cru::ui { -WindowClass::WindowClass(const String& name, WNDPROC window_proc, +namespace cru::platform::win { +WindowClass::WindowClass(const std::wstring& name, WNDPROC window_proc, HINSTANCE h_instance) : name_(name) { WNDCLASSEX window_class; diff --git a/src/pre.hpp b/src/pre.hpp deleted file mode 100644 index eefc828d..00000000 --- a/src/pre.hpp +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -#ifdef _DEBUG -#define CRU_DEBUG -#endif - -#define NOMINMAX -#define WIN32_LEAN_AND_MEAN - -#ifdef CRU_DEBUG -#define _CRTDBG_MAP_ALLOC -#include <crtdbg.h> -#include <cstdlib> -#endif diff --git a/src/timer.cpp b/src/timer.cpp deleted file mode 100644 index 40e32640..00000000 --- a/src/timer.cpp +++ /dev/null @@ -1,52 +0,0 @@ -#include "timer.hpp" - -#include "application.hpp" - -namespace cru { -TimerManager* TimerManager::GetInstance() { - return Application::GetInstance()->ResolveSingleton<TimerManager>( - [](auto) { return new TimerManager{}; }); -} - -UINT_PTR TimerManager::CreateTimer(const UINT milliseconds, const bool loop, - const TimerAction& action) { - const auto id = current_count_++; - ::SetTimer(Application::GetInstance()->GetGodWindow()->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(Application::GetInstance()->GetGodWindow()->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; -} - -TimerTask::TimerTask(const UINT_PTR id) : id_(id) {} - -void TimerTask::Cancel() const { TimerManager::GetInstance()->KillTimer(id_); } - -TimerTask SetTimeout(std::chrono::milliseconds milliseconds, - const TimerAction& action) { - const auto id = TimerManager::GetInstance()->CreateTimer( - static_cast<UINT>(milliseconds.count()), false, action); - return TimerTask(id); -} - -TimerTask SetInterval(std::chrono::milliseconds milliseconds, - const TimerAction& action) { - const auto id = TimerManager::GetInstance()->CreateTimer( - static_cast<UINT>(milliseconds.count()), true, action); - return TimerTask(id); -} -} // namespace cru diff --git a/src/timer.hpp b/src/timer.hpp deleted file mode 100644 index 7199adc2..00000000 --- a/src/timer.hpp +++ /dev/null @@ -1,64 +0,0 @@ -#pragma once -#include "pre.hpp" - -#include <Windows.h> -#include <chrono> -#include <functional> -#include <map> -#include <optional> - -#include "base.hpp" - -namespace cru { -using TimerAction = std::function<void()>; - -class TimerManager : public Object { - public: - static TimerManager* GetInstance(); - - private: - TimerManager() = default; - - public: - 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: - std::map<UINT_PTR, std::pair<bool, TimerAction>> map_{}; - UINT_PTR current_count_ = 0; -}; - -class TimerTask { - friend TimerTask SetTimeout(std::chrono::milliseconds milliseconds, - const TimerAction& action); - friend TimerTask SetInterval(std::chrono::milliseconds milliseconds, - const TimerAction& action); - - private: - explicit TimerTask(UINT_PTR id); - - public: - TimerTask(const TimerTask& other) = default; - TimerTask(TimerTask&& other) = default; - TimerTask& operator=(const TimerTask& other) = default; - TimerTask& operator=(TimerTask&& other) = default; - ~TimerTask() = default; - - void Cancel() const; - - private: - UINT_PTR id_; -}; - -TimerTask SetTimeout(std::chrono::milliseconds milliseconds, - const TimerAction& action); -TimerTask SetInterval(std::chrono::milliseconds milliseconds, - const TimerAction& action); -} // namespace cru diff --git a/src/util/format.hpp b/src/util/format.hpp deleted file mode 100644 index 7c1cee05..00000000 --- a/src/util/format.hpp +++ /dev/null @@ -1,100 +0,0 @@ -#pragma once -#include "pre.hpp" - -#include "base.hpp" - -namespace cru::util { -namespace details { -template <typename T> -struct TypeTag {}; - -constexpr StringView PlaceHolder(TypeTag<String>) { return StringView(L"{}"); } - -constexpr MultiByteStringView PlaceHolder(TypeTag<MultiByteString>) { - return MultiByteStringView("{}"); -} - -template <typename TString> -void FormatInternal(TString& string) { - const auto find_result = string.find(PlaceHolder(TypeTag<TString>{})); - if (find_result != TString::npos) - throw std::invalid_argument("There is more placeholders than args."); -} - -template <typename TString, typename T, typename... TRest> -void FormatInternal(TString& string, const T& arg, const TRest&... args) { - const auto find_result = string.find(PlaceHolder(TypeTag<TString>{})); - if (find_result == TString::npos) - throw std::invalid_argument("There is less placeholders than args."); - - string.replace(find_result, 2, FormatToString(arg, TypeTag<TString>{})); - FormatInternal<TString>(string, args...); -} -} // namespace details - -template <typename... T> -String Format(const StringView& format, const T&... args) { - String result(format); - details::FormatInternal<String>(result, args...); - return result; -} - -template <typename... T> -MultiByteString Format(const MultiByteStringView& format, const T&... args) { - MultiByteString result(format); - details::FormatInternal<MultiByteString>(result, args...); - return result; -} - -#define CRU_FORMAT_NUMBER(type) \ - inline String FormatToString(const type number, details::TypeTag<String>) { \ - return std::to_wstring(number); \ - } \ - inline MultiByteString FormatToString(const type number, \ - details::TypeTag<MultiByteString>) { \ - return std::to_string(number); \ - } - -CRU_FORMAT_NUMBER(int) -CRU_FORMAT_NUMBER(short) -CRU_FORMAT_NUMBER(long) -CRU_FORMAT_NUMBER(long long) -CRU_FORMAT_NUMBER(unsigned int) -CRU_FORMAT_NUMBER(unsigned short) -CRU_FORMAT_NUMBER(unsigned long) -CRU_FORMAT_NUMBER(unsigned long long) -CRU_FORMAT_NUMBER(float) -CRU_FORMAT_NUMBER(double) - -#undef CRU_FORMAT_NUMBER - -inline StringView FormatToString(const String& string, - details::TypeTag<String>) { - return string; -} - -inline MultiByteString FormatToString(const MultiByteString& string, - details::TypeTag<MultiByteString>) { - return string; -} - -inline StringView FormatToString(const StringView& string, - details::TypeTag<String>) { - return string; -} - -inline MultiByteStringView FormatToString(const MultiByteStringView& string, - details::TypeTag<MultiByteString>) { - return string; -} - -inline StringView FormatToString(const wchar_t* string, - details::TypeTag<String>) { - return StringView(string); -} - -inline MultiByteStringView FormatToString(const char* string, - details::TypeTag<MultiByteString>) { - return MultiByteString(string); -} -} // namespace cru::util |