mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-07-11 01:25:07 +02:00
Break out TabHolder from Common/UI/ViewGroup.cpp. A few new atlas images
This commit is contained in:
@@ -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" />
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -25,6 +25,10 @@
|
||||
|
||||
#include "MiscScreens.h"
|
||||
|
||||
namespace UI {
|
||||
class ChoiceStrip;
|
||||
}
|
||||
|
||||
class DisplayLayoutScreen : public UIDialogScreenWithGameBackground {
|
||||
public:
|
||||
DisplayLayoutScreen(const Path &filename);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {}
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#include "Common/UI/View.h"
|
||||
#include "Common/UI/ViewGroup.h"
|
||||
#include "Common/UI/TabHolder.h"
|
||||
#include "MiscScreens.h"
|
||||
|
||||
class ControlLayoutView;
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
|
Before Width: | Height: | Size: 203 KiB After Width: | Height: | Size: 200 KiB |
@@ -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
|
||||
Reference in New Issue
Block a user