UI: Break out the hierarchy functions into ui/root.cpp/h

This commit is contained in:
Henrik Rydgård 2020-03-08 14:49:29 +01:00
parent 3e6050beac
commit 271f79ea63
14 changed files with 244 additions and 202 deletions

View File

@ -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

View File

@ -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"

View File

@ -455,6 +455,7 @@
<ClInclude Include="..\..\ext\native\thread\prioritizedworkqueue.h" />
<ClInclude Include="..\..\ext\native\thread\threadpool.h" />
<ClInclude Include="..\..\ext\native\thread\threadutil.h" />
<ClInclude Include="..\..\ext\native\ui\root.h" />
<ClInclude Include="..\..\ext\native\ui\screen.h" />
<ClInclude Include="..\..\ext\native\ui\ui.h" />
<ClInclude Include="..\..\ext\native\ui\ui_context.h" />
@ -1608,6 +1609,7 @@
<ClCompile Include="..\..\ext\native\thread\prioritizedworkqueue.cpp" />
<ClCompile Include="..\..\ext\native\thread\threadpool.cpp" />
<ClCompile Include="..\..\ext\native\thread\threadutil.cpp" />
<ClCompile Include="..\..\ext\native\ui\root.cpp" />
<ClCompile Include="..\..\ext\native\ui\screen.cpp" />
<ClCompile Include="..\..\ext\native\ui\ui.cpp" />
<ClCompile Include="..\..\ext\native\ui\ui_context.cpp" />

View File

@ -145,6 +145,9 @@
<ClCompile Include="..\..\ext\native\input\input_state.cpp">
<Filter>input</Filter>
</ClCompile>
<ClCompile Include="..\..\ext\native\ui\root.cpp">
<Filter>ui</Filter>
</ClCompile>
<ClCompile Include="..\..\ext\native\ui\screen.cpp">
<Filter>ui</Filter>
</ClCompile>
@ -583,6 +586,9 @@
<ClInclude Include="..\..\ext\native\input\keycodes.h">
<Filter>input</Filter>
</ClInclude>
<ClInclude Include="..\..\ext\native\ui\root.h">
<Filter>ui</Filter>
</ClInclude>
<ClInclude Include="..\..\ext\native\ui\screen.h">
<Filter>ui</Filter>
</ClInclude>

View File

@ -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 \

View File

@ -438,6 +438,7 @@
<ClInclude Include="thin3d\thin3d_create.h" />
<ClInclude Include="thin3d\VulkanQueueRunner.h" />
<ClInclude Include="thin3d\VulkanRenderManager.h" />
<ClInclude Include="ui\root.h" />
<ClInclude Include="util\text\wrap_text.h" />
<ClInclude Include="gfx_es2\draw_buffer.h" />
<ClInclude Include="gfx_es2\draw_text.h" />
@ -1147,6 +1148,7 @@
<ClCompile Include="thin3d\thin3d_d3d11.cpp" />
<ClCompile Include="thin3d\VulkanQueueRunner.cpp" />
<ClCompile Include="thin3d\VulkanRenderManager.cpp" />
<ClCompile Include="ui\root.cpp" />
<ClCompile Include="util\text\wrap_text.cpp" />
<ClCompile Include="gfx_es2\draw_buffer.cpp" />
<ClCompile Include="gfx_es2\draw_text.cpp" />

View File

@ -347,6 +347,9 @@
<ClInclude Include="thin3d\d3d9_d3dcompiler_loader.h">
<Filter>thin3d</Filter>
</ClInclude>
<ClInclude Include="ui\root.h">
<Filter>ui</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="gfx\gl_debug_log.cpp">
@ -826,6 +829,9 @@
<ClCompile Include="thin3d\d3d9_d3dcompiler_loader.cpp">
<Filter>thin3d</Filter>
</ClCompile>
<ClCompile Include="ui\root.cpp">
<Filter>ui</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<Filter Include="gfx">

View File

@ -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})

202
ext/native/ui/root.cpp Normal file
View File

@ -0,0 +1,202 @@
#include <mutex>
#include "base/timeutil.h"
#include "ui/root.h"
#include "ui/viewgroup.h"
namespace UI {
static std::mutex focusLock;
static std::vector<int> 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<HeldKey> 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<std::mutex> 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<HeldKey>::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<std::mutex> 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<std::mutex> 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();
}
}

17
ext/native/ui/root.h Normal file
View File

@ -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);
}

View File

@ -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"

View File

@ -19,10 +19,6 @@ namespace UI {
const float ITEM_HEIGHT = 64.f;
static std::mutex focusLock;
static std::vector<int> 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::function<EventRe
return EVENT_DONE;
}
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();
}
// 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<HeldKey> 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<std::mutex> 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<HeldKey>::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<std::mutex> 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<std::mutex> 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

View File

@ -397,11 +397,4 @@ private:
std::set<int> 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

View File

@ -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 \