diff --git a/CMakeLists.txt b/CMakeLists.txt
index ed6f186c6b..4f91a430bc 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1040,6 +1040,8 @@ add_library(native STATIC
ext/native/thread/threadutil.h
ext/native/thread/threadpool.cpp
ext/native/thread/threadpool.h
+ ext/native/ui/root.cpp
+ ext/native/ui/root.h
ext/native/ui/screen.cpp
ext/native/ui/screen.h
ext/native/ui/ui.cpp
diff --git a/UI/EmuScreen.cpp b/UI/EmuScreen.cpp
index 95664c0d62..2708cd32fd 100644
--- a/UI/EmuScreen.cpp
+++ b/UI/EmuScreen.cpp
@@ -30,6 +30,7 @@
#include "input/input_state.h"
#include "math/curves.h"
+#include "ui/root.h"
#include "ui/ui.h"
#include "ui/ui_context.h"
#include "ui/ui_tween.h"
diff --git a/UWP/NativeUWP/NativeUWP.vcxproj b/UWP/NativeUWP/NativeUWP.vcxproj
index be3e378ef7..f6adacf851 100644
--- a/UWP/NativeUWP/NativeUWP.vcxproj
+++ b/UWP/NativeUWP/NativeUWP.vcxproj
@@ -455,6 +455,7 @@
+
@@ -1608,6 +1609,7 @@
+
diff --git a/UWP/NativeUWP/NativeUWP.vcxproj.filters b/UWP/NativeUWP/NativeUWP.vcxproj.filters
index 65587a1c3d..5e8aaecb8d 100644
--- a/UWP/NativeUWP/NativeUWP.vcxproj.filters
+++ b/UWP/NativeUWP/NativeUWP.vcxproj.filters
@@ -145,6 +145,9 @@
input
+
+ ui
+
ui
@@ -583,6 +586,9 @@
input
+
+ ui
+
ui
diff --git a/ext/native/Android.mk b/ext/native/Android.mk
index 09b69d4416..d10c21491f 100644
--- a/ext/native/Android.mk
+++ b/ext/native/Android.mk
@@ -89,6 +89,7 @@ LOCAL_SRC_FILES :=\
thin3d/VulkanRenderManager.cpp \
thin3d/VulkanQueueRunner.cpp \
thin3d/DataFormatGL.cpp \
+ ui/root.cpp \
ui/view.cpp \
ui/viewgroup.cpp \
ui/ui.cpp \
diff --git a/ext/native/native.vcxproj b/ext/native/native.vcxproj
index f28e6ca221..06c2979790 100644
--- a/ext/native/native.vcxproj
+++ b/ext/native/native.vcxproj
@@ -438,6 +438,7 @@
+
@@ -1147,6 +1148,7 @@
+
diff --git a/ext/native/native.vcxproj.filters b/ext/native/native.vcxproj.filters
index d466841ef5..82e64872aa 100644
--- a/ext/native/native.vcxproj.filters
+++ b/ext/native/native.vcxproj.filters
@@ -347,6 +347,9 @@
thin3d
+
+ ui
+
@@ -826,6 +829,9 @@
thin3d
+
+ ui
+
diff --git a/ext/native/ui/CMakeLists.txt b/ext/native/ui/CMakeLists.txt
index 40530c53b7..016db5350c 100644
--- a/ext/native/ui/CMakeLists.txt
+++ b/ext/native/ui/CMakeLists.txt
@@ -1,9 +1,10 @@
set(SRCS
ui.cpp
ui_context.cpp
- view.cpp
- viewgroup.cpp
+ view.cpp
+ viewgroup.cpp
screen.cpp
+ root.cpp
)
set(SRCS ${SRCS})
diff --git a/ext/native/ui/root.cpp b/ext/native/ui/root.cpp
new file mode 100644
index 0000000000..dd2b610a99
--- /dev/null
+++ b/ext/native/ui/root.cpp
@@ -0,0 +1,202 @@
+#include
+
+#include "base/timeutil.h"
+#include "ui/root.h"
+#include "ui/viewgroup.h"
+
+namespace UI {
+
+static std::mutex focusLock;
+static std::vector focusMoves;
+extern bool focusForced;
+
+void LayoutViewHierarchy(const UIContext &dc, ViewGroup *root) {
+ if (!root) {
+ ELOG("Tried to layout a view hierarchy from a zero pointer root");
+ return;
+ }
+ const Bounds &rootBounds = dc.GetBounds();
+
+ MeasureSpec horiz(EXACTLY, rootBounds.w);
+ MeasureSpec vert(EXACTLY, rootBounds.h);
+
+ // Two phases - measure contents, layout.
+ root->Measure(dc, horiz, vert);
+ // Root has a specified size. Set it, then let root layout all its children.
+ root->SetBounds(rootBounds);
+ root->Layout();
+}
+
+void MoveFocus(ViewGroup *root, FocusDirection direction) {
+ if (!GetFocusedView()) {
+ // Nothing was focused when we got in here. Focus the first non-group in the hierarchy.
+ root->SetFocus();
+ return;
+ }
+
+ NeighborResult neigh(0, 0);
+ neigh = root->FindNeighbor(GetFocusedView(), direction, neigh);
+
+ if (neigh.view) {
+ neigh.view->SetFocus();
+ root->SubviewFocused(neigh.view);
+ }
+}
+
+// TODO: Figure out where this should really live.
+// Simple simulation of key repeat on platforms and for gamepads where we don't
+// automatically get it.
+
+static int frameCount;
+
+// Ignore deviceId when checking for matches. Turns out that Ouya for example sends
+// completely broken input where the original keypresses have deviceId = 10 and the repeats
+// have deviceId = 0.
+struct HeldKey {
+ int key;
+ int deviceId;
+ double triggerTime;
+
+ // Ignores startTime
+ bool operator <(const HeldKey &other) const {
+ if (key < other.key) return true;
+ return false;
+ }
+ bool operator ==(const HeldKey &other) const { return key == other.key; }
+};
+
+static std::set heldKeys;
+
+const double repeatDelay = 15 * (1.0 / 60.0f); // 15 frames like before.
+const double repeatInterval = 5 * (1.0 / 60.0f); // 5 frames like before.
+
+bool KeyEvent(const KeyInput &key, ViewGroup *root) {
+ bool retval = false;
+ // Ignore repeats for focus moves.
+ if ((key.flags & (KEY_DOWN | KEY_IS_REPEAT)) == KEY_DOWN) {
+ if (IsDPadKey(key)) {
+ // Let's only repeat DPAD initially.
+ HeldKey hk;
+ hk.key = key.keyCode;
+ hk.deviceId = key.deviceId;
+ hk.triggerTime = time_now_d() + repeatDelay;
+
+ // Check if the key is already held. If it is, ignore it. This is to avoid
+ // multiple key repeat mechanisms colliding.
+ if (heldKeys.find(hk) != heldKeys.end()) {
+ return false;
+ }
+
+ heldKeys.insert(hk);
+ std::lock_guard lock(focusLock);
+ focusMoves.push_back(key.keyCode);
+ retval = true;
+ }
+ }
+ if (key.flags & KEY_UP) {
+ // We ignore the device ID here (in the comparator for HeldKey), due to the Ouya quirk mentioned above.
+ if (!heldKeys.empty()) {
+ HeldKey hk;
+ hk.key = key.keyCode;
+ hk.deviceId = key.deviceId;
+ hk.triggerTime = 0.0; // irrelevant
+ if (heldKeys.find(hk) != heldKeys.end()) {
+ heldKeys.erase(hk);
+ retval = true;
+ }
+ }
+ }
+
+ retval = root->Key(key);
+
+ // Ignore volume keys and stuff here. Not elegant but need to propagate bools through the view hierarchy as well...
+ switch (key.keyCode) {
+ case NKCODE_VOLUME_DOWN:
+ case NKCODE_VOLUME_UP:
+ case NKCODE_VOLUME_MUTE:
+ retval = false;
+ break;
+ }
+
+ return retval;
+}
+
+static void ProcessHeldKeys(ViewGroup *root) {
+ double now = time_now_d();
+
+restart:
+
+ for (std::set::iterator iter = heldKeys.begin(); iter != heldKeys.end(); ++iter) {
+ if (iter->triggerTime < now) {
+ KeyInput key;
+ key.keyCode = iter->key;
+ key.deviceId = iter->deviceId;
+ key.flags = KEY_DOWN;
+ KeyEvent(key, root);
+
+ std::lock_guard lock(focusLock);
+ focusMoves.push_back(key.keyCode);
+
+ // Cannot modify the current item when looping over a set, so let's do this instead.
+ HeldKey hk = *iter;
+ heldKeys.erase(hk);
+ hk.triggerTime = now + repeatInterval;
+ heldKeys.insert(hk);
+ goto restart;
+ }
+ }
+}
+
+bool TouchEvent(const TouchInput &touch, ViewGroup *root) {
+ focusForced = false;
+ root->Touch(touch);
+ if ((touch.flags & TOUCH_DOWN) && !focusForced) {
+ EnableFocusMovement(false);
+ }
+ return true;
+}
+
+bool AxisEvent(const AxisInput &axis, ViewGroup *root) {
+ root->Axis(axis);
+ return true;
+}
+
+void UpdateViewHierarchy(ViewGroup *root) {
+ ProcessHeldKeys(root);
+ frameCount++;
+
+ if (!root) {
+ ELOG("Tried to update a view hierarchy from a zero pointer root");
+ return;
+ }
+
+ if (focusMoves.size()) {
+ std::lock_guard lock(focusLock);
+ EnableFocusMovement(true);
+ if (!GetFocusedView()) {
+ View *defaultView = root->GetDefaultFocusView();
+ // Can't focus what you can't see.
+ if (defaultView && defaultView->GetVisibility() == V_VISIBLE) {
+ root->GetDefaultFocusView()->SetFocus();
+ } else {
+ root->SetFocus();
+ }
+ root->SubviewFocused(GetFocusedView());
+ } else {
+ for (size_t i = 0; i < focusMoves.size(); i++) {
+ switch (focusMoves[i]) {
+ case NKCODE_DPAD_LEFT: MoveFocus(root, FOCUS_LEFT); break;
+ case NKCODE_DPAD_RIGHT: MoveFocus(root, FOCUS_RIGHT); break;
+ case NKCODE_DPAD_UP: MoveFocus(root, FOCUS_UP); break;
+ case NKCODE_DPAD_DOWN: MoveFocus(root, FOCUS_DOWN); break;
+ }
+ }
+ }
+ focusMoves.clear();
+ }
+
+ root->Update();
+ DispatchEvents();
+}
+
+}
diff --git a/ext/native/ui/root.h b/ext/native/ui/root.h
new file mode 100644
index 0000000000..69730aa8e4
--- /dev/null
+++ b/ext/native/ui/root.h
@@ -0,0 +1,17 @@
+#pragma once
+
+#include "ui/ui_context.h"
+#include "input/input_state.h"
+
+namespace UI {
+
+class ViewGroup;
+
+void LayoutViewHierarchy(const UIContext &dc, ViewGroup *root);
+void UpdateViewHierarchy(ViewGroup *root);
+// Hooks arrow keys for navigation
+bool KeyEvent(const KeyInput &key, ViewGroup *root);
+bool TouchEvent(const TouchInput &touch, ViewGroup *root);
+bool AxisEvent(const AxisInput &axis, ViewGroup *root);
+
+}
diff --git a/ext/native/ui/ui_screen.cpp b/ext/native/ui/ui_screen.cpp
index 9ed9aa2b38..c69ec761a2 100644
--- a/ext/native/ui/ui_screen.cpp
+++ b/ext/native/ui/ui_screen.cpp
@@ -7,6 +7,7 @@
#include "ui/ui_screen.h"
#include "ui/ui_context.h"
#include "ui/screen.h"
+#include "ui/root.h"
#include "i18n/i18n.h"
#include "gfx_es2/draw_buffer.h"
diff --git a/ext/native/ui/viewgroup.cpp b/ext/native/ui/viewgroup.cpp
index 244f852fd6..d0961078eb 100644
--- a/ext/native/ui/viewgroup.cpp
+++ b/ext/native/ui/viewgroup.cpp
@@ -19,10 +19,6 @@ namespace UI {
const float ITEM_HEIGHT = 64.f;
-static std::mutex focusLock;
-static std::vector focusMoves;
-extern bool focusForced;
-
void ApplyGravity(const Bounds outer, const Margins &margins, float w, float h, int gravity, Bounds &inner) {
inner.w = w;
inner.h = h;
@@ -375,22 +371,6 @@ NeighborResult ViewGroup::FindNeighbor(View *view, FocusDirection direction, Nei
}
}
-void MoveFocus(ViewGroup *root, FocusDirection direction) {
- if (!GetFocusedView()) {
- // Nothing was focused when we got in here. Focus the first non-group in the hierarchy.
- root->SetFocus();
- return;
- }
-
- NeighborResult neigh(0, 0);
- neigh = root->FindNeighbor(GetFocusedView(), direction, neigh);
-
- if (neigh.view) {
- neigh.view->SetFocus();
- root->SubviewFocused(neigh.view);
- }
-}
-
// TODO: This code needs some cleanup/restructuring...
void LinearLayout::Measure(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert) {
MeasureBySpec(layoutParams_->width, 0.0f, horiz, &measuredWidth_);
@@ -1421,177 +1401,4 @@ bool StringVectorListAdaptor::AddEventCallback(View *view, std::functionMeasure(dc, horiz, vert);
- // Root has a specified size. Set it, then let root layout all its children.
- root->SetBounds(rootBounds);
- root->Layout();
-}
-
-// TODO: Figure out where this should really live.
-// Simple simulation of key repeat on platforms and for gamepads where we don't
-// automatically get it.
-
-static int frameCount;
-
-// Ignore deviceId when checking for matches. Turns out that Ouya for example sends
-// completely broken input where the original keypresses have deviceId = 10 and the repeats
-// have deviceId = 0.
-struct HeldKey {
- int key;
- int deviceId;
- double triggerTime;
-
- // Ignores startTime
- bool operator <(const HeldKey &other) const {
- if (key < other.key) return true;
- return false;
- }
- bool operator ==(const HeldKey &other) const { return key == other.key; }
-};
-
-static std::set heldKeys;
-
-const double repeatDelay = 15 * (1.0 / 60.0f); // 15 frames like before.
-const double repeatInterval = 5 * (1.0 / 60.0f); // 5 frames like before.
-
-bool KeyEvent(const KeyInput &key, ViewGroup *root) {
- bool retval = false;
- // Ignore repeats for focus moves.
- if ((key.flags & (KEY_DOWN | KEY_IS_REPEAT)) == KEY_DOWN) {
- if (IsDPadKey(key)) {
- // Let's only repeat DPAD initially.
- HeldKey hk;
- hk.key = key.keyCode;
- hk.deviceId = key.deviceId;
- hk.triggerTime = time_now_d() + repeatDelay;
-
- // Check if the key is already held. If it is, ignore it. This is to avoid
- // multiple key repeat mechanisms colliding.
- if (heldKeys.find(hk) != heldKeys.end()) {
- return false;
- }
-
- heldKeys.insert(hk);
- std::lock_guard lock(focusLock);
- focusMoves.push_back(key.keyCode);
- retval = true;
- }
- }
- if (key.flags & KEY_UP) {
- // We ignore the device ID here (in the comparator for HeldKey), due to the Ouya quirk mentioned above.
- if (!heldKeys.empty()) {
- HeldKey hk;
- hk.key = key.keyCode;
- hk.deviceId = key.deviceId;
- hk.triggerTime = 0.0; // irrelevant
- if (heldKeys.find(hk) != heldKeys.end()) {
- heldKeys.erase(hk);
- retval = true;
- }
- }
- }
-
- retval = root->Key(key);
-
- // Ignore volume keys and stuff here. Not elegant but need to propagate bools through the view hierarchy as well...
- switch (key.keyCode) {
- case NKCODE_VOLUME_DOWN:
- case NKCODE_VOLUME_UP:
- case NKCODE_VOLUME_MUTE:
- retval = false;
- break;
- }
-
- return retval;
-}
-
-static void ProcessHeldKeys(ViewGroup *root) {
- double now = time_now_d();
-
-restart:
-
- for (std::set::iterator iter = heldKeys.begin(); iter != heldKeys.end(); ++iter) {
- if (iter->triggerTime < now) {
- KeyInput key;
- key.keyCode = iter->key;
- key.deviceId = iter->deviceId;
- key.flags = KEY_DOWN;
- KeyEvent(key, root);
-
- std::lock_guard lock(focusLock);
- focusMoves.push_back(key.keyCode);
-
- // Cannot modify the current item when looping over a set, so let's do this instead.
- HeldKey hk = *iter;
- heldKeys.erase(hk);
- hk.triggerTime = now + repeatInterval;
- heldKeys.insert(hk);
- goto restart;
- }
- }
-}
-
-bool TouchEvent(const TouchInput &touch, ViewGroup *root) {
- focusForced = false;
- root->Touch(touch);
- if ((touch.flags & TOUCH_DOWN) && !focusForced) {
- EnableFocusMovement(false);
- }
- return true;
-}
-
-bool AxisEvent(const AxisInput &axis, ViewGroup *root) {
- root->Axis(axis);
- return true;
-}
-
-void UpdateViewHierarchy(ViewGroup *root) {
- ProcessHeldKeys(root);
- frameCount++;
-
- if (!root) {
- ELOG("Tried to update a view hierarchy from a zero pointer root");
- return;
- }
-
- if (focusMoves.size()) {
- std::lock_guard lock(focusLock);
- EnableFocusMovement(true);
- if (!GetFocusedView()) {
- View *defaultView = root->GetDefaultFocusView();
- // Can't focus what you can't see.
- if (defaultView && defaultView->GetVisibility() == V_VISIBLE) {
- root->GetDefaultFocusView()->SetFocus();
- } else {
- root->SetFocus();
- }
- root->SubviewFocused(GetFocusedView());
- } else {
- for (size_t i = 0; i < focusMoves.size(); i++) {
- switch (focusMoves[i]) {
- case NKCODE_DPAD_LEFT: MoveFocus(root, FOCUS_LEFT); break;
- case NKCODE_DPAD_RIGHT: MoveFocus(root, FOCUS_RIGHT); break;
- case NKCODE_DPAD_UP: MoveFocus(root, FOCUS_UP); break;
- case NKCODE_DPAD_DOWN: MoveFocus(root, FOCUS_DOWN); break;
- }
- }
- }
- focusMoves.clear();
- }
-
- root->Update();
- DispatchEvents();
-}
-
} // namespace UI
diff --git a/ext/native/ui/viewgroup.h b/ext/native/ui/viewgroup.h
index fb693c33a7..28d788e5ff 100644
--- a/ext/native/ui/viewgroup.h
+++ b/ext/native/ui/viewgroup.h
@@ -397,11 +397,4 @@ private:
std::set hidden_;
};
-void LayoutViewHierarchy(const UIContext &dc, ViewGroup *root);
-void UpdateViewHierarchy(ViewGroup *root);
-// Hooks arrow keys for navigation
-bool KeyEvent(const KeyInput &key, ViewGroup *root);
-bool TouchEvent(const TouchInput &touch, ViewGroup *root);
-bool AxisEvent(const AxisInput &axis, ViewGroup *root);
-
} // namespace UI
diff --git a/libretro/Makefile.common b/libretro/Makefile.common
index 89f7880c1d..b176364e8e 100644
--- a/libretro/Makefile.common
+++ b/libretro/Makefile.common
@@ -305,6 +305,7 @@ SOURCES_CXX += $(NATIVEDIR)/math/dataconv.cpp \
$(NATIVEDIR)/thin3d/DataFormatGL.cpp \
$(NATIVEDIR)/thread/threadutil.cpp \
$(NATIVEDIR)/thread/threadpool.cpp \
+ $(NATIVEDIR)/ui/root.cpp \
$(NATIVEDIR)/ui/screen.cpp \
$(NATIVEDIR)/ui/ui.cpp \
$(NATIVEDIR)/ui/ui_context.cpp \