Move Input and PiGui handling to GuiApplication

At the moment, the lifecycle is responsible for calling HandleEvents and rendering PiGui.
This can change once legacy code no longer depends on HandleEvents running after View::Draw3D().

Added virtual HandleEvent() call to dispatch to legacy UI code.
This is suboptimal; we need a better way that doesn't involve virtual function calls.
Use std::function or a better, lower-overhead delegate library
master
Webster Sheets 2020-03-31 00:59:44 -04:00
parent a1745867ff
commit e584780ce9
9 changed files with 175 additions and 114 deletions

View File

@ -9,7 +9,13 @@
#include <array>
void Input::Init(GameConfig *config)
Input::Input(const GameConfig *config) :
m_capturingMouse(false),
mouseYInvert(false),
joystickEnabled(true),
keyModState(0),
mouseButton(),
mouseMotion()
{
joystickEnabled = (config->Int("EnableJoystick")) ? true : false;
mouseYInvert = (config->Int("InvertMouseY")) ? true : false;
@ -22,8 +28,8 @@ void Input::InitGame()
//reset input states
keyState.clear();
keyModState = 0;
std::fill(mouseButton, mouseButton + COUNTOF(mouseButton), 0);
std::fill(mouseMotion, mouseMotion + COUNTOF(mouseMotion), 0);
mouseButton.fill(0);
mouseMotion.fill(0);
for (std::map<SDL_JoystickID, JoystickState>::iterator stick = joysticks.begin(); stick != joysticks.end(); ++stick) {
JoystickState &state = stick->second;
std::fill(state.buttons.begin(), state.buttons.end(), false);
@ -32,6 +38,11 @@ void Input::InitGame()
}
}
void Input::NewFrame()
{
mouseMotion.fill(0);
}
InputResponse Input::InputFrame::ProcessSDLEvent(SDL_Event &event)
{
bool matched = false;
@ -130,14 +141,14 @@ void Input::HandleSDLEvent(SDL_Event &event)
onKeyRelease.emit(&event.key.keysym);
break;
case SDL_MOUSEBUTTONDOWN:
if (event.button.button < COUNTOF(mouseButton)) {
if (event.button.button < mouseButton.size()) {
mouseButton[event.button.button] = 1;
onMouseButtonDown.emit(event.button.button,
event.button.x, event.button.y);
}
break;
case SDL_MOUSEBUTTONUP:
if (event.button.button < COUNTOF(mouseButton)) {
if (event.button.button < mouseButton.size()) {
mouseButton[event.button.button] = 0;
onMouseButtonUp.emit(event.button.button,
event.button.x, event.button.y);

View File

@ -16,10 +16,10 @@ class Input {
friend class Pi;
public:
Input() = default;
void Init(GameConfig *config);
Input(const GameConfig *config);
void InitGame();
void HandleSDLEvent(SDL_Event &ev);
void NewFrame();
// The Page->Group->Binding system serves as a thin veneer for the UI to make
// sane reasonings about how to structure the Options dialog.
@ -132,7 +132,7 @@ public:
void GetMouseMotion(int motion[2])
{
memcpy(motion, mouseMotion, sizeof(int) * 2);
std::copy_n(mouseMotion.data(), mouseMotion.size(), motion);
}
// Capturing the mouse hides the cursor, puts the mouse into relative mode,
@ -156,8 +156,8 @@ private:
std::map<SDL_Keycode, bool> keyState;
int keyModState;
char mouseButton[6];
int mouseMotion[2];
std::array<char, 6> mouseButton;
std::array<int, 2> mouseMotion;
bool m_capturingMouse;
bool joystickEnabled;

View File

@ -134,7 +134,7 @@ float Pi::amountOfBackgroundStarsDisplayed = 1.0f;
bool Pi::DrawGUI = true;
Graphics::Renderer *Pi::renderer;
RefCountedPtr<UI::Context> Pi::ui;
RefCountedPtr<PiGui> Pi::pigui;
PiGui *Pi::pigui = nullptr;
ModelCache *Pi::modelCache;
Intro *Pi::intro;
SDLGraphics *Pi::sdl;
@ -362,8 +362,7 @@ void Pi::App::Startup()
Pi::rng.IncRefCount(); // so nothing tries to free it
Pi::rng.seed(time(0));
Pi::input = new Input();
Pi::input->Init(Pi::config);
Pi::input = StartupInput(config);
Pi::input->onKeyPress.connect(sigc::ptr_fun(&Pi::HandleKeyDown));
// we can only do bindings once joysticks are initialised.
@ -372,6 +371,8 @@ void Pi::App::Startup()
RegisterInputBindings();
Pi::pigui = StartupPiGui();
// FIXME: move these into the appropriate class!
navTunnelDisplayed = (config->Int("DisplayNavTunnel")) ? true : false;
speedLinesDisplayed = (config->Int("SpeedLines")) ? true : false;
@ -439,9 +440,10 @@ void Pi::App::Shutdown()
BaseSphere::Uninit();
FaceParts::Uninit();
Graphics::Uninit();
Pi::pigui->Uninit();
ShutdownPiGui();
Pi::pigui = nullptr;
Pi::ui.Reset(0);
Pi::pigui.Reset(0);
Lua::UninitModules();
Lua::Uninit();
Gui::Uninit();
@ -453,6 +455,9 @@ void Pi::App::Shutdown()
ShutdownRenderer();
Pi::renderer = nullptr;
ShutdownInput();
Pi::input = nullptr;
delete Pi::config;
delete Pi::planner;
asyncJobQueue.reset();
@ -492,8 +497,6 @@ void LoadStep::Start()
// normal init flow, or drawing the init screen in C++ instead?
// Ideally we can initialize the ImGui related parts of pigui as soon as the renderer is online,
// and then load all the lua-related state once Lua's registered and online...
Pi::pigui.Reset(new PiGui);
Pi::pigui->Init(Pi::renderer->GetSDLWindow());
LuaObject<PiGui>::RegisterClass();
// Don't render the first frame, just make sure all of our fonts are loaded
@ -645,38 +648,7 @@ void MainMenu::Start()
void MainMenu::Update(float deltaTime)
{
SDL_Event event;
while (SDL_PollEvent(&event)) {
if (event.type == SDL_QUIT)
Pi::RequestQuit();
else {
Pi::pigui->ProcessEvent(&event);
if (Pi::pigui->WantCaptureMouse()) {
// don't process mouse event any further, imgui already handled it
switch (event.type) {
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
case SDL_MOUSEWHEEL:
case SDL_MOUSEMOTION:
continue;
default: break;
}
}
if (Pi::pigui->WantCaptureKeyboard()) {
// don't process keyboard event any further, imgui already handled it
switch (event.type) {
case SDL_KEYDOWN:
case SDL_KEYUP:
case SDL_TEXTINPUT:
continue;
default: break;
}
}
Pi::input->HandleSDLEvent(event);
}
}
Pi::GetApp()->HandleEvents();
Pi::intro->Draw(deltaTime);
@ -849,12 +821,13 @@ void Pi::HandleEscKey()
}
}
void Pi::App::HandleEvents()
// Return true if the event has been handled and shouldn't be passed through
// to the normal input system.
bool Pi::App::HandleEvent(SDL_Event &event)
{
PROFILE_SCOPED()
SDL_Event event;
// XXX for most keypresses SDL will generate KEYUP/KEYDOWN and TEXTINPUT
// HACK for most keypresses SDL will generate KEYUP/KEYDOWN and TEXTINPUT
// events. keybindings run off KEYUP/KEYDOWN. the console is opened/closed
// via keybinding. the console TextInput widget uses TEXTINPUT events. thus
// after switching the console, the stray TEXTINPUT event causes the
@ -862,64 +835,37 @@ void Pi::App::HandleEvents()
// this by setting this flag if the console was switched. if its set, we
// swallow the TEXTINPUT event this hack must remain until we have a
// unified input system
bool skipTextInput = false;
// This is safely able to be removed once GUI and newUI are gone
static bool skipTextInput = false;
Pi::input->mouseMotion[0] = Pi::input->mouseMotion[1] = 0;
while (SDL_PollEvent(&event)) {
if (event.type == SDL_QUIT) {
Pi::RequestQuit();
}
Pi::pigui->ProcessEvent(&event);
// Input system takes priority over mouse events when capturing the mouse
if (Pi::pigui->WantCaptureMouse() && !Pi::input->IsCapturingMouse()) {
// don't process mouse event any further, imgui already handled it
switch (event.type) {
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
case SDL_MOUSEWHEEL:
case SDL_MOUSEMOTION:
continue;
default: break;
}
}
if (Pi::pigui->WantCaptureKeyboard()) {
// don't process keyboard event any further, imgui already handled it
switch (event.type) {
case SDL_KEYDOWN:
case SDL_KEYUP:
case SDL_TEXTINPUT:
continue;
default: break;
}
}
if (skipTextInput && event.type == SDL_TEXTINPUT) {
skipTextInput = false;
continue;
}
if (ui->DispatchSDLEvent(event))
continue;
bool consoleActive = Pi::IsConsoleActive();
if (!consoleActive) {
KeyBindings::DispatchSDLEvent(&event);
if (currentView)
currentView->HandleSDLEvent(event);
} else
KeyBindings::toggleLuaConsole.CheckSDLEventAndDispatch(&event);
if (consoleActive != Pi::IsConsoleActive()) {
skipTextInput = true;
continue;
}
if (Pi::IsConsoleActive())
continue;
Gui::HandleSDLEvent(&event);
input->HandleSDLEvent(event);
if (skipTextInput && event.type == SDL_TEXTINPUT) {
skipTextInput = false;
return true;
}
if (ui->DispatchSDLEvent(event))
return true;
bool consoleActive = Pi::IsConsoleActive();
if (!consoleActive) {
KeyBindings::DispatchSDLEvent(&event);
if (currentView)
currentView->HandleSDLEvent(event);
} else {
KeyBindings::toggleLuaConsole.CheckSDLEventAndDispatch(&event);
}
if (consoleActive != Pi::IsConsoleActive()) {
skipTextInput = true;
return true;
}
if (Pi::IsConsoleActive())
return true;
Gui::HandleSDLEvent(&event);
return false;
}
void Pi::App::HandleRequests()

View File

@ -101,7 +101,7 @@ public:
void RunJobs();
void HandleRequests();
void HandleEvents();
bool HandleEvent(SDL_Event &ev) override;
private:
// msgs/requests that can be posted which the game processes at the end of a game loop in HandleRequests
@ -170,7 +170,7 @@ public:
#endif
static RefCountedPtr<UI::Context> ui;
static RefCountedPtr<PiGui> pigui;
static PiGui *pigui;
static Random rng;
static int statSceneTris;

View File

@ -4,6 +4,7 @@
#include "Application.h"
#include "FileSystem.h"
#include "OS.h"
#include "SDL.h"
#include "profiler/Profiler.h"
#include "utils.h"
@ -33,11 +34,14 @@ void Application::Startup()
#ifdef PIONEER_PROFILER
FileSystem::userFiles.MakeDirectory("profiler");
#endif
SDL_Init(SDL_INIT_EVENTS);
}
void Application::Shutdown()
{
FileSystem::Uninit();
SDL_Quit();
}
bool Application::StartLifecycle()

View File

@ -40,8 +40,6 @@ public:
m_nextLifecycle = l;
}
Application *GetApp();
private:
// set to true when you want to accumulate all updates in a lifecycle into a single profile frame
bool m_profilerAccumulate = false;

View File

@ -100,6 +100,54 @@ Graphics::RenderTarget *GuiApplication::CreateRenderTarget(const Graphics::Setti
return nullptr;
}
void GuiApplication::HandleEvents()
{
PROFILE_SCOPED()
SDL_Event event;
// FIXME: input state is right before handling updates because
// legacy UI code needs to run before input does.
// When HandleEvents() is moved to BeginFrame / PreUpdate, this call should go with it
m_input->NewFrame();
while (SDL_PollEvent(&event)) {
if (event.type == SDL_QUIT) {
RequestQuit();
}
m_pigui->ProcessEvent(&event);
// Input system takes priority over mouse events when capturing the mouse
if (m_pigui->WantCaptureMouse() && !m_input->IsCapturingMouse()) {
// don't process mouse event any further, imgui already handled it
switch (event.type) {
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
case SDL_MOUSEWHEEL:
case SDL_MOUSEMOTION:
continue;
default: break;
}
}
if (m_pigui->WantCaptureKeyboard()) {
// don't process keyboard event any further, imgui already handled it
switch (event.type) {
case SDL_KEYDOWN:
case SDL_KEYUP:
case SDL_TEXTINPUT:
continue;
default: break;
}
}
// TODO: virtual method dispatch for each event isn't great. Let's find a better solution
if (HandleEvent(event))
continue;
m_input->HandleSDLEvent(event);
}
}
Graphics::Renderer *GuiApplication::StartupRenderer(const GameConfig *config, bool hidden)
{
PROFILE_SCOPED()
@ -145,3 +193,28 @@ void GuiApplication::ShutdownRenderer()
SDL_QuitSubSystem(SDL_INIT_VIDEO);
}
Input *GuiApplication::StartupInput(const GameConfig *config)
{
m_input.reset(new Input(config));
return m_input.get();
}
void GuiApplication::ShutdownInput()
{
m_input.reset();
}
PiGui *GuiApplication::StartupPiGui()
{
m_pigui.Reset(new PiGui());
m_pigui->Init(m_renderer->GetSDLWindow());
return m_pigui.Get();
}
void GuiApplication::ShutdownPiGui()
{
m_pigui->Uninit();
m_pigui.Reset();
}

View File

@ -5,7 +5,10 @@
#include "Application.h"
#include "GameConfig.h"
#include "Input.h"
#include "RefCounted.h"
#include "SDL_events.h"
#include "pigui/PiGui.h"
#include "graphics/RenderState.h"
#include "graphics/RenderTarget.h"
@ -17,9 +20,11 @@ public:
Application(), m_applicationTitle(title)
{}
protected:
Graphics::Renderer *GetRenderer() { return m_renderer.get(); }
Input *GetInput() { return m_input.get(); }
PiGui *GetPiGui() { return m_pigui.Get(); }
protected:
// Called at the end of the frame automatically, blits the RT onto the application
// framebuffer
void DrawRenderTarget();
@ -27,9 +32,21 @@ protected:
// Call this from your Startup() method
Graphics::Renderer *StartupRenderer(const GameConfig *config, bool hidden = false);
// Call this from your Startup() method
Input *StartupInput(const GameConfig *config);
// Call this from your Startup() method
PiGui *StartupPiGui();
// Call this from your Shutdown() method
void ShutdownRenderer();
// Call this from your Shutdown() method
void ShutdownInput();
// Call this from your shutdown() method
void ShutdownPiGui();
// Hook to bind the RT and clear the screen.
// If you override BeginFrame, make sure you call this.
void BeginFrame() override;
@ -38,9 +55,21 @@ protected:
// If you override EndFrame, make sure you call this.
void EndFrame() override;
// Consume events from SDL and dispatch to pigui / input
void HandleEvents();
// Override point for classes to add custom event handling
virtual bool HandleEvent(SDL_Event &ev) { return false; }
// Override point to handle an application quit notification
virtual void HandleQuit(SDL_QuitEvent &ev) { RequestQuit(); }
private:
Graphics::RenderTarget *CreateRenderTarget(const Graphics::Settings &settings);
RefCountedPtr<PiGui> m_pigui;
std::unique_ptr<Input> m_input;
std::string m_applicationTitle;
std::unique_ptr<Graphics::Renderer> m_renderer;

View File

@ -116,7 +116,7 @@ static int l_engine_attr_ui(lua_State *l)
*/
static int l_engine_attr_pigui(lua_State *l)
{
LuaObject<PiGui>::PushToLua(Pi::pigui.Get());
LuaObject<PiGui>::PushToLua(Pi::pigui);
return 1;
}