diff --git a/Common/CMakeLists.txt b/Common/CMakeLists.txt
index fc7f95c67e..7c146831c6 100644
--- a/Common/CMakeLists.txt
+++ b/Common/CMakeLists.txt
@@ -281,6 +281,8 @@ add_library(Common STATIC
UI/Root.h
UI/Screen.cpp
UI/Screen.h
+ UI/ScreenManager.cpp
+ UI/ScreenManager.h
UI/UI.cpp
UI/UI.h
UI/Context.cpp
diff --git a/Common/Common.vcxproj b/Common/Common.vcxproj
index d11d158f10..042a8472ac 100644
--- a/Common/Common.vcxproj
+++ b/Common/Common.vcxproj
@@ -562,6 +562,7 @@
+
@@ -1022,6 +1023,7 @@
+
diff --git a/Common/Common.vcxproj.filters b/Common/Common.vcxproj.filters
index cd892a2ea1..e9d3d4dca2 100644
--- a/Common/Common.vcxproj.filters
+++ b/Common/Common.vcxproj.filters
@@ -798,6 +798,9 @@
GPU\OpenGL
+
+ UI
+
@@ -1472,6 +1475,9 @@
GPU\OpenGL
+
+ UI
+
diff --git a/Common/UI/PopupScreens.cpp b/Common/UI/PopupScreens.cpp
index 2195085816..de5f2503f6 100644
--- a/Common/UI/PopupScreens.cpp
+++ b/Common/UI/PopupScreens.cpp
@@ -7,6 +7,7 @@
#include "Common/UI/Context.h"
#include "Common/UI/Root.h"
#include "Common/UI/Notice.h"
+#include "Common/UI/ScreenManager.h"
#include "Common/StringUtils.h"
#include "Common/Data/Text/I18n.h"
#include "Common/System/System.h"
diff --git a/Common/UI/Screen.cpp b/Common/UI/Screen.cpp
index c447483e4b..e886ce5ab1 100644
--- a/Common/UI/Screen.cpp
+++ b/Common/UI/Screen.cpp
@@ -1,16 +1,7 @@
-#include "Common/System/Display.h"
#include "Common/System/Request.h"
-#include "Common/Input/InputState.h"
-#include "Common/UI/Root.h"
#include "Common/UI/Screen.h"
-#include "Common/UI/ScrollView.h"
#include "Common/UI/UI.h"
-#include "Common/UI/View.h"
-#include "Common/UI/ViewGroup.h"
-#include "Common/Data/Collections/TinySet.h"
-
#include "Common/Log.h"
-#include "Common/TimeUtil.h"
void Screen::focusChanged(ScreenFocusChange focusChange) {
const char *eventName = "";
@@ -50,489 +41,6 @@ Screen::~Screen() {
}
}
-ScreenManager::~ScreenManager() {
- shutdown();
-}
-
-void ScreenManager::switchScreen(Screen *screen) {
- if (!screen) {
- ERROR_LOG(Log::UI, "Can't switch to empty screen");
- return;
- }
-
- INFO_LOG(Log::UI, "ScreenManager::switchScreen('%s')", screen->tag());
-
- if (!nextStack_.empty() && screen == nextStack_.front().screen) {
- ERROR_LOG(Log::UI, "Already switching to this screen");
- return;
- }
-
- // Note that if a dialog is found, this will be a silent background switch that
- // will only become apparent if the dialog is closed. The previous screen will stick around
- // until that switch.
- // TODO: is this still true?
- if (!nextStack_.empty()) {
- for (int i = 0; i < nextStack_.size(); i++) {
- INFO_LOG(Log::UI, "NextStack contents[%d].screen->tag(): '%s'", i, nextStack_[i].screen->tag());
- }
-
- ERROR_LOG(Log::UI, "Already had a nextStack_! Asynchronous open while doing something? Deleting the new screen.");
- delete screen;
- return;
- }
- if (screen == nullptr) {
- WARN_LOG(Log::UI, "Switching to a zero screen, this can't be good");
- }
- if (stack_.empty() || screen != stack_.back().screen) {
- if (screen) {
- screen->setScreenManager(this);
- }
- nextStack_.push_back({ screen, 0 });
- }
-}
-
-void ScreenManager::cancelScreensAbove(Screen *screen) {
- bool found = false;
- for (int i = 0; i < stack_.size(); i++) {
- if (stack_[i].screen == screen) {
- found = true;
- }
- }
-
- if (found) {
- cancelScreensAbove_ = screen;
- }
-}
-
-void ScreenManager::update() {
- if (cancelScreensAbove_) {
- bool found = false;
- for (int i = (int)stack_.size() - 1; i >= 0; i--) {
- if (stack_[i].screen == cancelScreensAbove_) {
- break;
- }
- Layer temp = stack_.back();
- stack_.pop_back();
- delete temp.screen;
- }
- cancelScreensAbove_ = nullptr;
- }
-
- if (!nextStack_.empty()) {
- switchToNext();
- }
-
- if (overlayScreen_) {
- // NOTE: This is not a full UIScreen update, to avoid double global event processing.
- overlayScreen_->update();
- }
-
- // Process queued events.
- std::deque events;
- {
- std::lock_guard eventGuard(eventQueueLock_);
- events = std::move(eventQueue_);
- eventQueue_.clear();
- }
-
- for (const QueuedEvent &ev : events) {
- switch (ev.type) {
- case QueuedEventType::TOUCH:
- {
- const TouchInput &touch = ev.touch;
- // Send release all events to every screen layer.
- if (touch.flags & TouchInputFlags::RELEASE_ALL) {
- for (auto &layer : stack_) {
- Screen *screen = layer.screen;
- layer.screen->touch(screen->transformTouch(touch));
- }
- } else if (!stack_.empty()) {
- // Let the overlay know about touch-downs, to be able to dismiss popups.
- bool skip = false;
- if (overlayScreen_ && (touch.flags & TouchInputFlags::DOWN)) {
- skip = overlayScreen_->touch(overlayScreen_->transformTouch(touch));
- }
- if (!skip) {
- Screen *screen = stack_.back().screen;
- stack_.back().screen->touch(screen->transformTouch(touch));
- }
- }
- break;
- }
- case QueuedEventType::KEY:
- {
- const KeyInput &key = ev.key;
- // Send key up to every screen layer, to avoid stuck keys.
- if (key.flags & KeyInputFlags::UP) {
- for (auto &layer : stack_) {
- layer.screen->key(key);
- }
- } else if (!stack_.empty()) {
- stack_.back().screen->key(key);
- }
- break;
- }
- case QueuedEventType::AXIS:
- {
- const AxisInput &axis = ev.axis;
- if (!stack_.empty()) {
- stack_.back().screen->axis(axis);
- }
- break;
- }
- default:
- ERROR_LOG(Log::UI, "Unknown queued event type: %d", (int)ev.type);
- break;
- }
- }
-
- // Only the front screen gets update().
- if (!stack_.empty()) {
- Screen *backScreen = stack_.back().screen;
- backScreen->update();
- passInputToMapper_ = backScreen->PassInputToMapper();
- }
-}
-
-void ScreenManager::switchToNext() {
- if (nextStack_.empty()) {
- ERROR_LOG(Log::System, "switchToNext: No nextStack_!");
- }
-
- Layer temp = {nullptr, 0};
- if (!stack_.empty()) {
- temp = stack_.back();
- temp.screen->focusChanged(ScreenFocusChange::FOCUS_LOST_TOP);
- stack_.pop_back();
- }
- stack_.push_back(nextStack_.front());
- nextStack_.front().screen->focusChanged(ScreenFocusChange::FOCUS_BECAME_TOP);
- delete temp.screen;
- UI::SetFocusedView(nullptr, UI::FocusFlags::CAUSE_SCREEN_CHANGE);
-
- // When will this ever happen? Should handle focus here too?
- for (size_t i = 1; i < nextStack_.size(); ++i) {
- stack_.push_back(nextStack_[i]);
- }
- nextStack_.clear();
-}
-
-void ScreenManager::touch(const TouchInput &touch) {
- QueuedEvent ev{};
- ev.type = QueuedEventType::TOUCH;
- ev.touch = touch;
- std::lock_guard guard(eventQueueLock_);
- eventQueue_.push_back(ev);
-}
-
-void ScreenManager::key(const KeyInput &key) {
- QueuedEvent ev{};
- ev.type = QueuedEventType::KEY;
- ev.key = key;
- std::lock_guard guard(eventQueueLock_);
- eventQueue_.push_back(ev);
-}
-
-void ScreenManager::axis(const AxisInput *axes, size_t count) {
- QueuedEvent ev{};
- ev.type = QueuedEventType::AXIS;
- std::lock_guard guard(eventQueueLock_);
- for (size_t i = 0; i < count; i++) {
- ev.axis = axes[i];
- eventQueue_.push_back(ev);
- }
-}
-
-void ScreenManager::deviceLost() {
- for (auto &iter : stack_)
- iter.screen->deviceLost();
-}
-
-void ScreenManager::deviceRestored(Draw::DrawContext *draw) {
- draw_ = draw;
- for (auto &iter : stack_)
- iter.screen->deviceRestored(draw);
-}
-
-void ScreenManager::resized() {
- INFO_LOG(Log::UI, "ScreenManager::resized(g_display.dp_xres/dp_yres: %dx%d)", g_display.dp_xres, g_display.dp_yres);
- // Have to notify the whole stack, otherwise there will be problems when going back
- // to non-top screens.
- for (auto &layer : stack_) {
- layer.screen->resized();
- }
-}
-
-ScreenRenderFlags ScreenManager::render() {
- using namespace Draw;
-
- ScreenRenderFlags flags = ScreenRenderFlags::NONE;
-
- // First, go through the whole stack and have every screen render any non-backbuffer render passes.
- // In EmuScreen, this might result in running emulation.
- for (size_t i = 0; i < stack_.size(); i++) {
- const auto &layer = stack_[i];
- ScreenRenderMode mode = ScreenRenderMode::DEFAULT;
- if (i == stack_.size() - 1) {
- mode |= ScreenRenderMode::TOP;
- }
- flags |= layer.screen->PreRender(mode);
- }
-
- // Now, start the final render pass. This is now the ONLY place where binding the null fb is allowed.
- draw_->BindFramebufferAsRenderTarget(nullptr, {RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR}, "BackBuffer");
- getUIContext()->BeginFrame();
-
- const Draw::Viewport viewport{0.0f, 0.0f, (float)g_display.pixel_xres, (float)g_display.pixel_yres, 0.0f, 1.0f};
- draw_->SetViewport(viewport);
- draw_->SetScissorRect(0, 0, g_display.pixel_xres, g_display.pixel_yres);
- draw_->SetTargetSize(g_display.pixel_xres, g_display.pixel_yres);
-
- if (!stack_.empty()) {
- // Collect the screens to render
- TinySet layers;
-
- // Start at the end, collect screens to form the transparency stack.
- // Then we'll iterate them in reverse order.
- // Note that we skip the overlay screen, we handle it separately.
- // Additionally, we pick up a "background" screen. Normally it will be either
- // the EmuScreen or the actual global background screen.
- auto iter = stack_.end();
- Screen *coveringScreen = nullptr;
- Screen *foundBackgroundScreen = nullptr;
- bool first = true;
-
- do {
- --iter;
- ScreenRenderRole role = iter->screen->renderRole(first);
- if (!foundBackgroundScreen && (role & ScreenRenderRole::CAN_BE_BACKGROUND)) {
- // There still might be a screen that wants to be background - generally the EmuScreen if present.
- layers.push_back(iter->screen);
- foundBackgroundScreen = iter->screen;
- } else if (!coveringScreen) {
- layers.push_back(iter->screen);
- }
- if (iter->flags != LAYER_TRANSPARENT) {
- coveringScreen = iter->screen;
- }
- first = false;
- if (role & ScreenRenderRole::MUST_BE_FIRST) {
- break;
- }
- } while (iter != stack_.begin());
-
- if (backgroundScreen_ && !foundBackgroundScreen) {
- layers.push_back(backgroundScreen_);
- foundBackgroundScreen = backgroundScreen_;
- }
-
- // OK, now we iterate backwards over our little pile of collected screens.
- for (int i = (int)layers.size() - 1; i >= 0; i--) {
- ScreenRenderMode mode = ScreenRenderMode::DEFAULT;
- if (i == (int)layers.size() - 1) {
- // Bottom.
- mode = ScreenRenderMode::FIRST;
- if (i == 0) {
- mode |= ScreenRenderMode::TOP;
- }
- } else if (i == 0) {
- mode = ScreenRenderMode::TOP;
- } else {
- mode = ScreenRenderMode::BEHIND;
- }
- flags |= layers[i]->render(mode);
- }
-
- if (overlayScreen_) {
- // It doesn't care about mode.
- flags |= overlayScreen_->render(ScreenRenderMode::TOP);
- }
-
- getUIContext()->Flush();
-
- if (postRenderCb_) {
- // Really can't render anything after this! Will crash the screenshot mechanism if we do.
- postRenderCb_(getUIContext(), postRenderUserdata_);
- }
- } else {
- ERROR_LOG(Log::UI, "No current screen!");
- }
-
- processFinishDialog();
- return flags;
-}
-
-void ScreenManager::getFocusPosition(float &x, float &y, float &z) {
- UI::ScrollView::GetLastScrollPosition(x, y);
-
- UI::View *v = UI::GetFocusedView();
- x += v ? v->GetBounds().x : 0;
- y += v ? v->GetBounds().y : 0;
- z = stack_.size();
-}
-
-void ScreenManager::sendMessage(UIMessage message, const char *value) {
- if (message == UIMessage::RECREATE_VIEWS) {
- RecreateAllViews();
- } else if (message == UIMessage::LOST_FOCUS) {
- TouchInput input{};
- input.x = -50000.0f;
- input.y = -50000.0f;
- input.flags = TouchInputFlags::RELEASE_ALL;
- input.timestamp = time_now_d();
- input.id = 0;
- touch(input);
- }
-
- if (backgroundScreen_) {
- backgroundScreen_->sendMessage(message, value);
- }
-
- // NOTE: Changed this to send the message to all screens, instead of just the top one,
- // to allow EmuScreen to receive messages from popup menus. Hope this didn't break anything..
- // NOTE: We do not use the iterator solution here, because the screen array may change by sendMessage!
- // Although if it does, that's very bad.
- // We also pick out the stack size before, so we don't get infinite growth if a screen is pushed from the handler.
- // We probably should delay pushes, though...
- const size_t stack_size = stack_.size();
- for (int i = 0; i < stack_size; i++) {
- if (stack_[i].screen) {
- stack_[i].screen->sendMessage(message, value);
- }
- }
-}
-
-void ScreenManager::shutdown() {
- for (const auto &layer : stack_)
- delete layer.screen;
- stack_.clear();
- for (const auto &layer : nextStack_)
- delete layer.screen;
- nextStack_.clear();
- delete overlayScreen_;
- overlayScreen_ = nullptr;
- delete backgroundScreen_;
- backgroundScreen_ = nullptr;
-}
-
-void ScreenManager::push(Screen *screen, int layerFlags) {
- screen->setScreenManager(this);
- if (screen->isTransparent()) {
- layerFlags |= LAYER_TRANSPARENT;
- }
-
- // Release touches and unfocus.
- UI::SetFocusedView(nullptr, UI::FocusFlags::CAUSE_SCREEN_CHANGE);
- TouchInput input{};
- input.x = -50000.0f;
- input.y = -50000.0f;
- input.flags = TouchInputFlags::RELEASE_ALL;
- input.timestamp = time_now_d();
- input.id = 0;
- touch(input);
-
- Layer layer = {screen, layerFlags};
-
- if (!stack_.empty()) {
- stack_.back().screen->focusChanged(ScreenFocusChange::FOCUS_LOST_TOP);
- }
-
- if (nextStack_.empty()) {
- layer.screen->focusChanged(ScreenFocusChange::FOCUS_BECAME_TOP);
- stack_.push_back(layer);
- } else {
- nextStack_.push_back(layer);
- }
-}
-
-void ScreenManager::pop() {
- if (!stack_.empty()) {
- stack_.back().screen->focusChanged(ScreenFocusChange::FOCUS_LOST_TOP);
-
- delete stack_.back().screen;
- stack_.pop_back();
-
- if (!stack_.empty()) {
- stack_.back().screen->focusChanged(ScreenFocusChange::FOCUS_LOST_TOP);
- }
- } else {
- ERROR_LOG(Log::UI, "Can't pop when stack empty");
- }
-}
-
-void ScreenManager::RecreateAllViews() {
- for (auto &layer : stack_) {
- layer.screen->RecreateViews();
- }
-}
-
-void ScreenManager::finishDialog(Screen *dialog, DialogResult result) {
- if (stack_.empty()) {
- ERROR_LOG(Log::UI, "Must be in a dialog to finishDialog");
- return;
- }
- if (dialog != stack_.back().screen) {
- ERROR_LOG(Log::UI, "Wrong dialog being finished!");
- return;
- }
- dialog->onFinish(result);
- dialogFinished_ = dialog;
- dialogResult_ = result;
-}
-
-Screen *ScreenManager::dialogParent(const Screen *dialog) const {
- for (size_t i = 1; i < stack_.size(); ++i) {
- if (stack_[i].screen == dialog) {
- // The previous screen was the caller (not necessarily the topmost.)
- return stack_[i - 1].screen;
- }
- }
-
- return nullptr;
-}
-
-void ScreenManager::processFinishDialog() {
- if (dialogFinished_) {
- {
- // Another dialog may have been pushed before the render, so search for it.
- Screen *caller = dialogParent(dialogFinished_);
- bool erased = false;
- for (size_t i = 0; i < stack_.size(); ++i) {
- if (stack_[i].screen == dialogFinished_) {
- stack_[i].screen->focusChanged(ScreenFocusChange::FOCUS_LOST_TOP);
- stack_.erase(stack_.begin() + i);
- erased = true;
- }
- }
-
- if (erased && !stack_.empty()) {
- stack_.back().screen->focusChanged(ScreenFocusChange::FOCUS_BECAME_TOP);
- }
-
- if (!caller) {
- ERROR_LOG(Log::UI, "ERROR: no top screen when finishing dialog");
- } else if (caller != topScreen()) {
- // The caller may get confused if we call dialogFinished() now.
- WARN_LOG(Log::UI, "Skipping non-top dialog when finishing dialog.");
- } else {
- caller->dialogFinished(dialogFinished_, dialogResult_);
- }
- }
- delete dialogFinished_;
- dialogFinished_ = nullptr;
- }
-}
-
-void ScreenManager::SetBackgroundOverlayScreens(Screen *backgroundScreen, Screen *overlayScreen) {
- delete backgroundScreen_;
- backgroundScreen_ = backgroundScreen;
- backgroundScreen_->setScreenManager(this);
-
- delete overlayScreen_;
- overlayScreen_ = overlayScreen;
- overlayScreen_->setScreenManager(this);
-}
-
const char *DialogResultToString(DialogResult result) {
switch (result) {
case DR_NONE: return "DR_NONE";
diff --git a/Common/UI/Screen.h b/Common/UI/Screen.h
index 347f8ae456..03f4284021 100644
--- a/Common/UI/Screen.h
+++ b/Common/UI/Screen.h
@@ -14,11 +14,7 @@
#pragma once
#include
-#include
#include
-#include
-#include
-#include
#include "Common/Common.h"
#include "Common/Input/InputState.h"
@@ -28,6 +24,10 @@ namespace UI {
class View;
}
+namespace Draw {
+class DrawContext;
+}
+
enum DialogResult {
DR_NONE,
DR_OK,
@@ -41,10 +41,6 @@ const char *DialogResultToString(DialogResult result);
class ScreenManager;
class UIContext;
-namespace Draw {
- class DrawContext;
-}
-
enum class ScreenFocusChange {
FOCUS_LOST_TOP, // Another screen was pushed on top
FOCUS_BECAME_TOP, // Became the top screen again
@@ -58,12 +54,6 @@ enum class ScreenRenderMode {
};
ENUM_CLASS_BITOPS(ScreenRenderMode);
-enum class ScreenRenderFlags {
- NONE = 0,
- HANDLED_THROTTLING = 1,
-};
-ENUM_CLASS_BITOPS(ScreenRenderFlags);
-
enum class ScreenRenderRole {
NONE = 0,
CAN_BE_BACKGROUND = 1,
@@ -77,15 +67,6 @@ enum class ViewLayoutMode {
IgnoreBottomInset,
};
-enum class InputMode {
- None = 0,
- Keyboard = 1,
- Mouse = 2,
- Other = 4,
- ImDebuggerToggle = 8, // ugly hack. debugger and pause goes through.
-};
-ENUM_CLASS_BITOPS(InputMode);
-
enum class QueuedEventType : u8 {
KEY,
AXIS,
@@ -101,6 +82,21 @@ struct QueuedEvent {
};
};
+enum class ScreenRenderFlags {
+ NONE = 0,
+ HANDLED_THROTTLING = 1,
+};
+ENUM_CLASS_BITOPS(ScreenRenderFlags);
+
+enum class InputMode {
+ None = 0,
+ Keyboard = 1,
+ Mouse = 2,
+ Other = 4,
+ ImDebuggerToggle = 8, // ugly hack. debugger and pause goes through.
+};
+ENUM_CLASS_BITOPS(InputMode);
+
class Screen {
public:
Screen() = default;
@@ -126,8 +122,7 @@ public:
virtual void RecreateViews() {}
- // NOTE: This is polled every frame, not called on every input event. This is
- // a step towards eventually removing the ScreenManager input mutex.
+ // NOTE: This is polled every frame, not called on every input event.
virtual InputMode PassInputToMapper() const { return InputMode::None; }
ScreenManager *screenManager() { return screenManager_; }
@@ -154,110 +149,3 @@ private:
DISALLOW_COPY_AND_ASSIGN(Screen);
};
-
-class Transition {
-public:
- Transition() = default;
-};
-
-enum {
- LAYER_TRANSPARENT = 2,
-};
-
-typedef void (*PostRenderCallback)(UIContext *ui, void *userdata);
-
-class ScreenManager {
-public:
- virtual ~ScreenManager();
-
- void switchScreen(Screen *screen);
- void update();
-
- void setUIContext(UIContext *context) { uiContext_ = context; }
- UIContext *getUIContext() { return uiContext_; }
- Draw::DrawContext *getDrawContext() { return draw_; }
-
- void setPostRenderCallback(PostRenderCallback cb, void *userdata) {
- postRenderCb_ = cb;
- postRenderUserdata_ = userdata;
- }
-
- ScreenRenderFlags render();
- void resized();
- void shutdown();
-
- void deviceLost();
- void deviceRestored(Draw::DrawContext *draw);
-
- // Push a dialog box in front. Currently 1-level only.
- void push(Screen *screen, int layerFlags = 0);
-
- // Recreate all views
- void RecreateAllViews();
-
- // Pops the dialog away.
- void finishDialog(Screen *dialog, DialogResult result = DR_OK);
- Screen *dialogParent(const Screen *dialog) const;
-
- // Instant touch, separate from the update() mechanism.
- void touch(const TouchInput &touch);
- void key(const KeyInput &key);
- void axis(const AxisInput *axes, size_t count);
-
- void sendMessage(UIMessage message, const char *value);
-
- const Screen *topScreen() const {
- return stack_.empty() ? nullptr : stack_.back().screen;
- }
- Screen *topScreen() {
- return stack_.empty() ? nullptr : stack_.back().screen;
- }
-
- void cancelScreensAbove(Screen *screen);
-
- void getFocusPosition(float &x, float &y, float &z);
-
- // Will delete any existing overlay screen.
- void SetBackgroundOverlayScreens(Screen *backgroundScreen, Screen *overlayScreen);
-
- InputMode PassInputToMapper() const {
- return passInputToMapper_;
- }
-
-private:
- void pop();
- void switchToNext();
- void processFinishDialog();
-
- UIContext *uiContext_ = nullptr;
- Draw::DrawContext *draw_ = nullptr;
-
- PostRenderCallback postRenderCb_ = nullptr;
- void *postRenderUserdata_ = nullptr;
-
- const Screen *dialogFinished_ = nullptr;
- DialogResult dialogResult_{};
-
- Screen *backgroundScreen_ = nullptr;
- Screen *overlayScreen_ = nullptr;
-
- Screen *cancelScreensAbove_ = nullptr;
-
- struct Layer {
- Screen *screen;
- int flags; // From LAYER_ enum above
- UI::View *focusedView; // TODO: save focus here. Going for quick solution now to reset focus.
- };
-
- // Dialog stack. These are shown "on top" of base screens and the Android back button works as expected.
- // Used for options, in-game menus and other things you expect to be able to back out from onto something.
- std::vector stack_;
- std::vector nextStack_;
-
- std::unordered_map lastAxis_;
-
- std::mutex eventQueueLock_;
- std::deque eventQueue_;
-
- InputMode passInputToMapper_ = InputMode::None;
-};
diff --git a/Common/UI/ScreenManager.cpp b/Common/UI/ScreenManager.cpp
new file mode 100644
index 0000000000..d7bbbd3b80
--- /dev/null
+++ b/Common/UI/ScreenManager.cpp
@@ -0,0 +1,491 @@
+#include "Common/System/Display.h"
+#include "Common/Data/Collections/TinySet.h"
+#include "Common/UI/ScreenManager.h"
+#include "Common/UI/Root.h"
+#include "Common/UI/View.h"
+#include "Common/UI/ScrollView.h"
+#include "Common/GPU/thin3d.h"
+#include "Common/TimeUtil.h"
+
+ScreenManager::~ScreenManager() {
+ shutdown();
+}
+
+void ScreenManager::switchScreen(Screen *screen) {
+ if (!screen) {
+ ERROR_LOG(Log::UI, "Can't switch to empty screen");
+ return;
+ }
+
+ INFO_LOG(Log::UI, "ScreenManager::switchScreen('%s')", screen->tag());
+
+ if (!nextStack_.empty() && screen == nextStack_.front().screen) {
+ ERROR_LOG(Log::UI, "Already switching to this screen");
+ return;
+ }
+
+ // Note that if a dialog is found, this will be a silent background switch that
+ // will only become apparent if the dialog is closed. The previous screen will stick around
+ // until that switch.
+ // TODO: is this still true?
+ if (!nextStack_.empty()) {
+ for (int i = 0; i < nextStack_.size(); i++) {
+ INFO_LOG(Log::UI, "NextStack contents[%d].screen->tag(): '%s'", i, nextStack_[i].screen->tag());
+ }
+
+ ERROR_LOG(Log::UI, "Already had a nextStack_! Asynchronous open while doing something? Deleting the new screen.");
+ delete screen;
+ return;
+ }
+ if (screen == nullptr) {
+ WARN_LOG(Log::UI, "Switching to a zero screen, this can't be good");
+ }
+ if (stack_.empty() || screen != stack_.back().screen) {
+ if (screen) {
+ screen->setScreenManager(this);
+ }
+ nextStack_.push_back({screen, 0});
+ }
+}
+
+void ScreenManager::cancelScreensAbove(Screen *screen) {
+ bool found = false;
+ for (int i = 0; i < stack_.size(); i++) {
+ if (stack_[i].screen == screen) {
+ found = true;
+ }
+ }
+
+ if (found) {
+ cancelScreensAbove_ = screen;
+ }
+}
+
+void ScreenManager::update() {
+ if (cancelScreensAbove_) {
+ bool found = false;
+ for (int i = (int)stack_.size() - 1; i >= 0; i--) {
+ if (stack_[i].screen == cancelScreensAbove_) {
+ break;
+ }
+ Layer temp = stack_.back();
+ stack_.pop_back();
+ delete temp.screen;
+ }
+ cancelScreensAbove_ = nullptr;
+ }
+
+ if (!nextStack_.empty()) {
+ switchToNext();
+ }
+
+ if (overlayScreen_) {
+ // NOTE: This is not a full UIScreen update, to avoid double global event processing.
+ overlayScreen_->update();
+ }
+
+ // Process queued events.
+ std::deque events;
+ {
+ std::lock_guard eventGuard(eventQueueLock_);
+ events = std::move(eventQueue_);
+ eventQueue_.clear();
+ }
+
+ for (const QueuedEvent &ev : events) {
+ switch (ev.type) {
+ case QueuedEventType::TOUCH:
+ {
+ const TouchInput &touch = ev.touch;
+ // Send release all events to every screen layer.
+ if (touch.flags & TouchInputFlags::RELEASE_ALL) {
+ for (auto &layer : stack_) {
+ Screen *screen = layer.screen;
+ layer.screen->touch(screen->transformTouch(touch));
+ }
+ } else if (!stack_.empty()) {
+ // Let the overlay know about touch-downs, to be able to dismiss popups.
+ bool skip = false;
+ if (overlayScreen_ && (touch.flags & TouchInputFlags::DOWN)) {
+ skip = overlayScreen_->touch(overlayScreen_->transformTouch(touch));
+ }
+ if (!skip) {
+ Screen *screen = stack_.back().screen;
+ stack_.back().screen->touch(screen->transformTouch(touch));
+ }
+ }
+ break;
+ }
+ case QueuedEventType::KEY:
+ {
+ const KeyInput &key = ev.key;
+ // Send key up to every screen layer, to avoid stuck keys.
+ if (key.flags & KeyInputFlags::UP) {
+ for (auto &layer : stack_) {
+ layer.screen->key(key);
+ }
+ } else if (!stack_.empty()) {
+ stack_.back().screen->key(key);
+ }
+ break;
+ }
+ case QueuedEventType::AXIS:
+ {
+ const AxisInput &axis = ev.axis;
+ if (!stack_.empty()) {
+ stack_.back().screen->axis(axis);
+ }
+ break;
+ }
+ default:
+ ERROR_LOG(Log::UI, "Unknown queued event type: %d", (int)ev.type);
+ break;
+ }
+ }
+
+ // Only the front screen gets update().
+ if (!stack_.empty()) {
+ Screen *backScreen = stack_.back().screen;
+ backScreen->update();
+ passInputToMapper_ = backScreen->PassInputToMapper();
+ }
+}
+
+void ScreenManager::switchToNext() {
+ if (nextStack_.empty()) {
+ ERROR_LOG(Log::System, "switchToNext: No nextStack_!");
+ }
+
+ Layer temp = {nullptr, 0};
+ if (!stack_.empty()) {
+ temp = stack_.back();
+ temp.screen->focusChanged(ScreenFocusChange::FOCUS_LOST_TOP);
+ stack_.pop_back();
+ }
+ stack_.push_back(nextStack_.front());
+ nextStack_.front().screen->focusChanged(ScreenFocusChange::FOCUS_BECAME_TOP);
+ delete temp.screen;
+ UI::SetFocusedView(nullptr, UI::FocusFlags::CAUSE_SCREEN_CHANGE);
+
+ // When will this ever happen? Should handle focus here too?
+ for (size_t i = 1; i < nextStack_.size(); ++i) {
+ stack_.push_back(nextStack_[i]);
+ }
+ nextStack_.clear();
+}
+
+void ScreenManager::touch(const TouchInput &touch) {
+ QueuedEvent ev{};
+ ev.type = QueuedEventType::TOUCH;
+ ev.touch = touch;
+ std::lock_guard guard(eventQueueLock_);
+ eventQueue_.push_back(ev);
+}
+
+void ScreenManager::key(const KeyInput &key) {
+ QueuedEvent ev{};
+ ev.type = QueuedEventType::KEY;
+ ev.key = key;
+ std::lock_guard guard(eventQueueLock_);
+ eventQueue_.push_back(ev);
+}
+
+void ScreenManager::axis(const AxisInput *axes, size_t count) {
+ QueuedEvent ev{};
+ ev.type = QueuedEventType::AXIS;
+ std::lock_guard guard(eventQueueLock_);
+ for (size_t i = 0; i < count; i++) {
+ ev.axis = axes[i];
+ eventQueue_.push_back(ev);
+ }
+}
+
+void ScreenManager::deviceLost() {
+ for (auto &iter : stack_)
+ iter.screen->deviceLost();
+}
+
+void ScreenManager::deviceRestored(Draw::DrawContext *draw) {
+ draw_ = draw;
+ for (auto &iter : stack_)
+ iter.screen->deviceRestored(draw);
+}
+
+void ScreenManager::resized() {
+ INFO_LOG(Log::UI, "ScreenManager::resized(g_display.dp_xres/dp_yres: %dx%d)", g_display.dp_xres, g_display.dp_yres);
+ // Have to notify the whole stack, otherwise there will be problems when going back
+ // to non-top screens.
+ for (auto &layer : stack_) {
+ layer.screen->resized();
+ }
+}
+
+ScreenRenderFlags ScreenManager::render() {
+ using namespace Draw;
+
+ ScreenRenderFlags flags = ScreenRenderFlags::NONE;
+
+ // First, go through the whole stack and have every screen render any non-backbuffer render passes.
+ // In EmuScreen, this might result in running emulation.
+ for (size_t i = 0; i < stack_.size(); i++) {
+ const auto &layer = stack_[i];
+ ScreenRenderMode mode = ScreenRenderMode::DEFAULT;
+ if (i == stack_.size() - 1) {
+ mode |= ScreenRenderMode::TOP;
+ }
+ flags |= layer.screen->PreRender(mode);
+ }
+
+ // Now, start the final render pass. This is now the ONLY place where binding the null fb is allowed.
+ draw_->BindFramebufferAsRenderTarget(nullptr, {RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR}, "BackBuffer");
+ getUIContext()->BeginFrame();
+
+ const Draw::Viewport viewport{0.0f, 0.0f, (float)g_display.pixel_xres, (float)g_display.pixel_yres, 0.0f, 1.0f};
+ draw_->SetViewport(viewport);
+ draw_->SetScissorRect(0, 0, g_display.pixel_xres, g_display.pixel_yres);
+ draw_->SetTargetSize(g_display.pixel_xres, g_display.pixel_yres);
+
+ if (!stack_.empty()) {
+ // Collect the screens to render
+ TinySet layers;
+
+ // Start at the end, collect screens to form the transparency stack.
+ // Then we'll iterate them in reverse order.
+ // Note that we skip the overlay screen, we handle it separately.
+ // Additionally, we pick up a "background" screen. Normally it will be either
+ // the EmuScreen or the actual global background screen.
+ auto iter = stack_.end();
+ Screen *coveringScreen = nullptr;
+ Screen *foundBackgroundScreen = nullptr;
+ bool first = true;
+
+ do {
+ --iter;
+ ScreenRenderRole role = iter->screen->renderRole(first);
+ if (!foundBackgroundScreen && (role & ScreenRenderRole::CAN_BE_BACKGROUND)) {
+ // There still might be a screen that wants to be background - generally the EmuScreen if present.
+ layers.push_back(iter->screen);
+ foundBackgroundScreen = iter->screen;
+ } else if (!coveringScreen) {
+ layers.push_back(iter->screen);
+ }
+ if (iter->flags != LAYER_TRANSPARENT) {
+ coveringScreen = iter->screen;
+ }
+ first = false;
+ if (role & ScreenRenderRole::MUST_BE_FIRST) {
+ break;
+ }
+ } while (iter != stack_.begin());
+
+ if (backgroundScreen_ && !foundBackgroundScreen) {
+ layers.push_back(backgroundScreen_);
+ foundBackgroundScreen = backgroundScreen_;
+ }
+
+ // OK, now we iterate backwards over our little pile of collected screens.
+ for (int i = (int)layers.size() - 1; i >= 0; i--) {
+ ScreenRenderMode mode = ScreenRenderMode::DEFAULT;
+ if (i == (int)layers.size() - 1) {
+ // Bottom.
+ mode = ScreenRenderMode::FIRST;
+ if (i == 0) {
+ mode |= ScreenRenderMode::TOP;
+ }
+ } else if (i == 0) {
+ mode = ScreenRenderMode::TOP;
+ } else {
+ mode = ScreenRenderMode::BEHIND;
+ }
+ flags |= layers[i]->render(mode);
+ }
+
+ if (overlayScreen_) {
+ // It doesn't care about mode.
+ flags |= overlayScreen_->render(ScreenRenderMode::TOP);
+ }
+
+ getUIContext()->Flush();
+
+ if (postRenderCb_) {
+ // Really can't render anything after this! Will crash the screenshot mechanism if we do.
+ postRenderCb_(getUIContext(), postRenderUserdata_);
+ }
+ } else {
+ ERROR_LOG(Log::UI, "No current screen!");
+ }
+
+ processFinishDialog();
+ return flags;
+}
+
+void ScreenManager::getFocusPosition(float &x, float &y, float &z) {
+ UI::ScrollView::GetLastScrollPosition(x, y);
+
+ UI::View *v = UI::GetFocusedView();
+ x += v ? v->GetBounds().x : 0;
+ y += v ? v->GetBounds().y : 0;
+ z = stack_.size();
+}
+
+void ScreenManager::sendMessage(UIMessage message, const char *value) {
+ if (message == UIMessage::RECREATE_VIEWS) {
+ RecreateAllViews();
+ } else if (message == UIMessage::LOST_FOCUS) {
+ TouchInput input{};
+ input.x = -50000.0f;
+ input.y = -50000.0f;
+ input.flags = TouchInputFlags::RELEASE_ALL;
+ input.timestamp = time_now_d();
+ input.id = 0;
+ touch(input);
+ }
+
+ if (backgroundScreen_) {
+ backgroundScreen_->sendMessage(message, value);
+ }
+
+ // NOTE: Changed this to send the message to all screens, instead of just the top one,
+ // to allow EmuScreen to receive messages from popup menus. Hope this didn't break anything..
+ // NOTE: We do not use the iterator solution here, because the screen array may change by sendMessage!
+ // Although if it does, that's very bad.
+ // We also pick out the stack size before, so we don't get infinite growth if a screen is pushed from the handler.
+ // We probably should delay pushes, though...
+ const size_t stack_size = stack_.size();
+ for (int i = 0; i < stack_size; i++) {
+ if (stack_[i].screen) {
+ stack_[i].screen->sendMessage(message, value);
+ }
+ }
+}
+
+void ScreenManager::shutdown() {
+ for (const auto &layer : stack_)
+ delete layer.screen;
+ stack_.clear();
+ for (const auto &layer : nextStack_)
+ delete layer.screen;
+ nextStack_.clear();
+ delete overlayScreen_;
+ overlayScreen_ = nullptr;
+ delete backgroundScreen_;
+ backgroundScreen_ = nullptr;
+}
+
+void ScreenManager::push(Screen *screen, int layerFlags) {
+ screen->setScreenManager(this);
+ if (screen->isTransparent()) {
+ layerFlags |= LAYER_TRANSPARENT;
+ }
+
+ // Release touches and unfocus.
+ UI::SetFocusedView(nullptr, UI::FocusFlags::CAUSE_SCREEN_CHANGE);
+ TouchInput input{};
+ input.x = -50000.0f;
+ input.y = -50000.0f;
+ input.flags = TouchInputFlags::RELEASE_ALL;
+ input.timestamp = time_now_d();
+ input.id = 0;
+ touch(input);
+
+ Layer layer = {screen, layerFlags};
+
+ if (!stack_.empty()) {
+ stack_.back().screen->focusChanged(ScreenFocusChange::FOCUS_LOST_TOP);
+ }
+
+ if (nextStack_.empty()) {
+ layer.screen->focusChanged(ScreenFocusChange::FOCUS_BECAME_TOP);
+ stack_.push_back(layer);
+ } else {
+ nextStack_.push_back(layer);
+ }
+}
+
+void ScreenManager::pop() {
+ if (!stack_.empty()) {
+ stack_.back().screen->focusChanged(ScreenFocusChange::FOCUS_LOST_TOP);
+
+ delete stack_.back().screen;
+ stack_.pop_back();
+
+ if (!stack_.empty()) {
+ stack_.back().screen->focusChanged(ScreenFocusChange::FOCUS_LOST_TOP);
+ }
+ } else {
+ ERROR_LOG(Log::UI, "Can't pop when stack empty");
+ }
+}
+
+void ScreenManager::RecreateAllViews() {
+ for (auto &layer : stack_) {
+ layer.screen->RecreateViews();
+ }
+}
+
+void ScreenManager::finishDialog(Screen *dialog, DialogResult result) {
+ if (stack_.empty()) {
+ ERROR_LOG(Log::UI, "Must be in a dialog to finishDialog");
+ return;
+ }
+ if (dialog != stack_.back().screen) {
+ ERROR_LOG(Log::UI, "Wrong dialog being finished!");
+ return;
+ }
+ dialog->onFinish(result);
+ dialogFinished_ = dialog;
+ dialogResult_ = result;
+}
+
+Screen *ScreenManager::dialogParent(const Screen *dialog) const {
+ for (size_t i = 1; i < stack_.size(); ++i) {
+ if (stack_[i].screen == dialog) {
+ // The previous screen was the caller (not necessarily the topmost.)
+ return stack_[i - 1].screen;
+ }
+ }
+
+ return nullptr;
+}
+
+void ScreenManager::processFinishDialog() {
+ if (dialogFinished_) {
+ {
+ // Another dialog may have been pushed before the render, so search for it.
+ Screen *caller = dialogParent(dialogFinished_);
+ bool erased = false;
+ for (size_t i = 0; i < stack_.size(); ++i) {
+ if (stack_[i].screen == dialogFinished_) {
+ stack_[i].screen->focusChanged(ScreenFocusChange::FOCUS_LOST_TOP);
+ stack_.erase(stack_.begin() + i);
+ erased = true;
+ }
+ }
+
+ if (erased && !stack_.empty()) {
+ stack_.back().screen->focusChanged(ScreenFocusChange::FOCUS_BECAME_TOP);
+ }
+
+ if (!caller) {
+ ERROR_LOG(Log::UI, "ERROR: no top screen when finishing dialog");
+ } else if (caller != topScreen()) {
+ // The caller may get confused if we call dialogFinished() now.
+ WARN_LOG(Log::UI, "Skipping non-top dialog when finishing dialog.");
+ } else {
+ caller->dialogFinished(dialogFinished_, dialogResult_);
+ }
+ }
+ delete dialogFinished_;
+ dialogFinished_ = nullptr;
+ }
+}
+
+void ScreenManager::SetBackgroundOverlayScreens(Screen *backgroundScreen, Screen *overlayScreen) {
+ delete backgroundScreen_;
+ backgroundScreen_ = backgroundScreen;
+ backgroundScreen_->setScreenManager(this);
+
+ delete overlayScreen_;
+ overlayScreen_ = overlayScreen;
+ overlayScreen_->setScreenManager(this);
+}
diff --git a/Common/UI/ScreenManager.h b/Common/UI/ScreenManager.h
new file mode 100644
index 0000000000..c88c6f775d
--- /dev/null
+++ b/Common/UI/ScreenManager.h
@@ -0,0 +1,126 @@
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "Common/Common.h"
+#include "Common/Input/InputState.h"
+#include "Common/System/System.h"
+#include "Common/UI/Screen.h"
+
+class Screen;
+class UIContext;
+
+
+/*
+class Transition {
+public:
+ Transition() = default;
+};
+*/
+
+enum {
+ LAYER_TRANSPARENT = 2,
+};
+
+typedef void (*PostRenderCallback)(UIContext *ui, void *userdata);
+
+class ScreenManager {
+public:
+ virtual ~ScreenManager();
+
+ void switchScreen(Screen *screen);
+ void update();
+
+ void setUIContext(UIContext *context) { uiContext_ = context; }
+ UIContext *getUIContext() { return uiContext_; }
+ Draw::DrawContext *getDrawContext() { return draw_; }
+
+ void setPostRenderCallback(PostRenderCallback cb, void *userdata) {
+ postRenderCb_ = cb;
+ postRenderUserdata_ = userdata;
+ }
+
+ ScreenRenderFlags render();
+ void resized();
+ void shutdown();
+
+ void deviceLost();
+ void deviceRestored(Draw::DrawContext *draw);
+
+ // Push a dialog box in front. Currently 1-level only.
+ void push(Screen *screen, int layerFlags = 0);
+
+ // Recreate all views
+ void RecreateAllViews();
+
+ // Pops the dialog away.
+ void finishDialog(Screen *dialog, DialogResult result = DR_OK);
+ Screen *dialogParent(const Screen *dialog) const;
+
+ // Instant touch, separate from the update() mechanism.
+ void touch(const TouchInput &touch);
+ void key(const KeyInput &key);
+ void axis(const AxisInput *axes, size_t count);
+
+ void sendMessage(UIMessage message, const char *value);
+
+ const Screen *topScreen() const {
+ return stack_.empty() ? nullptr : stack_.back().screen;
+ }
+ Screen *topScreen() {
+ return stack_.empty() ? nullptr : stack_.back().screen;
+ }
+
+ void cancelScreensAbove(Screen *screen);
+
+ void getFocusPosition(float &x, float &y, float &z);
+
+ // Will delete any existing overlay screen.
+ void SetBackgroundOverlayScreens(Screen *backgroundScreen, Screen *overlayScreen);
+
+ InputMode PassInputToMapper() const {
+ return passInputToMapper_;
+ }
+
+private:
+ void pop();
+ void switchToNext();
+ void processFinishDialog();
+
+ UIContext *uiContext_ = nullptr;
+ Draw::DrawContext *draw_ = nullptr;
+
+ PostRenderCallback postRenderCb_ = nullptr;
+ void *postRenderUserdata_ = nullptr;
+
+ const Screen *dialogFinished_ = nullptr;
+ DialogResult dialogResult_{};
+
+ Screen *backgroundScreen_ = nullptr;
+ Screen *overlayScreen_ = nullptr;
+
+ Screen *cancelScreensAbove_ = nullptr;
+
+ struct Layer {
+ Screen *screen;
+ int flags; // From LAYER_ enum above
+ UI::View *focusedView; // TODO: save focus here. Going for quick solution now to reset focus.
+ };
+
+ // Dialog stack. These are shown "on top" of base screens and the Android back button works as expected.
+ // Used for options, in-game menus and other things you expect to be able to back out from onto something.
+ std::vector stack_;
+ std::vector nextStack_;
+
+ std::unordered_map lastAxis_;
+
+ std::mutex eventQueueLock_;
+ std::deque eventQueue_;
+
+ InputMode passInputToMapper_ = InputMode::None;
+};
diff --git a/Common/UI/UIScreen.cpp b/Common/UI/UIScreen.cpp
index 97d3b26d7c..4817514066 100644
--- a/Common/UI/UIScreen.cpp
+++ b/Common/UI/UIScreen.cpp
@@ -8,7 +8,7 @@
#include "Common/Input/KeyCodes.h"
#include "Common/UI/UIScreen.h"
#include "Common/UI/Context.h"
-#include "Common/UI/Screen.h"
+#include "Common/UI/ScreenManager.h"
#include "Common/UI/Root.h"
#include "Common/Render/DrawBuffer.h"
diff --git a/UI/AdhocServerScreen.cpp b/UI/AdhocServerScreen.cpp
index 9324423131..dbbf3761f7 100644
--- a/UI/AdhocServerScreen.cpp
+++ b/UI/AdhocServerScreen.cpp
@@ -18,6 +18,7 @@
#include "Common/Net/Resolve.h"
#include "Common/UI/Root.h"
#include "Common/UI/PopupScreens.h"
+#include "Common/UI/ScreenManager.h"
#include "Common/Data/Text/Parsers.h"
#include "Common/StringUtils.h"
#include "Common/Net/HTTPClient.h"
diff --git a/UI/ChatScreen.cpp b/UI/ChatScreen.cpp
index ba7ccc0be2..cbcaaef106 100644
--- a/UI/ChatScreen.cpp
+++ b/UI/ChatScreen.cpp
@@ -6,6 +6,7 @@
#include "Common/UI/View.h"
#include "Common/UI/ViewGroup.h"
#include "Common/UI/ScrollView.h"
+#include "Common/UI/ScreenManager.h"
#include "Common/UI/UI.h"
#include "Common/Data/Text/I18n.h"
diff --git a/UI/ControlMappingScreen.cpp b/UI/ControlMappingScreen.cpp
index d4993ee9b4..0c491b8905 100644
--- a/UI/ControlMappingScreen.cpp
+++ b/UI/ControlMappingScreen.cpp
@@ -26,6 +26,7 @@
#include "Common/UI/View.h"
#include "Common/UI/ViewGroup.h"
#include "Common/UI/Notice.h"
+#include "Common/UI/ScreenManager.h"
#include "Common/VR/PPSSPPVR.h"
#include "Common/Log.h"
#include "Common/Data/Color/RGBAUtil.h"
diff --git a/UI/CustomButtonMappingScreen.cpp b/UI/CustomButtonMappingScreen.cpp
index 0cfc94d570..0fc971c36b 100644
--- a/UI/CustomButtonMappingScreen.cpp
+++ b/UI/CustomButtonMappingScreen.cpp
@@ -23,6 +23,7 @@
#include "Common/UI/ViewGroup.h"
#include "Common/UI/ScrollView.h"
#include "Common/UI/PopupScreens.h"
+#include "Common/UI/ScreenManager.h"
#include "Common/Data/Text/I18n.h"
#include "Common/Data/Color/RGBAUtil.h"
diff --git a/UI/CwCheatScreen.cpp b/UI/CwCheatScreen.cpp
index 3bf8234b41..497d8a001a 100644
--- a/UI/CwCheatScreen.cpp
+++ b/UI/CwCheatScreen.cpp
@@ -27,6 +27,7 @@
#include "Common/StringUtils.h"
#include "Common/System/System.h"
#include "Common/System/Request.h"
+#include "Common/UI/ScreenManager.h"
#include "Core/System.h"
#include "Core/Config.h"
#include "Core/CwCheat.h"
diff --git a/UI/DevScreens.cpp b/UI/DevScreens.cpp
index b6cbef6abe..13fd7b1f8d 100644
--- a/UI/DevScreens.cpp
+++ b/UI/DevScreens.cpp
@@ -43,6 +43,7 @@
#include "Common/UI/ViewGroup.h"
#include "Common/UI/UI.h"
#include "Common/UI/IconCache.h"
+#include "Common/UI/ScreenManager.h"
#include "Common/Render/Text/draw_text.h"
#include "Common/Profiler/Profiler.h"
diff --git a/UI/DeveloperToolsScreen.cpp b/UI/DeveloperToolsScreen.cpp
index 0f3a728191..63321d4d9c 100644
--- a/UI/DeveloperToolsScreen.cpp
+++ b/UI/DeveloperToolsScreen.cpp
@@ -27,6 +27,7 @@
#include "Common/File/FileUtil.h"
#include "Common/Render/Text/draw_text.h"
#include "Common/StringUtils.h"
+#include "Common/UI/ScreenManager.h"
#include "GPU/Common/TextureReplacer.h"
#include "GPU/Common/PostShader.h"
#include "Core/MIPS/MIPSTracer.h"
diff --git a/UI/DisplayLayoutScreen.cpp b/UI/DisplayLayoutScreen.cpp
index 7c1aee5720..836c83ad30 100644
--- a/UI/DisplayLayoutScreen.cpp
+++ b/UI/DisplayLayoutScreen.cpp
@@ -25,6 +25,7 @@
#include "Common/UI/View.h"
#include "Common/UI/UIScreen.h"
#include "Common/UI/TabHolder.h"
+#include "Common/UI/ScreenManager.h"
#include "Common/Math/math_util.h"
#include "Common/System/NativeApp.h"
#include "Common/VR/PPSSPPVR.h"
diff --git a/UI/DriverManagerScreen.cpp b/UI/DriverManagerScreen.cpp
index 11355b4676..3ad5fa8219 100644
--- a/UI/DriverManagerScreen.cpp
+++ b/UI/DriverManagerScreen.cpp
@@ -7,6 +7,7 @@
#include "Common/StringUtils.h"
#include "Common/UI/PopupScreens.h"
#include "Common/UI/Notice.h"
+#include "Common/UI/ScreenManager.h"
#include "Core/Config.h"
#include "Core/System.h"
diff --git a/UI/EmuScreen.cpp b/UI/EmuScreen.cpp
index 004a858d3d..93cc0c9350 100644
--- a/UI/EmuScreen.cpp
+++ b/UI/EmuScreen.cpp
@@ -33,6 +33,7 @@ using namespace std::placeholders;
#include "Common/UI/Tween.h"
#include "Common/UI/View.h"
#include "Common/UI/AsyncImageFileView.h"
+#include "Common/UI/ScreenManager.h"
#include "Common/VR/PPSSPPVR.h"
#include "Common/Data/Text/I18n.h"
diff --git a/UI/GPUDriverTestScreen.cpp b/UI/GPUDriverTestScreen.cpp
index e1c854d0f8..8113f73507 100644
--- a/UI/GPUDriverTestScreen.cpp
+++ b/UI/GPUDriverTestScreen.cpp
@@ -2,6 +2,7 @@
#include "Common/Data/Text/I18n.h"
#include "Common/UI/UI.h"
#include "Common/UI/View.h"
+#include "Common/UI/ScreenManager.h"
#include "Common/GPU/Shader.h"
#include "Common/GPU/ShaderWriter.h"
diff --git a/UI/GameBrowser.cpp b/UI/GameBrowser.cpp
index a74bfc5033..68c9fbd6a3 100644
--- a/UI/GameBrowser.cpp
+++ b/UI/GameBrowser.cpp
@@ -25,6 +25,7 @@
#include "Common/UI/View.h"
#include "Common/UI/ViewGroup.h"
#include "Common/UI/Root.h"
+#include "Common/UI/ScreenManager.h"
#include "Common/Math/curves.h"
#include "Common/Net/URL.h"
diff --git a/UI/GameScreen.cpp b/UI/GameScreen.cpp
index a7c4584e2a..8f46736091 100644
--- a/UI/GameScreen.cpp
+++ b/UI/GameScreen.cpp
@@ -34,6 +34,7 @@
#include "Common/System/OSD.h"
#include "Common/System/Request.h"
#include "Common/System/NativeApp.h"
+#include "Common/UI/ScreenManager.h"
#include "Core/Config.h"
#include "Core/Reporting.h"
#include "Core/System.h"
diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp
index e0b978f4c2..a41d7382b0 100644
--- a/UI/GameSettingsScreen.cpp
+++ b/UI/GameSettingsScreen.cpp
@@ -30,6 +30,7 @@
#include "Common/UI/ViewGroup.h"
#include "Common/UI/Context.h"
#include "Common/UI/Notice.h"
+#include "Common/UI/ScreenManager.h"
#include "Common/Render/ManagedTexture.h"
#include "Common/VR/PPSSPPVR.h"
#include "Common/BitSet.h"
diff --git a/UI/InstallZipScreen.cpp b/UI/InstallZipScreen.cpp
index cf7bbf22f9..140b4040ed 100644
--- a/UI/InstallZipScreen.cpp
+++ b/UI/InstallZipScreen.cpp
@@ -19,6 +19,7 @@
#include "Common/UI/View.h"
#include "Common/UI/ViewGroup.h"
#include "Common/UI/Notice.h"
+#include "Common/UI/ScreenManager.h"
#include "Common/StringUtils.h"
#include "Common/File/FileUtil.h"
#include "Common/Data/Text/I18n.h"
diff --git a/UI/JitCompareScreen.cpp b/UI/JitCompareScreen.cpp
index d223a1f760..c64c451816 100644
--- a/UI/JitCompareScreen.cpp
+++ b/UI/JitCompareScreen.cpp
@@ -3,6 +3,7 @@
#include "UI/JitCompareScreen.h"
#include "Common/Data/Text/I18n.h"
#include "Common/UI/ViewGroup.h"
+#include "Common/UI/ScreenManager.h"
#include "Common/Render/DrawBuffer.h"
#include "Core/MemMap.h"
#include "Core/MIPS/MIPSTables.h"
diff --git a/UI/MainScreen.cpp b/UI/MainScreen.cpp
index 3cadee899b..a034919bd8 100644
--- a/UI/MainScreen.cpp
+++ b/UI/MainScreen.cpp
@@ -28,6 +28,7 @@
#include "Common/UI/View.h"
#include "Common/UI/ViewGroup.h"
+#include "Common/UI/ScreenManager.h"
#include "Common/File/FileUtil.h"
#include "Common/StringUtils.h"
#include "Core/System.h"
diff --git a/UI/MemStickScreen.cpp b/UI/MemStickScreen.cpp
index f794fe4cc6..40d291f903 100644
--- a/UI/MemStickScreen.cpp
+++ b/UI/MemStickScreen.cpp
@@ -24,6 +24,7 @@
#include "Common/UI/View.h"
#include "Common/UI/ViewGroup.h"
#include "Common/UI/PopupScreens.h"
+#include "Common/UI/ScreenManager.h"
#include "Common/StringUtils.h"
#include "Common/System/System.h"
diff --git a/UI/MiscScreens.cpp b/UI/MiscScreens.cpp
index d46075f2ec..28ec75c0ec 100644
--- a/UI/MiscScreens.cpp
+++ b/UI/MiscScreens.cpp
@@ -37,6 +37,7 @@
#include "Common/Data/Color/RGBAUtil.h"
#include "Common/Data/Encoding/Utf8.h"
#include "Common/Data/Text/I18n.h"
+#include "Common/UI/ScreenManager.h"
#include "Common/TimeUtil.h"
#include "Common/File/FileUtil.h"
#include "Common/Render/ManagedTexture.h"
diff --git a/UI/NativeApp.cpp b/UI/NativeApp.cpp
index 5f9cfb7610..62c07447da 100644
--- a/UI/NativeApp.cpp
+++ b/UI/NativeApp.cpp
@@ -56,6 +56,7 @@
#include "Common/GPU/thin3d.h"
#include "Common/UI/UI.h"
#include "Common/UI/Screen.h"
+#include "Common/UI/ScreenManager.h"
#include "Common/UI/Context.h"
#include "Common/UI/View.h"
#include "Common/UI/IconCache.h"
diff --git a/UI/PauseScreen.cpp b/UI/PauseScreen.cpp
index 32b2502e8a..e6c827374d 100644
--- a/UI/PauseScreen.cpp
+++ b/UI/PauseScreen.cpp
@@ -25,6 +25,7 @@
#include "Common/UI/UIScreen.h"
#include "Common/UI/PopupScreens.h"
#include "Common/UI/Notice.h"
+#include "Common/UI/ScreenManager.h"
#include "Common/GPU/thin3d.h"
#include "Common/Data/Text/I18n.h"
diff --git a/UI/RemoteISOScreen.cpp b/UI/RemoteISOScreen.cpp
index f616904ab8..cfd0775941 100644
--- a/UI/RemoteISOScreen.cpp
+++ b/UI/RemoteISOScreen.cpp
@@ -38,6 +38,7 @@
#include "Common/File/PathBrowser.h"
#include "Common/UI/PopupScreens.h"
#include "Common/UI/Notice.h"
+#include "Common/UI/ScreenManager.h"
#include "Common/Data/Format/JSONReader.h"
#include "Common/Data/Text/I18n.h"
#include "Common/Common.h"
diff --git a/UI/ReportScreen.cpp b/UI/ReportScreen.cpp
index 02080aaf3b..413d831fe3 100644
--- a/UI/ReportScreen.cpp
+++ b/UI/ReportScreen.cpp
@@ -23,6 +23,7 @@
#include "Common/UI/AsyncImageFileView.h"
#include "Common/UI/Context.h"
#include "Common/UI/ScrollView.h"
+#include "Common/UI/ScreenManager.h"
#include "Common/Data/Text/I18n.h"
#include "Common/File/FileUtil.h"
#include "Common/Log.h"
diff --git a/UI/RetroAchievementScreens.cpp b/UI/RetroAchievementScreens.cpp
index b2452df513..bb0a50a65e 100644
--- a/UI/RetroAchievementScreens.cpp
+++ b/UI/RetroAchievementScreens.cpp
@@ -14,6 +14,7 @@
#include "Core/Config.h"
#include "Core/RetroAchievements.h"
+#include "Common/UI/ScreenManager.h"
#include "UI/RetroAchievementScreens.h"
#include "UI/BackgroundAudio.h"
#include "UI/OnScreenDisplay.h"
diff --git a/UI/SavedataScreen.cpp b/UI/SavedataScreen.cpp
index a59cfddfbf..29aa0be2b9 100644
--- a/UI/SavedataScreen.cpp
+++ b/UI/SavedataScreen.cpp
@@ -31,6 +31,7 @@
#include "Common/UI/PopupScreens.h"
#include "Common/UI/ViewGroup.h"
#include "Common/UI/AsyncImageFileView.h"
+#include "Common/UI/ScreenManager.h"
#include "UI/SavedataScreen.h"
#include "UI/MainScreen.h"
#include "UI/GameInfoCache.h"
diff --git a/UI/SimpleDialogScreen.cpp b/UI/SimpleDialogScreen.cpp
index e0c0cc201c..1453a92b29 100644
--- a/UI/SimpleDialogScreen.cpp
+++ b/UI/SimpleDialogScreen.cpp
@@ -2,6 +2,7 @@
#include "Common/Data/Text/I18n.h"
#include "UI/SimpleDialogScreen.h"
#include "Common/UI/PopupScreens.h"
+#include "Common/UI/ScreenManager.h"
#include "UI/MiscViews.h"
ViewLayoutMode UISimpleBaseDialogScreen::LayoutMode() const {
diff --git a/UI/Store.cpp b/UI/Store.cpp
index 7185ef345c..c467a0f5b4 100644
--- a/UI/Store.cpp
+++ b/UI/Store.cpp
@@ -22,6 +22,7 @@
#include "Common/UI/ViewGroup.h"
#include "Common/UI/IconCache.h"
#include "Common/UI/ScrollView.h"
+#include "Common/UI/ScreenManager.h"
#include "Common/Render/DrawBuffer.h"
#include "Common/Log.h"
diff --git a/UI/SystemInfoScreen.cpp b/UI/SystemInfoScreen.cpp
index ca7e5ce2cd..cd0264322c 100644
--- a/UI/SystemInfoScreen.cpp
+++ b/UI/SystemInfoScreen.cpp
@@ -19,6 +19,7 @@
#include "Common/System/Request.h"
#include "Common/UI/Context.h"
#include "Common/UI/Notice.h"
+#include "Common/UI/ScreenManager.h"
#include "Core/System.h"
#include "Core/Config.h"
#include "GPU/GPUState.h" // ugh
diff --git a/UI/TabbedDialogScreen.cpp b/UI/TabbedDialogScreen.cpp
index ca9a3f842a..2542c20fc9 100644
--- a/UI/TabbedDialogScreen.cpp
+++ b/UI/TabbedDialogScreen.cpp
@@ -9,6 +9,7 @@
#include "Common/UI/ViewGroup.h"
#include "Common/UI/ScrollView.h"
#include "Common/UI/PopupScreens.h"
+#include "Common/UI/ScreenManager.h"
#include "UI/MiscViews.h"
#include "Common/UI/Context.h"
#include "UI/TabbedDialogScreen.h"
diff --git a/UI/TouchControlLayoutScreen.cpp b/UI/TouchControlLayoutScreen.cpp
index cfadb621f9..edf7481d05 100644
--- a/UI/TouchControlLayoutScreen.cpp
+++ b/UI/TouchControlLayoutScreen.cpp
@@ -25,6 +25,7 @@
#include "Common/System/Display.h"
#include "Common/UI/Context.h"
#include "Common/UI/PopupScreens.h"
+#include "Common/UI/ScreenManager.h"
#include "Common/CommonTypes.h"
#include "Common/Log.h"
diff --git a/UI/TouchControlVisibilityScreen.cpp b/UI/TouchControlVisibilityScreen.cpp
index cf052ed4dc..fd9a5c4d0c 100644
--- a/UI/TouchControlVisibilityScreen.cpp
+++ b/UI/TouchControlVisibilityScreen.cpp
@@ -21,6 +21,7 @@
#include "Common/StringUtils.h"
#include "Common/UI/TabHolder.h"
#include "Common/UI/PopupScreens.h"
+#include "Common/UI/ScreenManager.h"
#include "Core/Config.h"
diff --git a/UWP/CommonUWP/CommonUWP.vcxproj b/UWP/CommonUWP/CommonUWP.vcxproj
index fbed1c53ed..c06762e56d 100644
--- a/UWP/CommonUWP/CommonUWP.vcxproj
+++ b/UWP/CommonUWP/CommonUWP.vcxproj
@@ -221,6 +221,7 @@
+
@@ -406,6 +407,7 @@
+
@@ -503,4 +505,4 @@
-
+
\ No newline at end of file
diff --git a/UWP/CommonUWP/CommonUWP.vcxproj.filters b/UWP/CommonUWP/CommonUWP.vcxproj.filters
index ca0c481ca5..91ab265c90 100644
--- a/UWP/CommonUWP/CommonUWP.vcxproj.filters
+++ b/UWP/CommonUWP/CommonUWP.vcxproj.filters
@@ -591,6 +591,9 @@
ext\pugixml
+
+ UI
+
@@ -1145,6 +1148,9 @@
ext\pugixml
+
+ UI
+
@@ -1187,4 +1193,4 @@
ext\at3_standalone
-
+
\ No newline at end of file
diff --git a/android/jni/Android.mk b/android/jni/Android.mk
index cba82ed6a4..0a058f5f2c 100644
--- a/android/jni/Android.mk
+++ b/android/jni/Android.mk
@@ -386,6 +386,7 @@ EXEC_AND_LIB_FILES := \
$(SRC)/Common/UI/AsyncImageFileView.cpp \
$(SRC)/Common/UI/Root.cpp \
$(SRC)/Common/UI/Screen.cpp \
+ $(SRC)/Common/UI/ScreenManager.cpp \
$(SRC)/Common/UI/UI.cpp \
$(SRC)/Common/UI/Context.cpp \
$(SRC)/Common/UI/UIScreen.cpp \