MayaFlux 1.0.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
Context.cpp
Go to the documentation of this file.
1#include "Context.hpp"
2
4
9
11
12Context::Context(std::shared_ptr<Layer> layer,
13 std::shared_ptr<Core::Window> window,
14 Vruta::EventManager& event_manager,
15 std::string name)
16 : m_layer(std::move(layer))
17 , m_window(std::move(window))
18 , m_event_manager(event_manager)
19 , m_name(std::move(name))
20{
21 if (m_window) {
22 const auto& state = m_window->get_state();
23 m_known_width = state.current_width;
24 m_known_height = state.current_height;
25 }
27}
28
33
34// =============================================================================
35// Per-element callback registration
36// =============================================================================
37
38void Context::on_press(uint32_t id, IO::MouseButtons btn, PressFn fn)
39{
40 m_callbacks[id].press[static_cast<int>(btn)] = std::move(fn);
41}
42
44{
45 m_callbacks[id].release[static_cast<int>(btn)] = std::move(fn);
46}
47
48void Context::on_move(uint32_t id, MoveFn fn)
49{
50 m_callbacks[id].move = std::move(fn);
51}
52
53void Context::on_drag(uint32_t id, IO::MouseButtons btn, MoveFn fn)
54{
55 m_callbacks[id].drag[static_cast<int>(btn)] = std::move(fn);
56}
57
58void Context::on_enter(uint32_t id, EnterFn fn)
59{
60 m_callbacks[id].enter = std::move(fn);
61}
62
63void Context::on_leave(uint32_t id, LeaveFn fn)
64{
65 m_callbacks[id].leave = std::move(fn);
66}
67
68void Context::on_scroll(uint32_t id, ScrollFn fn)
69{
70 m_callbacks[id].scroll = std::move(fn);
71}
72
73void Context::on_press(uint32_t id, IO::Keys key, KeyFn fn)
74{
75 const int key_code = static_cast<int>(key);
76 m_callbacks[id].key_press[key_code] = std::move(fn);
77
78 if (!m_registered_keys[key_code].has_press) {
79 const std::string key_name = std::to_string(key_code);
81 std::make_shared<Vruta::Event>(
83 [this, key]() { handle_key_press(key); })),
84 m_name + "_key_press_" + key_name);
85 m_registered_keys[key_code].has_press = true;
86 }
87}
88
89void Context::on_release(uint32_t id, IO::Keys key, KeyFn fn)
90{
91 const int key_code = static_cast<int>(key);
92 m_callbacks[id].key_release[key_code] = std::move(fn);
93
94 if (!m_registered_keys[key_code].has_release) {
95 const std::string key_name = std::to_string(key_code);
97 std::make_shared<Vruta::Event>(
99 [this, key]() { handle_key_release(key); })),
100 m_name + "_key_release_" + key_name);
101 m_registered_keys[key_code].has_release = true;
102 }
103}
104
105void Context::on_held(uint32_t id, IO::Keys key, KeyFn fn)
106{
107 const int key_code = static_cast<int>(key);
108 m_callbacks[id].key_held[key_code] = std::move(fn);
109
110 if (!m_registered_keys[key_code].has_held) {
111 const std::string key_name = std::to_string(key_code);
113 std::make_shared<Vruta::Event>(
115 [this, key]() { handle_key_held(key); })),
116 m_name + "_key_held_" + key_name);
117 m_registered_keys[key_code].has_held = true;
118 }
119}
120
121void Context::on_text(uint32_t id, TextFn fn)
122{
123 m_callbacks[id].text = std::move(fn);
124}
125
127{
128 m_callbacks[id].focus_gained = std::move(fn);
129}
130
131void Context::on_focus_lost(uint32_t id, LeaveFn fn)
132{
133 m_callbacks[id].focus_lost = std::move(fn);
134}
135
136void Context::on_resize(uint32_t id, ResizeFn fn)
137{
138 m_callbacks[id].resize = std::move(fn);
139}
140
141void Context::detach_window(bool cancel_close)
142{
143 cancel_handlers(cancel_close);
144 m_window.reset();
145 m_hovered.reset();
146 m_focused.reset();
147 for (auto& drag : m_dragging)
148 drag.reset();
149 m_registered_keys.clear();
150 m_callbacks.clear();
151}
152
153void Context::on_close(uint32_t id, CloseFn fn)
154{
155 m_callbacks[id].close = std::move(fn);
156}
157
159{
160 if (m_focused) {
161 auto it = m_callbacks.find(*m_focused);
162 if (it != m_callbacks.end() && it->second.focus_lost)
163 it->second.focus_lost(*m_focused);
164 m_focused = std::nullopt;
165 }
166}
167
168void Context::unbind(uint32_t id)
169{
170 if (m_focused && *m_focused == id) {
171 auto it = m_callbacks.find(id);
172 if (it != m_callbacks.end() && it->second.focus_lost)
173 it->second.focus_lost(id);
174 m_focused = std::nullopt;
175 }
176
177 if (m_hovered && *m_hovered == id)
178 m_hovered = std::nullopt;
179
180 for (auto& dragging : m_dragging) {
181 if (dragging && *dragging == id)
182 dragging = std::nullopt;
183 }
184
185 m_callbacks.erase(id);
186}
187
189{
190 return m_window->get_event_source();
191}
192
194 uint32_t id,
195 std::shared_ptr<MappedState<float>> state,
196 IO::Keys decrease,
197 IO::Keys increase,
198 float delta,
199 float clamp_min,
200 float clamp_max)
201{
202 on_held(id, decrease, [state, delta, clamp_min, clamp_max](uint32_t) {
203 state->write(std::clamp(state->value - delta, clamp_min, clamp_max));
204 });
205
206 on_held(id, increase, [state, delta, clamp_min, clamp_max](uint32_t) {
207 state->write(std::clamp(state->value + delta, clamp_min, clamp_max));
208 });
209
210 return *this;
211}
212
214{
215 on_held(id, key, std::move(fn));
216 return *this;
217}
218
220 uint32_t id,
221 std::function<void(glm::vec2)> sink,
223{
224 if (!sink)
225 return *this;
226
227 on_drag(id, btn,
228 [sink = std::move(sink)](uint32_t, glm::vec2 ndc) { sink(ndc); });
229
230 return *this;
231}
232
233// =============================================================================
234// Handler registration / cancellation
235// =============================================================================
236
238{
240 std::make_shared<Vruta::Event>(
242 [this](double px, double py) {
243 handle_move(px, py);
245 })),
246 m_name + "_move");
247
249 std::make_shared<Vruta::Event>(
251 [this](double px, double py) { handle_press(px, py, IO::MouseButtons::Left); })),
252 m_name + "_press_left");
253
255 std::make_shared<Vruta::Event>(
257 [this](double px, double py) { handle_release(px, py, IO::MouseButtons::Left); })),
258 m_name + "_release_left");
259
261 std::make_shared<Vruta::Event>(
263 [this](double px, double py) { handle_press(px, py, IO::MouseButtons::Right); })),
264 m_name + "_press_right");
265
267 std::make_shared<Vruta::Event>(
269 [this](double px, double py) { handle_release(px, py, IO::MouseButtons::Right); })),
270 m_name + "_release_right");
271
273 std::make_shared<Vruta::Event>(
275 [this](double px, double py) { handle_press(px, py, IO::MouseButtons::Middle); })),
276 m_name + "_press_middle");
277
279 std::make_shared<Vruta::Event>(
281 [this](double px, double py) { handle_release(px, py, IO::MouseButtons::Middle); })),
282 m_name + "_release_middle");
283
285 std::make_shared<Vruta::Event>(
287 [this](double dx, double dy) { handle_scroll(dx, dy); })),
288 m_name + "_scroll");
289
291 std::make_shared<Vruta::Event>(
293 [this](uint32_t codepoint) { handle_text(codepoint); })),
294 m_name + "_text");
295
297 std::make_shared<Vruta::Event>(
299 [this](uint32_t w, uint32_t h) { handle_resize(w, h); })),
300 m_name + "_resize");
301
303 std::make_shared<Vruta::Event>(
305 [this]() { handle_close(); })),
306 m_name + "_close");
307}
308
309void Context::cancel_handlers(bool cancel_close)
310{
311 for (const char* suffix : {
312 "_move",
313 "_press_left", "_release_left",
314 "_press_right", "_release_right",
315 "_press_middle", "_release_middle",
316 "_scroll", "_text", "_resize" }) {
318 }
319
320 if (cancel_close)
322
323 for (const auto& [key_code, handlers] : m_registered_keys) {
324 const std::string key_name = std::to_string(key_code);
325 if (handlers.has_press)
326 m_event_manager.cancel_event(m_name + "_key_press_" + key_name);
327 if (handlers.has_release)
328 m_event_manager.cancel_event(m_name + "_key_release_" + key_name);
329 if (handlers.has_held)
330 m_event_manager.cancel_event(m_name + "_key_held_" + key_name);
331 }
332}
333
334// =============================================================================
335// Event dispatch
336// =============================================================================
337
338glm::vec2 Context::to_ndc(double px, double py) const noexcept
339{
340 const auto& s = m_window->get_state();
341 return {
342 (static_cast<float>(px) / static_cast<float>(s.current_width)) * 2.0F - 1.0F,
343 1.0F - (static_cast<float>(py) / static_cast<float>(s.current_height)) * 2.0F
344 };
345}
346
347Kinesis::AABB2D Context::to_ndc_rect(double x, double y, double w, double h) const noexcept
348{
349 return { .min = to_ndc(x, y + h), .max = to_ndc(x + w, y) };
350}
351
352void Context::handle_move(double px, double py)
353{
354 sync_size();
355 const glm::vec2 ndc = to_ndc(px, py);
356 const auto hit = m_layer->hit_test(ndc);
357
358 if (hit != m_hovered) {
359 if (m_hovered) {
360 auto it = m_callbacks.find(*m_hovered);
361 if (it != m_callbacks.end() && it->second.leave)
362 it->second.leave(*m_hovered);
363 }
364 if (hit) {
365 auto it = m_callbacks.find(*hit);
366 if (it != m_callbacks.end() && it->second.enter)
367 it->second.enter(*hit);
368 }
369 m_hovered = hit;
370 }
371
372 if (hit) {
373 auto it = m_callbacks.find(*hit);
374 if (it != m_callbacks.end() && it->second.move)
375 it->second.move(*hit, ndc);
376 }
377}
378
380{
381 sync_size();
382 const glm::vec2 ndc = to_ndc(px, py);
383 const auto hit = m_layer->hit_test(ndc);
384
385 const int btn_idx = static_cast<int>(btn);
386
387 if (hit != m_focused) {
388 if (m_focused) {
389 auto it = m_callbacks.find(*m_focused);
390 if (it != m_callbacks.end() && it->second.focus_lost)
391 it->second.focus_lost(*m_focused);
392 }
393
394 m_focused = hit;
395
396 if (hit) {
397 auto it = m_callbacks.find(*hit);
398 if (it != m_callbacks.end() && it->second.focus_gained)
399 it->second.focus_gained(*hit);
400 }
401 }
402
403 if (hit && m_callbacks.contains(*hit) && m_callbacks[*hit].drag.contains(btn_idx)) {
404 m_dragging[btn_idx] = hit;
405 }
406
407 if (hit) {
408 auto it = m_callbacks.find(*hit);
409 if (it != m_callbacks.end() && it->second.press.count(btn_idx))
410 it->second.press[btn_idx](*hit, ndc);
411 }
412}
413
415{
416 const int btn_idx = static_cast<int>(btn);
417 m_dragging[btn_idx] = std::nullopt;
418
419 const glm::vec2 ndc = to_ndc(px, py);
420 const auto hit = m_layer->hit_test(ndc);
421 if (!hit)
422 return;
423
424 auto it = m_callbacks.find(*hit);
425 if (it == m_callbacks.end())
426 return;
427
428 auto btn_it = it->second.release.find(static_cast<int>(btn));
429 if (btn_it != it->second.release.end() && btn_it->second)
430 btn_it->second(*hit, ndc);
431}
432
433void Context::handle_drags(double px, double py)
434{
435 const auto& source = m_window->get_event_source();
437 if (source.is_mouse_pressed(static_cast<int>(btn)))
438 handle_drag(px, py, btn);
439 }
440}
441
443{
444 const glm::vec2 ndc = to_ndc(px, py);
445 const int btn_idx = static_cast<int>(btn);
446
447 if (!m_dragging[btn_idx]) {
448 const auto hit = m_layer->hit_test(ndc);
449 if (hit)
450 m_dragging[btn_idx] = hit;
451 else
452 return;
453 }
454
455 auto it = m_callbacks.find(*m_dragging[btn_idx]);
456 if (it != m_callbacks.end() && it->second.drag.count(btn_idx))
457 it->second.drag[btn_idx](*m_dragging[btn_idx], ndc);
458}
459
460void Context::handle_scroll(double dx, double dy)
461{
462 sync_size();
463 if (!m_hovered)
464 return;
465
466 auto it = m_callbacks.find(*m_hovered);
467 if (it == m_callbacks.end() || !it->second.scroll)
468 return;
469
470 const auto [px, py] = m_window->get_event_source().get_mouse_position();
471 it->second.scroll(*m_hovered, to_ndc(px, py), dx, dy);
472}
473
474// =============================================================================
475// Keyboard event dispatch
476// =============================================================================
477
479{
480 sync_size();
481 if (!m_focused)
482 return;
483
484 auto it = m_callbacks.find(*m_focused);
485 if (it == m_callbacks.end())
486 return;
487
488 const int key_code = static_cast<int>(key);
489 auto key_it = it->second.key_press.find(key_code);
490 if (key_it != it->second.key_press.end())
491 key_it->second(*m_focused);
492}
493
495{
496 if (!m_focused)
497 return;
498
499 auto it = m_callbacks.find(*m_focused);
500 if (it == m_callbacks.end())
501 return;
502
503 const int key_code = static_cast<int>(key);
504 auto key_it = it->second.key_release.find(key_code);
505 if (key_it != it->second.key_release.end())
506 key_it->second(*m_focused);
507}
508
510{
511 if (!m_focused)
512 return;
513
514 auto it = m_callbacks.find(*m_focused);
515 if (it == m_callbacks.end())
516 return;
517
518 const int key_code = static_cast<int>(key);
519 auto key_it = it->second.key_held.find(key_code);
520 if (key_it != it->second.key_held.end())
521 key_it->second(*m_focused);
522}
523
524void Context::handle_text(uint32_t codepoint)
525{
526 sync_size();
527 if (!m_focused)
528 return;
529
530 auto it = m_callbacks.find(*m_focused);
531 if (it == m_callbacks.end() || !it->second.text)
532 return;
533
534 it->second.text(*m_focused, codepoint);
535}
536
538{
539 if (!m_window) {
540 return;
541 }
542
543 const auto& state = m_window->get_state();
544 if (state.current_width != m_known_width || state.current_height != m_known_height) {
545 handle_resize(state.current_width, state.current_height);
546 }
547}
548
549void Context::handle_resize(uint32_t width, uint32_t height)
550{
553 for (auto& [id, callbacks] : m_callbacks) {
554 if (callbacks.resize)
555 callbacks.resize(width, height);
556 }
557}
558
560{
561 auto* event_source = &m_window->get_event_source();
562 const std::string close_event_name = m_name + "_close";
563
564 for (auto& [id, callbacks] : m_callbacks) {
565 if (callbacks.close)
566 callbacks.close();
567 }
568 clear_focus();
569 detach_window(false);
570 event_source->defer([event_manager = &m_event_manager,
571 name = std::move(close_event_name)]() {
572 event_manager->cancel_event(name);
573 });
574}
575
576} // namespace MayaFlux::Portal::Forma
uint32_t h
Definition InkPress.cpp:29
std::weak_ptr< Core::Window > window
Definition Picker.cpp:23
std::string name
Definition VKDevice.cpp:123
uint32_t py
uint32_t px
uint32_t width
uint32_t height
std::function< void(uint32_t id)> KeyFn
Definition Context.hpp:48
std::unordered_map< int, KeyHandlerState > m_registered_keys
Definition Context.hpp:353
Context & wire_drag(uint32_t id, std::function< void(glm::vec2)> sink, IO::MouseButtons btn=IO::MouseButtons::Left)
Register a drag handler receiving only the cursor position.
Definition Context.cpp:219
void sync_size()
Run the resize callbacks if the window's size differs from the size they last saw.
Definition Context.cpp:537
std::optional< uint32_t > m_dragging[3]
Definition Context.hpp:382
void on_resize(uint32_t id, ResizeFn fn)
Called on every window resize.
Definition Context.cpp:136
void handle_key_release(IO::Keys key)
Definition Context.cpp:494
std::function< void(uint32_t id, uint32_t codepoint)> TextFn
Definition Context.hpp:49
std::shared_ptr< Layer > m_layer
Definition Context.hpp:355
void on_scroll(uint32_t id, ScrollFn fn)
Called on scroll while the cursor is over an element.
Definition Context.cpp:68
void handle_move(double px, double py)
Definition Context.cpp:352
void on_focus_gained(uint32_t id, EnterFn fn)
Called once when an element gains keyboard focus (via click).
Definition Context.cpp:126
void handle_drags(double px, double py)
Definition Context.cpp:433
void handle_key_press(IO::Keys key)
Definition Context.cpp:478
void on_enter(uint32_t id, EnterFn fn)
Called once when the cursor enters an element's region.
Definition Context.cpp:58
std::function< void(uint32_t id, glm::vec2 ndc)> MoveFn
Definition Context.hpp:44
std::function< void(uint32_t width, uint32_t height)> ResizeFn
Definition Context.hpp:50
Context(std::shared_ptr< Layer > layer, std::shared_ptr< Core::Window > window, Vruta::EventManager &event_manager, std::string name)
Construct and immediately register event coroutines.
Definition Context.cpp:12
void handle_key_held(IO::Keys key)
Definition Context.cpp:509
void unbind(uint32_t id)
Remove all callbacks registered for an element id.
Definition Context.cpp:168
std::function< void(uint32_t id, glm::vec2 ndc, double dx, double dy)> ScrollFn
Definition Context.hpp:47
Vruta::EventManager & m_event_manager
Definition Context.hpp:357
void on_close(uint32_t id, CloseFn fn)
Register a callback invoked when the Context window closes.
Definition Context.cpp:153
std::shared_ptr< Core::Window > m_window
Definition Context.hpp:356
std::function< void(uint32_t id, glm::vec2 ndc)> PressFn
Definition Context.hpp:43
void handle_release(double px, double py, IO::MouseButtons btn)
Definition Context.cpp:414
Context & press_and_hold(uint32_t id, IO::Keys key, KeyFn fn)
Register a key handler that fires on the initial press and on every repeat, with a deterministic firs...
Definition Context.cpp:213
const Vruta::WindowEventSource & event_source() const
Definition Context.cpp:188
void handle_drag(double px, double py, IO::MouseButtons btn)
Definition Context.cpp:442
std::function< void(uint32_t id)> LeaveFn
Definition Context.hpp:46
void cancel_handlers(bool cancel_close=true)
Definition Context.cpp:309
Kinesis::AABB2D to_ndc_rect(double x, double y, double w, double h) const noexcept
Rect of pixel size (w, h) at pixel position (x, y), top-left origin, converted to NDC from the window...
Definition Context.cpp:347
void on_text(uint32_t id, TextFn fn)
Called with each resolved Unicode codepoint while the element has keyboard focus.
Definition Context.cpp:121
void on_focus_lost(uint32_t id, LeaveFn fn)
Called once when an element loses keyboard focus.
Definition Context.cpp:131
void handle_resize(uint32_t width, uint32_t height)
Definition Context.cpp:549
void on_move(uint32_t id, MoveFn fn)
Called each move event while the cursor is over an element.
Definition Context.cpp:48
glm::vec2 to_ndc(double px, double py) const noexcept
Definition Context.cpp:338
std::unordered_map< uint32_t, ElementCallbacks > m_callbacks
Definition Context.hpp:361
void on_release(uint32_t id, IO::MouseButtons btn, PressFn fn)
Called when a mouse button is released over an element.
Definition Context.cpp:43
void on_press(uint32_t id, IO::MouseButtons btn, PressFn fn)
Called when a mouse button is pressed over an element.
Definition Context.cpp:38
void on_drag(uint32_t id, IO::MouseButtons btn, MoveFn fn)
Called on each mouse-move event while btn is held, tracking the element where the drag began even whe...
Definition Context.cpp:53
std::optional< uint32_t > m_hovered
Definition Context.hpp:359
void on_leave(uint32_t id, LeaveFn fn)
Called once when the cursor leaves an element's region.
Definition Context.cpp:63
std::function< void(uint32_t id)> EnterFn
Definition Context.hpp:45
void handle_press(double px, double py, IO::MouseButtons btn)
Definition Context.cpp:379
void handle_scroll(double dx, double dy)
Definition Context.cpp:460
Context & key_step(uint32_t id, std::shared_ptr< MappedState< float > > state, IO::Keys decrease, IO::Keys increase, float delta, float clamp_min=0.0F, float clamp_max=1.0F)
Attach key-delta handlers to a Mapped<float> element.
Definition Context.cpp:193
std::function< void()> CloseFn
Definition Context.hpp:51
void on_held(uint32_t id, IO::Keys key, KeyFn fn)
Called repeatedly while a key is held and the element has focus.
Definition Context.cpp:105
void handle_text(uint32_t codepoint)
Definition Context.cpp:524
void clear_focus()
Clear keyboard focus (no element focused).
Definition Context.cpp:158
~Context()
Cancel all registered event coroutines.
Definition Context.cpp:29
void detach_window(bool cancel_close=true)
Definition Context.cpp:141
std::optional< uint32_t > m_focused
Definition Context.hpp:383
Event wiring between a Layer and a window surface.
Definition Context.hpp:41
void add_event(const std::shared_ptr< Event > &event, const std::string &name="")
Add a event to the manager.
bool cancel_event(const std::shared_ptr< Event > &event)
Cancels and removes a event from the manager.
void defer(std::function< void()> action)
Run an action after the active outermost window-event dispatch.
Awaitable stream of window input events.
MouseButtons
Enumeration for mouse buttons.
Definition Keys.hpp:147
Vruta::Event key_released(std::shared_ptr< Core::Window > window, IO::Keys key, std::function< void()> callback)
Creates an Event coroutine that triggers on specific key release.
Vruta::Event key_pressed(std::shared_ptr< Core::Window > window, IO::Keys key, std::function< void()> callback)
Creates an Event coroutine that triggers on specific key press.
Vruta::Event mouse_moved(std::shared_ptr< Core::Window > window, std::function< void(double, double)> callback)
Creates an Event coroutine that triggers on mouse movement.
Vruta::Event mouse_scrolled(std::shared_ptr< Core::Window > window, std::function< void(double, double)> callback)
Creates an Event coroutine that triggers on mouse scroll.
Vruta::Event window_closed(std::shared_ptr< Core::Window > window, std::function< void()> callback)
Creates an Event coroutine that triggers on window close.
Vruta::Event key_held(std::shared_ptr< Core::Window > window, IO::Keys key, std::function< void()> callback)
Creates an Event coroutine that triggers on key press and repeats while held.
Vruta::Event mouse_released(std::shared_ptr< Core::Window > window, IO::MouseButtons button, std::function< void(double, double)> callback)
Creates an Event coroutine that triggers on specific mouse button release.
Vruta::Event mouse_pressed(std::shared_ptr< Core::Window > window, IO::MouseButtons button, std::function< void(double, double)> callback)
Creates an Event coroutine that triggers on specific mouse button press.
Vruta::Event window_resized(std::shared_ptr< Core::Window > window, std::function< void(uint32_t, uint32_t)> callback)
Creates an Event coroutine that triggers on window resize.
Vruta::Event text_input(std::shared_ptr< Core::Window > window, std::function< void(uint32_t)> callback)
Creates an Event coroutine that triggers on resolved text input.
Axis-aligned bounding rectangle in a 2D coordinate space.
Definition Bounds.hpp:63
Value carrier for a Mapped primitive.
Definition Mapped.hpp:40