Break out TabHolder from Common/UI/ViewGroup.cpp. A few new atlas images

This commit is contained in:
Henrik Rydgård
2025-10-23 11:19:20 +02:00
parent 5b3c3bd189
commit b45d07d6cb
22 changed files with 1405 additions and 2310 deletions
+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">
+310
View File
@@ -0,0 +1,310 @@
#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, 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;
}
}
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);
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;
}
} // namespace UI
+90
View File
@@ -0,0 +1,90 @@
#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;
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);
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);
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;
};
} // namespace
-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;
+3
View File
@@ -139,6 +139,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,
-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 {
+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
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"
-2
View File
@@ -936,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);
+2
View File
@@ -23,6 +23,7 @@
#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"
@@ -134,6 +135,7 @@ 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;
+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);
+1
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) {
+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;
+1
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"
+5
View File
@@ -129,6 +129,11 @@ 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},
};
static std::string PNGNameFromID(std::string_view id) {
+967 -1927
View File
File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 203 KiB

After

Width:  |  Height:  |  Size: 200 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