Bicameral Engine
GPU-resident engine for a magic-research open-world game
Loading...
Searching...
No Matches
window.cpp
Go to the documentation of this file.
1// window.cpp — Win32 の窓(T-0004)。使い方は window.h。
2// DPI はモニターごとに扱う(Per-Monitor v2)。クライアント領域の大きさは物理ピクセルで、スワップチェインもその大きさにする。
3#include "platform/window.h"
4
5#include <utility>
6
7#include "core/hresult.h"
8#include "core/log.h"
9
10namespace bicameral {
11 namespace {
12
13 constexpr wchar_t WINDOW_CLASS_NAME[] = L"BicameralEngineWindow";
14 constexpr DWORD WINDOW_STYLE = WS_OVERLAPPEDWINDOW;
15
16 bool RegisterWindowClass(WNDPROC procedure) {
17 const WNDCLASSEXW windowClass{
18 .cbSize = sizeof(WNDCLASSEXW),
19 .style = CS_HREDRAW | CS_VREDRAW,
20 .lpfnWndProc = procedure,
21 .hInstance = GetModuleHandleW(nullptr),
22 .hCursor = LoadCursorW(nullptr, IDC_ARROW),
23 .lpszClassName = WINDOW_CLASS_NAME,
24 };
25
26 // 2 つ目の窓を作るときは登録済み(ERROR_CLASS_ALREADY_EXISTS)なのでそれでよい
27 return RegisterClassExW(&windowClass) != 0 || GetLastError() == ERROR_CLASS_ALREADY_EXISTS;
28 }
29
30 } // namespace
31
32 std::expected<std::unique_ptr<Window>, std::string> Window::Create(std::wstring_view title, uint32_t clientWidth,
33 uint32_t clientHeight) {
34 // 高 DPI の画面でぼやけないように、モニターごとの DPI を自分で扱うと宣言する(最初の窓より前)
35 SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
36 if (!RegisterWindowClass(&Window::WindowProcedure))
37 return std::unexpected("窓のクラスを登録できない: " + DescribeHresult(HRESULT_FROM_WIN32(GetLastError())));
38
39 // 指定はクライアント領域の大きさ。枠の分を足して窓の大きさにする
40 RECT rect{
41 .left = 0, .top = 0, .right = static_cast<LONG>(clientWidth), .bottom = static_cast<LONG>(clientHeight)};
42 AdjustWindowRectEx(&rect, WINDOW_STYLE, FALSE, 0);
43
44 std::unique_ptr<Window> window(new Window());
45 const std::wstring titleText(title);
46
47 const HWND handle = CreateWindowExW(0, WINDOW_CLASS_NAME, titleText.c_str(), WINDOW_STYLE, CW_USEDEFAULT,
48 CW_USEDEFAULT, rect.right - rect.left, rect.bottom - rect.top, nullptr,
49 nullptr, GetModuleHandleW(nullptr), window.get());
50
51 if (handle == nullptr)
52 return std::unexpected("窓を作れない: " + DescribeHresult(HRESULT_FROM_WIN32(GetLastError())));
53
54 ShowWindow(handle, SW_SHOWNORMAL);
55
56 return window;
57 }
58
60 if (m_handle != nullptr)
61 DestroyWindow(m_handle);
62 }
63
65 MSG message{};
66 while (PeekMessageW(&message, nullptr, 0, 0, PM_REMOVE)) {
67 if (message.message == WM_QUIT)
68 m_closed = true;
69
70 TranslateMessage(&message);
71 DispatchMessageW(&message);
72 }
73
74 return !m_closed;
75 }
76
78 return std::exchange(m_resized, false);
79 }
80
81 std::vector<InputEvent> Window::TakeInputEvents() {
82 return std::exchange(m_inputEvents, {});
83 }
84
85 // --- 入力 ---
86
87 namespace {
88
89 // 位置は符号つきの 16bit(捕まえている間は窓の外 = 負にもなる。windowsx.h の GET_X_LPARAM と同じ)
90 InputEvent PointerAt(InputKind kind, LPARAM lParam) {
91 return {.kind = kind, .x = static_cast<int16_t>(LOWORD(lParam)), .y = static_cast<int16_t>(HIWORD(lParam))};
92 }
93
94 } // namespace
95
96 // ボタンを押している間はポインタを捕まえる(窓の外へドラッグしても離したことが届くように)
97 void Window::AddButton(InputKind kind, PointerButton button, LPARAM lParam) {
98 InputEvent event = PointerAt(kind, lParam);
99 event.button = button;
100 event.shift = (GetKeyState(VK_SHIFT) & 0x8000) != 0; // Shift + 左クリック = 押す(T-0098)
101 m_inputEvents.push_back(event);
102 const uint32_t bit = 1u << static_cast<uint32_t>(button);
103 const bool wasHeld = m_heldButtons != 0;
105 if (!wasHeld && m_heldButtons != 0)
106 SetCapture(m_handle);
107
108 if (wasHeld && m_heldButtons == 0)
109 ReleaseCapture();
110 }
111
112 // WHEEL_DELTA(120)ごとに 1 刻み。高分解能のホイールの端数は溜めておく
113 void Window::AddWheel(WPARAM wParam) {
114 m_wheelRemainder += GET_WHEEL_DELTA_WPARAM(wParam);
115 const int32_t steps = m_wheelRemainder / WHEEL_DELTA;
116 if (steps == 0)
117 return;
118
119 m_wheelRemainder -= steps * WHEEL_DELTA;
120 m_inputEvents.push_back({.kind = InputKind::Wheel, .wheelSteps = steps});
121 }
122
123 // --- メッセージ ---
124
125 LRESULT CALLBACK Window::WindowProcedure(HWND handle, UINT message, WPARAM wParam, LPARAM lParam) {
126 // CreateWindowExW に渡した this を、最初のメッセージ(WM_NCCREATE)で窓に結び付ける
127 if (message == WM_NCCREATE) {
128 // NOLINTNEXTLINE(performance-no-int-to-ptr) Win32 のメッセージは LPARAM にポインタを入れて渡す
129 const auto* create = reinterpret_cast<const CREATESTRUCTW*>(lParam);
130 auto* window = static_cast<Window*>(create->lpCreateParams);
131 window->m_handle = handle;
132 SetWindowLongPtrW(handle, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(window));
133 }
134
135 // NOLINTNEXTLINE(performance-no-int-to-ptr) GWLP_USERDATA に入れた this を取り出す
136 auto* window = reinterpret_cast<Window*>(GetWindowLongPtrW(handle, GWLP_USERDATA));
137 if (window == nullptr)
138 return DefWindowProcW(handle, message, wParam, lParam);
139
140 return window->HandleMessage(message, wParam, lParam);
141 }
142
143 LRESULT Window::HandleMessage(UINT message, WPARAM wParam, LPARAM lParam) {
144 const HWND handle = m_handle; // WM_NCDESTROY で m_handle を空にした後も DefWindowProcW に渡す
145 if (m_messageHook && handle != nullptr && m_messageHook(handle, message, wParam, lParam))
146 return 1;
147
148 switch (message) {
149 case WM_SIZE:
150 m_clientWidth = LOWORD(lParam);
151 m_clientHeight = HIWORD(lParam);
152 m_resized = true;
153 return 0;
154 case WM_LBUTTONDOWN: AddButton(InputKind::ButtonDown, PointerButton::Left, lParam); return 0;
155 case WM_LBUTTONUP: AddButton(InputKind::ButtonUp, PointerButton::Left, lParam); return 0;
156 case WM_RBUTTONDOWN: AddButton(InputKind::ButtonDown, PointerButton::Right, lParam); return 0;
157 case WM_RBUTTONUP: AddButton(InputKind::ButtonUp, PointerButton::Right, lParam); return 0;
158 case WM_MBUTTONDOWN: AddButton(InputKind::ButtonDown, PointerButton::Middle, lParam); return 0;
159 case WM_MBUTTONUP: AddButton(InputKind::ButtonUp, PointerButton::Middle, lParam); return 0;
160 case WM_MOUSEMOVE: m_inputEvents.push_back(PointerAt(InputKind::PointerMove, lParam)); return 0;
161 case WM_MOUSEWHEEL: AddWheel(wParam); return 0;
162 case WM_KEYDOWN:
163 m_inputEvents.push_back({.kind = InputKind::KeyDown,
164 .key = static_cast<uint32_t>(wParam),
165 .shift = (GetKeyState(VK_SHIFT) & 0x8000) != 0});
166
167 return 0;
168 case WM_CAPTURECHANGED: m_heldButtons = 0; return 0; // 別の窓に取られた(離したことは届かない)
169 case WM_DPICHANGED: {
170 // 新しい DPI で OS が勧める位置と大きさにする(クライアント領域の物理ピクセルが変わり、WM_SIZE が来る)
171 // NOLINTNEXTLINE(performance-no-int-to-ptr) WM_DPICHANGED は LPARAM に RECT のポインタを入れる
172 const auto* suggested = reinterpret_cast<const RECT*>(lParam);
173 SetWindowPos(m_handle, nullptr, suggested->left, suggested->top, suggested->right - suggested->left,
174 suggested->bottom - suggested->top, SWP_NOZORDER | SWP_NOACTIVATE);
175
176 return 0;
177 }
178 case WM_DESTROY:
179 m_closed = true;
180 PostQuitMessage(0);
181 return 0;
182 case WM_NCDESTROY:
183 SetWindowLongPtrW(handle, GWLP_USERDATA, 0);
184 m_handle = nullptr;
185 break;
186 default: break;
187 }
188
189 return DefWindowProcW(handle, message, wParam, lParam);
190 }
191
192} // namespace bicameral
uint32_t m_clientHeight
Definition window.h:68
static std::expected< std::unique_ptr< Window >, std::string > Create(std::wstring_view title, uint32_t clientWidth, uint32_t clientHeight)
Definition window.cpp:32
Window(const Window &)=delete
std::vector< InputEvent > TakeInputEvents()
Definition window.cpp:81
uint32_t m_heldButtons
Definition window.h:75
bool TakeResized()
Definition window.cpp:77
LRESULT HandleMessage(UINT message, WPARAM wParam, LPARAM lParam)
Definition window.cpp:143
uint32_t m_clientWidth
Definition window.h:67
std::vector< InputEvent > m_inputEvents
Definition window.h:73
MessageHook m_messageHook
Definition window.h:64
bool PumpMessages()
Definition window.cpp:64
void AddWheel(WPARAM wParam)
Definition window.cpp:113
static LRESULT CALLBACK WindowProcedure(HWND handle, UINT message, WPARAM wParam, LPARAM lParam)
Definition window.cpp:125
int32_t m_wheelRemainder
Definition window.h:74
void AddButton(InputKind kind, PointerButton button, LPARAM lParam)
Definition window.cpp:97
InputEvent PointerAt(InputKind kind, LPARAM lParam)
Definition window.cpp:90
bool RegisterWindowClass(WNDPROC procedure)
Definition window.cpp:16
PointerButton
Definition input.h:12
InputKind
Definition input.h:14
std::string DescribeHresult(HResult result)
Definition hresult.cpp:88