aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcrupest <crupest@outlook.com>2019-03-31 17:14:47 +0800
committercrupest <crupest@outlook.com>2019-03-31 17:14:47 +0800
commitfbfd90255731954fb80483f4ba7188d3611fafec (patch)
tree8e3283c911d7aec76130d6a1dc7f5d8a85512b59
parent877f65e2e2c40eecc7cfeb194dc9d391af60711b (diff)
downloadcru-fbfd90255731954fb80483f4ba7188d3611fafec.tar.gz
cru-fbfd90255731954fb80483f4ba7188d3611fafec.tar.bz2
cru-fbfd90255731954fb80483f4ba7188d3611fafec.zip
...
-rw-r--r--include/cru/common/base.hpp18
-rw-r--r--include/cru/common/format.hpp104
-rw-r--r--include/cru/common/pre_config.hpp5
-rw-r--r--include/cru/common/ui_base.hpp (renamed from src/ui/ui_base.hpp)15
-rw-r--r--include/cru/platform/debug.hpp8
-rw-r--r--include/cru/platform/native_window.hpp31
-rw-r--r--include/cru/platform/ui_applicaition.hpp28
-rw-r--r--include/cru/platform/win/exception.hpp (renamed from src/exception.hpp)25
-rw-r--r--include/cru/platform/win/god_window.hpp33
-rw-r--r--include/cru/platform/win/win_application.hpp53
-rw-r--r--include/cru/platform/win/win_pre_config.hpp6
-rw-r--r--include/cru/platform/win/window_class.hpp (renamed from src/ui/window_class.hpp)12
-rw-r--r--src/CMakeLists.txt14
-rw-r--r--src/application.cpp113
-rw-r--r--src/base.hpp39
-rw-r--r--src/cru_debug.cpp11
-rw-r--r--src/cru_debug.hpp50
-rw-r--r--src/exception.cpp42
-rw-r--r--src/platform_win/CMakeLists.txt10
-rw-r--r--src/platform_win/debug.cpp10
-rw-r--r--src/platform_win/exception.cpp50
-rw-r--r--src/platform_win/god_window.cpp70
-rw-r--r--src/platform_win/god_window_message.hpp6
-rw-r--r--src/platform_win/timer.cpp28
-rw-r--r--src/platform_win/timer.hpp34
-rw-r--r--src/platform_win/win_application.cpp70
-rw-r--r--src/platform_win/window_class.cpp (renamed from src/ui/window_class.cpp)8
-rw-r--r--src/pre.hpp14
-rw-r--r--src/timer.cpp52
-rw-r--r--src/timer.hpp64
-rw-r--r--src/util/format.hpp100
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