mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-06-16 05:13:58 -07:00
New input handling system
The new system allows to register all function objects as Input::Handler not only function pointers. To not get dangling references a handler is unsubscribed on it's destruction. The order in with the handlers are executed has to be specified by the slot-argument, instead of the reverse order of registration.
This commit is contained in:
parent
1ab55e7f2e
commit
9890d3eb8a
14 changed files with 616 additions and 230 deletions
|
|
@ -32,6 +32,7 @@
|
|||
#include "ps/GameSetup/GameSetup.h"
|
||||
#include "ps/Hotkey.h"
|
||||
#include "ps/XML/Xeromyces.h"
|
||||
#include "ps/VideoMode.h"
|
||||
#include "scriptinterface/FunctionWrapper.h"
|
||||
#include "scriptinterface/Object.h"
|
||||
#include "scriptinterface/ScriptConversions.h"
|
||||
|
|
@ -166,11 +167,10 @@ public:
|
|||
hotkeyNotification.key.repeat = 0;
|
||||
|
||||
// Init input and poll the event.
|
||||
InitInput();
|
||||
in_push_priority_event(hotkeyNotification);
|
||||
SDL_Event ev;
|
||||
while (in_poll_event(ev))
|
||||
in_dispatch_event(ev);
|
||||
const std::unique_ptr<InputHandlers> _{MakeInputHandlers()};
|
||||
g_VideoMode.m_InputManager.PushPriorityEvent(hotkeyNotification);
|
||||
for (SDL_Event& ev : g_VideoMode.m_InputManager.PollEvents())
|
||||
g_VideoMode.m_InputManager.DispatchEvent(ev);
|
||||
|
||||
const ScriptInterface& pageScriptInterface = *(g_GUI->GetActiveGUI()->GetScriptInterface());
|
||||
ScriptRequest prq(pageScriptInterface);
|
||||
|
|
@ -191,9 +191,9 @@ public:
|
|||
|
||||
// We are listening to KeyDown events, so repeat shouldn't matter.
|
||||
hotkeyNotification.key.repeat = 1;
|
||||
in_push_priority_event(hotkeyNotification);
|
||||
while (in_poll_event(ev))
|
||||
in_dispatch_event(ev);
|
||||
g_VideoMode.m_InputManager.PushPriorityEvent(hotkeyNotification);
|
||||
for (SDL_Event& ev : g_VideoMode.m_InputManager.PollEvents())
|
||||
g_VideoMode.m_InputManager.DispatchEvent(ev);
|
||||
|
||||
hotkey_pressed_value = false;
|
||||
Script::GetProperty(prq, global, "state_before", &js_hotkey_pressed_value);
|
||||
|
|
@ -206,9 +206,9 @@ public:
|
|||
TS_ASSERT_EQUALS(hotkey_pressed_value, true);
|
||||
|
||||
hotkeyNotification.type = SDL_KEYUP;
|
||||
in_push_priority_event(hotkeyNotification);
|
||||
while (in_poll_event(ev))
|
||||
in_dispatch_event(ev);
|
||||
g_VideoMode.m_InputManager.PushPriorityEvent(hotkeyNotification);
|
||||
for (SDL_Event& ev : g_VideoMode.m_InputManager.PollEvents())
|
||||
g_VideoMode.m_InputManager.DispatchEvent(ev);
|
||||
|
||||
hotkey_pressed_value = true;
|
||||
Script::GetProperty(prq, global, "state_before", &js_hotkey_pressed_value);
|
||||
|
|
|
|||
|
|
@ -1,97 +0,0 @@
|
|||
/* Copyright (C) 2026 Wildfire Games.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included
|
||||
* in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* SDL input redirector; dispatches to multiple handlers.
|
||||
*/
|
||||
|
||||
#include "precompiled.h"
|
||||
|
||||
#include "input.h"
|
||||
|
||||
#include "lib/debug.h"
|
||||
#include "lib/external_libraries/libsdl.h"
|
||||
#include "lib/status.h"
|
||||
|
||||
#include <SDL_events.h>
|
||||
#include <cstddef>
|
||||
#include <list>
|
||||
|
||||
const size_t MAX_HANDLERS = 10;
|
||||
static InHandler handler_stack[MAX_HANDLERS];
|
||||
static size_t handler_stack_top = 0;
|
||||
|
||||
static std::list<SDL_Event> priority_events;
|
||||
|
||||
void in_add_handler(InHandler handler)
|
||||
{
|
||||
ENSURE(handler);
|
||||
|
||||
if(handler_stack_top >= MAX_HANDLERS)
|
||||
WARN_IF_ERR(ERR::LIMIT);
|
||||
|
||||
handler_stack[handler_stack_top++] = handler;
|
||||
}
|
||||
|
||||
void in_reset_handlers()
|
||||
{
|
||||
handler_stack_top = 0;
|
||||
}
|
||||
|
||||
// send ev to each handler until one returns IN_HANDLED
|
||||
void in_dispatch_event(const SDL_Event& ev)
|
||||
{
|
||||
for(int i = (int)handler_stack_top-1; i >= 0; i--)
|
||||
{
|
||||
ENSURE(handler_stack[i]);
|
||||
InReaction ret = handler_stack[i](ev);
|
||||
// .. done, return
|
||||
if(ret == IN_HANDLED)
|
||||
return;
|
||||
// .. next handler
|
||||
else if(ret == IN_PASS)
|
||||
continue;
|
||||
// .. invalid return value
|
||||
else
|
||||
DEBUG_WARN_ERR(ERR::LOGIC); // invalid handler return value
|
||||
}
|
||||
}
|
||||
|
||||
void in_push_priority_event(const SDL_Event& event)
|
||||
{
|
||||
priority_events.push_back(event);
|
||||
}
|
||||
|
||||
int in_poll_priority_event(SDL_Event& event)
|
||||
{
|
||||
if (priority_events.empty())
|
||||
return 0;
|
||||
|
||||
event = priority_events.front();
|
||||
priority_events.pop_front();
|
||||
return 1;
|
||||
}
|
||||
|
||||
int in_poll_event(SDL_Event& event)
|
||||
{
|
||||
return in_poll_priority_event(event) ? 1 : SDL_PollEvent(&event);
|
||||
}
|
||||
|
|
@ -42,29 +42,4 @@ enum InReaction
|
|||
IN_HANDLED = 2
|
||||
};
|
||||
|
||||
typedef InReaction (*InHandler)(const SDL_Event&);
|
||||
|
||||
// register an input handler, which will receive all subsequent events first.
|
||||
// events are passed to other handlers if handler returns IN_PASS.
|
||||
extern void in_add_handler(InHandler handler);
|
||||
|
||||
// remove all registered input handlers
|
||||
extern void in_reset_handlers();
|
||||
|
||||
// send event to each handler (newest first) until one returns true
|
||||
extern void in_dispatch_event(const SDL_Event& event);
|
||||
|
||||
// push an event onto the back of a high-priority queue - the new event will
|
||||
// be returned by in_poll_event before any standard SDL events
|
||||
extern void in_push_priority_event(const SDL_Event& event);
|
||||
|
||||
// reads events that were pushed by in_push_priority_event
|
||||
// returns 1 if an event was read, 0 otherwise.
|
||||
extern int in_poll_priority_event(SDL_Event& event);
|
||||
|
||||
// reads events that were pushed by in_push_priority_event, or, if there are
|
||||
// no high-priority events) reads from the SDL event queue with SDL_PollEvent.
|
||||
// returns 1 if an event was read, 0 otherwise.
|
||||
extern int in_poll_event(SDL_Event& event);
|
||||
|
||||
#endif // #ifndef INCLUDED_INPUT
|
||||
|
|
|
|||
110
source/main.cpp
110
source/main.cpp
|
|
@ -41,7 +41,6 @@ that of Atlas depending on commandline parameters.
|
|||
#include "lib/file/file_system.h"
|
||||
#include "lib/file/vfs/vfs.h"
|
||||
#include "lib/frequency_filter.h"
|
||||
#include "lib/input.h"
|
||||
#include "lib/path.h"
|
||||
#include "lib/posix/posix_types.h"
|
||||
#include "lib/secure_crt.h"
|
||||
|
|
@ -66,6 +65,7 @@ that of Atlas depending on commandline parameters.
|
|||
#include "ps/GameSetup/Paths.h"
|
||||
#include "ps/Globals.h"
|
||||
#include "ps/Hotkey.h"
|
||||
#include "ps/Input.h"
|
||||
#include "ps/Loader.h"
|
||||
#include "ps/Mod.h"
|
||||
#include "ps/ModInstaller.h"
|
||||
|
|
@ -281,8 +281,7 @@ static void PumpEvents()
|
|||
|
||||
PROFILE3("dispatch events");
|
||||
|
||||
SDL_Event ev{};
|
||||
while (in_poll_event(ev))
|
||||
for (SDL_Event& ev : g_VideoMode.m_InputManager.PollEvents())
|
||||
{
|
||||
PROFILE2("event");
|
||||
if (g_GUI)
|
||||
|
|
@ -292,7 +291,7 @@ static void PumpEvents()
|
|||
std::string data = Script::StringifyJSON(rq, &tmpVal);
|
||||
PROFILE2_ATTR("%s", data.c_str());
|
||||
}
|
||||
in_dispatch_event(ev);
|
||||
g_VideoMode.m_InputManager.DispatchEvent(ev);
|
||||
}
|
||||
|
||||
g_TouchInput.Frame();
|
||||
|
|
@ -524,19 +523,6 @@ static void NonVisualFrame()
|
|||
QuitEngine(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
static void MainControllerInit()
|
||||
{
|
||||
// add additional input handlers only needed by this controller:
|
||||
|
||||
// must be registered after gui_handler. Should mayhap even be last.
|
||||
in_add_handler(MainInputHandler);
|
||||
}
|
||||
|
||||
static void MainControllerShutdown()
|
||||
{
|
||||
in_reset_handlers();
|
||||
}
|
||||
|
||||
static std::optional<RL::Interface> CreateRLInterface(const CmdLineArgs& args)
|
||||
{
|
||||
if (!args.Has("rl-interface"))
|
||||
|
|
@ -751,58 +737,82 @@ static void RunGameOrAtlas(const std::span<const char* const> argv)
|
|||
std::optional<DAP::Interface> dapInterface{CreateDAPInterface(args)};
|
||||
#endif // CONFIG2_DAP_INTERFACE
|
||||
|
||||
std::optional<ScriptInterface> guiScriptInterface;
|
||||
|
||||
if (isVisual)
|
||||
{
|
||||
guiScriptInterface.emplace("Engine", "gui", *g_ScriptContext);
|
||||
InitGraphics(args, 0, installedMods, *g_ScriptContext, *guiScriptInterface);
|
||||
MainControllerInit();
|
||||
}
|
||||
else if (!InitNonVisual(args))
|
||||
g_Shutdown = ShutdownType::Quit;
|
||||
|
||||
try
|
||||
{
|
||||
// MSVC doesn't support copy elision in ternary expressions. So we use a lambda instead.
|
||||
std::optional<RL::Interface> rlInterface{[&]() -> std::optional<RL::Interface>
|
||||
class VisualData
|
||||
{
|
||||
public:
|
||||
VisualData(const CmdLineArgs& args, std::vector<CStr>& installedMods) :
|
||||
inputHandlers{InitGraphics(args, 0, installedMods, *g_ScriptContext,
|
||||
scriptInterface)}
|
||||
{
|
||||
if (g_Shutdown == ShutdownType::None)
|
||||
return CreateRLInterface(args);
|
||||
}
|
||||
|
||||
VisualData(const VisualData&) = delete;
|
||||
VisualData& operator=(const VisualData&) = delete;
|
||||
VisualData(VisualData&&) = delete;
|
||||
VisualData& operator=(VisualData&) = delete;
|
||||
~VisualData() = default;
|
||||
|
||||
private:
|
||||
ScriptInterface scriptInterface{"Engine", "gui", *g_ScriptContext};
|
||||
std::unique_ptr<InputHandlers> inputHandlers;
|
||||
Input::Handler<InReaction(&)(const SDL_Event&)> mainInputHandler{
|
||||
g_VideoMode.m_InputManager, Input::Slot::PRIMARY, MainInputHandler};
|
||||
};
|
||||
|
||||
// MSVC doesn't support copy elision in ternary expressions. So we use a lambda instead.
|
||||
const std::optional<VisualData> visualData{[&]() -> std::optional<VisualData>
|
||||
{
|
||||
if (isVisual)
|
||||
return std::make_optional<VisualData>(args, installedMods);
|
||||
else
|
||||
return std::nullopt;
|
||||
}()};
|
||||
if (!isVisual && !InitNonVisual(args))
|
||||
g_Shutdown = ShutdownType::Quit;
|
||||
|
||||
while (g_Shutdown == ShutdownType::None)
|
||||
try
|
||||
{
|
||||
if (isVisual)
|
||||
// MSVC doesn't support copy elision in ternary expressions. So we use a lambda instead.
|
||||
std::optional<RL::Interface> rlInterface{[&]() -> std::optional<RL::Interface>
|
||||
{
|
||||
if (g_Shutdown == ShutdownType::None)
|
||||
return CreateRLInterface(args);
|
||||
else
|
||||
return std::nullopt;
|
||||
}()};
|
||||
|
||||
while (g_Shutdown == ShutdownType::None)
|
||||
{
|
||||
if (isVisual)
|
||||
{
|
||||
#if CONFIG2_DAP_INTERFACE
|
||||
Frame(rlInterface ? &*rlInterface : nullptr, fixedFrameFrequency, dapInterface ? &*dapInterface : nullptr);
|
||||
Frame(rlInterface ? &*rlInterface : nullptr, fixedFrameFrequency,
|
||||
dapInterface ? &*dapInterface : nullptr);
|
||||
#else
|
||||
Frame(rlInterface ? &*rlInterface : nullptr, fixedFrameFrequency);
|
||||
Frame(rlInterface ? &*rlInterface : nullptr, fixedFrameFrequency);
|
||||
#endif
|
||||
else if(rlInterface)
|
||||
rlInterface->TryApplyMessage();
|
||||
else
|
||||
NonVisualFrame();
|
||||
}
|
||||
else if(rlInterface)
|
||||
rlInterface->TryApplyMessage();
|
||||
else
|
||||
NonVisualFrame();
|
||||
}
|
||||
|
||||
}
|
||||
catch (const RL::SetupError&)
|
||||
{
|
||||
rlInterfaceError = true;
|
||||
}
|
||||
|
||||
ShutdownNetworkAndUI();
|
||||
}
|
||||
catch (const RL::SetupError&)
|
||||
{
|
||||
rlInterfaceError = true;
|
||||
}
|
||||
|
||||
ShutdownNetworkAndUI();
|
||||
guiScriptInterface.reset();
|
||||
|
||||
#if CONFIG2_DAP_INTERFACE
|
||||
dapInterface.reset();
|
||||
#endif // CONFIG2_DAP_INTERFACE
|
||||
|
||||
ShutdownConfigAndSubsequent();
|
||||
MainControllerShutdown();
|
||||
|
||||
} while (g_Shutdown == ShutdownType::Restart);
|
||||
|
||||
if (rlInterfaceError)
|
||||
|
|
|
|||
|
|
@ -35,7 +35,6 @@
|
|||
#include "lib/file/vfs/vfs.h"
|
||||
#include "lib/file/vfs/vfs_path.h"
|
||||
#include "lib/file/vfs/vfs_util.h"
|
||||
#include "lib/input.h"
|
||||
#include "lib/path.h"
|
||||
#include "lib/status.h"
|
||||
#include "lib/sysdep/os.h"
|
||||
|
|
@ -254,44 +253,41 @@ static void InitPs(bool setup_gui, const CStrW& gui_page, ScriptInterface* srcSc
|
|||
g_GUI->SwitchPage(gui_page, srcScriptInterface, initData);
|
||||
}
|
||||
|
||||
void InitInput()
|
||||
[[nodiscard]] std::unique_ptr<InputHandlers> MakeInputHandlers()
|
||||
{
|
||||
g_Joystick.Initialise();
|
||||
|
||||
// register input handlers
|
||||
// This stack is constructed so the first added, will be the last
|
||||
// one called. This is important, because each of the handlers
|
||||
// has the potential to block events to go further down
|
||||
// in the chain. I.e. the last one in the list added, is the
|
||||
// only handler that can block all messages before they are
|
||||
// processed.
|
||||
in_add_handler(game_view_handler);
|
||||
std::unique_ptr<InputHandlers> handlers{std::make_unique<InputHandlers>()};
|
||||
handlers->emplace(g_VideoMode.m_InputManager, Input::Slot::GAME_VIEW, game_view_handler);
|
||||
|
||||
in_add_handler(CProfileViewer::InputThunk);
|
||||
handlers->emplace(g_VideoMode.m_InputManager, Input::Slot::PROFILE_VIEWER, CProfileViewer::InputThunk);
|
||||
|
||||
in_add_handler(HotkeyInputActualHandler);
|
||||
handlers->emplace(g_VideoMode.m_InputManager, Input::Slot::HOTKEY_INPUT, HotkeyInputActualHandler);
|
||||
|
||||
// gui_handler needs to be registered after (i.e. called before!) the
|
||||
// hotkey handler so that input boxes can be typed in without
|
||||
// setting off hotkeys.
|
||||
in_add_handler(gui_handler);
|
||||
handlers->emplace(g_VideoMode.m_InputManager, Input::Slot::GUI, gui_handler);
|
||||
// Likewise for the console.
|
||||
in_add_handler(conInputHandler);
|
||||
handlers->emplace(g_VideoMode.m_InputManager, Input::Slot::CONSOLE, conInputHandler);
|
||||
|
||||
in_add_handler(touch_input_handler);
|
||||
handlers->emplace(g_VideoMode.m_InputManager, Input::Slot::TOUCH_INPUT, touch_input_handler);
|
||||
|
||||
// Should be called after scancode map update (i.e. after the global input, but before UI).
|
||||
// This never blocks the event, but it does some processing necessary for hotkeys,
|
||||
// which are triggered later down the input chain.
|
||||
// (by calling this before the UI, we can use 'EventWouldTriggerHotkey' in the UI).
|
||||
in_add_handler(HotkeyInputPrepHandler);
|
||||
handlers->emplace(g_VideoMode.m_InputManager, Input::Slot::HOTKEY_INPUT_PREPARATION,
|
||||
HotkeyInputPrepHandler);
|
||||
|
||||
// These two must be called first (i.e. pushed last)
|
||||
// GlobalsInputHandler deals with some important global state,
|
||||
// such as which scancodes are being pressed, mouse buttons pressed, etc.
|
||||
// while HotkeyStateChange updates the map of active hotkeys.
|
||||
in_add_handler(GlobalsInputHandler);
|
||||
in_add_handler(HotkeyStateChange);
|
||||
handlers->emplace(g_VideoMode.m_InputManager, Input::Slot::GLOBAL, GlobalsInputHandler);
|
||||
handlers->emplace(g_VideoMode.m_InputManager, Input::Slot::HOTKEY_STATE_CHANGE, HotkeyStateChange);
|
||||
|
||||
return handlers;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -635,8 +631,9 @@ bool Init(const CmdLineArgs& args, int flags)
|
|||
return true;
|
||||
}
|
||||
|
||||
void InitGraphics(const CmdLineArgs& args, int flags, const std::vector<CStr>& installedMods,
|
||||
ScriptContext& scriptContext, ScriptInterface& scriptInterface)
|
||||
[[nodiscard]] std::unique_ptr<InputHandlers> InitGraphics(const CmdLineArgs& args, int flags,
|
||||
const std::vector<CStr>& installedMods, ScriptContext& scriptContext,
|
||||
ScriptInterface& scriptInterface)
|
||||
{
|
||||
const bool setup_vmode = (flags & INIT_HAVE_VMODE) == 0;
|
||||
|
||||
|
|
@ -678,7 +675,7 @@ void InitGraphics(const CmdLineArgs& args, int flags, const std::vector<CStr>& i
|
|||
// create renderer
|
||||
new CRenderer(g_VideoMode.GetBackendDevice());
|
||||
|
||||
InitInput();
|
||||
std::unique_ptr<InputHandlers> handlers{MakeInputHandlers()};
|
||||
|
||||
// TODO: Is this the best place for this?
|
||||
if (VfsDirectoryExists(L"maps/"))
|
||||
|
|
@ -709,6 +706,8 @@ void InitGraphics(const CmdLineArgs& args, int flags, const std::vector<CStr>& i
|
|||
// (delete game data, switch GUI page, show error, etc.)
|
||||
CancelLoad(CStr(e.what()).FromUTF8());
|
||||
}
|
||||
|
||||
return handlers;
|
||||
}
|
||||
|
||||
bool InitNonVisual(const CmdLineArgs& args)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (C) 2024 Wildfire Games.
|
||||
/* Copyright (C) 2026 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
|
|
@ -19,7 +19,9 @@
|
|||
#define INCLUDED_GAMESETUP
|
||||
|
||||
#include "ps/CStr.h"
|
||||
#include "ps/Input.h"
|
||||
|
||||
#include <queue>
|
||||
#include <vector>
|
||||
|
||||
class CmdLineArgs;
|
||||
|
|
@ -70,13 +72,16 @@ void InitVfs(const CmdLineArgs& args);
|
|||
* `ShutdownConfigAndSubsequent` has to be called later.
|
||||
*/
|
||||
extern bool Init(const CmdLineArgs& args, int flags);
|
||||
extern void InitInput();
|
||||
|
||||
using InputHandlers = std::queue<Input::Handler<InReaction(&)(const SDL_Event&)>>;
|
||||
[[nodiscard]] std::unique_ptr<InputHandlers> MakeInputHandlers();
|
||||
|
||||
/**
|
||||
* `ShutdownNetworkAndUI` has to be called later.
|
||||
*/
|
||||
void InitGraphics(const CmdLineArgs& args, int flags, const std::vector<CStr>& installedMods,
|
||||
ScriptContext& scriptContext, ScriptInterface& scriptInterface);
|
||||
[[nodiscard]] std::unique_ptr<InputHandlers> InitGraphics(const CmdLineArgs& args, int flags,
|
||||
const std::vector<CStr>& installedMods, ScriptContext& scriptContext,
|
||||
ScriptInterface& scriptInterface);
|
||||
|
||||
/**
|
||||
* `ShutdownNetworkAndUI` has to be called later.
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@
|
|||
#include "ps/Globals.h"
|
||||
#include "ps/KeyName.h"
|
||||
#include "ps/Profiler2.h"
|
||||
#include "ps/VideoMode.h"
|
||||
|
||||
#include <SDL_events.h>
|
||||
#include <SDL_mouse.h>
|
||||
|
|
@ -429,7 +430,7 @@ InReaction HotkeyInputActualHandler(const SDL_Event& ev)
|
|||
SDL_Event hotkeyPressNotification{};
|
||||
hotkeyPressNotification.type = hotkey.retriggered ? SDL_HOTKEYPRESS_SILENT : SDL_HOTKEYPRESS;
|
||||
hotkeyPressNotification.user.data1 = const_cast<char*>(hotkey.mapping->name.c_str());
|
||||
in_push_priority_event(hotkeyPressNotification);
|
||||
g_VideoMode.m_InputManager.PushPriorityEvent(hotkeyPressNotification);
|
||||
}
|
||||
|
||||
// Send a HotkeyDown event on every key, mouseButton and mouseWheel event.
|
||||
|
|
@ -444,7 +445,7 @@ InReaction HotkeyInputActualHandler(const SDL_Event& ev)
|
|||
SDL_Event hotkeyDownNotification{};
|
||||
hotkeyDownNotification.type = SDL_HOTKEYDOWN;
|
||||
hotkeyDownNotification.user.data1 = const_cast<char*>(hotkey.mapping->name.c_str());
|
||||
in_push_priority_event(hotkeyDownNotification);
|
||||
g_VideoMode.m_InputManager.PushPriorityEvent(hotkeyDownNotification);
|
||||
}
|
||||
|
||||
// Release instantaneous events (e.g. mouse wheel) right away.
|
||||
|
|
@ -457,7 +458,7 @@ InReaction HotkeyInputActualHandler(const SDL_Event& ev)
|
|||
SDL_Event hotkeyNotification{};
|
||||
hotkeyNotification.type = hotkey.wasRetriggered ? SDL_HOTKEYUP_SILENT : SDL_HOTKEYUP;
|
||||
hotkeyNotification.user.data1 = const_cast<char*>(hotkey.name);
|
||||
in_push_priority_event(hotkeyNotification);
|
||||
g_VideoMode.m_InputManager.PushPriorityEvent(hotkeyNotification);
|
||||
}
|
||||
|
||||
return IN_PASS;
|
||||
|
|
@ -481,7 +482,7 @@ void ResetActiveHotkeys()
|
|||
SDL_Event hotkeyNotification;
|
||||
hotkeyNotification.type = hotkey.retriggered ? SDL_HOTKEYUP_SILENT : SDL_HOTKEYUP;
|
||||
hotkeyNotification.user.data1 = const_cast<char*>(hotkey.mapping->name.c_str());
|
||||
in_push_priority_event(hotkeyNotification);
|
||||
g_VideoMode.m_InputManager.PushPriorityEvent(hotkeyNotification);
|
||||
}
|
||||
pressedHotkeys.clear();
|
||||
activeScancodes.clear();
|
||||
|
|
|
|||
130
source/ps/Input.cpp
Normal file
130
source/ps/Input.cpp
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
/* Copyright (C) 2026 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 0 A.D. is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "precompiled.h"
|
||||
|
||||
#include "ps/Input.h"
|
||||
|
||||
#include "lib/debug.h"
|
||||
#include "lib/external_libraries/libsdl.h"
|
||||
#include "ps/Profile.h"
|
||||
#include "ps/TouchInput.h"
|
||||
#include "scriptinterface/JSON.h"
|
||||
#include "scriptinterface/ScriptConversions.h"
|
||||
#include "scriptinterface/ScriptRequest.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace Input
|
||||
{
|
||||
Manager::Manager() :
|
||||
m_PriorityEvents{std::make_unique<std::queue<SDL_Event>>()}
|
||||
{
|
||||
}
|
||||
|
||||
Manager::~Manager() = default;
|
||||
|
||||
void Manager::PushPriorityEvent(const SDL_Event& event)
|
||||
{
|
||||
m_PriorityEvents->push(event);
|
||||
}
|
||||
|
||||
void Manager::DispatchEvent(const SDL_Event& event)
|
||||
{
|
||||
// Looks like std::find_if, but std::find_if does not guarantee the order of the handlers.
|
||||
for (const auto handler : m_Handlers)
|
||||
{
|
||||
if (handler && (*handler)(event) == IN_HANDLED)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Manager::PollEventsResult::EventIterator::EventIterator(PollEventsResult& range) :
|
||||
m_Storage{&range}
|
||||
{
|
||||
++*this;
|
||||
}
|
||||
|
||||
Manager::PollEventsResult::EventIterator::reference Manager::PollEventsResult::EventIterator::operator*()
|
||||
{
|
||||
return *m_Storage->m_Event;
|
||||
}
|
||||
|
||||
Manager::PollEventsResult::EventIterator::pointer Manager::PollEventsResult::EventIterator::operator->()
|
||||
{
|
||||
return &**this;
|
||||
}
|
||||
|
||||
Manager::PollEventsResult::EventIterator& Manager::PollEventsResult::EventIterator::operator++()
|
||||
{
|
||||
std::queue<SDL_Event>& priorityEvents{m_Storage->m_PriorityEvents};
|
||||
if (!priorityEvents.empty())
|
||||
{
|
||||
*m_Storage->m_Event = priorityEvents.front();
|
||||
priorityEvents.pop();
|
||||
}
|
||||
else if (!SDL_PollEvent(m_Storage->m_Event.get()))
|
||||
m_Storage = nullptr;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
Manager::PollEventsResult::EventIterator& Manager::PollEventsResult::EventIterator::operator++(int)
|
||||
{
|
||||
++(*this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool Manager::PollEventsResult::EventIterator::operator==(const EventIterator& other) const
|
||||
{
|
||||
return m_Storage == other.m_Storage;
|
||||
}
|
||||
|
||||
Manager::PollEventsResult::PollEventsResult(std::queue<SDL_Event>& priorityEvents) :
|
||||
m_PriorityEvents{priorityEvents},
|
||||
m_Event{std::make_unique<SDL_Event>()}
|
||||
{
|
||||
}
|
||||
|
||||
Manager::PollEventsResult::EventIterator Manager::PollEventsResult::begin()
|
||||
{
|
||||
return EventIterator{*this};
|
||||
}
|
||||
|
||||
Manager::PollEventsResult::EventIterator Manager::PollEventsResult::end()
|
||||
{
|
||||
return EventIterator{};
|
||||
}
|
||||
|
||||
Manager::PollEventsResult Manager::PollEvents()
|
||||
{
|
||||
return PollEventsResult{*m_PriorityEvents};
|
||||
}
|
||||
|
||||
HandlerBase::HandlerBase(HandlerBase*& pos) noexcept :
|
||||
toReset{&pos}
|
||||
{
|
||||
const auto old = std::exchange(*toReset, this);
|
||||
ENSURE(old == nullptr && "There is already a handler registered to this slot.");
|
||||
}
|
||||
|
||||
HandlerBase::~HandlerBase()
|
||||
{
|
||||
const auto old = std::exchange(*toReset, nullptr);
|
||||
ENSURE(old == this && "No handler is registered to this slot.");
|
||||
}
|
||||
} // namespace Input
|
||||
187
source/ps/Input.h
Normal file
187
source/ps/Input.h
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
/* Copyright (C) 2026 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 0 A.D. is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef INCLUDED_INPUT_HANDLER
|
||||
#define INCLUDED_INPUT_HANDLER
|
||||
|
||||
#include "lib/input.h"
|
||||
|
||||
#include <array>
|
||||
#include <concepts>
|
||||
#include <cstddef>
|
||||
#include <queue>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <type_traits>
|
||||
|
||||
class ScriptRequest;
|
||||
|
||||
namespace Input
|
||||
{
|
||||
class HandlerBase;
|
||||
|
||||
// A slot for each handler. Numbers are in invocation order. A handler can discard events. The first handler
|
||||
// is the only which gets all events.
|
||||
namespace Slot
|
||||
{
|
||||
constexpr std::integral_constant<size_t, 0> PRIMARY;
|
||||
|
||||
// These two must be called first `globalsInput` deals with some important global state, such as which
|
||||
// scancodes are being pressed, mouse buttons pressed, etc. while hotkeyStateChange updates the map of
|
||||
// active hotkeys.
|
||||
constexpr std::integral_constant<size_t, 1> HOTKEY_STATE_CHANGE;
|
||||
constexpr std::integral_constant<size_t, 2> GLOBAL;
|
||||
|
||||
// Should be called after scancode map update (i.e. after the global input, but before UI). This never
|
||||
// blocks the event, but it does some processing necessary for hotkeys, which are triggered later down the
|
||||
// input chain. (by calling this before the UI, we can use `EventWouldTriggerHotkey` in the UI).
|
||||
constexpr std::integral_constant<size_t, 3> HOTKEY_INPUT_PREPARATION;
|
||||
|
||||
constexpr std::integral_constant<size_t, 4> TOUCH_INPUT;
|
||||
|
||||
// The console handler needs to be called before the hotkey handler so that text can be typed in without
|
||||
// setting off hotkeys.
|
||||
constexpr std::integral_constant<size_t, 5> CONSOLE;
|
||||
// Likewise for gui.
|
||||
constexpr std::integral_constant<size_t, 6> GUI;
|
||||
constexpr std::integral_constant<size_t, 7> HOTKEY_INPUT;
|
||||
constexpr std::integral_constant<size_t, 8> PROFILE_VIEWER;
|
||||
constexpr std::integral_constant<size_t, 9> GAME_VIEW;
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds a pointer to all registered `HandlerBase`s.
|
||||
* It does unregister all registered `Handler`s in the destructor.
|
||||
*/
|
||||
class Manager
|
||||
{
|
||||
public:
|
||||
Manager();
|
||||
// The `Manager` needs to have a constant memory location (the `Handler`s hold a pointer to it).
|
||||
Manager(Manager&) = delete;
|
||||
Manager& operator=(Manager&) = delete;
|
||||
Manager(Manager&&) = delete;
|
||||
Manager& operator=(Manager&&) = delete;
|
||||
~Manager();
|
||||
|
||||
void PushPriorityEvent(const SDL_Event& event);
|
||||
void DispatchEvent(const SDL_Event& event);
|
||||
|
||||
template<size_t slot>
|
||||
HandlerBase*& Get() noexcept
|
||||
{
|
||||
return std::get<slot>(m_Handlers);
|
||||
}
|
||||
|
||||
class PollEventsResult
|
||||
{
|
||||
public:
|
||||
class EventIterator
|
||||
{
|
||||
public:
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using value_type = SDL_Event;
|
||||
using pointer = value_type*;
|
||||
using reference = value_type&;
|
||||
using iterator_category = std::input_iterator_tag;
|
||||
|
||||
EventIterator() = default;
|
||||
explicit EventIterator(PollEventsResult& range);
|
||||
|
||||
reference operator*();
|
||||
pointer operator->();
|
||||
|
||||
EventIterator& operator++();
|
||||
EventIterator& operator++(int);
|
||||
|
||||
bool operator==(const EventIterator& other) const;
|
||||
|
||||
private:
|
||||
PollEventsResult* m_Storage{nullptr};
|
||||
};
|
||||
|
||||
explicit PollEventsResult(std::queue<SDL_Event>& priorityEvents);
|
||||
|
||||
EventIterator begin();
|
||||
EventIterator end();
|
||||
|
||||
private:
|
||||
std::queue<SDL_Event>& m_PriorityEvents;
|
||||
const std::unique_ptr<SDL_Event> m_Event;
|
||||
};
|
||||
|
||||
PollEventsResult PollEvents();
|
||||
|
||||
private:
|
||||
std::array<HandlerBase*, 10> m_Handlers{{}};
|
||||
const std::unique_ptr<std::queue<SDL_Event>> m_PriorityEvents;
|
||||
};
|
||||
|
||||
/**
|
||||
* Type-erased callable
|
||||
*/
|
||||
class HandlerBase
|
||||
{
|
||||
protected:
|
||||
/**
|
||||
* Can't not be constructed themself, use `Handler` instead.
|
||||
* @param pos On construction the pointer is set to @c this. On
|
||||
* destruction the pointer will be set to nullptr.
|
||||
*/
|
||||
explicit HandlerBase(HandlerBase*& pos) noexcept;
|
||||
virtual ~HandlerBase();
|
||||
|
||||
// A `HandlerBase` needs to have a constant memory location (the `Manager` does hold a pointer to it).
|
||||
HandlerBase(HandlerBase&) = delete;
|
||||
HandlerBase& operator=(HandlerBase&) = delete;
|
||||
HandlerBase(HandlerBase&&) = delete;
|
||||
HandlerBase& operator=(HandlerBase&&) = delete;
|
||||
|
||||
public:
|
||||
virtual InReaction operator()(const SDL_Event& event) = 0;
|
||||
|
||||
private:
|
||||
HandlerBase** toReset;
|
||||
};
|
||||
|
||||
/**
|
||||
* Usable type to register a handler to the associated `Manager`.
|
||||
*/
|
||||
template<std::invocable<const SDL_Event&> Callback>
|
||||
class Handler final : private HandlerBase
|
||||
{
|
||||
public:
|
||||
// `slot` specifies when(in which order) the `callback` of this `Handler` is executed.
|
||||
template<size_t slot>
|
||||
explicit Handler(Manager& manager, std::integral_constant<size_t, slot>, Callback func) :
|
||||
HandlerBase{manager.Get<slot>()},
|
||||
callback{std::move(func)}
|
||||
{
|
||||
}
|
||||
~Handler() final = default;
|
||||
|
||||
private:
|
||||
InReaction operator()(const SDL_Event& event) final
|
||||
{
|
||||
return callback(event);
|
||||
}
|
||||
|
||||
Callback callback;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // INCLUDED_INPUT_HANDLER
|
||||
|
|
@ -18,6 +18,7 @@
|
|||
#ifndef INCLUDED_VIDEOMODE
|
||||
#define INCLUDED_VIDEOMODE
|
||||
|
||||
#include "ps/Input.h"
|
||||
#include "renderer/backend/Backend.h"
|
||||
|
||||
#include <memory>
|
||||
|
|
@ -144,7 +145,10 @@ private:
|
|||
bool m_IsInitialised = false;
|
||||
|
||||
SDL_Window* m_Window = nullptr;
|
||||
public:
|
||||
Input::Manager m_InputManager;
|
||||
|
||||
private:
|
||||
// Initial desktop settings.
|
||||
// Frequency is in Hz, and BPP means bits per pixels (not bytes per pixels).
|
||||
int m_PreferredW = 0;
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@
|
|||
#include "ps/Filesystem.h"
|
||||
#include "ps/Globals.h"
|
||||
#include "ps/Hotkey.h"
|
||||
#include "ps/VideoMode.h"
|
||||
|
||||
#include <SDL_events.h>
|
||||
#include <SDL_keyboard.h>
|
||||
|
|
@ -52,16 +53,18 @@ private:
|
|||
|
||||
void fakeInput(const char* key, bool keyDown)
|
||||
{
|
||||
SDL_Event ev;
|
||||
ev.type = keyDown ? SDL_KEYDOWN : SDL_KEYUP;
|
||||
ev.key.repeat = 0;
|
||||
ev.key.keysym.scancode = SDL_GetScancodeFromName(key);
|
||||
GlobalsInputHandler(ev);
|
||||
HotkeyInputPrepHandler(ev);
|
||||
HotkeyInputActualHandler(ev);
|
||||
{
|
||||
SDL_Event ev;
|
||||
ev.type = keyDown ? SDL_KEYDOWN : SDL_KEYUP;
|
||||
ev.key.repeat = 0;
|
||||
ev.key.keysym.scancode = SDL_GetScancodeFromName(key);
|
||||
GlobalsInputHandler(ev);
|
||||
HotkeyInputPrepHandler(ev);
|
||||
HotkeyInputActualHandler(ev);
|
||||
}
|
||||
hotkeyPress = false;
|
||||
hotkeyUp = false;
|
||||
while(in_poll_priority_event(ev))
|
||||
for (const SDL_Event& ev : g_VideoMode.m_InputManager.PollEvents())
|
||||
{
|
||||
hotkeyUp |= ev.type == SDL_HOTKEYUP;
|
||||
hotkeyPress |= ev.type == SDL_HOTKEYPRESS;
|
||||
|
|
|
|||
168
source/ps/tests/test_Input.h
Normal file
168
source/ps/tests/test_Input.h
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
/* Copyright (C) 2026 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 0 A.D. is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "lib/self_test.h"
|
||||
|
||||
#include "lib/external_libraries/libsdl.h"
|
||||
#include "ps/Input.h"
|
||||
|
||||
#include <iterator>
|
||||
|
||||
class TestInput : public CxxTest::TestSuite
|
||||
{
|
||||
static std::uint32_t GetEventType(const int numevents)
|
||||
{
|
||||
std::uint32_t eventType{SDL_RegisterEvents(numevents)};
|
||||
TS_ASSERT_DIFFERS(eventType, std::numeric_limits<std::uint32_t>::max());
|
||||
TS_ASSERT_STR_EQUALS(SDL_GetError(), "");
|
||||
return eventType;
|
||||
}
|
||||
|
||||
static SDL_Event MakeEvent(const std::uint32_t eventType)
|
||||
{
|
||||
SDL_Event ev{};
|
||||
ev.type = eventType;
|
||||
return ev;
|
||||
}
|
||||
|
||||
static void PushEvent(const std::uint32_t eventType)
|
||||
{
|
||||
SDL_Event ev{MakeEvent(eventType)};
|
||||
TS_ASSERT_EQUALS(SDL_PushEvent(&ev), 1);
|
||||
TS_ASSERT_STR_EQUALS(SDL_GetError(), "");
|
||||
}
|
||||
|
||||
static void PushPriorityEvent(Input::Manager& manager, const std::uint32_t eventType)
|
||||
{
|
||||
const SDL_Event ev{MakeEvent(eventType)};
|
||||
manager.PushPriorityEvent(ev);
|
||||
}
|
||||
|
||||
public:
|
||||
void setUp()
|
||||
{
|
||||
SDL_Init(SDL_INIT_EVENTS);
|
||||
}
|
||||
|
||||
void tearDown()
|
||||
{
|
||||
SDL_Quit();
|
||||
}
|
||||
|
||||
void test_NoEvent()
|
||||
{
|
||||
Input::Manager manager;
|
||||
auto range = manager.PollEvents();
|
||||
TS_ASSERT_EQUALS(std::distance(range.begin(), range.end()), 0);
|
||||
}
|
||||
|
||||
void test_Event()
|
||||
{
|
||||
Input::Manager manager;
|
||||
PushEvent(GetEventType(1));
|
||||
auto range = manager.PollEvents();
|
||||
TS_ASSERT_EQUALS(std::distance(range.begin(), range.end()), 1);
|
||||
}
|
||||
|
||||
void test_PriorityEvent()
|
||||
{
|
||||
Input::Manager manager;
|
||||
PushPriorityEvent(manager, GetEventType(1));
|
||||
auto range = manager.PollEvents();
|
||||
TS_ASSERT_EQUALS(std::distance(range.begin(), range.end()), 1);
|
||||
}
|
||||
|
||||
void test_PriorityOrder()
|
||||
{
|
||||
Input::Manager manager;
|
||||
const std::uint32_t eventTypeStart{SDL_RegisterEvents(2)};
|
||||
const std::uint32_t priorityEventType{eventTypeStart + 1};
|
||||
|
||||
PushEvent(eventTypeStart);
|
||||
PushPriorityEvent(manager, priorityEventType);
|
||||
PushEvent(eventTypeStart);
|
||||
|
||||
auto range = manager.PollEvents();
|
||||
auto iter = range.begin();
|
||||
TS_ASSERT_DIFFERS(iter, range.end());
|
||||
TS_ASSERT_EQUALS(iter->type, priorityEventType);
|
||||
++iter;
|
||||
TS_ASSERT_DIFFERS(iter, range.end());
|
||||
TS_ASSERT_EQUALS(iter->type, eventTypeStart);
|
||||
++iter;
|
||||
TS_ASSERT_DIFFERS(iter, range.end());
|
||||
TS_ASSERT_EQUALS(iter->type, eventTypeStart);
|
||||
++iter;
|
||||
TS_ASSERT_EQUALS(iter, range.end());
|
||||
}
|
||||
|
||||
void test_Dispatch()
|
||||
{
|
||||
Input::Manager manager;
|
||||
bool triggered{false};
|
||||
Input::Handler _{manager, std::integral_constant<size_t, 0>{}, [&](const SDL_Event&){
|
||||
triggered = true;
|
||||
return IN_HANDLED;
|
||||
}};
|
||||
|
||||
TS_ASSERT(!triggered);
|
||||
SDL_Event ev{MakeEvent(GetEventType(1))};
|
||||
manager.DispatchEvent(ev);
|
||||
TS_ASSERT(triggered);
|
||||
}
|
||||
|
||||
void test_DispatchFilter()
|
||||
{
|
||||
Input::Manager manager;
|
||||
const std::uint32_t eventTypeStart{SDL_RegisterEvents(2)};
|
||||
const std::uint32_t filteredEventType{eventTypeStart + 1};
|
||||
[[maybe_unused]] Input::Handler filter{manager, std::integral_constant<size_t, 0>{},
|
||||
[&](const SDL_Event& ev){
|
||||
return ev.type == filteredEventType ? IN_HANDLED : IN_PASS;
|
||||
}};
|
||||
|
||||
bool triggered{false};
|
||||
[[maybe_unused]] Input::Handler test{manager, std::integral_constant<size_t, 1>{},
|
||||
[&](const SDL_Event&){
|
||||
triggered = true;
|
||||
return IN_HANDLED;
|
||||
}};
|
||||
|
||||
SDL_Event ev0{MakeEvent(filteredEventType)};
|
||||
manager.DispatchEvent(ev0);
|
||||
TS_ASSERT(!triggered);
|
||||
SDL_Event ev1{MakeEvent(eventTypeStart)};
|
||||
manager.DispatchEvent(ev1);
|
||||
TS_ASSERT(triggered);
|
||||
}
|
||||
|
||||
void test_Unsubscribe()
|
||||
{
|
||||
Input::Manager manager;
|
||||
bool triggered{false};
|
||||
{
|
||||
Input::Handler _{manager, std::integral_constant<size_t, 0>{}, [&](const SDL_Event&){
|
||||
triggered = true;
|
||||
return IN_HANDLED;
|
||||
}};
|
||||
}
|
||||
|
||||
SDL_Event ev{MakeEvent(GetEventType(1))};
|
||||
manager.DispatchEvent(ev);
|
||||
TS_ASSERT(!triggered);
|
||||
}
|
||||
};
|
||||
|
|
@ -74,6 +74,7 @@ const int g_InitFlags = INIT_HAVE_VMODE | INIT_NO_GUI;
|
|||
std::optional<FileLogger> g_FileLogger;
|
||||
|
||||
std::optional<ScriptInterface> g_ScriptInterface;
|
||||
std::unique_ptr<InputHandlers> g_InputHandlers;
|
||||
}
|
||||
|
||||
MESSAGEHANDLER(Init)
|
||||
|
|
@ -131,7 +132,7 @@ MESSAGEHANDLER(InitGraphics)
|
|||
g_VideoMode.CreateBackendDevice(false);
|
||||
|
||||
g_ScriptInterface.emplace("Engine", "GUIManager", *g_ScriptContext);
|
||||
InitGraphics(g_AtlasGameLoop->args, g_InitFlags, {}, *g_ScriptContext, *g_ScriptInterface);
|
||||
g_InputHandlers = InitGraphics(g_AtlasGameLoop->args, g_InitFlags, {}, *g_ScriptContext, *g_ScriptInterface);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -143,7 +144,7 @@ MESSAGEHANDLER(Shutdown)
|
|||
|
||||
AtlasView::DestroyViews();
|
||||
g_AtlasGameLoop->view = AtlasView::GetView_None();
|
||||
|
||||
g_InputHandlers.reset();
|
||||
ShutdownNetworkAndUI();
|
||||
g_ScriptInterface.reset();
|
||||
ShutdownConfigAndSubsequent();
|
||||
|
|
@ -252,9 +253,8 @@ QUERYHANDLER(RenderLoop)
|
|||
RendererIncrementalLoad();
|
||||
|
||||
// Pump SDL events (e.g. hotkeys)
|
||||
SDL_Event ev{};
|
||||
while (in_poll_priority_event(ev))
|
||||
in_dispatch_event(ev);
|
||||
for (SDL_Event& ev : g_VideoMode.m_InputManager.PollEvents())
|
||||
g_VideoMode.m_InputManager.DispatchEvent(ev);
|
||||
|
||||
if (g_GUI)
|
||||
g_GUI->TickObjects();
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@
|
|||
#include "maths/MathUtil.h"
|
||||
#include "ps/Game.h"
|
||||
#include "ps/GameSetup/Config.h"
|
||||
#include "ps/VideoMode.h"
|
||||
#include "renderer/Renderer.h"
|
||||
#include "scriptinterface/ScriptInterface.h"
|
||||
#include "simulation2/components/ICmpSoundManager.h"
|
||||
|
|
@ -118,7 +119,7 @@ MESSAGEHANDLER(GuiMouseButtonEvent)
|
|||
msg->pos->GetScreenSpace(x, y);
|
||||
ev.button.x = static_cast<u16>(Clamp<int>(x, 0, g_xres));
|
||||
ev.button.y = static_cast<u16>(Clamp<int>(y, 0, g_yres));
|
||||
in_dispatch_event(ev);
|
||||
g_VideoMode.m_InputManager.DispatchEvent(ev);
|
||||
}
|
||||
|
||||
MESSAGEHANDLER(GuiMouseMotionEvent)
|
||||
|
|
@ -129,7 +130,7 @@ MESSAGEHANDLER(GuiMouseMotionEvent)
|
|||
msg->pos->GetScreenSpace(x, y);
|
||||
ev.motion.x = static_cast<u16>(Clamp<int>(x, 0, g_xres));
|
||||
ev.motion.y = static_cast<u16>(Clamp<int>(y, 0, g_yres));
|
||||
in_dispatch_event(ev);
|
||||
g_VideoMode.m_InputManager.DispatchEvent(ev);
|
||||
}
|
||||
|
||||
MESSAGEHANDLER(GuiKeyEvent)
|
||||
|
|
@ -138,7 +139,7 @@ MESSAGEHANDLER(GuiKeyEvent)
|
|||
ev.type = msg->pressed ? SDL_KEYDOWN : SDL_KEYUP;
|
||||
ev.key.keysym.sym = static_cast<SDL_Keycode>(static_cast<int>(msg->sdlkey));
|
||||
ev.key.keysym.scancode = SDL_GetScancodeFromKey(static_cast<SDL_Keycode>(static_cast<int>(msg->sdlkey)));
|
||||
in_dispatch_event(ev);
|
||||
g_VideoMode.m_InputManager.DispatchEvent(ev);
|
||||
}
|
||||
|
||||
MESSAGEHANDLER(GuiCharEvent)
|
||||
|
|
@ -151,13 +152,13 @@ MESSAGEHANDLER(GuiCharEvent)
|
|||
ev.text.type = SDL_TEXTEDITING;
|
||||
ev.text.text[0] = static_cast<char>(msg->sdlkey);
|
||||
ev.text.text[1] = '\0';
|
||||
in_dispatch_event(ev);
|
||||
g_VideoMode.m_InputManager.DispatchEvent(ev);
|
||||
|
||||
ev.type = SDL_TEXTINPUT;
|
||||
ev.text.type = SDL_TEXTINPUT;
|
||||
ev.text.text[0] = static_cast<char>(msg->sdlkey);
|
||||
ev.text.text[1] = '\0';
|
||||
in_dispatch_event(ev);
|
||||
g_VideoMode.m_InputManager.DispatchEvent(ev);
|
||||
}
|
||||
|
||||
} // namespace AtlasMessage
|
||||
|
|
|
|||
Loading…
Reference in a new issue