MayaFlux 0.4.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
Win32Window.cpp
Go to the documentation of this file.
1#ifdef MAYAFLUX_PLATFORM_WINDOWS
2
3#include "Win32Window.hpp"
4#include "KeyMapping.hpp"
5
7
8#include <windowsx.h>
9
10namespace MayaFlux::Core {
11
12// ============================================================================
13// Window class registration
14// ============================================================================
15
16static constexpr const wchar_t* k_class_name = L"MayaFluxWindow";
17
18static void register_window_class(HINSTANCE hinstance)
19{
20 static std::once_flag s_flag;
21 std::call_once(s_flag, [&]() {
22 WNDCLASSEXW wc {};
23 wc.cbSize = sizeof(wc);
24 wc.style = CS_HREDRAW | CS_VREDRAW;
25 wc.lpfnWndProc = Win32Window::wnd_proc;
26 wc.hInstance = hinstance;
27 wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
28 wc.lpszClassName = k_class_name;
29 RegisterClassExW(&wc);
30 });
31}
32
33// ============================================================================
34// Constructor / destructor
35// ============================================================================
36
37Win32Window::Win32Window(const WindowCreateInfo& create_info,
38 const GlobalGraphicsConfig& graphics_config)
39 : m_create_info(create_info)
40 , m_key_repeat_config(graphics_config.key_repeat_config)
41{
42 m_hinstance = GetModuleHandleW(nullptr);
43
44 m_ui_thread = std::thread([this]() { ui_thread_main(); });
45
46 m_hwnd_ready.wait(false);
47}
48
49Win32Window::~Win32Window()
50{
51 destroy();
52}
53
54// ============================================================================
55// UI thread
56// ============================================================================
57
58void Win32Window::ui_thread_main()
59{
60 register_window_class(m_hinstance);
61
62 auto wtitle = std::wstring(m_create_info.title.begin(), m_create_info.title.end());
63
64 m_hwnd = CreateWindowExW(
65 0,
66 k_class_name,
67 wtitle.c_str(),
68 WS_OVERLAPPEDWINDOW,
69 CW_USEDEFAULT, CW_USEDEFAULT,
70 static_cast<int>(m_create_info.width),
71 static_cast<int>(m_create_info.height),
72 nullptr, nullptr, m_hinstance, nullptr);
73
74 SetWindowLongPtrW(m_hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this));
75
76 m_hwnd_ready.store(true);
77 m_hwnd_ready.notify_all();
78
79 MSG msg {};
80 while (GetMessageW(&msg, nullptr, 0, 0)) {
81 TranslateMessage(&msg);
82 DispatchMessageW(&msg);
83 }
84}
85
86// ============================================================================
87// WndProc
88// ============================================================================
89
90LRESULT CALLBACK Win32Window::wnd_proc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
91{
92 auto* self = reinterpret_cast<Win32Window*>(GetWindowLongPtrW(hwnd, GWLP_USERDATA));
93
94 if (!self)
95 return DefWindowProcW(hwnd, msg, wp, lp);
96
97 switch (msg) {
98 case WM_CLOSE:
99 self->m_should_close.store(true);
100 {
101 WindowEvent ev;
102 ev.type = WindowEventType::WINDOW_CLOSED;
103 ev.timestamp = 0.0;
104 self->push_event(ev);
105 }
106 DestroyWindow(hwnd);
107 return 0;
108
109 case WM_DESTROY:
110 PostQuitMessage(0);
111 return 0;
112
113 case WM_SIZE: {
114 auto w = static_cast<uint32_t>(LOWORD(lp));
115 auto h = static_cast<uint32_t>(HIWORD(lp));
116 self->m_state.current_width = static_cast<int>(w);
117 self->m_state.current_height = static_cast<int>(h);
118 WindowEvent ev;
119 ev.type = WindowEventType::WINDOW_RESIZED;
120 ev.timestamp = 0.0;
121 ev.data = WindowEvent::ResizeData { .width = w, .height = h };
122 self->push_event(ev);
123 return 0;
124 }
125
126 case WM_SETFOCUS: {
127 self->m_state.is_focused = true;
128 WindowEvent ev;
129 ev.type = WindowEventType::WINDOW_FOCUS_GAINED;
130 ev.timestamp = 0.0;
131 self->push_event(ev);
132 return 0;
133 }
134
135 case WM_KILLFOCUS: {
136 self->m_state.is_focused = false;
137 WindowEvent ev;
138 ev.type = WindowEventType::WINDOW_FOCUS_LOST;
139 ev.timestamp = 0.0;
140 self->push_event(ev);
141 return 0;
142 }
143
144 case WM_KEYDOWN:
145 case WM_SYSKEYDOWN: {
146 if (lp & (1 << 30))
147 return 0;
148
149 const uint32_t scancode = static_cast<uint32_t>(HIWORD(lp) & 0x1FF);
150 IO::Keys key = from_win32_scancode(scancode);
151 if (key == IO::Keys::Unknown)
152 key = from_win32_key(wp);
153
154 const WindowEvent::KeyData kd {
155 .key = static_cast<int16_t>(key),
156 .scancode = static_cast<int32_t>(scancode),
157 .mods = 0
158 };
159
160 self->m_held_keys_ui[kd.key] = kd;
161 self->m_keys_dirty.store(true, std::memory_order_release);
162 WindowEvent ev;
163 ev.type = WindowEventType::KEY_PRESSED;
164 ev.timestamp = 0.0;
165 ev.data = kd;
166 self->push_event(ev);
167 return 0;
168 }
169
170 case WM_KEYUP:
171 case WM_SYSKEYUP: {
172 const uint32_t scancode = static_cast<uint32_t>(HIWORD(lp) & 0x1FF);
173 IO::Keys key = from_win32_scancode(scancode);
174 if (key == IO::Keys::Unknown)
175 key = from_win32_key(wp);
176
177 const WindowEvent::KeyData kd {
178 .key = static_cast<int16_t>(key),
179 .scancode = static_cast<int32_t>(scancode),
180 .mods = 0
181 };
182
183 self->m_held_keys_ui.erase(kd.key);
184 self->m_keys_dirty.store(true, std::memory_order_release);
185 WindowEvent ev;
186 ev.type = WindowEventType::KEY_RELEASED;
187 ev.timestamp = 0.0;
188 ev.data = kd;
189 self->push_event(ev);
190 return 0;
191 }
192
193 case WM_MOUSEMOVE: {
194 WindowEvent ev;
195 ev.type = WindowEventType::MOUSE_MOTION;
196 ev.timestamp = 0.0;
197 ev.data = WindowEvent::MousePosData {
198 .x = static_cast<double>(GET_X_LPARAM(lp)),
199 .y = static_cast<double>(GET_Y_LPARAM(lp))
200 };
201 self->push_event(ev);
202 return 0;
203 }
204
205 case WM_LBUTTONDOWN:
206 case WM_RBUTTONDOWN:
207 case WM_MBUTTONDOWN: {
208 IO::MouseButtons btn = (msg == WM_LBUTTONDOWN) ? IO::MouseButtons::Left
209 : (msg == WM_RBUTTONDOWN) ? IO::MouseButtons::Right
211 WindowEvent ev;
212 ev.type = WindowEventType::MOUSE_BUTTON_PRESSED;
213 ev.timestamp = 0.0;
214 ev.data = WindowEvent::MouseButtonData {
215 .button = static_cast<int8_t>(btn),
216 .mods = 0
217 };
218 self->push_event(ev);
219 return 0;
220 }
221
222 case WM_LBUTTONUP:
223 case WM_RBUTTONUP:
224 case WM_MBUTTONUP: {
225 IO::MouseButtons btn = (msg == WM_LBUTTONUP) ? IO::MouseButtons::Left
226 : (msg == WM_RBUTTONUP) ? IO::MouseButtons::Right
228 WindowEvent ev;
229 ev.type = WindowEventType::MOUSE_BUTTON_RELEASED;
230 ev.timestamp = 0.0;
231 ev.data = WindowEvent::MouseButtonData {
232 .button = static_cast<int8_t>(btn),
233 .mods = 0
234 };
235 self->push_event(ev);
236 return 0;
237 }
238
239 case WM_MOUSEWHEEL: {
240 double delta = static_cast<double>(GET_WHEEL_DELTA_WPARAM(wp)) / WHEEL_DELTA;
241 WindowEvent ev;
242 ev.type = WindowEventType::MOUSE_SCROLLED;
243 ev.timestamp = 0.0;
244 ev.data = WindowEvent::ScrollData { .x_offset = 0.0, .y_offset = delta };
245 self->push_event(ev);
246 return 0;
247 }
248
249 default:
250 break;
251 }
252
253 return DefWindowProcW(hwnd, msg, wp, lp);
254}
255
256// ============================================================================
257// push_event (UI thread)
258// ============================================================================
259
260void Win32Window::push_event(WindowEvent ev)
261{
262 (void)m_event_queue.push(ev);
263}
264
265// ============================================================================
266// poll (graphics thread)
267// ============================================================================
268
269void Win32Window::poll()
270{
271 if (m_keys_dirty.exchange(false, std::memory_order_acq_rel)) {
272 const bool was_empty = m_held_keys.empty();
273 m_held_keys = m_held_keys_ui;
274 if (was_empty && !m_held_keys.empty())
275 m_repeat_next_tick = GetTickCount64() + m_key_repeat_config.initial_delay_ms;
276 }
277
278 while (auto ev = m_event_queue.pop()) {
279 m_event_source.signal(*ev);
280 if (m_event_callback)
281 m_event_callback(*ev);
282 }
283
284 const ULONGLONG now = GetTickCount64();
285 if (!m_held_keys.empty() && now >= m_repeat_next_tick) {
286 m_repeat_next_tick = now + m_key_repeat_config.interval_ms;
287 for (const auto& [k, kd] : m_held_keys) {
288 WindowEvent rev;
289 rev.type = WindowEventType::KEY_REPEAT;
290 rev.timestamp = 0.0;
291 rev.data = kd;
292 m_event_source.signal(rev);
293 if (m_event_callback)
294 m_event_callback(rev);
295 }
296 }
297}
298
299// ============================================================================
300// Window interface
301// ============================================================================
302
303void Win32Window::show()
304{
305 if (m_hwnd)
306 ShowWindow(m_hwnd, SW_SHOW);
307}
308
309void Win32Window::hide()
310{
311 if (m_hwnd)
312 ShowWindow(m_hwnd, SW_HIDE);
313}
314
315void Win32Window::destroy()
316{
317 if (m_hwnd) {
318 PostMessageW(m_hwnd, WM_CLOSE, 0, 0);
319 m_hwnd = nullptr;
320 }
321 if (m_ui_thread.joinable())
322 m_ui_thread.join();
323}
324
325bool Win32Window::should_close() const
326{
327 return m_should_close.load();
328}
329
330void Win32Window::set_event_callback(WindowEventCallback callback)
331{
332 m_event_callback = std::move(callback);
333}
334
335void Win32Window::set_title(const std::string& title)
336{
337 m_create_info.title = title;
338 if (m_hwnd) {
339 auto wt = std::wstring(title.begin(), title.end());
340 SetWindowTextW(m_hwnd, wt.c_str());
341 }
342}
343
344void Win32Window::set_size(uint32_t width, uint32_t height)
345{
346 m_create_info.width = width;
347 m_create_info.height = height;
348 if (m_hwnd)
349 SetWindowPos(m_hwnd, nullptr, 0, 0,
350 static_cast<int>(width), static_cast<int>(height),
351 SWP_NOMOVE | SWP_NOZORDER);
352}
353
354void Win32Window::set_position(uint32_t x, uint32_t y)
355{
356 if (m_hwnd)
357 SetWindowPos(m_hwnd, nullptr,
358 static_cast<int>(x), static_cast<int>(y),
359 0, 0, SWP_NOSIZE | SWP_NOZORDER);
360}
361
362void Win32Window::set_color(const std::array<float, 4>& color)
363{
364 m_create_info.clear_color = color;
365}
366
367// ============================================================================
368// Render tracking (mirrors GlfwWindow)
369// ============================================================================
370
371void Win32Window::register_rendering_buffer(std::shared_ptr<Buffers::VKBuffer> buffer)
372{
373 std::lock_guard lock(m_render_tracking_mutex);
374 m_rendering_buffers.push_back(buffer);
375}
376
377void Win32Window::unregister_rendering_buffer(std::shared_ptr<Buffers::VKBuffer> buffer)
378{
379 std::lock_guard lock(m_render_tracking_mutex);
380 std::erase_if(m_rendering_buffers,
381 [&buffer](const auto& wp) {
382 auto sp = wp.lock();
383 return !sp || sp == buffer;
384 });
385}
386
387void Win32Window::track_frame_command(uint64_t cmd_id)
388{
389 m_frame_commands.push_back(cmd_id);
390}
391
392const std::vector<uint64_t>& Win32Window::get_frame_commands() const
393{
394 return m_frame_commands;
395}
396
397void Win32Window::clear_frame_commands()
398{
399 m_frame_commands.clear();
400}
401
402std::vector<std::shared_ptr<Buffers::VKBuffer>> Win32Window::get_rendering_buffers() const
403{
404 std::lock_guard lock(m_render_tracking_mutex);
405 std::vector<std::shared_ptr<Buffers::VKBuffer>> out;
406 out.reserve(m_rendering_buffers.size());
407 for (auto& wp : m_rendering_buffers) {
408 if (auto sp = wp.lock())
409 out.push_back(sp);
410 }
411 return out;
412}
413
414} // namespace MayaFlux::Core
415
416#endif // MAYAFLUX_PLATFORM_WINDOWS
uint32_t width
Definition Decoder.cpp:59
uint32_t h
Definition InkPress.cpp:28
MouseButtons
Enumeration for mouse buttons.
Definition Keys.hpp:147
@ IO
Networking, file handling, streaming.
@ CALLBACK
Use callback for custom transition.