Merge pull request #20913 from hrydgard/ui-refactor

UI refactoring, better back button on tabbed dialog like SettingsScreen
This commit is contained in:
Henrik Rydgård authored and GitHub committed 2025-10-23 14:12:47 +02:00
commit 9bed94d8f9
37 files changed
+1592 -2440

No files matched your search

+2
View File
@@ -884,6 +884,8 @@ add_library(Common STATIC
Common/UI/ViewGroup.h
Common/UI/ScrollView.cpp
Common/UI/ScrollView.h
Common/UI/TabHolder.cpp
Common/UI/TabHolder.h
Common/UI/PopupScreens.cpp
Common/UI/PopupScreens.h
Common/BitScan.h
+2
View File
@@ -538,6 +538,7 @@
<ClInclude Include="UI\Root.h" />
<ClInclude Include="UI\Screen.h" />
<ClInclude Include="UI\ScrollView.h" />
<ClInclude Include="UI\TabHolder.h" />
<ClInclude Include="UI\Tween.h" />
<ClInclude Include="UI\UI.h" />
<ClInclude Include="UI\UIScreen.h" />
@@ -983,6 +984,7 @@
<ClCompile Include="UI\Root.cpp" />
<ClCompile Include="UI\Screen.cpp" />
<ClCompile Include="UI\ScrollView.cpp" />
<ClCompile Include="UI\TabHolder.cpp" />
<ClCompile Include="UI\Tween.cpp" />
<ClCompile Include="UI\UI.cpp" />
<ClCompile Include="UI\UIScreen.cpp" />
+6
View File
@@ -698,6 +698,9 @@
<ClInclude Include="..\ext\nanosvg\src\nanosvgrast.h">
<Filter>ext\nanosvg</Filter>
</ClInclude>
<ClInclude Include="UI\TabHolder.h">
<Filter>UI</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="ABI.cpp" />
@@ -1302,6 +1305,9 @@
<ClCompile Include="Render\AtlasGen.cpp">
<Filter>Render</Filter>
</ClCompile>
<ClCompile Include="UI\TabHolder.cpp">
<Filter>UI</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<Filter Include="Crypto">
+9 -4
View File
@@ -31,7 +31,8 @@ void EventTriggered(Event *e, EventParams params) {
g_dispatchQueue.push_front(item);
}
void DispatchEvents() {
DialogResult DispatchEvents() {
DialogResult result = DR_NONE;
while (!g_dispatchQueue.empty()) {
DispatchQueueItem item;
if (g_dispatchQueue.empty())
@@ -40,8 +41,12 @@ void DispatchEvents() {
g_dispatchQueue.pop_back();
if (item.e) {
item.e->Dispatch(item.params);
if (item.params.bubbleResult != DR_NONE) {
result = item.params.bubbleResult;
}
}
}
return result;
}
void RemoveQueuedEventsByView(View *view) {
@@ -363,13 +368,13 @@ restart:
}
}
void UpdateViewHierarchy(ViewGroup *root) {
DialogResult UpdateViewHierarchy(ViewGroup *root) {
ProcessHeldKeys(root);
frameCount++;
if (!root) {
ERROR_LOG(Log::UI, "Tried to update a view hierarchy from a zero pointer root");
return;
return DR_NONE;
}
if (focusMoves.size()) {
@@ -402,7 +407,7 @@ void UpdateViewHierarchy(ViewGroup *root) {
}
root->Update();
DispatchEvents();
return DispatchEvents();
}
} // namespace UI
+3 -2
View File
@@ -4,6 +4,7 @@
#include "Common/UI/Context.h"
#include "Common/Input/InputState.h"
#include "Common/UI/Screen.h"
namespace UI {
@@ -17,12 +18,12 @@ void RemoveQueuedEventsByEvent(Event *e);
void RemoveQueuedEventsByView(View * v);
void EventTriggered(Event *e, EventParams params);
void DispatchEvents();
DialogResult DispatchEvents();
class ViewGroup;
void LayoutViewHierarchy(const UIContext &dc, ViewGroup *root, bool ignoreInsets);
void UpdateViewHierarchy(ViewGroup *root);
DialogResult UpdateViewHierarchy(ViewGroup *root);
enum class KeyEventResult {
IGNORE_KEY, // Don't let it be processed.
+1
View File
@@ -28,6 +28,7 @@ namespace UI {
}
enum DialogResult {
DR_NONE,
DR_OK,
DR_CANCEL,
DR_YES,
+320
View File
@@ -0,0 +1,320 @@
#include "Common/Math/curves.h"
#include "Common/Data/Text/I18n.h"
#include "Common/UI/TabHolder.h"
#include "Common/UI/Root.h"
#include "Common/UI/ScrollView.h"
#include "Common/UI/ViewGroup.h"
#include "Common/UI/UIScreen.h"
namespace UI {
TabHolder::TabHolder(Orientation orientation, float stripSize, TabHolderFlags flags, View *bannerView, LayoutParams *layoutParams)
: LinearLayout(Opposite(orientation), layoutParams) {
SetSpacing(0.0f);
if (orientation == ORIENT_HORIZONTAL) {
// This orientation supports adding a back button.
tabStrip_ = new ChoiceStrip(orientation, new LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
tabStrip_->SetTopTabs(true);
if (flags & TabHolderFlags::BackButton) {
tabStrip_->AddBackButton();
}
tabScroll_ = new ScrollView(orientation, new LayoutParams(FILL_PARENT, WRAP_CONTENT));
tabScroll_->Add(tabStrip_);
Add(tabScroll_);
} else {
tabContainer_ = new LinearLayout(ORIENT_VERTICAL, new LayoutParams(stripSize, FILL_PARENT));
tabStrip_ = new ChoiceStrip(orientation, new LayoutParams(FILL_PARENT, FILL_PARENT));
tabStrip_->SetTopTabs(true);
tabScroll_ = new ScrollView(orientation, new LinearLayoutParams(1.0f));
tabScroll_->Add(tabStrip_);
tabContainer_->Add(tabScroll_);
Add(tabContainer_);
}
tabStrip_->OnChoice.Handle(this, &TabHolder::OnTabClick);
Add(new Spacer(4.0f))->SetSeparator();
ViewGroup *contentHolder_ = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, FILL_PARENT, 1.0f));
if (bannerView) {
contentHolder_->Add(bannerView);
bannerView_ = bannerView;
}
contents_ = contentHolder_->Add(new AnchorLayout(new LinearLayoutParams(FILL_PARENT, FILL_PARENT, 1.0f)));
contents_->SetClip(true);
Add(contentHolder_);
}
void TabHolder::AddBack(UIScreen *parent) {
if (tabContainer_) {
auto di = GetI18NCategory(I18NCat::DIALOG);
tabContainer_->Add(new Choice(di->T("Back"), "", false, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT, 0.0f, Margins(0, 0, 10, 10))))->OnClick.Handle<UIScreen>(parent, &UIScreen::OnBack);
}
}
void TabHolder::AddTabContents(std::string_view title, ViewGroup *tabContents) {
tabs_.push_back(tabContents);
tabStrip_->AddChoice(title);
contents_->Add(tabContents);
if (tabs_.size() > 1)
tabContents->SetVisibility(V_GONE);
tabContents->ReplaceLayoutParams(new AnchorLayoutParams(FILL_PARENT, FILL_PARENT));
// Will be filled in later.
tabTweens_.push_back(nullptr);
// This entry doesn't need one.
createFuncs_.push_back(nullptr);
}
void TabHolder::AddTabDeferred(std::string_view title, std::function<ViewGroup *()> createCb) {
tabs_.push_back(nullptr); // marker
tabStrip_->AddChoice(title);
tabTweens_.push_back(nullptr);
createFuncs_.push_back(createCb);
if (tabs_.size() == 1) {
EnsureTab(0);
}
}
void TabHolder::EnsureAllCreated() {
for (int i = 0; i < createFuncs_.size(); i++) {
if (createFuncs_[i]) {
EnsureTab(i);
tabs_[i]->SetVisibility(i == currentTab_ ? V_VISIBLE : V_GONE);
}
}
}
bool TabHolder::EnsureTab(int index) {
_dbg_assert_(index >= 0 && index < createFuncs_.size());
if (!tabs_[index]) {
_dbg_assert_(index < createFuncs_.size());
_dbg_assert_(createFuncs_[index]);
std::function<UI::ViewGroup * ()> func;
createFuncs_[index].swap(func);
ViewGroup *tabContents = func();
tabs_[index] = tabContents;
contents_->Add(tabContents);
tabContents->ReplaceLayoutParams(new AnchorLayoutParams(FILL_PARENT, FILL_PARENT));
return true;
} else {
return false;
}
}
bool TabHolder::SetCurrentTab(int tab, bool skipTween) {
if (tab >= (int)tabs_.size()) {
// Ignore
return false;
}
bool created = false;
if (tab != currentTab_) {
_dbg_assert_(tabs_[currentTab_]); // we should always have a tab to switch *from*.
created = EnsureTab(tab);
}
auto setupTween = [&](View *view, AnchorTranslateTween *&tween) {
_dbg_assert_(view != nullptr);
if (tween)
return;
tween = new AnchorTranslateTween(0.15f, bezierEaseInOut);
tween->Finish.Add([&](EventParams &e) {
e.v->SetVisibility(tabs_[currentTab_] == e.v ? V_VISIBLE : V_GONE);
});
view->AddTween(tween)->Persist();
};
if (tab != currentTab_) {
Orientation orient = Opposite(orientation_);
// Direction from which the new tab will come.
float dir = tab < currentTab_ ? -1.0f : 1.0f;
// First, setup any missing tweens.
setupTween(tabs_[currentTab_], tabTweens_[currentTab_]);
setupTween(tabs_[tab], tabTweens_[tab]);
// Currently displayed, so let's reset it.
if (skipTween) {
tabs_[currentTab_]->SetVisibility(V_GONE);
tabTweens_[tab]->Reset(Point2D(0.0f, 0.0f));
tabTweens_[tab]->Apply(tabs_[tab]);
} else {
tabTweens_[currentTab_]->Reset(Point2D(0.0f, 0.0f));
if (orient == ORIENT_HORIZONTAL) {
tabTweens_[tab]->Reset(Point2D(bounds_.w * dir, 0.0f));
tabTweens_[currentTab_]->Divert(Point2D(bounds_.w * -dir, 0.0f));
} else {
tabTweens_[tab]->Reset(Point2D(0.0f, bounds_.h * dir));
tabTweens_[currentTab_]->Divert(Point2D(0.0f, bounds_.h * -dir));
}
// Actually move it to the initial position now, just to avoid any flicker.
tabTweens_[tab]->Apply(tabs_[tab]);
tabTweens_[tab]->Divert(Point2D(0.0f, 0.0f));
}
tabs_[tab]->SetVisibility(V_VISIBLE);
currentTab_ = tab;
}
tabStrip_->SetSelection(tab, false);
return created;
}
void TabHolder::OnTabClick(EventParams &e) {
// We have e.b set when it was an explicit click action.
// In that case, we make the view gone and then visible - this scrolls scrollviews to the top.
if (e.b != 0) {
EnsureTab(e.a);
SetCurrentTab((int)e.a);
}
}
void TabHolder::PersistData(PersistStatus status, std::string anonId, PersistMap &storage) {
ViewGroup::PersistData(status, anonId, storage);
std::string tag = Tag();
if (tag.empty()) {
tag = anonId;
}
PersistBuffer &buffer = storage["TabHolder::" + tag];
switch (status) {
case PERSIST_SAVE:
buffer.resize(1);
buffer[0] = currentTab_;
break;
case PERSIST_RESTORE:
if (buffer.size() == 1) {
if (SetCurrentTab(buffer[0], true)) {
// Re-run PersistData. TODO: Only need to do it for the new tab.
ViewGroup::PersistData(status, anonId, storage);
}
}
break;
}
}
void TabHolder::EnableTab(int tab, bool enabled) {
tabStrip_->EnableChoice(tab, enabled);
}
ChoiceStrip::ChoiceStrip(Orientation orientation, LayoutParams *layoutParams)
: LinearLayout(orientation, layoutParams) {
SetSpacing(0.0f);
}
void ChoiceStrip::AddChoice(std::string_view title) {
StickyChoice *c = new StickyChoice(title, "",
orientation_ == ORIENT_HORIZONTAL ?
nullptr :
new LinearLayoutParams(FILL_PARENT, ITEM_HEIGHT));
c->OnClick.Handle(this, &ChoiceStrip::OnChoiceClick);
Add(c);
choices_.push_back(c);
if (selected_ == (int)choices_.size() - 1)
c->Press();
}
void ChoiceStrip::AddChoice(ImageID buttonImage) {
StickyChoice *c = new StickyChoice(buttonImage,
orientation_ == ORIENT_HORIZONTAL ?
nullptr :
new LinearLayoutParams(FILL_PARENT, ITEM_HEIGHT));
c->OnClick.Handle(this, &ChoiceStrip::OnChoiceClick);
Add(c);
choices_.push_back(c);
if (selected_ == (int)choices_.size() - 1)
c->Press();
}
void ChoiceStrip::AddBackButton() {
Choice *c = new Choice(ImageID("I_NAVIGATE_BACK"));
c->OnClick.Add([](EventParams &e) {
e.bubbleResult = DR_BACK;
});
// c->OnClick.Handle(this, &UIScreen::OnBack);
Add(c);
Add(new Spacer(5.0f));
}
void ChoiceStrip::OnChoiceClick(EventParams &e) {
// Unstick the other choices that weren't clicked.
for (int i = 0; i < (int)choices_.size(); i++) {
if (choices_[i] != e.v) {
choices_[i]->Release();
} else {
selected_ = i;
}
}
EventParams e2{};
e2.v = selected_ < choices_.size() ? choices_[selected_] : nullptr;
e2.a = selected_;
// Set to 1 to indicate an explicit click.
e2.b = 1;
// Dispatch immediately (we're already on the UI thread as we're in an event handler).
OnChoice.Dispatch(e2);
}
void ChoiceStrip::SetSelection(int sel, bool triggerClick) {
int prevSelected = selected_;
if (selected_ < choices_.size()) {
StickyChoice *prevChoice = choices_[selected_];
prevChoice->Release();
}
selected_ = sel;
if (selected_ < choices_.size()) {
StickyChoice *newChoice = choices_[selected_];
newChoice->Press();
if (topTabs_ && prevSelected != selected_) {
EventParams e{};
e.v = choices_[selected_];
e.a = selected_;
// Set to 0 to indicate a selection change (not a click.)
e.b = triggerClick ? 1 : 0;
OnChoice.Trigger(e);
}
}
}
void ChoiceStrip::EnableChoice(int choice, bool enabled) {
if (choice < (int)choices_.size()) {
choices_[choice]->SetEnabled(enabled);
}
}
bool ChoiceStrip::Key(const KeyInput &input) {
bool ret = false;
if (topTabs_ && (input.flags & KEY_DOWN)) {
if (IsTabLeftKey(input)) {
if (selected_ > 0) {
SetSelection(selected_ - 1, true);
UI::PlayUISound(UI::UISound::TOGGLE_OFF); // Maybe make specific sounds for this at some point?
}
ret = true;
} else if (IsTabRightKey(input)) {
if (selected_ < (int)choices_.size() - 1) {
SetSelection(selected_ + 1, true);
UI::PlayUISound(UI::UISound::TOGGLE_ON);
}
ret = true;
}
}
return ret || ViewGroup::Key(input);
}
std::string ChoiceStrip::DescribeText() const {
auto u = GetI18NCategory(I18NCat::UI_ELEMENTS);
return DescribeListUnordered(u->T("Choices:"));
}
} // namespace UI
+97
View File
@@ -0,0 +1,97 @@
#pragma once
#include <string_view>
#include "Common/UI/View.h"
#include "Common/UI/ViewGroup.h"
#include "Common/UI/Tween.h"
namespace UI {
class ChoiceStrip;
class ScrollView;
enum class TabHolderFlags {
Default = 0,
BackButton = 1,
};
ENUM_CLASS_BITOPS(TabHolderFlags);
class TabHolder : public LinearLayout {
public:
TabHolder(Orientation orientation, float stripSize, TabHolderFlags flags, View *bannerView, LayoutParams *layoutParams);
template <class T>
T *AddTab(std::string_view title, T *tabContents) {
AddTabContents(title, tabContents);
return tabContents;
}
void AddTabDeferred(std::string_view title, std::function<ViewGroup *()> createCb);
void EnableTab(int tab, bool enabled);
void AddBack(UIScreen *parent);
// Returns true if the tab wasn't created before (but is now).
bool SetCurrentTab(int tab, bool skipTween = false);
int GetCurrentTab() const { return currentTab_; }
std::string DescribeLog() const override { return "TabHolder: " + View::DescribeLog(); }
void PersistData(PersistStatus status, std::string anonId, PersistMap &storage) override;
void EnsureAllCreated();
LinearLayout *Container() { return tabContainer_; }
const std::vector<ViewGroup *> &GetTabContentViews() const {
return tabs_;
}
private:
void AddTabContents(std::string_view title, ViewGroup *tabContents);
void OnTabClick(EventParams &e);
bool EnsureTab(int index); // return true if it actually created a tab.
View *bannerView_ = nullptr;
LinearLayout *tabContainer_ = nullptr;
ChoiceStrip *tabStrip_ = nullptr;
ScrollView *tabScroll_ = nullptr;
ViewGroup *contents_ = nullptr;
int currentTab_ = 0;
std::vector<ViewGroup *> tabs_;
std::vector<AnchorTranslateTween *> tabTweens_;
std::vector<std::function<ViewGroup *()>> createFuncs_;
};
class ChoiceStrip : public LinearLayout {
public:
ChoiceStrip(Orientation orientation, LayoutParams *layoutParams = 0);
void AddChoice(std::string_view title);
void AddChoice(ImageID buttonImage);
void AddBackButton();
int GetSelection() const { return selected_; }
void SetSelection(int sel, bool triggerClick);
void EnableChoice(int choice, bool enabled);
bool Key(const KeyInput &input) override;
void SetTopTabs(bool tabs) { topTabs_ = tabs; }
std::string DescribeLog() const override { return "ChoiceStrip: " + View::DescribeLog(); }
std::string DescribeText() const override;
Event OnChoice;
private:
void OnChoiceClick(EventParams &e);
std::vector<UI::StickyChoice *> choices_;
int selected_ = 0; // Can be controlled with L/R.
bool topTabs_ = false;
};
} // namespace
+4 -1
View File
@@ -149,7 +149,10 @@ void UIScreen::update() {
DoRecreateViews();
if (root_) {
UpdateViewHierarchy(root_);
DialogResult result = UpdateViewHierarchy(root_);
if (result != DR_NONE) {
TriggerFinish(result);
}
}
while (true) {
-1
View File
@@ -21,7 +21,6 @@
namespace UI {
static constexpr Size ITEM_HEIGHT = 64.f;
static constexpr float MIN_TEXT_SCALE = 0.8f;
static constexpr float MAX_ITEM_SIZE = 65535.0f;
+8 -3
View File
@@ -22,6 +22,7 @@
#include "Common/Input/KeyCodes.h"
#include "Common/Common.h"
#include "Common/UI/Screen.h" // for DialogResult
#undef small
@@ -139,6 +140,9 @@ typedef float Size; // can also be WRAP_CONTENT or FILL_PARENT.
static constexpr Size WRAP_CONTENT = -1.0f;
static constexpr Size FILL_PARENT = -2.0f;
// TODO: This needs to move to the theme.
static constexpr Size ITEM_HEIGHT = 64.f;
// Gravity
enum Gravity {
G_LEFT = 0,
@@ -240,10 +244,11 @@ struct MeasureSpec {
// Should cover all bases.
struct EventParams {
View *v;
uint32_t a, b, x, y;
float f;
View *v = nullptr;
uint32_t a = 0, b = 0, x = 0, y = 0;
float f = 0.0f;
std::string s;
DialogResult bubbleResult = DialogResult::DR_NONE;
};
typedef std::function<void(EventParams &)> EventCallback;
-296
View File
@@ -24,8 +24,6 @@
namespace UI {
static constexpr Size ITEM_HEIGHT = 64.f;
void ApplyGravity(const Bounds &outer, const Margins &margins, float w, float h, int gravity, Bounds &inner) {
inner.w = w;
inner.h = h;
@@ -957,300 +955,6 @@ std::string GridLayoutList::DescribeText() const {
return DescribeListOrdered(u->T("List:"));
}
TabHolder::TabHolder(Orientation orientation, float stripSize, View *bannerView, LayoutParams *layoutParams)
: LinearLayout(Opposite(orientation), layoutParams) {
SetSpacing(0.0f);
if (orientation == ORIENT_HORIZONTAL) {
tabStrip_ = new ChoiceStrip(orientation, new LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
tabStrip_->SetTopTabs(true);
tabScroll_ = new ScrollView(orientation, new LayoutParams(FILL_PARENT, WRAP_CONTENT));
tabScroll_->Add(tabStrip_);
Add(tabScroll_);
} else {
tabContainer_ = new LinearLayout(ORIENT_VERTICAL, new LayoutParams(stripSize, FILL_PARENT));
tabStrip_ = new ChoiceStrip(orientation, new LayoutParams(FILL_PARENT, FILL_PARENT));
tabStrip_->SetTopTabs(true);
tabScroll_ = new ScrollView(orientation, new LinearLayoutParams(1.0f));
tabScroll_->Add(tabStrip_);
tabContainer_->Add(tabScroll_);
Add(tabContainer_);
}
tabStrip_->OnChoice.Handle(this, &TabHolder::OnTabClick);
Add(new Spacer(4.0f))->SetSeparator();
ViewGroup *contentHolder_ = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, FILL_PARENT, 1.0f));
if (bannerView) {
contentHolder_->Add(bannerView);
bannerView_ = bannerView;
}
contents_ = contentHolder_->Add(new AnchorLayout(new LinearLayoutParams(FILL_PARENT, FILL_PARENT, 1.0f)));
contents_->SetClip(true);
Add(contentHolder_);
}
void TabHolder::AddBack(UIScreen *parent) {
if (tabContainer_) {
auto di = GetI18NCategory(I18NCat::DIALOG);
tabContainer_->Add(new Choice(di->T("Back"), "", false, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT, 0.0f, Margins(0, 0, 10, 10))))->OnClick.Handle<UIScreen>(parent, &UIScreen::OnBack);
}
}
void TabHolder::AddTabContents(std::string_view title, ViewGroup *tabContents) {
tabs_.push_back(tabContents);
tabStrip_->AddChoice(title);
contents_->Add(tabContents);
if (tabs_.size() > 1)
tabContents->SetVisibility(V_GONE);
tabContents->ReplaceLayoutParams(new AnchorLayoutParams(FILL_PARENT, FILL_PARENT));
// Will be filled in later.
tabTweens_.push_back(nullptr);
// This entry doesn't need one.
createFuncs_.push_back(nullptr);
}
void TabHolder::AddTabDeferred(std::string_view title, std::function<ViewGroup *()> createCb) {
tabs_.push_back(nullptr); // marker
tabStrip_->AddChoice(title);
tabTweens_.push_back(nullptr);
createFuncs_.push_back(createCb);
if (tabs_.size() == 1) {
EnsureTab(0);
}
}
void TabHolder::EnsureAllCreated() {
for (int i = 0; i < createFuncs_.size(); i++) {
if (createFuncs_[i]) {
EnsureTab(i);
tabs_[i]->SetVisibility(i == currentTab_ ? V_VISIBLE : V_GONE);
}
}
}
bool TabHolder::EnsureTab(int index) {
_dbg_assert_(index >= 0 && index < createFuncs_.size());
if (!tabs_[index]) {
_dbg_assert_(index < createFuncs_.size());
_dbg_assert_(createFuncs_[index]);
std::function<UI::ViewGroup * ()> func;
createFuncs_[index].swap(func);
ViewGroup *tabContents = func();
tabs_[index] = tabContents;
contents_->Add(tabContents);
tabContents->ReplaceLayoutParams(new AnchorLayoutParams(FILL_PARENT, FILL_PARENT));
return true;
} else {
return false;
}
}
bool TabHolder::SetCurrentTab(int tab, bool skipTween) {
if (tab >= (int)tabs_.size()) {
// Ignore
return false;
}
bool created = false;
if (tab != currentTab_) {
_dbg_assert_(tabs_[currentTab_]); // we should always have a tab to switch *from*.
created = EnsureTab(tab);
}
auto setupTween = [&](View *view, AnchorTranslateTween *&tween) {
_dbg_assert_(view != nullptr);
if (tween)
return;
tween = new AnchorTranslateTween(0.15f, bezierEaseInOut);
tween->Finish.Add([&](EventParams &e) {
e.v->SetVisibility(tabs_[currentTab_] == e.v ? V_VISIBLE : V_GONE);
});
view->AddTween(tween)->Persist();
};
if (tab != currentTab_) {
Orientation orient = Opposite(orientation_);
// Direction from which the new tab will come.
float dir = tab < currentTab_ ? -1.0f : 1.0f;
// First, setup any missing tweens.
setupTween(tabs_[currentTab_], tabTweens_[currentTab_]);
setupTween(tabs_[tab], tabTweens_[tab]);
// Currently displayed, so let's reset it.
if (skipTween) {
tabs_[currentTab_]->SetVisibility(V_GONE);
tabTweens_[tab]->Reset(Point2D(0.0f, 0.0f));
tabTweens_[tab]->Apply(tabs_[tab]);
} else {
tabTweens_[currentTab_]->Reset(Point2D(0.0f, 0.0f));
if (orient == ORIENT_HORIZONTAL) {
tabTweens_[tab]->Reset(Point2D(bounds_.w * dir, 0.0f));
tabTweens_[currentTab_]->Divert(Point2D(bounds_.w * -dir, 0.0f));
} else {
tabTweens_[tab]->Reset(Point2D(0.0f, bounds_.h * dir));
tabTweens_[currentTab_]->Divert(Point2D(0.0f, bounds_.h * -dir));
}
// Actually move it to the initial position now, just to avoid any flicker.
tabTweens_[tab]->Apply(tabs_[tab]);
tabTweens_[tab]->Divert(Point2D(0.0f, 0.0f));
}
tabs_[tab]->SetVisibility(V_VISIBLE);
currentTab_ = tab;
}
tabStrip_->SetSelection(tab, false);
return created;
}
void TabHolder::OnTabClick(EventParams &e) {
// We have e.b set when it was an explicit click action.
// In that case, we make the view gone and then visible - this scrolls scrollviews to the top.
if (e.b != 0) {
EnsureTab(e.a);
SetCurrentTab((int)e.a);
}
}
void TabHolder::PersistData(PersistStatus status, std::string anonId, PersistMap &storage) {
ViewGroup::PersistData(status, anonId, storage);
std::string tag = Tag();
if (tag.empty()) {
tag = anonId;
}
PersistBuffer &buffer = storage["TabHolder::" + tag];
switch (status) {
case PERSIST_SAVE:
buffer.resize(1);
buffer[0] = currentTab_;
break;
case PERSIST_RESTORE:
if (buffer.size() == 1) {
if (SetCurrentTab(buffer[0], true)) {
// Re-run PersistData. TODO: Only need to do it for the new tab.
ViewGroup::PersistData(status, anonId, storage);
}
}
break;
}
}
ChoiceStrip::ChoiceStrip(Orientation orientation, LayoutParams *layoutParams)
: LinearLayout(orientation, layoutParams) {
SetSpacing(0.0f);
}
void ChoiceStrip::AddChoice(std::string_view title) {
StickyChoice *c = new StickyChoice(title, "",
orientation_ == ORIENT_HORIZONTAL ?
nullptr :
new LinearLayoutParams(FILL_PARENT, ITEM_HEIGHT));
c->OnClick.Handle(this, &ChoiceStrip::OnChoiceClick);
Add(c);
if (selected_ == (int)views_.size() - 1)
c->Press();
}
void ChoiceStrip::AddChoice(ImageID buttonImage) {
StickyChoice *c = new StickyChoice(buttonImage,
orientation_ == ORIENT_HORIZONTAL ?
nullptr :
new LinearLayoutParams(FILL_PARENT, ITEM_HEIGHT));
c->OnClick.Handle(this, &ChoiceStrip::OnChoiceClick);
Add(c);
if (selected_ == (int)views_.size() - 1)
c->Press();
}
void ChoiceStrip::OnChoiceClick(EventParams &e) {
// Unstick the other choices that weren't clicked.
for (int i = 0; i < (int)views_.size(); i++) {
if (views_[i] != e.v) {
Choice(i)->Release();
} else {
selected_ = i;
}
}
EventParams e2{};
e2.v = views_[selected_];
e2.a = selected_;
// Set to 1 to indicate an explicit click.
e2.b = 1;
// Dispatch immediately (we're already on the UI thread as we're in an event handler).
OnChoice.Dispatch(e2);
}
void ChoiceStrip::SetSelection(int sel, bool triggerClick) {
int prevSelected = selected_;
StickyChoice *prevChoice = Choice(selected_);
if (prevChoice)
prevChoice->Release();
selected_ = sel;
StickyChoice *newChoice = Choice(selected_);
if (newChoice) {
newChoice->Press();
if (topTabs_ && prevSelected != selected_) {
EventParams e{};
e.v = views_[selected_];
e.a = selected_;
// Set to 0 to indicate a selection change (not a click.)
e.b = triggerClick ? 1 : 0;
OnChoice.Trigger(e);
}
}
}
void ChoiceStrip::EnableChoice(int choice, bool enabled) {
if (choice < (int)views_.size()) {
Choice(choice)->SetEnabled(enabled);
}
}
bool ChoiceStrip::Key(const KeyInput &input) {
bool ret = false;
if (topTabs_ && (input.flags & KEY_DOWN)) {
if (IsTabLeftKey(input)) {
if (selected_ > 0) {
SetSelection(selected_ - 1, true);
UI::PlayUISound(UI::UISound::TOGGLE_OFF); // Maybe make specific sounds for this at some point?
}
ret = true;
} else if (IsTabRightKey(input)) {
if (selected_ < (int)views_.size() - 1) {
SetSelection(selected_ + 1, true);
UI::PlayUISound(UI::UISound::TOGGLE_ON);
}
ret = true;
}
}
return ret || ViewGroup::Key(input);
}
std::string ChoiceStrip::DescribeText() const {
auto u = GetI18NCategory(I18NCat::UI_ELEMENTS);
return DescribeListUnordered(u->T("Choices:"));
}
StickyChoice *ChoiceStrip::Choice(int index) {
if ((size_t)index < views_.size())
return static_cast<StickyChoice *>(views_[index]);
return nullptr;
}
CollapsibleSection::CollapsibleSection(std::string_view title, LayoutParams *layoutParams) : LinearLayout(ORIENT_VERTICAL, layoutParams) {
open_ = &localOpen_;
SetSpacing(0.0f);
-78
View File
@@ -260,84 +260,6 @@ public:
std::string DescribeText() const override;
};
class ChoiceStrip : public LinearLayout {
public:
ChoiceStrip(Orientation orientation, LayoutParams *layoutParams = 0);
void AddChoice(std::string_view title);
void AddChoice(ImageID buttonImage);
int GetSelection() const { return selected_; }
void SetSelection(int sel, bool triggerClick);
void EnableChoice(int choice, bool enabled);
bool Key(const KeyInput &input) override;
void SetTopTabs(bool tabs) { topTabs_ = tabs; }
std::string DescribeLog() const override { return "ChoiceStrip: " + View::DescribeLog(); }
std::string DescribeText() const override;
Event OnChoice;
private:
StickyChoice *Choice(int index);
void OnChoiceClick(EventParams &e);
int selected_ = 0; // Can be controlled with L/R.
bool topTabs_ = false;
};
class TabHolder : public LinearLayout {
public:
TabHolder(Orientation orientation, float stripSize, View *bannerView, LayoutParams *layoutParams = 0);
template <class T>
T *AddTab(std::string_view title, T *tabContents) {
AddTabContents(title, tabContents);
return tabContents;
}
void AddTabDeferred(std::string_view title, std::function<ViewGroup *()> createCb);
void EnableTab(int tab, bool enabled) {
tabStrip_->EnableChoice(tab, enabled);
}
void AddBack(UIScreen *parent);
// Returns true if the tab wasn't created before (but is now).
bool SetCurrentTab(int tab, bool skipTween = false);
int GetCurrentTab() const { return currentTab_; }
std::string DescribeLog() const override { return "TabHolder: " + View::DescribeLog(); }
void PersistData(PersistStatus status, std::string anonId, PersistMap &storage) override;
void EnsureAllCreated();
LinearLayout *Container() { return tabContainer_; }
const std::vector<ViewGroup *> &GetTabContentViews() const {
return tabs_;
}
private:
void AddTabContents(std::string_view title, ViewGroup *tabContents);
void OnTabClick(EventParams &e);
bool EnsureTab(int index); // return true if it actually created a tab.
View *bannerView_ = nullptr;
LinearLayout *tabContainer_ = nullptr;
ChoiceStrip *tabStrip_ = nullptr;
ScrollView *tabScroll_ = nullptr;
ViewGroup *contents_ = nullptr;
int currentTab_ = 0;
std::vector<ViewGroup *> tabs_;
std::vector<AnchorTranslateTween *> tabTweens_;
std::vector<std::function<ViewGroup *()>> createFuncs_;
};
class CollapsibleHeader;
class CollapsibleSection : public LinearLayout {
+3
View File
@@ -245,6 +245,8 @@ static const ConfigSetting generalSettings[] = {
ConfigSetting("CwCheatRefreshRate", &g_Config.iCwCheatRefreshIntervalMs, 77, CfgFlag::PER_GAME),
ConfigSetting("CwCheatScrollPosition", &g_Config.fCwCheatScrollPosition, 0.0f, CfgFlag::PER_GAME),
ConfigSetting("GameListScrollPosition", &g_Config.fGameListScrollPosition, 0.0f, CfgFlag::DEFAULT),
ConfigSetting("HomebrewScrollPosition", &g_Config.fHomebrewScrollPosition, 0.0f, CfgFlag::DEFAULT),
ConfigSetting("RemoteScrollPosition", &g_Config.fRemoteScrollPosition, 0.0f, CfgFlag::DEFAULT),
ConfigSetting("DebugOverlay", &g_Config.iDebugOverlay, 0, CfgFlag::DONT_SAVE),
ConfigSetting("DefaultTab", &g_Config.iDefaultTab, 0, CfgFlag::DEFAULT),
ConfigSetting("DisableHLEFlags", &g_Config.iDisableHLE, 0, CfgFlag::PER_GAME),
@@ -270,6 +272,7 @@ static const ConfigSetting generalSettings[] = {
ConfigSetting("GridView1", &g_Config.bGridView1, true, CfgFlag::DEFAULT),
ConfigSetting("GridView2", &g_Config.bGridView2, true, CfgFlag::DEFAULT),
ConfigSetting("GridView3", &g_Config.bGridView3, false, CfgFlag::DEFAULT),
ConfigSetting("GridView4", &g_Config.bGridView4, false, CfgFlag::DEFAULT),
ConfigSetting("RightAnalogUp", &g_Config.iRightAnalogUp, 0, CfgFlag::PER_GAME),
ConfigSetting("RightAnalogDown", &g_Config.iRightAnalogDown, 0, CfgFlag::PER_GAME),
ConfigSetting("RightAnalogLeft", &g_Config.iRightAnalogLeft, 0, CfgFlag::PER_GAME),
+4 -1
View File
@@ -260,6 +260,8 @@ public:
int iCwCheatRefreshIntervalMs;
float fCwCheatScrollPosition;
float fGameListScrollPosition;
float fHomebrewScrollPosition;
float fRemoteScrollPosition;
int iBloomHack; //0 = off, 1 = safe, 2 = balanced, 3 = aggressive
int iSkipGPUReadbackMode; // 0 = off, 1 = skip, 2 = to texture
int iSplineBezierQuality; // 0 = low , 1 = Intermediate , 2 = High
@@ -350,10 +352,11 @@ public:
// 0 - no tilt, 1 - analog stick, 2 - D-Pad, 3 - Action Buttons (Tri, Cross, Square, Circle)
int iTiltInputType;
// The three tabs.
// The four tabs (including Remote last)
bool bGridView1;
bool bGridView2;
bool bGridView3;
bool bGridView4;
// Right analog binding
int iRightAnalogUp;
+2 -2
View File
@@ -474,7 +474,7 @@ void ShaderListScreen::CreateViews() {
LinearLayout *layout = new LinearLayout(ORIENT_VERTICAL);
root_ = layout;
tabs_ = new TabHolder(ORIENT_HORIZONTAL, 40, nullptr, new LinearLayoutParams(1.0));
tabs_ = new TabHolder(ORIENT_HORIZONTAL, 40, TabHolderFlags::Default, nullptr, new LinearLayoutParams(1.0));
tabs_->SetTag("DevShaderList");
layout->Add(tabs_);
layout->Add(new Button(di->T("Back")))->OnClick.Handle<UIScreen>(this, &UIScreen::OnBack);
@@ -549,7 +549,7 @@ void FrameDumpTestScreen::CreateViews() {
auto di = GetI18NCategory(I18NCat::DIALOG);
TabHolder *tabHolder;
tabHolder = new TabHolder(ORIENT_VERTICAL, 200, nullptr, new AnchorLayoutParams(10, 0, 10, 0, false));
tabHolder = new TabHolder(ORIENT_VERTICAL, 200, TabHolderFlags::Default, nullptr, new AnchorLayoutParams(10, 0, 10, 0, false));
root_->Add(tabHolder);
tabHolder->AddBack(this);
tabHolder->SetTag("DumpTypes");
+1 -1
View File
@@ -24,8 +24,8 @@
#include "Common/UI/Context.h"
#include "Common/UI/View.h"
#include "Common/UI/UIScreen.h"
#include "Common/UI/TabHolder.h"
#include "Common/Math/math_util.h"
#include "Common/System/Display.h"
#include "Common/System/NativeApp.h"
#include "Common/VR/PPSSPPVR.h"
#include "Common/StringUtils.h"
+4
View File
@@ -25,6 +25,10 @@
#include "MiscScreens.h"
namespace UI {
class ChoiceStrip;
}
class DisplayLayoutScreen : public UIDialogScreenWithGameBackground {
public:
DisplayLayoutScreen(const Path &filename);
+1 -3
View File
@@ -7,7 +7,7 @@
#include "Core/Config.h"
#include "Core/System.h"
#include "UI/View.h"
#include "Common/UI/View.h"
#include "UI/DriverManagerScreen.h"
#include "UI/GameSettingsScreen.h" // for triggerrestart
#include "UI/OnScreenDisplay.h"
@@ -79,8 +79,6 @@ public:
std::string name_;
};
static constexpr UI::Size ITEM_HEIGHT = 64.f;
DriverChoice::DriverChoice(const std::string &driverName, bool current, UI::LayoutParams *layoutParams) : UI::LinearLayout(UI::ORIENT_VERTICAL, layoutParams), name_(driverName) {
using namespace UI;
SetSpacing(2.0f);
+1 -1
View File
@@ -301,7 +301,7 @@ void GPUDriverTestScreen::CreateViews() {
AnchorLayout *anchor = new AnchorLayout();
root_ = anchor;
tabHolder_ = new TabHolder(ORIENT_HORIZONTAL, 30.0f, nullptr, new AnchorLayoutParams(FILL_PARENT, FILL_PARENT, false));
tabHolder_ = new TabHolder(ORIENT_HORIZONTAL, 30.0f, TabHolderFlags::Default, nullptr, new AnchorLayoutParams(FILL_PARENT, FILL_PARENT, false));
anchor->Add(tabHolder_);
tabHolder_->AddTab("Discard", new LinearLayout(ORIENT_VERTICAL));
tabHolder_->AddTab("Shader", new LinearLayout(ORIENT_VERTICAL));
+1
View File
@@ -4,6 +4,7 @@
#include "Common/UI/Context.h"
#include "Common/UI/View.h"
#include "Common/UI/ViewGroup.h"
#include "Common/UI/TabHolder.h"
#include "Common/Log.h"
#include "UI/MiscScreens.h"
+20 -32
View File
@@ -299,7 +299,13 @@ void GameSettingsScreen::CreateGraphicsSettings(UI::ViewGroup *graphicsSettings)
static const char *internalResolutions[] = { "Auto (1:1)", "1x PSP", "2x PSP", "3x PSP", "4x PSP", "5x PSP", "6x PSP", "7x PSP", "8x PSP", "9x PSP", "10x PSP" };
resolutionChoice_ = graphicsSettings->Add(new PopupMultiChoice(&g_Config.iInternalResolution, gr->T("Rendering Resolution"), internalResolutions, 0, ARRAY_SIZE(internalResolutions), I18NCat::GRAPHICS, screenManager()));
resolutionChoice_->OnChoice.Handle(this, &GameSettingsScreen::OnResolutionChange);
resolutionChoice_->OnChoice.Add([](UI::EventParams &e) {
if (g_Config.iAndroidHwScale == 1) {
System_RecreateActivity();
}
Reporting::UpdateConfig();
System_PostUIMessage(UIMessage::GPU_RENDER_RESIZED);
});
resolutionChoice_->SetEnabledFunc([] {
return !g_Config.bSoftwareRendering && !g_Config.bSkipBufferEffects;
});
@@ -784,8 +790,12 @@ void GameSettingsScreen::CreateControlsSettings(UI::ViewGroup *controlsSettings)
int deviceType = System_GetPropertyInt(SYSPROP_DEVICE_TYPE);
controlsSettings->Add(new ItemHeader(ms->T("Controls")));
controlsSettings->Add(new Choice(co->T("Control Mapping")))->OnClick.Handle(this, &GameSettingsScreen::OnControlMapping);
controlsSettings->Add(new Choice(co->T("Calibrate Analog Stick")))->OnClick.Handle(this, &GameSettingsScreen::OnCalibrateAnalogs);
controlsSettings->Add(new Choice(co->T("Control Mapping")))->OnClick.Add([this](UI::EventParams &e) {
screenManager()->push(new ControlMappingScreen(gamePath_));
});
controlsSettings->Add(new Choice(co->T("Calibrate Analog Stick")))->OnClick.Add([this](UI::EventParams &e) {
screenManager()->push(new AnalogSetupScreen(gamePath_));
});
controlsSettings->Add(new PopupSliderChoiceFloat(&g_Config.fAnalogTriggerThreshold, 0.02f, 0.98f, 0.75f, co->T("Analog trigger threshold"), screenManager()));
#if defined(USING_WIN_UI) || (PPSSPP_PLATFORM(LINUX) && !PPSSPP_PLATFORM(ANDROID))
@@ -804,7 +814,9 @@ void GameSettingsScreen::CreateControlsSettings(UI::ViewGroup *controlsSettings)
}
return "";
}));
customizeTilt->OnClick.Handle(this, &GameSettingsScreen::OnTiltCustomize);
customizeTilt->OnClick.Add([this](UI::EventParams &e) {
screenManager()->push(new TouchControlLayoutScreen(gamePath_));
});
} else if (System_GetPropertyInt(SYSPROP_DEVICE_TYPE) == DEVICE_TYPE_VR) { // TODO: This seems like a regression
controlsSettings->Add(new CheckBox(&g_Config.bHapticFeedback, co->T("HapticFeedback", "Haptic Feedback (vibration)")));
}
@@ -814,7 +826,9 @@ void GameSettingsScreen::CreateControlsSettings(UI::ViewGroup *controlsSettings)
controlsSettings->Add(new ItemHeader(co->T("OnScreen", "On-Screen Touch Controls")));
controlsSettings->Add(new CheckBox(&g_Config.bShowTouchControls, co->T("OnScreen", "On-Screen Touch Controls")));
layoutEditorChoice_ = controlsSettings->Add(new Choice(co->T("Customize Touch Controls")));
layoutEditorChoice_->OnClick.Handle(this, &GameSettingsScreen::OnTouchControlLayout);
layoutEditorChoice_->OnClick.Add([this](UI::EventParams &e) {
screenManager()->push(new TouchControlLayoutScreen(gamePath_));
});
layoutEditorChoice_->SetEnabledPtr(&g_Config.bShowTouchControls);
Choice *gesture = controlsSettings->Add(new Choice(co->T("Gesture mapping")));
@@ -922,8 +936,6 @@ public:
MacAddressChooser(RequesterToken token, Path gamePath, std::string *value, std::string_view title, ScreenManager *screenManager, UI::LayoutParams *layoutParams = nullptr);
};
static constexpr UI::Size ITEM_HEIGHT = 64.f;
MacAddressChooser::MacAddressChooser(RequesterToken token, Path gamePath_, std::string *value, std::string_view title, ScreenManager *screenManager, UI::LayoutParams *layoutParams) : UI::LinearLayout(UI::ORIENT_HORIZONTAL, layoutParams) {
using namespace UI;
SetSpacing(5.0f);
@@ -1601,14 +1613,6 @@ void GameSettingsScreen::OnFullscreenMultiChange(UI::EventParams &e) {
System_ToggleFullscreenState(g_Config.UseFullScreen() ? "1" : "0");
}
void GameSettingsScreen::OnResolutionChange(UI::EventParams &e) {
if (g_Config.iAndroidHwScale == 1) {
System_RecreateActivity();
}
Reporting::UpdateConfig();
System_PostUIMessage(UIMessage::GPU_RENDER_RESIZED);
}
void GameSettingsScreen::onFinish(DialogResult result) {
Reporting::Enable(enableReports_, "report.ppsspp.org");
Reporting::UpdateConfig();
@@ -1806,22 +1810,6 @@ void GameSettingsScreen::OnTextureShaderChange(UI::EventParams &e) {
g_Config.bTexHardwareScaling = g_Config.sTextureShaderName != "Off";
}
void GameSettingsScreen::OnControlMapping(UI::EventParams &e) {
screenManager()->push(new ControlMappingScreen(gamePath_));
}
void GameSettingsScreen::OnCalibrateAnalogs(UI::EventParams &e) {
screenManager()->push(new AnalogSetupScreen(gamePath_));
}
void GameSettingsScreen::OnTouchControlLayout(UI::EventParams &e) {
screenManager()->push(new TouchControlLayoutScreen(gamePath_));
}
void GameSettingsScreen::OnTiltCustomize(UI::EventParams &e) {
screenManager()->push(new TiltAnalogSettingsScreen(gamePath_));
};
void GameSettingsScreen::CallbackRestoreDefaults(bool yes) {
if (yes) {
g_Config.RestoreDefaults(RestoreSettingsBits::SETTINGS);
@@ -2055,7 +2043,7 @@ void GestureMappingScreen::CreateViews() {
root_ = new AnchorLayout(new LayoutParams(FILL_PARENT, FILL_PARENT));
AddStandardBack(root_);
TabHolder *tabHolder = new TabHolder(ORIENT_VERTICAL, 200, nullptr, new AnchorLayoutParams(10, 0, 10, 0, false));
TabHolder *tabHolder = new TabHolder(ORIENT_VERTICAL, 200, TabHolderFlags::Default, nullptr, new AnchorLayoutParams(10, 0, 10, 0, false));
root_->Add(tabHolder);
ScrollView *rightPanel = new ScrollView(ORIENT_VERTICAL);
tabHolder->AddTab(co->T("Gesture"), rightPanel);
-7
View File
@@ -74,12 +74,6 @@ private:
std::string memstickDisplay_;
// Event handlers
void OnControlMapping(UI::EventParams &e);
void OnCalibrateAnalogs(UI::EventParams &e);
void OnTouchControlLayout(UI::EventParams &e);
void OnTiltCustomize(UI::EventParams &e);
// Global settings handlers
void OnAutoFrameskip(UI::EventParams &e);
void OnTextureShader(UI::EventParams &e);
@@ -92,7 +86,6 @@ private:
void OnChangeBackground(UI::EventParams &e);
void OnFullscreenChange(UI::EventParams &e);
void OnFullscreenMultiChange(UI::EventParams &e);
void OnResolutionChange(UI::EventParams &e);
void OnRestoreDefaultSettings(UI::EventParams &e);
void OnRenderingBackend(UI::EventParams &e);
void OnRenderingDevice(UI::EventParams &e);
+61 -72
View File
@@ -1068,6 +1068,59 @@ MainScreen::~MainScreen() {
g_BackgroundAudio.SetGame(Path());
}
#if PPSSPP_PLATFORM(IOS)
constexpr std::string_view getGamesUri = "https://www.ppsspp.org/getgames_ios";
constexpr std::string_view getHomebrewUri = "https://www.ppsspp.org/gethomebrew_ios";
#else
constexpr std::string_view getGamesUri = "https://www.ppsspp.org/getgames";
constexpr std::string_view getHomebrewUri = "https://www.ppsspp.org/gethomebrew";
#endif
constexpr std::string_view remoteGamesUri = "https://www.ppsspp.org/docs/reference/disc-streaming";
void MainScreen::CreateRecentTab() {
using namespace UI;
auto mm = GetI18NCategory(I18NCat::MAINMENU);
ScrollView *scrollRecentGames = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT));
scrollRecentGames->SetTag("MainScreenRecentGames");
GameBrowser *tabRecentGames = new GameBrowser(GetRequesterToken(),
Path("!RECENT"), BrowseFlags::NONE, &g_Config.bGridView1, screenManager(), "", "",
new LinearLayoutParams(FILL_PARENT, FILL_PARENT));
scrollRecentGames->Add(tabRecentGames);
gameBrowsers_.push_back(tabRecentGames);
tabHolder_->AddTab(mm->T("Recent"), scrollRecentGames);
tabRecentGames->OnChoice.Handle(this, &MainScreen::OnGameSelectedInstant);
tabRecentGames->OnHoldChoice.Handle(this, &MainScreen::OnGameSelected);
tabRecentGames->OnHighlight.Handle(this, &MainScreen::OnGameHighlight);
}
GameBrowser *MainScreen::CreateBrowserTab(const Path &path, std::string_view title, std::string_view howToTitle, std::string_view howToUri, BrowseFlags browseFlags, bool *bGridView, float *scrollPos) {
using namespace UI;
auto mm = GetI18NCategory(I18NCat::MAINMENU);
ScrollView *scrollView = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT));
scrollView->SetTag(title); // Re-use title as tag, should be fine.
GameBrowser *gameBrowser = new GameBrowser(GetRequesterToken(), path, browseFlags, bGridView, screenManager(),
mm->T(howToTitle), howToUri,
new LinearLayoutParams(FILL_PARENT, FILL_PARENT));
scrollView->Add(gameBrowser);
gameBrowsers_.push_back(gameBrowser);
tabHolder_->AddTab(mm->T(title), scrollView);
if (scrollPos) {
scrollView->RememberPosition(scrollPos);
}
gameBrowser->OnChoice.Handle(this, &MainScreen::OnGameSelectedInstant);
gameBrowser->OnHoldChoice.Handle(this, &MainScreen::OnGameSelected);
gameBrowser->OnHighlight.Handle(this, &MainScreen::OnGameHighlight);
return gameBrowser;
}
void MainScreen::CreateViews() {
// Information in the top left.
// Back button to the bottom left.
@@ -1078,7 +1131,7 @@ void MainScreen::CreateViews() {
auto mm = GetI18NCategory(I18NCat::MAINMENU);
tabHolder_ = new TabHolder(ORIENT_HORIZONTAL, 64, nullptr, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT, 1.0f));
tabHolder_ = new TabHolder(ORIENT_HORIZONTAL, 64, TabHolderFlags::Default, nullptr, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT, 1.0f));
ViewGroup *leftColumn = tabHolder_;
tabHolder_->SetTag("MainScreenGames");
gameBrowsers_.clear();
@@ -1094,81 +1147,18 @@ void MainScreen::CreateViews() {
}
if (showRecent) {
ScrollView *scrollRecentGames = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT));
scrollRecentGames->SetTag("MainScreenRecentGames");
GameBrowser *tabRecentGames = new GameBrowser(GetRequesterToken(),
Path("!RECENT"), BrowseFlags::NONE, &g_Config.bGridView1, screenManager(), "", "",
new LinearLayoutParams(FILL_PARENT, FILL_PARENT));
scrollRecentGames->Add(tabRecentGames);
gameBrowsers_.push_back(tabRecentGames);
tabHolder_->AddTab(mm->T("Recent"), scrollRecentGames);
tabRecentGames->OnChoice.Handle(this, &MainScreen::OnGameSelectedInstant);
tabRecentGames->OnHoldChoice.Handle(this, &MainScreen::OnGameSelected);
tabRecentGames->OnHighlight.Handle(this, &MainScreen::OnGameHighlight);
CreateRecentTab();
}
Button *focusButton = nullptr;
if (hasStorageAccess) {
scrollAllGames_ = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT));
scrollAllGames_->SetTag("MainScreenAllGames");
ScrollView *scrollHomebrew = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT));
scrollHomebrew->SetTag("MainScreenHomebrew");
#if PPSSPP_PLATFORM(IOS)
std::string_view getGamesUri = "https://www.ppsspp.org/getgames_ios";
std::string_view getHomebrewUri = "https://www.ppsspp.org/gethomebrew_ios";
#else
std::string_view getGamesUri = "https://www.ppsspp.org/getgames";
std::string_view getHomebrewUri = "https://www.ppsspp.org/gethomebrew";
#endif
GameBrowser *tabAllGames = new GameBrowser(GetRequesterToken(), Path(g_Config.currentDirectory), BrowseFlags::STANDARD, &g_Config.bGridView2, screenManager(),
mm->T("How to get games"), getGamesUri,
new LinearLayoutParams(FILL_PARENT, FILL_PARENT));
GameBrowser *tabHomebrew = new GameBrowser(GetRequesterToken(), GetSysDirectory(DIRECTORY_GAME), BrowseFlags::HOMEBREW_STORE, &g_Config.bGridView3, screenManager(),
mm->T("How to get homebrew & demos", "How to get homebrew && demos"), getHomebrewUri,
new LinearLayoutParams(FILL_PARENT, FILL_PARENT));
scrollAllGames_->Add(tabAllGames);
gameBrowsers_.push_back(tabAllGames);
scrollHomebrew->Add(tabHomebrew);
gameBrowsers_.push_back(tabHomebrew);
tabHolder_->AddTab(mm->T("Games"), scrollAllGames_);
tabHolder_->AddTab(mm->T("Homebrew & Demos"), scrollHomebrew);
scrollAllGames_->RememberPosition(&g_Config.fGameListScrollPosition);
tabAllGames->OnChoice.Handle(this, &MainScreen::OnGameSelectedInstant);
tabHomebrew->OnChoice.Handle(this, &MainScreen::OnGameSelectedInstant);
tabAllGames->OnHoldChoice.Handle(this, &MainScreen::OnGameSelected);
tabHomebrew->OnHoldChoice.Handle(this, &MainScreen::OnGameSelected);
tabAllGames->OnHighlight.Handle(this, &MainScreen::OnGameHighlight);
tabHomebrew->OnHighlight.Handle(this, &MainScreen::OnGameHighlight);
CreateBrowserTab(Path(g_Config.currentDirectory), "Games", "How to get games", getGamesUri, BrowseFlags::STANDARD, &g_Config.bGridView2, &g_Config.fGameListScrollPosition);
CreateBrowserTab(GetSysDirectory(DIRECTORY_GAME), "Homebrew & Demos", "How to get homebrew & demos", getHomebrewUri, BrowseFlags::HOMEBREW_STORE, &g_Config.bGridView3, &g_Config.fHomebrewScrollPosition);
if (g_Config.bRemoteTab && !g_Config.sLastRemoteISOServer.empty()) {
auto ri = GetI18NCategory(I18NCat::REMOTEISO);
ScrollView *scrollRemote = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT));
scrollRemote->SetTag("MainScreenRemote");
Path remotePath(FormatRemoteISOUrl(g_Config.sLastRemoteISOServer.c_str(), g_Config.iLastRemoteISOPort, RemoteSubdir().c_str()));
GameBrowser *tabRemote = new GameBrowser(GetRequesterToken(), remotePath, BrowseFlags::NAVIGATE, &g_Config.bGridView3, screenManager(),
ri->T("Remote disc streaming"), "https://www.ppsspp.org/docs/reference/disc-streaming",
new LinearLayoutParams(FILL_PARENT, FILL_PARENT));
tabRemote->SetHomePath(remotePath);
scrollRemote->Add(tabRemote);
gameBrowsers_.push_back(tabRemote);
tabHolder_->AddTab(ri->T("Remote disc streaming"), scrollRemote);
tabRemote->OnChoice.Handle(this, &MainScreen::OnGameSelectedInstant);
tabRemote->OnHoldChoice.Handle(this, &MainScreen::OnGameSelected);
tabRemote->OnHighlight.Handle(this, &MainScreen::OnGameHighlight);
GameBrowser *remoteBrowser = CreateBrowserTab(remotePath, "Remote disc streaming", "Remote disc streaming", remoteGamesUri, BrowseFlags::NAVIGATE, &g_Config.bGridView4, &g_Config.fRemoteScrollPosition);
remoteBrowser->SetHomePath(remotePath);
}
if (g_recentFiles.HasAny()) {
@@ -1202,7 +1192,6 @@ void MainScreen::CreateViews() {
leftColumn->Add(new Spacer(new LinearLayoutParams(0.1f)));
}
} else {
scrollAllGames_ = nullptr;
if (!showRecent) {
leftColumn = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT, 1.0f));
// Just so it's destroyed on recreate.
@@ -1227,7 +1216,6 @@ void MainScreen::CreateViews() {
ViewGroup *rightColumn = new ScrollView(ORIENT_VERTICAL);
LinearLayout *rightColumnItems = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT));
rightColumnItems->SetSpacing(0.0f);
rightColumn->Add(rightColumnItems);
std::string versionString = PPSSPP_GIT_VERSION;
// Strip the 'v' from the displayed version, and shorten the commit hash.
@@ -1308,6 +1296,7 @@ void MainScreen::CreateViews() {
// Officially, iOS apps should not have exit buttons. Remove it to maximize app store review chances.
rightColumnChoices->Add(new Choice(mm->T("Exit")))->OnClick.Handle(this, &MainScreen::OnExit);
#endif
rightColumn->Add(rightColumnItems);
if (vertical) {
root_ = new LinearLayout(ORIENT_VERTICAL);
@@ -1579,7 +1568,7 @@ void UmdReplaceScreen::CreateViews() {
auto mm = GetI18NCategory(I18NCat::MAINMENU);
auto di = GetI18NCategory(I18NCat::DIALOG);
TabHolder *leftColumn = new TabHolder(ORIENT_HORIZONTAL, 64, nullptr, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT, 1.0));
TabHolder *leftColumn = new TabHolder(ORIENT_HORIZONTAL, 64, TabHolderFlags::Default, nullptr, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT, 1.0));
leftColumn->SetTag("UmdReplace");
leftColumn->SetClip(true);
+6 -1
View File
@@ -18,10 +18,12 @@
#pragma once
#include <functional>
#include <string_view>
#include "Common/File/Path.h"
#include "Common/UI/UIScreen.h"
#include "Common/UI/ViewGroup.h"
#include "Common/UI/TabHolder.h"
#include "UI/MiscScreens.h"
#include "Common/File/PathBrowser.h"
@@ -131,6 +133,10 @@ public:
protected:
void CreateViews() override;
void CreateRecentTab();
GameBrowser *CreateBrowserTab(const Path &path, std::string_view title, std::string_view howToTitle, std::string_view howToUri, BrowseFlags browseFlags, bool *bGridView, float *scrollPos);
UI::ViewGroup *CreateButtons(bool vertical);
void DrawBackground(UIContext &dc) override;
void update() override;
void sendMessage(UIMessage message, const char *value) override;
@@ -165,7 +171,6 @@ protected:
bool lockBackgroundAudio_ = false;
bool lastVertical_ = false;
bool confirmedTemporary_ = false;
UI::ScrollView *scrollAllGames_ = nullptr;
bool searchKeyModifier_ = false;
bool searchChanged_ = false;
std::string searchFilter_;
+1 -1
View File
@@ -595,7 +595,7 @@ void RemoteISOBrowseScreen::CreateViews() {
using namespace UI;
TabHolder *leftColumn = new TabHolder(ORIENT_HORIZONTAL, 64, nullptr, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT));
TabHolder *leftColumn = new TabHolder(ORIENT_HORIZONTAL, 64, TabHolderFlags::Default, nullptr, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT));
tabHolder_ = leftColumn;
tabHolder_->SetTag("RemoteGames");
gameBrowsers_.clear();
+1 -2
View File
@@ -2,6 +2,7 @@
#include "Common/System/Request.h"
#include "Common/UI/View.h"
#include "Common/UI/ViewGroup.h"
#include "Common/UI/TabHolder.h"
#include "Common/UI/Context.h"
#include "Common/Data/Text/I18n.h"
#include "Common/UI/IconCache.h"
@@ -26,8 +27,6 @@ public:
UI::UISound sound_;
};
static constexpr UI::Size ITEM_HEIGHT = 64.f;
AudioFileChooser::AudioFileChooser(RequesterToken token, std::string *value, std::string_view title, UI::UISound sound, UI::LayoutParams *layoutParams) : UI::LinearLayout(UI::ORIENT_HORIZONTAL, layoutParams), sound_(sound) {
using namespace UI;
SetSpacing(2.0f);
+3 -3
View File
@@ -4,6 +4,7 @@
#include "Common/System/NativeApp.h"
#include "Common/System/Request.h"
#include "Common/System/Display.h"
#include "Common/UI/TabHolder.h"
#include "UI/TabbedDialogScreen.h"
void TabbedUIDialogScreenWithGameBackground::AddTab(const char *tag, std::string_view title, std::function<void(UI::LinearLayout *)> createCallback, bool isSearch) {
@@ -39,13 +40,12 @@ void TabbedUIDialogScreenWithGameBackground::CreateViews() {
if (vertical) {
auto di = GetI18NCategory(I18NCat::DIALOG);
LinearLayout *verticalLayout = new LinearLayout(ORIENT_VERTICAL, new LayoutParams(FILL_PARENT, FILL_PARENT));
tabHolder_ = new TabHolder(ORIENT_HORIZONTAL, 200, filterNotice_, new LinearLayoutParams(1.0f));
tabHolder_ = new TabHolder(ORIENT_HORIZONTAL, 200, TabHolderFlags::BackButton, filterNotice_, new LinearLayoutParams(1.0f));
verticalLayout->Add(tabHolder_);
CreateExtraButtons(verticalLayout, 0);
verticalLayout->Add(new Choice(di->T("Back"), "", false, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT, 0.0f, Margins(10, 0))))->OnClick.Handle<UIScreen>(this, &UIScreen::OnBack);
root_->Add(verticalLayout);
} else {
tabHolder_ = new TabHolder(ORIENT_VERTICAL, 200, filterNotice_, new AnchorLayoutParams(10, 0, 10, 0, false));
tabHolder_ = new TabHolder(ORIENT_VERTICAL, 200, TabHolderFlags::Default, filterNotice_, new AnchorLayoutParams(10, 0, 10, 0, false));
CreateExtraButtons(tabHolder_->Container(), 10);
tabHolder_->AddBack(this);
root_->Add(tabHolder_);
+4
View File
@@ -8,6 +8,10 @@
#include "Core/ConfigValues.h"
#include "UI/MiscScreens.h"
namespace UI {
class TabHolder;
}
class TabbedUIDialogScreenWithGameBackground : public UIDialogScreenWithGameBackground {
public:
TabbedUIDialogScreenWithGameBackground(const Path &gamePath) : UIDialogScreenWithGameBackground(gamePath) {}
+1
View File
@@ -19,6 +19,7 @@
#include "Common/UI/View.h"
#include "Common/UI/ViewGroup.h"
#include "Common/UI/TabHolder.h"
#include "MiscScreens.h"
class ControlLayoutView;
+3 -2
View File
@@ -15,6 +15,7 @@
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#include "Common/UI/TabHolder.h"
#include "Common/Render/TextureAtlas.h"
#include "Common/Data/Text/I18n.h"
#include "Common/StringUtils.h"
@@ -57,7 +58,7 @@ void TouchControlVisibilityScreen::CreateViews() {
Choice *toggleAll = new Choice(di->T("Toggle All"), "", false, new AnchorLayoutParams(leftColumnWidth - 10, WRAP_CONTENT, 10, NONE, NONE, 84));
root_->Add(toggleAll)->OnClick.Handle(this, &TouchControlVisibilityScreen::OnToggleAll);
TabHolder *tabHolder = new TabHolder(ORIENT_VERTICAL, leftColumnWidth, nullptr, new AnchorLayoutParams(10, 0, 10, 0, false));
TabHolder *tabHolder = new TabHolder(ORIENT_VERTICAL, leftColumnWidth, TabHolderFlags::Default, nullptr, new AnchorLayoutParams(10, 0, 10, 0, false));
tabHolder->SetTag("TouchControlVisibility");
root_->Add(tabHolder);
ScrollView *rightPanel = new ScrollView(ORIENT_VERTICAL);
@@ -142,7 +143,7 @@ void RightAnalogMappingScreen::CreateViews() {
root_ = new AnchorLayout(new LayoutParams(FILL_PARENT, FILL_PARENT));
Choice *back = new Choice(di->T("Back"), "", false, new AnchorLayoutParams(leftColumnWidth - 10, WRAP_CONTENT, 10, NONE, NONE, 10));
root_->Add(back)->OnClick.Handle<UIScreen>(this, &UIScreen::OnBack);
TabHolder *tabHolder = new TabHolder(ORIENT_VERTICAL, leftColumnWidth, nullptr, new AnchorLayoutParams(10, 0, 10, 0, false));
TabHolder *tabHolder = new TabHolder(ORIENT_VERTICAL, leftColumnWidth, TabHolderFlags::Default, nullptr, new AnchorLayoutParams(10, 0, 10, 0, false));
root_->Add(tabHolder);
ScrollView *rightPanel = new ScrollView(ORIENT_VERTICAL);
tabHolder->AddTab(co->T("Binds"), rightPanel);
+8
View File
@@ -129,6 +129,14 @@ static const ImageMeta imageIDs[] = {
{"I_PIN", false},
{"I_UNPIN", false},
{"I_FOLDER_PINNED", false},
{"I_FILLED_CIRCLE_1", false},
{"I_FILLED_CIRCLE_2", false},
{"I_FILLED_CIRCLE_3", false},
{"I_FILLED_CIRCLE_4", false},
{"I_FILLED_CIRCLE_5", false},
{"I_SETTINGS_DISPLAY", false},
{"I_NAVIGATE_BACK", false},
{"I_NAVIGATE_FORWARD", false},
};
static std::string PNGNameFromID(std::string_view id) {
+2
View File
@@ -221,6 +221,7 @@
<ClInclude Include="..\..\Common\UI\Root.h" />
<ClInclude Include="..\..\Common\UI\Screen.h" />
<ClInclude Include="..\..\Common\UI\ScrollView.h" />
<ClInclude Include="..\..\Common\UI\TabHolder.h" />
<ClInclude Include="..\..\Common\UI\Tween.h" />
<ClInclude Include="..\..\Common\UI\UI.h" />
<ClInclude Include="..\..\Common\UI\UIScreen.h" />
@@ -384,6 +385,7 @@
<ClCompile Include="..\..\Common\UI\Root.cpp" />
<ClCompile Include="..\..\Common\UI\Screen.cpp" />
<ClCompile Include="..\..\Common\UI\ScrollView.cpp" />
<ClCompile Include="..\..\Common\UI\TabHolder.cpp" />
<ClCompile Include="..\..\Common\UI\Tween.cpp" />
<ClCompile Include="..\..\Common\UI\UI.cpp" />
<ClCompile Include="..\..\Common\UI\UIScreen.cpp" />
+6
View File
@@ -523,6 +523,9 @@
<ClCompile Include="..\..\Common\Render\AtlasGen.cpp">
<Filter>Render</Filter>
</ClCompile>
<ClCompile Include="..\..\Common\UI\TabHolder.cpp">
<Filter>UI</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="targetver.h" />
@@ -1005,6 +1008,9 @@
<ClInclude Include="..\..\Common\Render\AtlasGen.h">
<Filter>Render</Filter>
</ClInclude>
<ClInclude Include="..\..\Common\UI\TabHolder.h">
<Filter>UI</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="..\..\Common\Math\fast\fast_matrix_neon.S">
+1
View File
@@ -365,6 +365,7 @@ EXEC_AND_LIB_FILES := \
$(SRC)/Common/UI/View.cpp \
$(SRC)/Common/UI/ViewGroup.cpp \
$(SRC)/Common/UI/ScrollView.cpp \
$(SRC)/Common/UI/TabHolder.cpp \
$(SRC)/Common/UI/PopupScreens.cpp \
$(SRC)/Common/Serialize/Serializer.cpp \
$(SRC)/Common/ArmCPUDetect.cpp \
+1001 -1927
View File
File diff suppressed because it is too large. Load diff

Before

Width:  |  Height:  |  Size: 203 KiB

After

Width:  |  Height:  |  Size: 203 KiB

+5
View File
@@ -0,0 +1,5 @@
Most of the vectors in images.svg are custom drawn by hrydgard and NABN00B.
Some are derived from public domain SVGs from the following sites:
www.svgrepo.com