MayaFlux 1.0.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_ENTERSIZEMOVE:
114 self->m_in_size_move.store(true, std::memory_order_release);
115 return 0;
116
117 case WM_EXITSIZEMOVE: {
118 self->m_in_size_move.store(false, std::memory_order_release);
119 WindowEvent ev;
120 ev.type = WindowEventType::WINDOW_RESIZED;
121 ev.timestamp = 0.0;
122 ev.data = WindowEvent::ResizeData {
123 .width = static_cast<uint32_t>(self->m_state.current_width),
124 .height = static_cast<uint32_t>(self->m_state.current_height)
125 };
126 self->push_event(ev);
127 return 0;
128 }
129
130 case WM_SIZE: {
131 auto w = static_cast<uint32_t>(LOWORD(lp));
132 auto h = static_cast<uint32_t>(HIWORD(lp));
133 self->m_state.current_width = static_cast<int>(w);
134 self->m_state.current_height = static_cast<int>(h);
135 WindowEvent ev;
136 ev.type = WindowEventType::WINDOW_RESIZED;
137 ev.timestamp = 0.0;
138 ev.data = WindowEvent::ResizeData { .width = w, .height = h };
139 self->push_event(ev);
140 return 0;
141 }
142
143 case WM_SETFOCUS: {
144 self->m_state.is_focused = true;
145 WindowEvent ev;
146 ev.type = WindowEventType::WINDOW_FOCUS_GAINED;
147 ev.timestamp = 0.0;
148 self->push_event(ev);
149 return 0;
150 }
151
152 case WM_KILLFOCUS: {
153 self->m_state.is_focused = false;
154 WindowEvent ev;
155 ev.type = WindowEventType::WINDOW_FOCUS_LOST;
156 ev.timestamp = 0.0;
157 self->push_event(ev);
158 return 0;
159 }
160
161 case WM_KEYDOWN:
162 case WM_SYSKEYDOWN: {
163 if (lp & (1 << 30))
164 return 0;
165
166 const uint32_t scancode = static_cast<uint32_t>(HIWORD(lp) & 0x1FF);
167 IO::Keys key = from_win32_scancode(scancode);
168 if (key == IO::Keys::Unknown)
169 key = from_win32_key(wp);
170
171 const WindowEvent::KeyData kd {
172 .key = static_cast<int16_t>(key),
173 .scancode = static_cast<int32_t>(scancode),
174 .mods = 0
175 };
176
177 self->m_held_keys_ui[kd.key] = kd;
178 self->m_keys_dirty.store(true, std::memory_order_release);
179 WindowEvent ev;
180 ev.type = WindowEventType::KEY_PRESSED;
181 ev.timestamp = 0.0;
182 ev.data = kd;
183 self->push_event(ev);
184 return 0;
185 }
186
187 case WM_KEYUP:
188 case WM_SYSKEYUP: {
189 const uint32_t scancode = static_cast<uint32_t>(HIWORD(lp) & 0x1FF);
190 IO::Keys key = from_win32_scancode(scancode);
191 if (key == IO::Keys::Unknown)
192 key = from_win32_key(wp);
193
194 const WindowEvent::KeyData kd {
195 .key = static_cast<int16_t>(key),
196 .scancode = static_cast<int32_t>(scancode),
197 .mods = 0
198 };
199
200 self->m_held_keys_ui.erase(kd.key);
201 self->m_keys_dirty.store(true, std::memory_order_release);
202 WindowEvent ev;
203 ev.type = WindowEventType::KEY_RELEASED;
204 ev.timestamp = 0.0;
205 ev.data = kd;
206 self->push_event(ev);
207 return 0;
208 }
209
210 case WM_CHAR: {
211 const auto unit = static_cast<uint16_t>(wp);
212
213 if (unit >= 0xD800 && unit <= 0xDBFF) {
214 self->m_pending_high_surrogate = unit;
215 return 0;
216 }
217
218 uint32_t codepoint = unit;
219 if (unit >= 0xDC00 && unit <= 0xDFFF && self->m_pending_high_surrogate != 0) {
220 codepoint = 0x10000
221 + (static_cast<uint32_t>(self->m_pending_high_surrogate - 0xD800) << 10)
222 + (static_cast<uint32_t>(unit - 0xDC00));
223 self->m_pending_high_surrogate = 0;
224 } else if (unit >= 0xDC00 && unit <= 0xDFFF) {
225 self->m_pending_high_surrogate = 0;
226 return 0;
227 } else {
228 self->m_pending_high_surrogate = 0;
229 }
230
231 if (!is_text_input_codepoint(codepoint)) {
232 return 0;
233 }
234
235 WindowEvent ev;
236 ev.type = WindowEventType::TEXT_INPUT;
237 ev.timestamp = 0.0;
238 ev.data = WindowEvent::TextData { .codepoint = codepoint };
239 self->push_event(ev);
240 return 0;
241 }
242
243 case WM_MOUSEMOVE: {
244 WindowEvent ev;
245 ev.type = WindowEventType::MOUSE_MOTION;
246 ev.timestamp = 0.0;
247 ev.data = WindowEvent::MousePosData {
248 .x = static_cast<double>(GET_X_LPARAM(lp)),
249 .y = static_cast<double>(GET_Y_LPARAM(lp))
250 };
251 self->push_event(ev);
252 return 0;
253 }
254
255 case WM_LBUTTONDOWN:
256 case WM_RBUTTONDOWN:
257 case WM_MBUTTONDOWN: {
258 IO::MouseButtons btn = (msg == WM_LBUTTONDOWN) ? IO::MouseButtons::Left
259 : (msg == WM_RBUTTONDOWN) ? IO::MouseButtons::Right
261 WindowEvent ev;
262 ev.type = WindowEventType::MOUSE_BUTTON_PRESSED;
263 ev.timestamp = 0.0;
264 ev.data = WindowEvent::MouseButtonData {
265 .button = static_cast<int8_t>(btn),
266 .mods = 0
267 };
268 self->push_event(ev);
269 return 0;
270 }
271
272 case WM_LBUTTONUP:
273 case WM_RBUTTONUP:
274 case WM_MBUTTONUP: {
275 IO::MouseButtons btn = (msg == WM_LBUTTONUP) ? IO::MouseButtons::Left
276 : (msg == WM_RBUTTONUP) ? IO::MouseButtons::Right
278 WindowEvent ev;
279 ev.type = WindowEventType::MOUSE_BUTTON_RELEASED;
280 ev.timestamp = 0.0;
281 ev.data = WindowEvent::MouseButtonData {
282 .button = static_cast<int8_t>(btn),
283 .mods = 0
284 };
285 self->push_event(ev);
286 return 0;
287 }
288
289 case WM_MOUSEWHEEL: {
290 double delta = static_cast<double>(GET_WHEEL_DELTA_WPARAM(wp)) / WHEEL_DELTA;
291 WindowEvent ev;
292 ev.type = WindowEventType::MOUSE_SCROLLED;
293 ev.timestamp = 0.0;
294 ev.data = WindowEvent::ScrollData { .x_offset = 0.0, .y_offset = delta };
295 self->push_event(ev);
296 return 0;
297 }
298
299 default:
300 break;
301 }
302
303 return DefWindowProcW(hwnd, msg, wp, lp);
304}
305
306// ============================================================================
307// push_event (UI thread)
308// ============================================================================
309
310void Win32Window::push_event(WindowEvent ev)
311{
312 (void)m_event_queue.push(ev);
313}
314
315// ============================================================================
316// poll (graphics thread)
317// ============================================================================
318
319void Win32Window::poll()
320{
321 if (m_keys_dirty.exchange(false, std::memory_order_acq_rel)) {
322 const bool was_empty = m_held_keys.empty();
323 m_held_keys = m_held_keys_ui;
324 if (was_empty && !m_held_keys.empty())
325 m_repeat_next_tick = GetTickCount64() + m_key_repeat_config.initial_delay_ms;
326 }
327
328 while (auto ev = m_event_queue.pop()) {
329 const bool suppress_signal = ev->type == WindowEventType::WINDOW_RESIZED
330 && m_in_size_move.load(std::memory_order_acquire);
331 if (!suppress_signal)
332 m_event_source.signal(*ev);
333 if (m_event_callback)
334 m_event_callback(*ev);
335 }
336
337 const ULONGLONG now = GetTickCount64();
338 if (!m_held_keys.empty() && now >= m_repeat_next_tick) {
339 m_repeat_next_tick = now + m_key_repeat_config.interval_ms;
340 for (const auto& [k, kd] : m_held_keys) {
341 WindowEvent rev;
342 rev.type = WindowEventType::KEY_REPEAT;
343 rev.timestamp = 0.0;
344 rev.data = kd;
345 m_event_source.signal(rev);
346 if (m_event_callback)
347 m_event_callback(rev);
348 }
349 }
350}
351
352// ============================================================================
353// Window interface
354// ============================================================================
355
356void Win32Window::show()
357{
358 if (m_hwnd)
359 ShowWindow(m_hwnd, SW_SHOW);
360}
361
362void Win32Window::hide()
363{
364 if (m_hwnd)
365 ShowWindow(m_hwnd, SW_HIDE);
366}
367
368void Win32Window::destroy()
369{
370 if (m_hwnd) {
371 PostMessageW(m_hwnd, WM_CLOSE, 0, 0);
372 m_hwnd = nullptr;
373 }
374 if (m_ui_thread.joinable())
375 m_ui_thread.join();
376}
377
378bool Win32Window::should_close() const
379{
380 return m_should_close.load();
381}
382
383void Win32Window::request_close()
384{
385 if (!m_should_close.exchange(true)) {
386 WindowEvent ev;
387 ev.type = WindowEventType::WINDOW_CLOSED;
388 ev.timestamp = 0.0;
389 push_event(ev);
390 }
391}
392
393void Win32Window::set_event_callback(WindowEventCallback callback)
394{
395 m_event_callback = std::move(callback);
396}
397
398void Win32Window::set_title(const std::string& title)
399{
400 m_create_info.title = title;
401 if (m_hwnd) {
402 auto wt = std::wstring(title.begin(), title.end());
403 SetWindowTextW(m_hwnd, wt.c_str());
404 }
405}
406
407void Win32Window::set_size(uint32_t width, uint32_t height)
408{
409 m_create_info.width = width;
410 m_create_info.height = height;
411 if (m_hwnd)
412 SetWindowPos(m_hwnd, nullptr, 0, 0,
413 static_cast<int>(width), static_cast<int>(height),
414 SWP_NOMOVE | SWP_NOZORDER);
415}
416
417void Win32Window::set_position(uint32_t x, uint32_t y)
418{
419 if (m_hwnd)
420 SetWindowPos(m_hwnd, nullptr,
421 static_cast<int>(x), static_cast<int>(y),
422 0, 0, SWP_NOSIZE | SWP_NOZORDER);
423}
424
425void Win32Window::set_color(const std::array<float, 4>& color)
426{
427 m_create_info.clear_color = color;
428}
429
430// ============================================================================
431// Render tracking
432// ============================================================================
433
434void Win32Window::register_rendering_buffer(std::shared_ptr<Buffers::VKBuffer> buffer)
435{
436 std::lock_guard lock(m_render_tracking_mutex);
437 m_rendering_buffers.push_back(buffer);
438}
439
440void Win32Window::unregister_rendering_buffer(std::shared_ptr<Buffers::VKBuffer> buffer)
441{
442 std::lock_guard lock(m_render_tracking_mutex);
443 std::erase_if(m_rendering_buffers,
444 [&buffer](const auto& wp) {
445 auto sp = wp.lock();
446 return !sp || sp == buffer;
447 });
448}
449
450void Win32Window::track_frame_command(uint64_t cmd_id)
451{
452 m_frame_commands.push_back(cmd_id);
453}
454
455const std::vector<uint64_t>& Win32Window::get_frame_commands() const
456{
457 return m_frame_commands;
458}
459
460void Win32Window::clear_frame_commands()
461{
462 m_frame_commands.clear();
463}
464
465std::vector<std::shared_ptr<Buffers::VKBuffer>> Win32Window::get_rendering_buffers() const
466{
467 std::lock_guard lock(m_render_tracking_mutex);
468 std::vector<std::shared_ptr<Buffers::VKBuffer>> out;
469 out.reserve(m_rendering_buffers.size());
470 for (auto& wp : m_rendering_buffers) {
471 if (auto sp = wp.lock())
472 out.push_back(sp);
473 }
474 return out;
475}
476
477} // namespace MayaFlux::Core
478
479#endif // MAYAFLUX_PLATFORM_WINDOWS
glm::vec3 color
Definition Decoder.cpp:97
uint32_t h
Definition InkPress.cpp:29
std::shared_ptr< NetworkGeometryBuffer > buffer
PickCallback callback
Definition Picker.cpp:22
std::vector< float > * out
uint32_t width
uint32_t height
constexpr bool is_text_input_codepoint(uint32_t codepoint) noexcept
Check if a Unicode codepoint is valid for text input.
Definition Window.hpp:217
MouseButtons
Enumeration for mouse buttons.
Definition Keys.hpp:147
@ IO
Networking, file handling, streaming.
@ CALLBACK
Use callback for custom transition.
void destroy(Surface &surface, uint32_t id)
Tear down an element and everything related to it.
Definition Forma.cpp:90