UI input: fix dialog click/type failures (ImGui event queue + SDL text input)

This commit is contained in:
mchughalex 2026-07-11 02:05:06 +01:00
parent ef55119407
commit 700e7108c3
6 changed files with 221 additions and 62 deletions

View file

@ -77,8 +77,12 @@ class ImGuiDrawer : public WindowInputListener, public UIDrawer {
void RenderDrawLists(ImDrawData* data, UIDrawContext& ui_draw_context); void RenderDrawLists(ImDrawData* data, UIDrawContext& ui_draw_context);
void AddDialogImpl(ImGuiDialog* dialog);
void RemoveDialogImpl(ImGuiDialog* dialog);
void ClearInput(); void ClearInput();
void OnKey(KeyEvent& e, bool is_down); void OnKey(KeyEvent& e, bool is_down);
static int MouseEventButtonToImGui(const MouseEvent& e);
void UpdateMousePosition(float x, float y); void UpdateMousePosition(float x, float y);
void SwitchToPhysicalMouseAndUpdateMousePosition(const MouseEvent& e); void SwitchToPhysicalMouseAndUpdateMousePosition(const MouseEvent& e);
@ -108,6 +112,15 @@ class ImGuiDrawer : public WindowInputListener, public UIDrawer {
// detaching the presenter. // detaching the presenter.
std::unique_ptr<ImmediateTexture> font_texture_; std::unique_ptr<ImmediateTexture> font_texture_;
// Bit mask of ImGui mouse buttons the drawer has seen pressed, used for
// window mouse capture bookkeeping (io.MouseDown can't be used - input is
// queued via the ImGui event API and only reflected at NewFrame).
uint32_t mouse_buttons_down_ = 0;
// Whether the platform window has been asked to activate text input
// (character event delivery / IME) because a text widget is active.
bool text_input_active_ = false;
// If there's an active pointer, the ImGui mouse is controlled by this touch. // If there's an active pointer, the ImGui mouse is controlled by this touch.
// If it's TouchEvent::kPointerIDNone, the ImGui mouse is controlled by the // If it's TouchEvent::kPointerIDNone, the ImGui mouse is controlled by the
// mouse. // mouse.

View file

@ -311,6 +311,14 @@ class Window {
// the cursor instantly. // the cursor instantly.
void SetCursorVisibility(CursorVisibility new_cursor_visibility); void SetCursorVisibility(CursorVisibility new_cursor_visibility);
// Enables or disables platform text input (character event delivery / IME)
// while a text widget is active. Characters are delivered to input listeners
// via OnKeyChar. On some platforms character events are always delivered
// (Win32 WM_CHAR, GTK) and this is a no-op; on SDL, character events are
// only delivered while text input is started, so this must be kept in sync
// by whoever hosts text widgets (done by ImGuiDrawer).
virtual void SetTextInputActive(bool /*active*/) {}
bool HasFocus() const { return HasActualState() ? has_focus_ : false; } bool HasFocus() const { return HasActualState() ? has_focus_ : false; }
// May be applied in a delayed way or dropped at all, HasFocus will not // May be applied in a delayed way or dropped at all, HasFocus will not
// necessarily be true immediately. // necessarily be true immediately.

View file

@ -25,6 +25,8 @@ class SDLWindow final : public Window {
static void HandleSDLEvent(const SDL_Event& event); static void HandleSDLEvent(const SDL_Event& event);
void SetTextInputActive(bool active) override;
protected: protected:
uint32_t GetLatestDpiImpl() const override; uint32_t GetLatestDpiImpl() const override;
bool OpenImpl() override; bool OpenImpl() override;
@ -51,6 +53,8 @@ class SDLWindow final : public Window {
WindowDestructionReceiver& destruction_receiver); WindowDestructionReceiver& destruction_receiver);
void HandleMouseWheel(const SDL_MouseWheelEvent& event, void HandleMouseWheel(const SDL_MouseWheelEvent& event,
WindowDestructionReceiver& destruction_receiver); WindowDestructionReceiver& destruction_receiver);
void HandleTextInput(const SDL_TextInputEvent& event,
WindowDestructionReceiver& destruction_receiver);
void RevealAutoHiddenCursor(); void RevealAutoHiddenCursor();
void SetCursorAutoHideTimer(); void SetCursorAutoHideTimer();
void RemoveCursorAutoHideTimer(); void RemoveCursorAutoHideTimer();

View file

@ -377,7 +377,9 @@ class KeyboardInputDialog : public XamDialog {
} }
if (ImGui::InputText("##body", text_buffer_.data(), text_buffer_.size(), if (ImGui::InputText("##body", text_buffer_.data(), text_buffer_.size(),
ImGuiInputTextFlags_EnterReturnsTrue)) { ImGuiInputTextFlags_EnterReturnsTrue)) {
text_ = std::string(text_buffer_.data(), text_buffer_.size()); // Note: constructing from the pointer only - the buffer is
// NUL-padded to max_length, and (data, size) would keep the padding.
text_ = std::string(text_buffer_.data());
cancelled_ = false; cancelled_ = false;
ImGui::CloseCurrentPopup(); ImGui::CloseCurrentPopup();
Close(); Close();
@ -390,7 +392,7 @@ class KeyboardInputDialog : public XamDialog {
} }
} }
if (ImGui::Button("OK")) { if (ImGui::Button("OK")) {
text_ = std::string(text_buffer_.data(), text_buffer_.size()); text_ = std::string(text_buffer_.data());
cancelled_ = false; cancelled_ = false;
ImGui::CloseCurrentPopup(); ImGui::CloseCurrentPopup();
Close(); Close();

View file

@ -22,6 +22,7 @@
#include <rex/ui/imgui_drawer.h> #include <rex/ui/imgui_drawer.h>
#include <rex/ui/ui_event.h> #include <rex/ui/ui_event.h>
#include <rex/ui/window.h> #include <rex/ui/window.h>
#include <rex/ui/windowed_app_context.h>
#include <imgui.h> #include <imgui.h>
@ -44,11 +45,11 @@ ImGuiDrawer::~ImGuiDrawer() {
SetPresenter(nullptr); SetPresenter(nullptr);
if (!dialogs_.empty()) { if (!dialogs_.empty()) {
window_->RemoveInputListener(this); window_->RemoveInputListener(this);
if (internal_state_) { if (touch_pointer_id_ == TouchEvent::kPointerIDNone && mouse_buttons_down_) {
ImGui::SetCurrentContext(internal_state_); window_->ReleaseMouse();
if (touch_pointer_id_ == TouchEvent::kPointerIDNone && ImGui::IsAnyMouseDown()) { }
window_->ReleaseMouse(); if (text_input_active_) {
} window_->SetTextInputActive(false);
} }
} }
if (internal_state_) { if (internal_state_) {
@ -59,6 +60,20 @@ ImGuiDrawer::~ImGuiDrawer() {
void ImGuiDrawer::AddDialog(ImGuiDialog* dialog) { void ImGuiDrawer::AddDialog(ImGuiDialog* dialog) {
assert_not_null(dialog); assert_not_null(dialog);
// Dialogs may be constructed on kernel/guest threads (XamShow*UI dispatch);
// the dialog list, the window input listener list and the presenter UI
// drawer list are all UI-thread state, so marshal to the UI thread. If the
// UI loop is already gone (shutdown), skip - the dialog will never be drawn
// and the dispatch path deletes it.
WindowedAppContext& app_context = window_->app_context();
if (!app_context.IsInUIThread()) {
app_context.CallInUIThreadSynchronous([this, dialog]() { AddDialogImpl(dialog); });
return;
}
AddDialogImpl(dialog);
}
void ImGuiDrawer::AddDialogImpl(ImGuiDialog* dialog) {
// Check if already added. // Check if already added.
if (std::find(dialogs_.cbegin(), dialogs_.cend(), dialog) != dialogs_.cend()) { if (std::find(dialogs_.cbegin(), dialogs_.cend(), dialog) != dialogs_.cend()) {
return; return;
@ -78,6 +93,15 @@ void ImGuiDrawer::AddDialog(ImGuiDialog* dialog) {
void ImGuiDrawer::RemoveDialog(ImGuiDialog* dialog) { void ImGuiDrawer::RemoveDialog(ImGuiDialog* dialog) {
assert_not_null(dialog); assert_not_null(dialog);
WindowedAppContext& app_context = window_->app_context();
if (!app_context.IsInUIThread()) {
app_context.CallInUIThreadSynchronous([this, dialog]() { RemoveDialogImpl(dialog); });
return;
}
RemoveDialogImpl(dialog);
}
void ImGuiDrawer::RemoveDialogImpl(ImGuiDialog* dialog) {
auto it = std::find(dialogs_.cbegin(), dialogs_.cend(), dialog); auto it = std::find(dialogs_.cbegin(), dialogs_.cend(), dialog);
if (it == dialogs_.cend()) { if (it == dialogs_.cend()) {
return; return;
@ -265,9 +289,19 @@ std::optional<ImGuiKey> ImGuiDrawer::VirtualKeyToImGuiKey(VirtualKey vkey) {
{ui::VirtualKey::kF11, ImGuiKey_F11}, {ui::VirtualKey::kF11, ImGuiKey_F11},
{ui::VirtualKey::kF12, ImGuiKey_F12}, {ui::VirtualKey::kF12, ImGuiKey_F12},
// Modifiers // Modifiers
// Win32 sends the generic VKs (WM_KEYDOWN gives VK_SHIFT etc.), SDL and
// GTK send the sided ones - both must be mapped.
{ui::VirtualKey::kShift, ImGuiKey_LeftShift}, {ui::VirtualKey::kShift, ImGuiKey_LeftShift},
{ui::VirtualKey::kControl, ImGuiKey_LeftCtrl}, {ui::VirtualKey::kControl, ImGuiKey_LeftCtrl},
{ui::VirtualKey::kMenu, ImGuiKey_LeftAlt}, {ui::VirtualKey::kMenu, ImGuiKey_LeftAlt},
{ui::VirtualKey::kLShift, ImGuiKey_LeftShift},
{ui::VirtualKey::kRShift, ImGuiKey_RightShift},
{ui::VirtualKey::kLControl, ImGuiKey_LeftCtrl},
{ui::VirtualKey::kRControl, ImGuiKey_RightCtrl},
{ui::VirtualKey::kLMenu, ImGuiKey_LeftAlt},
{ui::VirtualKey::kRMenu, ImGuiKey_RightAlt},
{ui::VirtualKey::kLWin, ImGuiKey_LeftSuper},
{ui::VirtualKey::kRWin, ImGuiKey_RightSuper},
{ui::VirtualKey::kCapital, ImGuiKey_CapsLock}, {ui::VirtualKey::kCapital, ImGuiKey_CapsLock},
{ui::VirtualKey::kNumLock, ImGuiKey_NumLock}, {ui::VirtualKey::kNumLock, ImGuiKey_NumLock},
{ui::VirtualKey::kScroll, ImGuiKey_ScrollLock}, {ui::VirtualKey::kScroll, ImGuiKey_ScrollLock},
@ -402,7 +436,15 @@ void ImGuiDrawer::Draw(UIDrawContext& ui_draw_context) {
if (reset_mouse_position_after_next_frame_) { if (reset_mouse_position_after_next_frame_) {
reset_mouse_position_after_next_frame_ = false; reset_mouse_position_after_next_frame_ = false;
io.MousePos = ImVec2(-FLT_MAX, -FLT_MAX); io.AddMousePosEvent(-FLT_MAX, -FLT_MAX);
}
// Keep the platform text input state in sync with whether a text widget is
// active (SDL only delivers character events while text input is started).
bool want_text_input = io.WantTextInput && !dialogs_.empty();
if (want_text_input != text_input_active_) {
text_input_active_ = want_text_input;
window_->SetTextInputActive(want_text_input);
} }
// Detaching is deferred if the last dialog is removed during drawing, perform // Detaching is deferred if the last dialog is removed during drawing, perform
@ -476,30 +518,40 @@ void ImGuiDrawer::OnKeyChar(KeyEvent& e) {
} }
} }
int ImGuiDrawer::MouseEventButtonToImGui(const MouseEvent& e) {
switch (e.button()) {
case rex::ui::MouseEvent::Button::kLeft:
return 0;
case rex::ui::MouseEvent::Button::kRight:
return 1;
default:
// Ignored.
return -1;
}
}
void ImGuiDrawer::OnMouseDown(MouseEvent& e) { void ImGuiDrawer::OnMouseDown(MouseEvent& e) {
SwitchToPhysicalMouseAndUpdateMousePosition(e); SwitchToPhysicalMouseAndUpdateMousePosition(e);
auto& io = GetIO(); auto& io = GetIO();
int button = -1; int button = MouseEventButtonToImGui(e);
switch (e.button()) { if (button >= 0) {
case rex::ui::MouseEvent::Button::kLeft: { if (!(mouse_buttons_down_ & (UINT32_C(1) << button))) {
button = 0; if (!mouse_buttons_down_) {
break;
}
case rex::ui::MouseEvent::Button::kRight: {
button = 1;
break;
}
default: {
// Ignored.
break;
}
}
if (button >= 0 && button < std::size(io.MouseDown)) {
if (!io.MouseDown[button]) {
if (!ImGui::IsAnyMouseDown()) {
window_->CaptureMouse(); window_->CaptureMouse();
} }
io.MouseDown[button] = true; mouse_buttons_down_ |= UINT32_C(1) << button;
}
// Queue rather than write io.MouseDown directly: the event queue keeps the
// position-then-button ordering and spreads a same-frame press+release
// over multiple NewFrames, so clicks register at the position they
// actually happened at and fast clicks aren't lost when the UI frame rate
// lags behind input.
io.AddMouseButtonEvent(button, true);
if (io.WantCaptureMouse) {
// Keep presses over the UI out of lower-Z listeners (game input,
// keybinds). Releases are deliberately never eaten so lower listeners
// can't be left with a stuck button.
e.set_handled(true);
} }
} }
} }
@ -511,35 +563,25 @@ void ImGuiDrawer::OnMouseMove(MouseEvent& e) {
void ImGuiDrawer::OnMouseUp(MouseEvent& e) { void ImGuiDrawer::OnMouseUp(MouseEvent& e) {
SwitchToPhysicalMouseAndUpdateMousePosition(e); SwitchToPhysicalMouseAndUpdateMousePosition(e);
auto& io = GetIO(); auto& io = GetIO();
int button = -1; int button = MouseEventButtonToImGui(e);
switch (e.button()) { if (button >= 0) {
case rex::ui::MouseEvent::Button::kLeft: { if (mouse_buttons_down_ & (UINT32_C(1) << button)) {
button = 0; mouse_buttons_down_ &= ~(UINT32_C(1) << button);
break; if (!mouse_buttons_down_) {
}
case rex::ui::MouseEvent::Button::kRight: {
button = 1;
break;
}
default: {
// Ignored.
break;
}
}
if (button >= 0 && button < std::size(io.MouseDown)) {
if (io.MouseDown[button]) {
io.MouseDown[button] = false;
if (!ImGui::IsAnyMouseDown()) {
window_->ReleaseMouse(); window_->ReleaseMouse();
} }
} }
io.AddMouseButtonEvent(button, false);
} }
} }
void ImGuiDrawer::OnMouseWheel(MouseEvent& e) { void ImGuiDrawer::OnMouseWheel(MouseEvent& e) {
SwitchToPhysicalMouseAndUpdateMousePosition(e); SwitchToPhysicalMouseAndUpdateMousePosition(e);
auto& io = GetIO(); auto& io = GetIO();
io.MouseWheel += float(e.scroll_y()) / float(MouseEvent::kScrollPerDetent); io.AddMouseWheelEvent(0.0f, float(e.scroll_y()) / float(MouseEvent::kScrollPerDetent));
if (io.WantCaptureMouse) {
e.set_handled(true);
}
} }
void ImGuiDrawer::OnTouchEvent(TouchEvent& e) { void ImGuiDrawer::OnTouchEvent(TouchEvent& e) {
@ -550,8 +592,13 @@ void ImGuiDrawer::OnTouchEvent(TouchEvent& e) {
// The latest pointer needs to be controlling the ImGui mouse. // The latest pointer needs to be controlling the ImGui mouse.
if (touch_pointer_id_ == TouchEvent::kPointerIDNone) { if (touch_pointer_id_ == TouchEvent::kPointerIDNone) {
// Switching from the mouse to touch input. // Switching from the mouse to touch input.
if (ImGui::IsAnyMouseDown()) { if (mouse_buttons_down_) {
std::memset(io.MouseDown, 0, sizeof(io.MouseDown)); for (int button = 0; button < 32; ++button) {
if (mouse_buttons_down_ & (UINT32_C(1) << button)) {
io.AddMouseButtonEvent(button, false);
}
}
mouse_buttons_down_ = 0;
window_->ReleaseMouse(); window_->ReleaseMouse();
} }
} }
@ -563,27 +610,32 @@ void ImGuiDrawer::OnTouchEvent(TouchEvent& e) {
} }
UpdateMousePosition(e.x(), e.y()); UpdateMousePosition(e.x(), e.y());
if (action == TouchEvent::Action::kUp || action == TouchEvent::Action::kCancel) { if (action == TouchEvent::Action::kUp || action == TouchEvent::Action::kCancel) {
io.MouseDown[0] = false; io.AddMouseButtonEvent(0, false);
touch_pointer_id_ = TouchEvent::kPointerIDNone; touch_pointer_id_ = TouchEvent::kPointerIDNone;
// Make sure that after a touch, the ImGui mouse isn't hovering over // Make sure that after a touch, the ImGui mouse isn't hovering over
// anything. // anything.
reset_mouse_position_after_next_frame_ = true; reset_mouse_position_after_next_frame_ = true;
} else { } else {
io.MouseDown[0] = true; io.AddMouseButtonEvent(0, true);
reset_mouse_position_after_next_frame_ = false; reset_mouse_position_after_next_frame_ = false;
} }
} }
void ImGuiDrawer::ClearInput() { void ImGuiDrawer::ClearInput() {
auto& io = GetIO(); auto& io = GetIO();
if (touch_pointer_id_ == TouchEvent::kPointerIDNone && ImGui::IsAnyMouseDown()) { if (touch_pointer_id_ == TouchEvent::kPointerIDNone && mouse_buttons_down_) {
window_->ReleaseMouse(); window_->ReleaseMouse();
} }
io.MousePos = ImVec2(-FLT_MAX, -FLT_MAX); mouse_buttons_down_ = 0;
std::memset(io.MouseDown, 0, sizeof(io.MouseDown)); io.ClearEventsQueue();
io.ClearInputKeys(); io.ClearInputKeys();
io.ClearInputMouse();
touch_pointer_id_ = TouchEvent::kPointerIDNone; touch_pointer_id_ = TouchEvent::kPointerIDNone;
reset_mouse_position_after_next_frame_ = false; reset_mouse_position_after_next_frame_ = false;
if (text_input_active_) {
text_input_active_ = false;
window_->SetTextInputActive(false);
}
} }
void ImGuiDrawer::OnKey(KeyEvent& e, bool is_down) { void ImGuiDrawer::OnKey(KeyEvent& e, bool is_down) {
@ -594,35 +646,47 @@ void ImGuiDrawer::OnKey(KeyEvent& e, bool is_down) {
} }
switch (virtual_key) { switch (virtual_key) {
case VirtualKey::kShift: case VirtualKey::kShift:
io.KeyShift = is_down; case VirtualKey::kLShift:
case VirtualKey::kRShift:
io.AddKeyEvent(ImGuiMod_Shift, is_down);
break; break;
case VirtualKey::kControl: case VirtualKey::kControl:
io.KeyCtrl = is_down; case VirtualKey::kLControl:
case VirtualKey::kRControl:
io.AddKeyEvent(ImGuiMod_Ctrl, is_down);
break; break;
case VirtualKey::kMenu: case VirtualKey::kMenu:
// FIXME(Triang3l): Doesn't work in xenia-ui-window-demo. case VirtualKey::kLMenu:
io.KeyAlt = is_down; case VirtualKey::kRMenu:
io.AddKeyEvent(ImGuiMod_Alt, is_down);
break; break;
case VirtualKey::kLWin: case VirtualKey::kLWin:
io.KeySuper = is_down; case VirtualKey::kRWin:
io.AddKeyEvent(ImGuiMod_Super, is_down);
break; break;
default: default:
break; break;
} }
// While a text field is active, keep key presses away from app keybinds and
// the game (typing a name must not toggle overlays or drive MnK input).
// Releases always propagate so lower listeners can't get stuck keys.
if (is_down && io.WantTextInput) {
e.set_handled(true);
}
} }
void ImGuiDrawer::UpdateMousePosition(float x, float y) { void ImGuiDrawer::UpdateMousePosition(float x, float y) {
auto& io = GetIO(); auto& io = GetIO();
float physical_to_logical = float(window_->GetMediumDpi()) / float(window_->GetDpi()); float physical_to_logical = float(window_->GetMediumDpi()) / float(window_->GetDpi());
io.MousePos.x = x * physical_to_logical; io.AddMousePosEvent(x * physical_to_logical, y * physical_to_logical);
io.MousePos.y = y * physical_to_logical;
} }
void ImGuiDrawer::SwitchToPhysicalMouseAndUpdateMousePosition(const MouseEvent& e) { void ImGuiDrawer::SwitchToPhysicalMouseAndUpdateMousePosition(const MouseEvent& e) {
if (touch_pointer_id_ != TouchEvent::kPointerIDNone) { if (touch_pointer_id_ != TouchEvent::kPointerIDNone) {
touch_pointer_id_ = TouchEvent::kPointerIDNone; touch_pointer_id_ = TouchEvent::kPointerIDNone;
auto& io = GetIO(); auto& io = GetIO();
std::memset(io.MouseDown, 0, sizeof(io.MouseDown)); // Release the ImGui touch-driven button.
io.AddMouseButtonEvent(0, false);
// Nothing needs to be done regarding CaptureMouse and ReleaseMouse - all // Nothing needs to be done regarding CaptureMouse and ReleaseMouse - all
// buttons as well as mouse capture have been released when switching to // buttons as well as mouse capture have been released when switching to
// touch input, the mouse is never captured during touch input, and now // touch input, the mouse is never captured during touch input, and now

View file

@ -376,6 +376,9 @@ void SDLWindow::HandleSDLEvent(const SDL_Event& event) {
case SDL_EVENT_KEY_UP: case SDL_EVENT_KEY_UP:
window_id = event.key.windowID; window_id = event.key.windowID;
break; break;
case SDL_EVENT_TEXT_INPUT:
window_id = event.text.windowID;
break;
case SDL_EVENT_MOUSE_BUTTON_DOWN: case SDL_EVENT_MOUSE_BUTTON_DOWN:
case SDL_EVENT_MOUSE_BUTTON_UP: case SDL_EVENT_MOUSE_BUTTON_UP:
window_id = event.button.windowID; window_id = event.button.windowID;
@ -589,6 +592,9 @@ void SDLWindow::HandleEvent(const SDL_Event& event) {
case SDL_EVENT_KEY_UP: case SDL_EVENT_KEY_UP:
HandleKey(event.key, false, destruction_receiver); HandleKey(event.key, false, destruction_receiver);
break; break;
case SDL_EVENT_TEXT_INPUT:
HandleTextInput(event.text, destruction_receiver);
break;
case SDL_EVENT_MOUSE_BUTTON_DOWN: case SDL_EVENT_MOUSE_BUTTON_DOWN:
HandleMouseButton(event.button, true, destruction_receiver); HandleMouseButton(event.button, true, destruction_receiver);
break; break;
@ -662,6 +668,68 @@ void SDLWindow::HandleMouseMotion(const SDL_MouseMotionEvent& event,
OnMouseMove(e, destruction_receiver); OnMouseMove(e, destruction_receiver);
} }
void SDLWindow::HandleTextInput(const SDL_TextInputEvent& event,
WindowDestructionReceiver& destruction_receiver) {
if (!event.text) {
return;
}
SDL_Keymod mod = SDL_GetModState();
const uint8_t* s = reinterpret_cast<const uint8_t*>(event.text);
while (*s) {
// Decode one UTF-8 codepoint.
uint32_t codepoint = 0;
uint32_t continuation_count = 0;
uint8_t lead = *s++;
if (lead < 0x80) {
codepoint = lead;
} else if ((lead & 0xE0) == 0xC0) {
codepoint = lead & 0x1F;
continuation_count = 1;
} else if ((lead & 0xF0) == 0xE0) {
codepoint = lead & 0x0F;
continuation_count = 2;
} else if ((lead & 0xF8) == 0xF0) {
codepoint = lead & 0x07;
continuation_count = 3;
} else {
// Stray continuation or invalid lead byte - skip.
continue;
}
bool valid = true;
for (uint32_t i = 0; i < continuation_count; ++i) {
uint8_t continuation = *s;
if ((continuation & 0xC0) != 0x80) {
valid = false;
break;
}
codepoint = (codepoint << 6) | (continuation & 0x3F);
++s;
}
if (!valid || !codepoint) {
continue;
}
// Mirrors the Win32 WM_CHAR path - the character is carried in the
// KeyEvent's virtual key field.
KeyEvent e(this, VirtualKey(codepoint), 1, false, (mod & SDL_KMOD_SHIFT) != 0,
(mod & SDL_KMOD_CTRL) != 0, (mod & SDL_KMOD_ALT) != 0, (mod & SDL_KMOD_GUI) != 0);
OnKeyChar(e, destruction_receiver);
if (destruction_receiver.IsWindowDestroyedOrClosed()) {
return;
}
}
}
void SDLWindow::SetTextInputActive(bool active) {
if (!window_) {
return;
}
if (active) {
SDL_StartTextInput(window_);
} else {
SDL_StopTextInput(window_);
}
}
void SDLWindow::HandleMouseWheel(const SDL_MouseWheelEvent& event, void SDLWindow::HandleMouseWheel(const SDL_MouseWheelEvent& event,
WindowDestructionReceiver& destruction_receiver) { WindowDestructionReceiver& destruction_receiver) {
RevealAutoHiddenCursor(); RevealAutoHiddenCursor();