Merge pull request #20152 from hrydgard/lazy-tab-loading

Settings: Load tabs on demand, instead of all at once
This commit is contained in:
Henrik Rydgård 2025-03-24 10:13:48 +01:00 committed by GitHub
commit 0afacd3c03
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 379 additions and 199 deletions

View file

@ -957,7 +957,7 @@ std::string GridLayoutList::DescribeText() const {
return DescribeListOrdered(u->T("List:"));
}
TabHolder::TabHolder(Orientation orientation, float stripSize, LayoutParams *layoutParams)
TabHolder::TabHolder(Orientation orientation, float stripSize, View *bannerView, LayoutParams *layoutParams)
: LinearLayout(Opposite(orientation), layoutParams) {
SetSpacing(0.0f);
if (orientation == ORIENT_HORIZONTAL) {
@ -979,8 +979,14 @@ TabHolder::TabHolder(Orientation orientation, float stripSize, LayoutParams *lay
Add(new Spacer(4.0f))->SetSeparator();
contents_ = new AnchorLayout(new LinearLayoutParams(FILL_PARENT, FILL_PARENT, 1.0f));
Add(contents_)->SetClip(true);
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) {
@ -990,16 +996,56 @@ void TabHolder::AddBack(UIScreen *parent) {
}
}
void TabHolder::AddTabContents(std::string_view title, View *tabContents) {
tabContents->ReplaceLayoutParams(new AnchorLayoutParams(FILL_PARENT, FILL_PARENT));
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);
// Pre-create the first tab in a non-deferred way.
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);
}
}
}
void 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));
}
}
void TabHolder::SetCurrentTab(int tab, bool skipTween) {
@ -1008,7 +1054,10 @@ void TabHolder::SetCurrentTab(int tab, bool skipTween) {
return;
}
EnsureTab(tab);
auto setupTween = [&](View *view, AnchorTranslateTween *&tween) {
_dbg_assert_(view != nullptr);
if (tween)
return;
@ -1051,6 +1100,7 @@ void TabHolder::SetCurrentTab(int tab, bool skipTween) {
tabs_[tab]->SetVisibility(V_VISIBLE);
currentTab_ = tab;
EnsureTab(currentTab_);
}
tabStrip_->SetSelection(tab, false);
}
@ -1059,6 +1109,7 @@ EventReturn 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);
}
return EVENT_DONE;

View file

@ -71,7 +71,7 @@ public:
void Clear();
void PersistData(PersistStatus status, std::string anonId, PersistMap &storage) override;
View *GetViewByIndex(int index) { return views_[index]; }
View *GetViewByIndex(int index) const { return views_[index]; }
int GetNumSubviews() const { return (int)views_.size(); }
void SetHasDropShadow(bool has) { hasDropShadow_ = has; }
void SetDropShadowExpand(float s) { dropShadowExpand_ = s; }
@ -291,13 +291,14 @@ private:
class TabHolder : public LinearLayout {
public:
TabHolder(Orientation orientation, float stripSize, LayoutParams *layoutParams = 0);
TabHolder(Orientation orientation, float stripSize, View *bannerView, LayoutParams *layoutParams = 0);
template <class T>
T *AddTab(std::string_view title, T *tabContents) {
AddTabContents(title, (View *)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);
}
@ -311,20 +312,29 @@ public:
void PersistData(PersistStatus status, std::string anonId, PersistMap &storage) override;
void EnsureAllCreated();
LinearLayout *Container() { return tabContainer_; }
private:
void AddTabContents(std::string_view title, View *tabContents);
EventReturn OnTabClick(EventParams &e);
const std::vector<ViewGroup *> &GetTabContentViews() const {
return tabs_;
}
private:
void AddTabContents(std::string_view title, ViewGroup *tabContents);
EventReturn OnTabClick(EventParams &e);
void EnsureTab(int index);
View *bannerView_ = nullptr;
LinearLayout *tabContainer_ = nullptr;
ChoiceStrip *tabStrip_ = nullptr;
ScrollView *tabScroll_ = nullptr;
AnchorLayout *contents_ = nullptr;
ViewGroup *contents_ = nullptr;
int currentTab_ = 0;
std::vector<View *> tabs_;
std::vector<ViewGroup *> tabs_;
std::vector<AnchorTranslateTween *> tabTweens_;
std::vector<std::function<ViewGroup *()>> createFuncs_;
};
class CollapsibleHeader;

View file

@ -1351,7 +1351,7 @@ PPGeImage::PPGeImage(std::string_view pspFilename)
PPGeImage::PPGeImage(u32 pngPointer, size_t pngSize)
: filename_(""), png_(pngPointer), size_(pngSize) {
if (!Memory::IsValidRange(this->png_, this->size_)) {
if (!Memory::IsValidRange(this->png_, (u32)this->size_)) {
WARN_LOG(Log::sceGe, "Created PPGeImage from invalid memory range %08x (%08x bytes). Will not be drawn.");
}
}
@ -1417,7 +1417,7 @@ bool PPGeImage::IsValid() {
if (loadFailed_)
return false;
if (!Memory::IsValidRange(this->png_, this->size_)) {
if (!Memory::IsValidRange(this->png_, (u32)this->size_)) {
return false;
}

View file

@ -523,9 +523,44 @@ void SystemInfoScreen::CreateTabs() {
auto sy = GetI18NCategory(I18NCat::SYSTEM);
auto gr = GetI18NCategory(I18NCat::GRAPHICS);
LinearLayout *deviceSpecs = AddTab("Device Info", si->T("Device Info"));
AddTab("Device Info", si->T("Device Info"), [this](UI::LinearLayout *parent) {
CreateDeviceInfoTab(parent);
});
AddTab("Storage", si->T("Storage"), [this](UI::LinearLayout *parent) {
CreateStorageTab(parent);
});
AddTab("DevSystemInfoBuildConfig", si->T("Build Config"), [this](UI::LinearLayout *parent) {
CreateBuildConfigTab(parent);
});
AddTab("DevSystemInfoCPUExt", si->T("CPU Extensions"), [this](UI::LinearLayout *parent) {
CreateCPUExtensionsTab(parent);
});
if (GetGPUBackend() == GPUBackend::OPENGL) {
AddTab("DevSystemInfoOGLExt", si->T("OGL Extensions"), [this](UI::LinearLayout *parent) {
CreateOpenGLExtsTab(parent);
});
} else if (GetGPUBackend() == GPUBackend::VULKAN) {
AddTab("DevSystemInfoVulkanExt", si->T("Vulkan Extensions"), [this](UI::LinearLayout *parent) {
CreateVulkanExtsTab(parent);
});
}
AddTab("DevSystemInfoDriverBugs", si->T("Driver bugs"), [this](UI::LinearLayout *parent) {
CreateDriverBugsTab(parent);
});
AddTab("DevSystemInfoInternals", si->T("Internals"), [this](UI::LinearLayout *parent) {
CreateInternalsTab(parent);
});
}
CollapsibleSection *systemInfo = deviceSpecs->Add(new CollapsibleSection(si->T("System Information")));
void SystemInfoScreen::CreateDeviceInfoTab(UI::LinearLayout *deviceSpecs) {
using namespace Draw;
using namespace UI;
auto di = GetI18NCategory(I18NCat::DIALOG);
auto si = GetI18NCategory(I18NCat::SYSINFO);
auto gr = GetI18NCategory(I18NCat::GRAPHICS);
UI::CollapsibleSection *systemInfo = deviceSpecs->Add(new UI::CollapsibleSection(si->T("System Information")));
systemInfo->Add(new Choice(si->T("Copy summary to clipboard")))->OnClick.Handle(this, &SystemInfoScreen::CopySummaryToClipboard);
systemInfo->Add(new InfoItem(si->T("System Name", "Name"), System_GetProperty(SYSPROP_NAME)));
@ -546,7 +581,7 @@ void SystemInfoScreen::CreateTabs() {
systemInfo->Add(new InfoItem(si->T("Debugger Present"), di->T("Yes")));
}
CollapsibleSection *cpuInfo = deviceSpecs->Add(new CollapsibleSection(si->T("CPU Information")));
UI::CollapsibleSection *cpuInfo = deviceSpecs->Add(new UI::CollapsibleSection(si->T("CPU Information")));
// Don't bother showing the CPU name if we don't have one.
if (strcmp(cpu_info.brand_string, "Unknown") != 0) {
@ -721,8 +756,12 @@ void SystemInfoScreen::CreateTabs() {
}
}
}
}
LinearLayout *storage = AddTab("Storage", si->T("Storage"));
void SystemInfoScreen::CreateStorageTab(UI::LinearLayout *storage) {
using namespace UI;
auto si = GetI18NCategory(I18NCat::SYSINFO);
storage->Add(new ItemHeader(si->T("Directories")));
// Intentionally non-translated
@ -732,6 +771,7 @@ void SystemInfoScreen::CreateTabs() {
storage->Add(new InfoItem("DefaultCurrentDir", g_Config.defaultCurrentDirectory.ToVisualString()));
#if PPSSPP_PLATFORM(ANDROID)
auto di = GetI18NCategory(I18NCat::DIALOG);
storage->Add(new InfoItem("ExtFilesDir", g_extFilesDir));
bool scoped = System_GetPropertyBool(SYSPROP_ANDROID_SCOPED_STORAGE);
storage->Add(new InfoItem("Scoped Storage", scoped ? di->T("Yes") : di->T("No")));
@ -740,8 +780,12 @@ void SystemInfoScreen::CreateTabs() {
storage->Add(new InfoItem("IsStoragePreservedLegacy", Android_IsExternalStoragePreservedLegacy() ? di->T("Yes") : di->T("No")));
}
#endif
}
LinearLayout *buildConfig = AddTab("DevSystemInfoBuildConfig", si->T("Build Config"));
void SystemInfoScreen::CreateBuildConfigTab(UI::LinearLayout *buildConfig) {
using namespace UI;
auto si = GetI18NCategory(I18NCat::SYSINFO);
buildConfig->Add(new ItemHeader(si->T("Build Configuration")));
#ifdef ANDROID_LEGACY
@ -773,17 +817,28 @@ void SystemInfoScreen::CreateTabs() {
if (System_GetPropertyBool(SYSPROP_APP_GOLD)) {
buildConfig->Add(new InfoItem("GOLD", ""));
}
}
void SystemInfoScreen::CreateCPUExtensionsTab(UI::LinearLayout *cpuExtensions) {
using namespace UI;
auto si = GetI18NCategory(I18NCat::SYSINFO);
LinearLayout *cpuExtensions = AddTab("DevSystemInfoCPUExt", si->T("CPU Extensions"));
cpuExtensions->Add(new ItemHeader(si->T("CPU Extensions")));
std::vector<std::string> exts = cpu_info.Features();
for (std::string &ext : exts) {
cpuExtensions->Add(new TextView(ext, new LayoutParams(FILL_PARENT, WRAP_CONTENT)))->SetFocusable(true);
}
}
LinearLayout *driverBugs = AddTab("DevSystemInfoDriverBugs", si->T("Driver bugs"));
void SystemInfoScreen::CreateDriverBugsTab(UI::LinearLayout *driverBugs) {
using namespace UI;
using namespace Draw;
auto si = GetI18NCategory(I18NCat::SYSINFO);
bool anyDriverBugs = false;
Draw::DrawContext *draw = screenManager()->getDrawContext();
for (int i = 0; i < (int)draw->GetBugs().MaxBugIndex(); i++) {
if (draw->GetBugs().Has(i)) {
anyDriverBugs = true;
@ -794,90 +849,92 @@ void SystemInfoScreen::CreateTabs() {
if (!anyDriverBugs) {
driverBugs->Add(new TextView(si->T("No GPU driver bugs detected"), new LayoutParams(FILL_PARENT, WRAP_CONTENT)))->SetFocusable(true);
}
}
if (GetGPUBackend() == GPUBackend::OPENGL) {
LinearLayout *gpuExtensions = AddTab("DevSystemInfoOGLExt", si->T("OGL Extensions"));
void SystemInfoScreen::CreateOpenGLExtsTab(UI::LinearLayout *gpuExtensions) {
using namespace UI;
if (!gl_extensions.IsGLES) {
gpuExtensions->Add(new ItemHeader(si->T("OpenGL Extensions")));
} else if (gl_extensions.GLES3) {
gpuExtensions->Add(new ItemHeader(si->T("OpenGL ES 3.0 Extensions")));
} else {
gpuExtensions->Add(new ItemHeader(si->T("OpenGL ES 2.0 Extensions")));
}
exts.clear();
SplitString(g_all_gl_extensions, ' ', exts);
std::sort(exts.begin(), exts.end());
for (auto &extension : exts) {
gpuExtensions->Add(new TextView(extension, new LayoutParams(FILL_PARENT, WRAP_CONTENT)))->SetFocusable(true);
}
auto si = GetI18NCategory(I18NCat::SYSINFO);
Draw::DrawContext *draw = screenManager()->getDrawContext();
exts.clear();
SplitString(g_all_egl_extensions, ' ', exts);
std::sort(exts.begin(), exts.end());
// If there aren't any EGL extensions, no need to show the tab.
if (exts.size() > 0) {
LinearLayout *eglExtensions = AddTab("EglExt", si->T("EGL Extensions"));
eglExtensions->SetSpacing(0);
eglExtensions->Add(new ItemHeader(si->T("EGL Extensions")));
for (auto &extension : exts) {
eglExtensions->Add(new TextView(extension, new LayoutParams(FILL_PARENT, WRAP_CONTENT)))->SetFocusable(true);
}
}
} else if (GetGPUBackend() == GPUBackend::VULKAN) {
LinearLayout *gpuExtensions = AddTab("DevSystemInfoOGLExt", si->T("Vulkan Features"));
CollapsibleSection *vulkanFeatures = gpuExtensions->Add(new CollapsibleSection(si->T("Vulkan Features")));
std::vector<std::string> features = draw->GetFeatureList();
for (auto &feature : features) {
vulkanFeatures->Add(new TextView(feature, new LayoutParams(FILL_PARENT, WRAP_CONTENT)))->SetFocusable(true);
}
CollapsibleSection *presentModes = gpuExtensions->Add(new CollapsibleSection(si->T("Present modes")));
for (auto mode : draw->GetPresentModeList(di->T("Current"))) {
presentModes->Add(new TextView(mode, new LayoutParams(FILL_PARENT, WRAP_CONTENT)))->SetFocusable(true);
}
CollapsibleSection *colorFormats = gpuExtensions->Add(new CollapsibleSection(si->T("Display Color Formats")));
for (auto &format : draw->GetSurfaceFormatList()) {
colorFormats->Add(new TextView(format, new LayoutParams(FILL_PARENT, WRAP_CONTENT)))->SetFocusable(true);
}
CollapsibleSection *enabledExtensions = gpuExtensions->Add(new CollapsibleSection(std::string(si->T("Vulkan Extensions")) + " (" + std::string(di->T("Enabled")) + ")"));
std::vector<std::string> extensions = draw->GetExtensionList(true, true);
std::sort(extensions.begin(), extensions.end());
for (auto &extension : extensions) {
enabledExtensions->Add(new TextView(extension, new LayoutParams(FILL_PARENT, WRAP_CONTENT)))->SetFocusable(true);
}
// Also get instance extensions
enabledExtensions->Add(new ItemHeader(si->T("Instance")));
extensions = draw->GetExtensionList(false, true);
std::sort(extensions.begin(), extensions.end());
for (auto &extension : extensions) {
enabledExtensions->Add(new TextView(extension, new LayoutParams(FILL_PARENT, WRAP_CONTENT)))->SetFocusable(true);
}
CollapsibleSection *vulkanExtensions = gpuExtensions->Add(new CollapsibleSection(si->T("Vulkan Extensions")));
extensions = draw->GetExtensionList(true, false);
std::sort(extensions.begin(), extensions.end());
for (auto &extension : extensions) {
vulkanExtensions->Add(new TextView(extension, new LayoutParams(FILL_PARENT, WRAP_CONTENT)))->SetFocusable(true);
}
vulkanExtensions->Add(new ItemHeader(si->T("Instance")));
// Also get instance extensions
extensions = draw->GetExtensionList(false, false);
std::sort(extensions.begin(), extensions.end());
for (auto &extension : extensions) {
vulkanExtensions->Add(new TextView(extension, new LayoutParams(FILL_PARENT, WRAP_CONTENT)))->SetFocusable(true);
}
if (!gl_extensions.IsGLES) {
gpuExtensions->Add(new ItemHeader(si->T("OpenGL Extensions")));
} else if (gl_extensions.GLES3) {
gpuExtensions->Add(new ItemHeader(si->T("OpenGL ES 3.0 Extensions")));
} else {
gpuExtensions->Add(new ItemHeader(si->T("OpenGL ES 2.0 Extensions")));
}
#ifdef _DEBUG
LinearLayout *internals = AddTab("DevSystemInfoInternals", si->T("Internals"));
CreateInternalsTab(internals);
#endif
std::vector<std::string> exts;
SplitString(g_all_gl_extensions, ' ', exts);
std::sort(exts.begin(), exts.end());
for (auto &extension : exts) {
gpuExtensions->Add(new TextView(extension, new LayoutParams(FILL_PARENT, WRAP_CONTENT)))->SetFocusable(true);
}
exts.clear();
SplitString(g_all_egl_extensions, ' ', exts);
std::sort(exts.begin(), exts.end());
// If there aren't any EGL extensions, no need to show the tab.
gpuExtensions->Add(new ItemHeader(si->T("EGL Extensions")));
for (auto &extension : exts) {
gpuExtensions->Add(new TextView(extension, new LayoutParams(FILL_PARENT, WRAP_CONTENT)))->SetFocusable(true);
}
}
void SystemInfoScreen::CreateVulkanExtsTab(UI::LinearLayout *gpuExtensions) {
using namespace UI;
auto si = GetI18NCategory(I18NCat::SYSINFO);
auto di = GetI18NCategory(I18NCat::DIALOG);
Draw::DrawContext *draw = screenManager()->getDrawContext();
CollapsibleSection *vulkanFeatures = gpuExtensions->Add(new CollapsibleSection(si->T("Vulkan Features")));
std::vector<std::string> features = draw->GetFeatureList();
for (const auto &feature : features) {
vulkanFeatures->Add(new TextView(feature, new LayoutParams(FILL_PARENT, WRAP_CONTENT)))->SetFocusable(true);
}
CollapsibleSection *presentModes = gpuExtensions->Add(new CollapsibleSection(si->T("Present modes")));
for (const auto &mode : draw->GetPresentModeList(di->T("Current"))) {
presentModes->Add(new TextView(mode, new LayoutParams(FILL_PARENT, WRAP_CONTENT)))->SetFocusable(true);
}
CollapsibleSection *colorFormats = gpuExtensions->Add(new CollapsibleSection(si->T("Display Color Formats")));
for (const auto &format : draw->GetSurfaceFormatList()) {
colorFormats->Add(new TextView(format, new LayoutParams(FILL_PARENT, WRAP_CONTENT)))->SetFocusable(true);
}
CollapsibleSection *enabledExtensions = gpuExtensions->Add(new CollapsibleSection(std::string(si->T("Vulkan Extensions")) + " (" + std::string(di->T("Enabled")) + ")"));
std::vector<std::string> extensions = draw->GetExtensionList(true, true);
std::sort(extensions.begin(), extensions.end());
for (auto &extension : extensions) {
enabledExtensions->Add(new TextView(extension, new LayoutParams(FILL_PARENT, WRAP_CONTENT)))->SetFocusable(true);
}
// Also get instance extensions
enabledExtensions->Add(new ItemHeader(si->T("Instance")));
extensions = draw->GetExtensionList(false, true);
std::sort(extensions.begin(), extensions.end());
for (auto &extension : extensions) {
enabledExtensions->Add(new TextView(extension, new LayoutParams(FILL_PARENT, WRAP_CONTENT)))->SetFocusable(true);
}
CollapsibleSection *vulkanExtensions = gpuExtensions->Add(new CollapsibleSection(si->T("Vulkan Extensions")));
extensions = draw->GetExtensionList(true, false);
std::sort(extensions.begin(), extensions.end());
for (auto &extension : extensions) {
vulkanExtensions->Add(new TextView(extension, new LayoutParams(FILL_PARENT, WRAP_CONTENT)))->SetFocusable(true);
}
vulkanExtensions->Add(new ItemHeader(si->T("Instance")));
// Also get instance extensions
extensions = draw->GetExtensionList(false, false);
std::sort(extensions.begin(), extensions.end());
for (auto &extension : extensions) {
vulkanExtensions->Add(new TextView(extension, new LayoutParams(FILL_PARENT, WRAP_CONTENT)))->SetFocusable(true);
}
}
void SystemInfoScreen::CreateInternalsTab(UI::ViewGroup *internals) {
@ -1007,7 +1064,7 @@ void ShaderListScreen::CreateViews() {
LinearLayout *layout = new LinearLayout(ORIENT_VERTICAL);
root_ = layout;
tabs_ = new TabHolder(ORIENT_HORIZONTAL, 40, new LinearLayoutParams(1.0));
tabs_ = new TabHolder(ORIENT_HORIZONTAL, 40, nullptr, new LinearLayoutParams(1.0));
tabs_->SetTag("DevShaderList");
layout->Add(tabs_);
layout->Add(new Button(di->T("Back")))->OnClick.Handle<UIScreen>(this, &UIScreen::OnBack);
@ -1083,7 +1140,7 @@ void FrameDumpTestScreen::CreateViews() {
auto di = GetI18NCategory(I18NCat::DIALOG);
TabHolder *tabHolder;
tabHolder = new TabHolder(ORIENT_VERTICAL, 200, new AnchorLayoutParams(10, 0, 10, 0, false));
tabHolder = new TabHolder(ORIENT_VERTICAL, 200, nullptr, new AnchorLayoutParams(10, 0, 10, 0, false));
root_->Add(tabHolder);
AddStandardBack(root_);
tabHolder->SetTag("DumpTypes");

View file

@ -115,7 +115,17 @@ public:
protected:
UI::EventReturn CopySummaryToClipboard(UI::EventParams &e);
bool ShowSearchControls() const override { return false; }
private:
void CreateDeviceInfoTab(UI::LinearLayout *deviceInfo);
void CreateStorageTab(UI::LinearLayout *storage);
void CreateBuildConfigTab(UI::LinearLayout *storage);
void CreateCPUExtensionsTab(UI::LinearLayout *storage);
void CreateDriverBugsTab(UI::LinearLayout *storage);
void CreateInternalsTab(UI::ViewGroup *internals);
void CreateOpenGLExtsTab(UI::LinearLayout *gpuExtensions);
void CreateVulkanExtsTab(UI::LinearLayout *gpuExtensions);
};
class GPIGPOScreen : public PopupScreen {

View file

@ -152,8 +152,9 @@ void DriverManagerScreen::CreateTabs() {
using namespace UI;
auto gr = GetI18NCategory(I18NCat::GRAPHICS);
LinearLayout *drivers = AddTab("DriverManagerDrivers", gr->T("Drivers"));
CreateDriverTab(drivers);
AddTab("DriverManagerDrivers", gr->T("Drivers"), [this](UI::LinearLayout *parent) {
CreateDriverTab(parent);
});
}
void DriverManagerScreen::CreateDriverTab(UI::ViewGroup *drivers) {

View file

@ -212,10 +212,6 @@ EmuScreen::EmuScreen(const Path &filename)
// Usually, we don't want focus movement enabled on this screen, so disable on start.
// Only if you open chat or dev tools do we want it to start working.
UI::EnableFocusMovement(false);
// TODO: Do this only on demand.
IMGUI_CHECKVERSION();
ImGui::CreateContext();
}
bool EmuScreen::bootAllowStorage(const Path &filename) {
@ -460,7 +456,7 @@ void EmuScreen::bootComplete() {
EmuScreen::~EmuScreen() {
if (imguiInited_) {
ImGui_ImplThin3d_Shutdown();
ImGui::DestroyContext();
ImGui::DestroyContext(ctx_);
}
std::string gameID = g_paramSFO.GetValueString("DISC_ID");
@ -1770,6 +1766,11 @@ void EmuScreen::runImDebugger() {
Draw::DrawContext *draw = screenManager()->getDrawContext();
if (!imguiInited_) {
imguiInited_ = true;
// TODO: Do this only on demand.
IMGUI_CHECKVERSION();
ctx_ = ImGui::CreateContext();
ImGui_ImplPlatform_Init(GetSysDirectory(DIRECTORY_SYSTEM) / "imgui.ini");
imDebugger_ = std::make_unique<ImDebugger>();

View file

@ -154,6 +154,8 @@ private:
bool lastImguiEnabled_ = false;
std::vector<VirtKey> queuedVirtKeys_;
ImGuiContext *ctx_ = nullptr;
};
bool MustRunBehind();

View file

@ -301,7 +301,7 @@ void GPUDriverTestScreen::CreateViews() {
AnchorLayout *anchor = new AnchorLayout();
root_ = anchor;
tabHolder_ = new TabHolder(ORIENT_HORIZONTAL, 30.0f, new AnchorLayoutParams(FILL_PARENT, FILL_PARENT, false));
tabHolder_ = new TabHolder(ORIENT_HORIZONTAL, 30.0f, 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));

View file

@ -250,29 +250,37 @@ void GameSettingsScreen::CreateTabs() {
using namespace UI;
auto ms = GetI18NCategory(I18NCat::MAINSETTINGS);
LinearLayout *graphicsSettings = AddTab("GameSettingsGraphics", ms->T("Graphics"));
CreateGraphicsSettings(graphicsSettings);
AddTab("GameSettingsGraphics", ms->T("Graphics"), [this](UI::LinearLayout *parent) {
CreateGraphicsSettings(parent);
});
LinearLayout *controlsSettings = AddTab("GameSettingsControls", ms->T("Controls"));
CreateControlsSettings(controlsSettings);
AddTab("GameSettingsControls", ms->T("Controls"), [this](UI::LinearLayout *parent) {
CreateControlsSettings(parent);
});
LinearLayout *audioSettings = AddTab("GameSettingsAudio", ms->T("Audio"));
CreateAudioSettings(audioSettings);
AddTab("GameSettingsAudio", ms->T("Audio"), [this](UI::LinearLayout *parent) {
CreateAudioSettings(parent);
});
LinearLayout *networkingSettings = AddTab("GameSettingsNetworking", ms->T("Networking"));
CreateNetworkingSettings(networkingSettings);
AddTab("GameSettingsNetworking", ms->T("Networking"), [this](UI::LinearLayout *parent) {
CreateNetworkingSettings(parent);
});
LinearLayout *tools = AddTab("GameSettingsTools", ms->T("Tools"));
CreateToolsSettings(tools);
AddTab("GameSettingsTools", ms->T("Tools"), [this](UI::LinearLayout *parent) {
CreateToolsSettings(parent);
});
LinearLayout *systemSettings = AddTab("GameSettingsSystem", ms->T("System"));
systemSettings->SetSpacing(0);
CreateSystemSettings(systemSettings);
AddTab("GameSettingsSystem", ms->T("System"), [this](UI::LinearLayout *parent) {
parent->SetSpacing(0);
CreateSystemSettings(parent);
});
int deviceType = System_GetPropertyInt(SYSPROP_DEVICE_TYPE);
if ((deviceType == DEVICE_TYPE_VR) || g_Config.bForceVR) {
LinearLayout *vrSettings = AddTab("GameSettingsVR", ms->T("VR"));
CreateVRSettings(vrSettings);
AddTab("GameSettingsVR", ms->T("VR"), [this](UI::LinearLayout *parent) {
CreateVRSettings(parent);
});
}
}
@ -2538,7 +2546,7 @@ void GestureMappingScreen::CreateViews() {
root_ = new AnchorLayout(new LayoutParams(FILL_PARENT, FILL_PARENT));
AddStandardBack(root_);
TabHolder *tabHolder = new TabHolder(ORIENT_VERTICAL, 200, new AnchorLayoutParams(10, 0, 10, 0, false));
TabHolder *tabHolder = new TabHolder(ORIENT_VERTICAL, 200, nullptr, new AnchorLayoutParams(10, 0, 10, 0, false));
root_->Add(tabHolder);
ScrollView *rightPanel = new ScrollView(ORIENT_VERTICAL);
tabHolder->AddTab(co->T("Gesture"), rightPanel);

View file

@ -1105,7 +1105,7 @@ void MainScreen::CreateViews() {
auto mm = GetI18NCategory(I18NCat::MAINMENU);
tabHolder_ = new TabHolder(ORIENT_HORIZONTAL, 64, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT, 1.0f));
tabHolder_ = new TabHolder(ORIENT_HORIZONTAL, 64, nullptr, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT, 1.0f));
ViewGroup *leftColumn = tabHolder_;
tabHolder_->SetTag("MainScreenGames");
gameBrowsers_.clear();
@ -1653,7 +1653,7 @@ void UmdReplaceScreen::CreateViews() {
auto mm = GetI18NCategory(I18NCat::MAINMENU);
auto di = GetI18NCategory(I18NCat::DIALOG);
TabHolder *leftColumn = new TabHolder(ORIENT_HORIZONTAL, 64, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT, 1.0));
TabHolder *leftColumn = new TabHolder(ORIENT_HORIZONTAL, 64, nullptr, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT, 1.0));
leftColumn->SetTag("UmdReplace");
leftColumn->SetClip(true);

View file

@ -278,12 +278,14 @@ RemoteISOScreen::RemoteISOScreen(const Path &filename) : TabbedUIDialogScreenWit
void RemoteISOScreen::CreateTabs() {
auto ri = GetI18NCategory(I18NCat::REMOTEISO);
UI::LinearLayout *connect = AddTab("Connect", ri->T("Connect"));
connect->SetSpacing(5.0f);
CreateConnectTab(connect);
AddTab("Connect", ri->T("Connect"), [this](UI::LinearLayout *connect) {
connect->SetSpacing(5.0f);
CreateConnectTab(connect);
});
UI::LinearLayout *settings = AddTab("Settings", ri->T("Settings"));
CreateSettingsTab(settings);
AddTab("Settings", ri->T("Settings"), [this](UI::LinearLayout *settings) {
CreateSettingsTab(settings);
});
}
void RemoteISOScreen::update() {
@ -592,7 +594,7 @@ void RemoteISOBrowseScreen::CreateViews() {
bool vertical = UseVerticalLayout();
TabHolder *leftColumn = new TabHolder(ORIENT_HORIZONTAL, 64, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT));
TabHolder *leftColumn = new TabHolder(ORIENT_HORIZONTAL, 64, nullptr, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT));
tabHolder_ = leftColumn;
tabHolder_->SetTag("RemoteGames");
gameBrowsers_.clear();

View file

@ -62,16 +62,21 @@ AudioFileChooser::AudioFileChooser(RequesterToken token, std::string *value, std
void RetroAchievementsListScreen::CreateTabs() {
auto ac = GetI18NCategory(I18NCat::ACHIEVEMENTS);
UI::LinearLayout *achievements = AddTab("Achievements", ac->T("Achievements"));
achievements->SetSpacing(5.0f);
CreateAchievementsTab(achievements);
AddTab("Achievements", ac->T("Achievements"), [this](UI::LinearLayout *parent) {
parent->SetSpacing(5.0f);
CreateAchievementsTab(parent);
});
UI::LinearLayout *leaderboards = AddTab("Leaderboards", ac->T("Leaderboards"));
leaderboards->SetSpacing(5.0f);
CreateLeaderboardsTab(leaderboards);
AddTab("Leaderboards", ac->T("Leaderboards"), [this](UI::LinearLayout *parent) {
parent->SetSpacing(5.0f);
CreateLeaderboardsTab(parent);
});
#ifdef _DEBUG
CreateStatisticsTab(AddTab("AchievementsStatistics", ac->T("Statistics")));
AddTab("AchievementsStatistics", ac->T("Statistics"), [this](UI::LinearLayout *parent) {
parent->SetSpacing(5.0f);
CreateStatisticsTab(parent);
});
#endif
}
@ -200,7 +205,15 @@ void RetroAchievementsLeaderboardScreen::CreateTabs() {
const rc_client_leaderboard_t *leaderboard = rc_client_get_leaderboard_info(Achievements::GetClient(), leaderboardID_);
using namespace UI;
UI::LinearLayout *layout = AddTab("AchievementsLeaderboard", leaderboard->title);
AddTab("AchievementsLeaderboard", leaderboard->title, [this, leaderboard](UI::LinearLayout *parent) {
CreateLeaderboardTab(parent, leaderboard);
});
}
void RetroAchievementsLeaderboardScreen::CreateLeaderboardTab(UI::LinearLayout *layout, const rc_client_leaderboard_t *leaderboard) {
using namespace UI;
auto ac = GetI18NCategory(I18NCat::ACHIEVEMENTS);
layout->Add(new TextView(leaderboard->description));
layout->Add(new ItemHeader(ac->T("Leaderboard")));
@ -254,10 +267,16 @@ void RetroAchievementsSettingsScreen::CreateTabs() {
using namespace UI;
CreateAccountTab(AddTab("AchievementsAccount", ac->T("Account")));
AddTab("AchievementsAccount", ac->T("Account"), [this](UI::LinearLayout *layout) {
CreateAccountTab(layout);
});
// Don't bother creating this tab if we don't have a file browser.
CreateCustomizeTab(AddTab("AchievementsCustomize", ac->T("Customize")));
CreateDeveloperToolsTab(AddTab("AchievementsDeveloperTools", sy->T("Developer Tools")));
AddTab("AchievementsCustomize", ac->T("Customize"), [this](UI::LinearLayout *layout) {
CreateCustomizeTab(layout);
});
AddTab("AchievementsDeveloperTools", sy->T("Developer Tools"), [this](UI::LinearLayout *layout) {
CreateDeveloperToolsTab(layout);
});
}
void RetroAchievementsSettingsScreen::sendMessage(UIMessage message, const char *value) {

View file

@ -66,6 +66,7 @@ protected:
bool ShowSearchControls() const override { return false; }
private:
void CreateLeaderboardTab(UI::LinearLayout *layout, const rc_client_leaderboard_t *leaderboard);
void FetchEntries();
void Poll();

View file

@ -666,11 +666,13 @@ void SavedataScreen::CreateTabs() {
using namespace UI;
auto sa = GetI18NCategory(I18NCat::SAVEDATA);
LinearLayout *savedata = AddTab("SavedataBrowser", sa->T("Save Data"));
CreateSavedataTab(savedata);
AddTab("SavedataBrowser", sa->T("Save Data"), [this](UI::LinearLayout *parent) {
CreateSavedataTab(parent);
});
LinearLayout *savestate = AddTab("SavedataStatesBrowser", sa->T("Save States"));
CreateSavestateTab(savestate);
AddTab("SavedataStatesBrowser", sa->T("Save States"), [this](UI::LinearLayout *parent) {
CreateSavestateTab(parent);
});
}
void SavedataScreen::CreateExtraButtons(UI::LinearLayout *verticalLayout, int margins) {

View file

@ -6,26 +6,18 @@
#include "Common/System/Display.h"
#include "UI/TabbedDialogScreen.h"
UI::LinearLayout *TabbedUIDialogScreenWithGameBackground::AddTab(const char *tag, std::string_view title, bool isSearch) {
void TabbedUIDialogScreenWithGameBackground::AddTab(const char *tag, std::string_view title, std::function<void(UI::LinearLayout *)> createCallback, bool isSearch) {
using namespace UI;
ViewGroup *scroll = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, FILL_PARENT));
scroll->SetTag(tag);
LinearLayout *contents = new LinearLayoutList(ORIENT_VERTICAL);
contents->SetSpacing(0);
scroll->Add(contents);
tabHolder_->AddTab(title, scroll);
if (!isSearch) {
settingTabContents_.push_back(contents);
auto se = GetI18NCategory(I18NCat::SEARCH);
auto notice = contents->Add(new TextView(se->T("Filtering settings by '%1'"), new LinearLayoutParams(Margins(20, 5))));
notice->SetVisibility(V_GONE);
settingTabFilterNotices_.push_back(notice);
}
return contents;
tabHolder_->AddTabDeferred(title, [this, createCallback = std::move(createCallback), tag, title, isSearch]() -> UI::ViewGroup * {
ViewGroup *scroll = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, FILL_PARENT));
scroll->SetTag(tag);
LinearLayout *contents = new LinearLayoutList(ORIENT_VERTICAL);
contents->SetSpacing(0);
scroll->Add(contents);
createCallback(contents);
return scroll;
});
}
void TabbedUIDialogScreenWithGameBackground::CreateViews() {
@ -40,16 +32,20 @@ void TabbedUIDialogScreenWithGameBackground::CreateViews() {
root_ = new AnchorLayout(new LayoutParams(FILL_PARENT, FILL_PARENT));
auto se = GetI18NCategory(I18NCat::SEARCH);
filterNotice_ = new TextView("(filter notice, you shouldn't see this text", new LinearLayoutParams(Margins(20, 5)));
filterNotice_->SetVisibility(V_GONE);
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, new LinearLayoutParams(1.0f));
tabHolder_ = new TabHolder(ORIENT_HORIZONTAL, 200, 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(0))))->OnClick.Handle<UIScreen>(this, &UIScreen::OnBack);
root_->Add(verticalLayout);
} else {
tabHolder_ = new TabHolder(ORIENT_VERTICAL, 200, new AnchorLayoutParams(10, 0, 10, 0, false));
tabHolder_ = new TabHolder(ORIENT_VERTICAL, 200, filterNotice_, new AnchorLayoutParams(10, 0, 10, 0, false));
CreateExtraButtons(tabHolder_->Container(), 10);
tabHolder_->AddBack(this);
root_->Add(tabHolder_);
@ -57,8 +53,6 @@ void TabbedUIDialogScreenWithGameBackground::CreateViews() {
tabHolder_->SetTag(tag()); // take the tag from the screen.
root_->SetDefaultFocusView(tabHolder_);
settingTabContents_.clear();
settingTabFilterNotices_.clear();
float leftSide = 40.0f;
if (!vertical) {
@ -81,26 +75,26 @@ void TabbedUIDialogScreenWithGameBackground::CreateViews() {
// Hide search if screen is too small.
int deviceType = System_GetPropertyInt(SYSPROP_DEVICE_TYPE);
if ((g_display.dp_xres < g_display.dp_yres || g_display.dp_yres >= 500) && (deviceType != DEVICE_TYPE_VR) && ShowSearchControls()) {
auto se = GetI18NCategory(I18NCat::SEARCH);
auto ms = GetI18NCategory(I18NCat::MAINSETTINGS);
// Search
LinearLayout *searchSettings = AddTab("GameSettingsSearch", ms->T("Search"), true);
auto ms = GetI18NCategory(I18NCat::MAINSETTINGS);
AddTab("GameSettingsSearch", ms->T("Search"), [this](UI::LinearLayout *searchSettings) {
auto se = GetI18NCategory(I18NCat::SEARCH);
searchSettings->Add(new ItemHeader(se->T("Find settings")));
searchSettings->Add(new PopupTextInputChoice(GetRequesterToken(), &searchFilter_, se->T("Filter"), "", 64, screenManager()))->OnChange.Add([=](UI::EventParams &e) {
System_PostUIMessage(UIMessage::GAMESETTINGS_SEARCH, StripSpaces(searchFilter_));
return UI::EVENT_DONE;
});
searchSettings->Add(new ItemHeader(se->T("Find settings")));
searchSettings->Add(new PopupTextInputChoice(GetRequesterToken(), &searchFilter_, se->T("Filter"), "", 64, screenManager()))->OnChange.Add([=](UI::EventParams &e) {
System_PostUIMessage(UIMessage::GAMESETTINGS_SEARCH, StripSpaces(searchFilter_));
return UI::EVENT_DONE;
});
clearSearchChoice_ = searchSettings->Add(new Choice(se->T("Clear filter")));
clearSearchChoice_->OnClick.Add([=](UI::EventParams &e) {
System_PostUIMessage(UIMessage::GAMESETTINGS_SEARCH, "");
return UI::EVENT_DONE;
});
clearSearchChoice_ = searchSettings->Add(new Choice(se->T("Clear filter")));
clearSearchChoice_->OnClick.Add([=](UI::EventParams &e) {
System_PostUIMessage(UIMessage::GAMESETTINGS_SEARCH, "");
return UI::EVENT_DONE;
});
noSearchResults_ = searchSettings->Add(new TextView(se->T("No settings matched '%1'"), new LinearLayoutParams(Margins(20, 5))));
noSearchResults_ = searchSettings->Add(new TextView(se->T("No settings matched '%1'"), new LinearLayoutParams(Margins(20, 5))));
}, true);
ApplySearchFilter();
}
}
}
@ -122,16 +116,37 @@ void TabbedUIDialogScreenWithGameBackground::RecreateViews() {
}
void TabbedUIDialogScreenWithGameBackground::ApplySearchFilter() {
using namespace UI;
auto se = GetI18NCategory(I18NCat::SEARCH);
bool matches = searchFilter_.empty();
for (int t = 0; t < (int)settingTabContents_.size(); ++t) {
auto tabContents = settingTabContents_[t];
bool tabMatches = searchFilter_.empty();
tabHolder_->EnsureAllCreated();
// Show an indicator that a filter is applied.
settingTabFilterNotices_[t]->SetVisibility(tabMatches ? UI::V_GONE : UI::V_VISIBLE);
settingTabFilterNotices_[t]->SetText(ApplySafeSubstitutions(se->T("Filtering settings by '%1'"), searchFilter_));
// Show an indicator that a filter is applied.
filterNotice_->SetVisibility(searchFilter_.empty() ? UI::V_GONE : UI::V_VISIBLE);
filterNotice_->SetText(ApplySafeSubstitutions(se->T("Filtering settings by '%1'"), searchFilter_));
bool matches = searchFilter_.empty();
const std::vector<ViewGroup *> settingTabs = tabHolder_->GetTabContentViews();
for (int t = 0; t < (int)settingTabs.size(); ++t) {
const ViewGroup *tabContents = settingTabs[t];
std::string_view tag = tabContents->Tag();
// Dive down to the actual list of settings.
// TODO: Do this recursively instead.
while (tabContents->GetNumSubviews() == 1) {
View *v = tabContents->GetViewByIndex(0);
if (v->IsViewGroup()) {
tabContents = (ViewGroup *)v;
}
}
if (tag == "GameSettingsSearch") {
continue;
}
bool tabMatches = searchFilter_.empty();
UI::View *lastHeading = nullptr;
for (int i = 1; i < tabContents->GetNumSubviews(); ++i) {

View file

@ -1,6 +1,7 @@
#pragma once
#include "ppsspp_config.h"
#include <functional>
#include "Common/UI/UIScreen.h"
#include "Common/System/System.h"
@ -11,7 +12,7 @@ class TabbedUIDialogScreenWithGameBackground : public UIDialogScreenWithGameBack
public:
TabbedUIDialogScreenWithGameBackground(const Path &gamePath) : UIDialogScreenWithGameBackground(gamePath) {}
UI::LinearLayout *AddTab(const char *tag, std::string_view title, bool isSearch = false);
void AddTab(const char *tag, std::string_view title, std::function<void(UI::LinearLayout *)> createCallback, bool isSearch = false);
void CreateViews() override;
protected:
@ -30,10 +31,10 @@ private:
void ApplySearchFilter();
UI::TabHolder *tabHolder_ = nullptr;
std::vector<UI::LinearLayout *> settingTabContents_;
std::vector<UI::TextView *> settingTabFilterNotices_;
UI::TextView *filterNotice_ = nullptr;
UI::Choice *clearSearchChoice_ = nullptr;
UI::TextView *noSearchResults_ = nullptr;
// If we recreate the views while this is active we show it again
std::string oldSettingInfo_;
std::string searchFilter_;

View file

@ -57,7 +57,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, new AnchorLayoutParams(10, 0, 10, 0, false));
TabHolder *tabHolder = new TabHolder(ORIENT_VERTICAL, leftColumnWidth, nullptr, new AnchorLayoutParams(10, 0, 10, 0, false));
tabHolder->SetTag("TouchControlVisibility");
root_->Add(tabHolder);
ScrollView *rightPanel = new ScrollView(ORIENT_VERTICAL);
@ -144,7 +144,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, new AnchorLayoutParams(10, 0, 10, 0, false));
TabHolder *tabHolder = new TabHolder(ORIENT_VERTICAL, leftColumnWidth, nullptr, new AnchorLayoutParams(10, 0, 10, 0, false));
root_->Add(tabHolder);
ScrollView *rightPanel = new ScrollView(ORIENT_VERTICAL);
tabHolder->AddTab(co->T("Binds"), rightPanel);