diff --git a/Common/Math/geom2d.h b/Common/Math/geom2d.h index 5297faece6..155e34eec7 100644 --- a/Common/Math/geom2d.h +++ b/Common/Math/geom2d.h @@ -54,6 +54,10 @@ struct Bounds { return Bounds(x_ - radius, y_ - radius, radius * 2.0f, radius * 2.0f); } + static Bounds FromCenterWH(float x_, float y_, float w, float h) { + return Bounds(x_ - w * 0.5f, y_ - h * 0.5f, w, h); + } + float GetSize(Orientation o) const { return (o == ORIENT_HORIZONTAL) ? w : h; } @@ -116,6 +120,10 @@ struct Bounds { return Bounds(x + left, y + top, w - left - right, h - top - bottom); } + float AspectRatio() const { + return w / h; + } + float x; float y; float w; diff --git a/Common/UI/PopupScreens.cpp b/Common/UI/PopupScreens.cpp index 218066fca8..c7fbc8e2c0 100644 --- a/Common/UI/PopupScreens.cpp +++ b/Common/UI/PopupScreens.cpp @@ -267,6 +267,13 @@ void PopupContextMenuScreen::CreatePopupContents(UI::ViewGroup *parent) { ap->top = sourceView_->GetBounds().y2(); } +void PopupCallbackScreen::CreatePopupContents(ViewGroup *parent) { + createViews_(parent); + for (int i = 0; i < parent->GetNumSubviews(); i++) { + parent->GetViewByIndex(i)->SetAutoResult(DialogResult::DR_OK); + } +} + std::string ChopTitle(const std::string &title) { size_t pos = title.find('\n'); if (pos != title.npos) { diff --git a/Common/UI/PopupScreens.h b/Common/UI/PopupScreens.h index 6042dea487..bfd3879f7a 100644 --- a/Common/UI/PopupScreens.h +++ b/Common/UI/PopupScreens.h @@ -241,7 +241,6 @@ struct ContextMenuItem { class PopupContextMenuScreen : public PopupScreen { public: PopupContextMenuScreen(const ContextMenuItem *items, size_t itemCount, I18NCat category, UI::View *sourceView); - void CreatePopupContents(ViewGroup *parent) override; const char *tag() const override { return "ContextMenuPopup"; } @@ -252,6 +251,7 @@ public: UI::Event OnChoice; private: + void CreatePopupContents(ViewGroup *parent) override; const ContextMenuItem *items_; size_t itemCount_; I18NCat category_; @@ -259,6 +259,21 @@ private: std::vector enabled_; }; +class PopupCallbackScreen : public PopupScreen { +public: + PopupCallbackScreen(std::function createViews, UI::View *sourceView) : PopupScreen(""), createViews_(createViews) { + if (sourceView) { + SetPopupOrigin(sourceView); + } + } + + const char *tag() const override { return "ContextMenuPopup"; } + +private: + void CreatePopupContents(ViewGroup *parent) override; + std::function createViews_; +}; + // Reads and writes value to determine the current selection. class PopupMultiChoice : public AbstractChoiceWithValueDisplay { public: diff --git a/Common/UI/ScrollView.cpp b/Common/UI/ScrollView.cpp index 30b9c12ff0..4ce5e86c8c 100644 --- a/Common/UI/ScrollView.cpp +++ b/Common/UI/ScrollView.cpp @@ -226,15 +226,19 @@ float ScrollView::ChildSize() const { return std::max(0.01f, views_[0]->GetBounds().GetSize(orientation_) + extraSpace); } +float ScrollView::ScrollMax() const { + const float childHeight = ChildSize(); + return std::max(0.0f, childHeight - bounds_.GetSize(orientation_)); +} + ScrollView::Bob ScrollView::ComputeBob() const { Bob bob{}; if (views_.empty()) { return bob; } const float childHeight = ChildSize(); - float scrollMax = std::max(0.0f, childHeight - bounds_.h); - float ratio = bounds_.h / childHeight; - + const float ratio = bounds_.h / childHeight; + const float scrollMax = ScrollMax(); if (ratio < 1.0f && scrollMax > 0.0f) { bob.show = true; bob.thickness = draggingBob_ ? 15.0f : 6.0f; @@ -260,6 +264,8 @@ void ScrollView::Draw(UIContext &dc) { // If not anchored at the top of the screen exactly, and not scrolled to the top, // draw a subtle drop shadow to indicate scrollability. + + const float darkness = 0.4f; if (bounds_.y > 0.0f && orientation_ == ORIENT_VERTICAL) { float radius = 20.0f; @@ -269,11 +275,33 @@ void ScrollView::Draw(UIContext &dc) { shadowBounds.y -= radius * 2; shadowBounds.h = radius * 2; - float fade = std::clamp(scrollPos_ * 0.1f, 0.0f, 0.6f); + float fade = std::clamp(scrollPos_ * 0.05f, 0.0f, 1.0f) * darkness; dc.DrawRectDropShadow(shadowBounds, radius, fade); } + // Same at the bottom. + float y2 = dc.GetLayoutBounds().y2(); + if (bounds_.y2() < y2 && orientation_ == ORIENT_VERTICAL) { + float radius = 20.0f; + + Bounds shadowBounds = bounds_; + shadowBounds.x -= radius; + shadowBounds.w += radius * 2; + shadowBounds.y = bounds_.y2() - radius * 2; + shadowBounds.h = radius * 2; + + float fade = std::clamp((ScrollMax() - scrollPos_) * 0.05f, 0.0f, 1.0f) * darkness; + + dc.DrawRectDropShadow(shadowBounds, radius, fade); + } + + // If there are any additional views, we treat them as overlays attached at the top - they don't scroll, and they're + // drawn on top of the shadow. TODO: Give them an idea about the scroll position so they can fade in? + for (size_t i = 1; i < views_.size(); ++i) { + views_[i]->Draw(dc); + } + dc.PopScissor(); // Vertical scroll bob. We don't support a horizontal yet. diff --git a/Common/UI/ScrollView.h b/Common/UI/ScrollView.h index 480644f675..f090c9f9d9 100644 --- a/Common/UI/ScrollView.h +++ b/Common/UI/ScrollView.h @@ -48,6 +48,7 @@ public: private: float ChildSize() const; // in the scrolled direction + float ScrollMax() const; float HardClampedScrollPos(float pos) const; // TODO: Don't adjust pull_ within this! diff --git a/Common/UI/TabHolder.cpp b/Common/UI/TabHolder.cpp index 05fabaf4e1..86a30437ab 100644 --- a/Common/UI/TabHolder.cpp +++ b/Common/UI/TabHolder.cpp @@ -8,7 +8,7 @@ namespace UI { -TabHolder::TabHolder(Orientation orientation, float stripSize, TabHolderFlags flags, View *bannerView, LayoutParams *layoutParams) +TabHolder::TabHolder(Orientation orientation, float stripSize, TabHolderFlags flags, View *bannerView, std::function contextMenuCallback, LayoutParams *layoutParams) : LinearLayout(Opposite(orientation), layoutParams), tabOrientation_(orientation), flags_(flags) { SetSpacing(0.0f); if (orientation == ORIENT_HORIZONTAL) { @@ -26,6 +26,13 @@ TabHolder::TabHolder(Orientation orientation, float stripSize, TabHolderFlags fl tabScroll_ = new ScrollView(orientation, new LinearLayoutParams(1.0f)); tabScroll_->Add(tabStrip_); container->Add(tabScroll_); + if (contextMenuCallback) { + Choice *menuChoice = new Choice(ImageID("I_THREE_DOTS"), new LinearLayoutParams(ITEM_HEIGHT, ITEM_HEIGHT)); + menuChoice->OnClick.Add([contextMenuCallback](EventParams &e) { + contextMenuCallback(); + }); + container->Add(menuChoice); + } Add(container); } else { tabScroll_ = new ScrollView(orientation, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)); diff --git a/Common/UI/TabHolder.h b/Common/UI/TabHolder.h index 4c49fa17e6..9c8d791860 100644 --- a/Common/UI/TabHolder.h +++ b/Common/UI/TabHolder.h @@ -21,7 +21,7 @@ ENUM_CLASS_BITOPS(TabHolderFlags); class TabHolder : public LinearLayout { public: - TabHolder(Orientation orientation, float stripSize, TabHolderFlags flags, View *bannerView, LayoutParams *layoutParams); + TabHolder(Orientation orientation, float stripSize, TabHolderFlags flags, View *bannerView, std::function contextMenuCallback, LayoutParams *layoutParams); template T *AddTab(std::string_view title, ImageID imageId, T *tabContents) { diff --git a/Common/UI/View.cpp b/Common/UI/View.cpp index 8e5cb7870e..31a3771bef 100644 --- a/Common/UI/View.cpp +++ b/Common/UI/View.cpp @@ -221,6 +221,9 @@ void Clickable::DrawBG(UIContext &dc, const Style &style) { void Clickable::ClickInternal() { UI::EventParams e{}; e.v = this; + if (hasAutoResult_) { + e.bubbleResult = autoResult_; + } OnClick.Trigger(e); }; diff --git a/Common/UI/View.h b/Common/UI/View.h index 80ba75612d..05b7555ca1 100644 --- a/Common/UI/View.h +++ b/Common/UI/View.h @@ -463,6 +463,10 @@ public: } virtual void Recurse(void (*func)(View *view)) {} + virtual void SetAutoResult(DialogResult result) { + hasAutoResult_ = true; + autoResult_ = result; + } protected: // Inputs to layout @@ -482,6 +486,8 @@ protected: // Whether to use popup colors for styling. bool popupStyle_ = false; + bool hasAutoResult_ = false; + DialogResult autoResult_ = DR_OK; private: std::function enabledFunc_; @@ -893,6 +899,9 @@ public: virtual void Toggle(); virtual bool Toggled() const; + // we don't allow these for checkboxes. + virtual void SetAutoResult(DialogResult result) {} + protected: void ClickInternal() override; @@ -998,7 +1007,6 @@ private: bool big_ = false; }; - class TextView : public InertView { public: TextView(std::string_view text, LayoutParams *layoutParams = 0) diff --git a/Common/UI/ViewGroup.h b/Common/UI/ViewGroup.h index 899f80be93..cda6ca77a9 100644 --- a/Common/UI/ViewGroup.h +++ b/Common/UI/ViewGroup.h @@ -170,6 +170,8 @@ public: : LayoutParams(w, h, LP_LINEAR), weight(wgt), gravity(grav), margins(mgn), hasMargins_(true) {} LinearLayoutParams(Size w, Size h, const Margins &mgn) : LayoutParams(w, h, LP_LINEAR), weight(0.0f), gravity(Gravity::G_TOPLEFT), margins(mgn), hasMargins_(true) {} + LinearLayoutParams(Size w, Size h, Gravity grav, const Margins &mgn) + : LayoutParams(w, h, LP_LINEAR), weight(0.0f), gravity(grav), margins(mgn), hasMargins_(true) {} LinearLayoutParams(Size w, Size h, float wgt, const Margins &mgn) : LayoutParams(w, h, LP_LINEAR), weight(wgt), gravity(Gravity::G_TOPLEFT), margins(mgn), hasMargins_(true) {} LinearLayoutParams(const Margins &mgn) diff --git a/UI/Background.cpp b/UI/Background.cpp index 9bf86a4f7c..4368e9b887 100644 --- a/UI/Background.cpp +++ b/UI/Background.cpp @@ -443,6 +443,12 @@ uint32_t GetBackgroundColorWithAlpha(const UIContext &dc) { return colorAlpha(colorBlend(dc.GetTheme().backgroundColor, 0, 0.5f), 0.65f); // 0.65 = 166 = A6 } +enum class BackgroundFillMode { + Stretch = 0, + CropToScreen = 1, + FitToScreen = 2, +}; + void DrawGameBackground(UIContext &dc, const Path &gamePath, float x, float y, float z) { using namespace Draw; using namespace UI; @@ -454,10 +460,42 @@ void DrawGameBackground(UIContext &dc, const Path &gamePath, float x, float y, f } GameInfoTex *pic = (ginfo && ginfo->Ready(GameInfoFlags::PIC1)) ? ginfo->GetPIC1() : nullptr; - if (pic) { + if (pic && pic->texture) { dc.GetDrawContext()->BindTexture(0, pic->texture); uint32_t color = whiteAlpha(ease((time_now_d() - pic->timeLoaded) * 3)) & 0xFFc0c0c0; - dc.Draw()->DrawTexRect(dc.GetBounds(), 0, 0, 1, 1, color); + + // TODO: Make this configurable? + const BackgroundFillMode mode = BackgroundFillMode::CropToScreen; + + const Bounds screenBounds = dc.GetBounds(); + float imageW = screenBounds.w; + float imageH = screenBounds.h; + float imageAspect = (float)pic->texture->Width() / (float)pic->texture->Height(); + + // Fit the image into the screen bounds according to the fill mode. + if (imageAspect > screenBounds.AspectRatio()) { + // Image is wider than screen. + if (mode == BackgroundFillMode::CropToScreen) { + // Crop width. + imageW = screenBounds.h * imageAspect; + } else if (mode == BackgroundFillMode::FitToScreen) { + // Fit height. + imageH = screenBounds.w / imageAspect; + } + } else { + // Image is taller than screen. + if (mode == BackgroundFillMode::CropToScreen) { + // Crop height. + imageH = screenBounds.w / imageAspect; + } else if (mode == BackgroundFillMode::FitToScreen) { + // Fit width. + imageW = screenBounds.h / imageAspect; + } + } + + Bounds finalImageBounds = Bounds::FromCenterWH(screenBounds.centerX(), screenBounds.centerY(), imageW, imageH); + + dc.Draw()->DrawTexRect(finalImageBounds, 0, 0, 1, 1, color); dc.Flush(); dc.RebindTexture(); } else { diff --git a/UI/ControlMappingScreen.cpp b/UI/ControlMappingScreen.cpp index 83b2b9fcf7..eac5ed1143 100644 --- a/UI/ControlMappingScreen.cpp +++ b/UI/ControlMappingScreen.cpp @@ -232,7 +232,7 @@ static const BindingCategory cats[] = { {}, // sentinel }; -void ControlMappingScreen::CreateExtraButtons(UI::LinearLayout *verticalLayout, int margins) { +void ControlMappingScreen::CreateExtraButtons(UI::ViewGroup *verticalLayout, int margins) { using namespace UI; auto km = GetI18NCategory(I18NCat::KEYMAPPING); verticalLayout->Add(new Choice(km->T("Clear All")))->OnClick.Add([](UI::EventParams &) { @@ -480,7 +480,7 @@ void KeyMappingNewMouseKeyDialog::axis(const AxisInput &axis) { } } -AnalogCalibrationScreen::AnalogCalibrationScreen(const Path &gamePath) : UITwoPaneBaseDialogScreen(gamePath) { +AnalogCalibrationScreen::AnalogCalibrationScreen(const Path &gamePath) : UITwoPaneBaseDialogScreen(gamePath, TwoPaneFlags::Default) { mapper_.SetCallbacks( [](int vkey, bool down) {}, [](int vkey, float analogValue) {}, @@ -532,10 +532,10 @@ void AnalogCalibrationScreen::axis(const AxisInput &axis) { std::string_view AnalogCalibrationScreen::GetTitle() const { auto co = GetI18NCategory(I18NCat::CONTROLS); - return co->T("Analog Settings"); + return co->T("Calibrate analog stick"); } -void AnalogCalibrationScreen::CreateSettingsViews(UI::LinearLayout *scrollContents) { +void AnalogCalibrationScreen::CreateSettingsViews(UI::ViewGroup *scrollContents) { using namespace UI; auto co = GetI18NCategory(I18NCat::CONTROLS); @@ -551,7 +551,7 @@ void AnalogCalibrationScreen::CreateSettingsViews(UI::LinearLayout *scrollConten scrollContents->Add(new Choice(co->T("Reset to defaults")))->OnClick.Handle(this, &AnalogCalibrationScreen::OnResetToDefaults); } -void AnalogCalibrationScreen::CreateContentViews(UI::LinearLayout *parent) { +void AnalogCalibrationScreen::CreateContentViews(UI::ViewGroup *parent) { using namespace UI; auto co = GetI18NCategory(I18NCat::CONTROLS); diff --git a/UI/ControlMappingScreen.h b/UI/ControlMappingScreen.h index 43bb825085..4741fac0dc 100644 --- a/UI/ControlMappingScreen.h +++ b/UI/ControlMappingScreen.h @@ -39,7 +39,7 @@ class SingleControlMapper; class ControlMappingScreen : public UITabbedBaseDialogScreen { public: - ControlMappingScreen(const Path &gamePath) : UITabbedBaseDialogScreen(gamePath) { + ControlMappingScreen(const Path &gamePath) : UITabbedBaseDialogScreen(gamePath, TabDialogFlags::ContextMenuInPortrait) { categoryToggles_[0] = true; categoryToggles_[1] = true; categoryToggles_[2] = true; @@ -49,7 +49,7 @@ public: protected: void CreateTabs() override; - void CreateExtraButtons(UI::LinearLayout *verticalLayout, int margins) override; + void CreateExtraButtons(UI::ViewGroup *verticalLayout, int margins) override; void update() override; private: @@ -137,8 +137,8 @@ public: const char *tag() const override { return "AnalogSetup"; } protected: - void CreateSettingsViews(UI::LinearLayout *parent) override; - void CreateContentViews(UI::LinearLayout *parent) override; + void CreateSettingsViews(UI::ViewGroup *parent) override; + void CreateContentViews(UI::ViewGroup *parent) override; std::string_view GetTitle() const override; private: diff --git a/UI/GPUDriverTestScreen.cpp b/UI/GPUDriverTestScreen.cpp index 29ebd09cf2..b000505964 100644 --- a/UI/GPUDriverTestScreen.cpp +++ b/UI/GPUDriverTestScreen.cpp @@ -301,7 +301,7 @@ void GPUDriverTestScreen::CreateViews() { AnchorLayout *anchor = new AnchorLayout(); root_ = anchor; - tabHolder_ = new TabHolder(ORIENT_HORIZONTAL, 30.0f, TabHolderFlags::Default, nullptr, new AnchorLayoutParams(FILL_PARENT, FILL_PARENT, Centering::None)); + tabHolder_ = new TabHolder(ORIENT_HORIZONTAL, 30.0f, TabHolderFlags::Default, nullptr, nullptr, new AnchorLayoutParams(FILL_PARENT, FILL_PARENT, Centering::None)); anchor->Add(tabHolder_); tabHolder_->AddTab("Discard", ImageID::invalid(), new LinearLayout(ORIENT_VERTICAL)); tabHolder_->AddTab("Shader", ImageID::invalid(), new LinearLayout(ORIENT_VERTICAL)); diff --git a/UI/GameScreen.cpp b/UI/GameScreen.cpp index 1f7624b313..0d66ed988d 100644 --- a/UI/GameScreen.cpp +++ b/UI/GameScreen.cpp @@ -56,13 +56,11 @@ constexpr GameInfoFlags g_desiredFlags = GameInfoFlags::PARAM_SFO | GameInfoFlags::ICON | GameInfoFlags::PIC0 | GameInfoFlags::PIC1 | GameInfoFlags::UNCOMPRESSED_SIZE | GameInfoFlags::SIZE; -GameScreen::GameScreen(const Path &gamePath, bool inGame) : UIBaseDialogScreen(gamePath), inGame_(inGame) { +GameScreen::GameScreen(const Path &gamePath, bool inGame) : UITwoPaneBaseDialogScreen(gamePath, TwoPaneFlags::SettingsToTheRight | TwoPaneFlags::SettingsInContextMenu), inGame_(inGame) { g_BackgroundAudio.SetGame(gamePath); System_PostUIMessage(UIMessage::GAME_SELECTED, gamePath.ToString()); - // Start fetching info, we'll display it as it arrives. We keep the known fetch status in knownFlags_, - // and recreate views whenever info->hasFlags != knownFlags_. - g_gameInfoCache->GetInfo(NULL, gamePath_, g_desiredFlags, &knownFlags_); + info_ = g_gameInfoCache->GetInfo(NULL, gamePath_, g_desiredFlags, &knownFlags_); } GameScreen::~GameScreen() { @@ -109,14 +107,26 @@ void GameScreen::update() { } } -void GameScreen::CreateViews() { - GameInfoFlags hasFlags; - std::shared_ptr info = g_gameInfoCache->GetInfo(NULL, gamePath_, g_desiredFlags, &hasFlags); - if (!info) { +static bool FileTypeSupportsCRC(IdentifiedFileType fileType) { + switch (fileType) { + case IdentifiedFileType::PSP_PBP: + case IdentifiedFileType::PSP_PBP_DIRECTORY: + case IdentifiedFileType::PSP_ISO_NP: + case IdentifiedFileType::PSP_ISO: + return true; + default: + return false; + } +} + +void GameScreen::CreateContentViews(UI::ViewGroup *parent) { + if (!info_) { // Shouldn't happen return; } + const bool portrait = GetDeviceOrientation() == DeviceOrientation::Portrait; + auto di = GetI18NCategory(I18NCat::DIALOG); auto ga = GetI18NCategory(I18NCat::GAME); @@ -127,74 +137,76 @@ void GameScreen::CreateViews() { Margins actionMenuMargins(0, 15, 15, 0); - root_ = new LinearLayout(ORIENT_HORIZONTAL); + ScrollView *leftScroll = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(1.0f, Margins(8))); - ViewGroup *leftColumn = new AnchorLayout(new LinearLayoutParams(1.0f)); - root_->Add(leftColumn); + ViewGroup *leftColumn = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)); - bool fileTypeSupportCRC = false; - if (info) { - switch (info->fileType) { - case IdentifiedFileType::PSP_PBP: - case IdentifiedFileType::PSP_PBP_DIRECTORY: - case IdentifiedFileType::PSP_ISO_NP: - case IdentifiedFileType::PSP_ISO: - fileTypeSupportCRC = true; - break; + leftScroll->Add(leftColumn); - default: - break; - } - } + parent->Add(leftScroll); - AddStandardBack(leftColumn); - - ViewGroup *badgeHolder = new LinearLayout(ORIENT_HORIZONTAL, new AnchorLayoutParams(10, 10, 110, NONE)); - LinearLayout *mainGameInfo = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(1.0f)); - mainGameInfo->SetSpacing(3.0f); + const bool fileTypeSupportCRC = FileTypeSupportsCRC(info_->fileType); // Need an explicit size here because homebrew uses screenshots as icons. - badgeHolder->Add(new GameIconView(gamePath_, 2.0f, new LinearLayoutParams(144 * 2, 80 * 2, UI::Margins(0)))); - badgeHolder->Add(mainGameInfo); - leftColumn->Add(badgeHolder); + LinearLayout *mainGameInfo; + if (portrait) { + mainGameInfo = new LinearLayout(ORIENT_VERTICAL); + leftColumn->Add(new Spacer(8.0f)); + leftColumn->Add(new GameIconView(gamePath_, 2.0f, new LinearLayoutParams(UI::Margins(0)))); + leftColumn->Add(mainGameInfo); + } else { + mainGameInfo = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(1.0f)); + ViewGroup *badgeHolder = new LinearLayout(ORIENT_HORIZONTAL); + badgeHolder->Add(new GameIconView(gamePath_, 2.0f, new LinearLayoutParams(144 * 2, 80 * 2, UI::Margins(0)))); + badgeHolder->Add(mainGameInfo); + leftColumn->Add(badgeHolder); + } + mainGameInfo->SetSpacing(3.0f); + + GameDBInfo dbInfo; + std::vector dbInfos; + const bool inGameDB = g_gameDB.GetGameInfos(info_->id_version, &dbInfos); + + if (knownFlags_ & GameInfoFlags::PARAM_SFO) { + // Show the game ID title below the icon. The top title will be from the DB. + std::string title = info_->GetTitle(); + + TextView *tvTitle = mainGameInfo->Add(new TextView(title, ALIGN_LEFT | FLAG_WRAP_TEXT, false, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT))); + tvTitle->SetShadow(true); + + std::string regionID = ReplaceAll(info_->id_version, "_", " v"); + regionID += ": "; + + if (info_->region != GameRegion::UNKNOWN) { + regionID += GameRegionToString(info_->region); + } else { + regionID += IdentifiedFileTypeToString(info_->fileType); + } + + TextView *tvID = mainGameInfo->Add(new TextView(regionID, ALIGN_LEFT | FLAG_WRAP_TEXT, true, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT))); + tvID->SetShadow(true); + } LinearLayout *infoLayout = new LinearLayout(ORIENT_VERTICAL, new AnchorLayoutParams(10, 200, NONE, NONE)); leftColumn->Add(infoLayout); - if (hasFlags & GameInfoFlags::PARAM_SFO) { - // TODO: Add non-translated title here if available in gameDB. - TextView *tvTitle = mainGameInfo->Add(new TextView(info->GetTitle(), ALIGN_LEFT | FLAG_WRAP_TEXT, false, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT))); - tvTitle->SetShadow(true); - - TextView *tvID = mainGameInfo->Add(new TextView(ReplaceAll(info->id_version, "_", " v"), ALIGN_LEFT | FLAG_WRAP_TEXT, true, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT))); - tvID->SetShadow(true); - - if (info->region != GameRegion::UNKNOWN) { - TextView *tvRegion = mainGameInfo->Add(new TextView(ga->T(GameRegionToString(info->region)), ALIGN_LEFT, true, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT))); - tvRegion->SetShadow(true); - } else { - TextView *tvFileType = mainGameInfo->Add(new TextView(ga->T(IdentifiedFileTypeToString(info->fileType)), ALIGN_LEFT, true, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT))); - tvFileType->SetShadow(true); - } - } - - if ((hasFlags & GameInfoFlags::UNCOMPRESSED_SIZE) && (hasFlags & GameInfoFlags::SIZE)) { + if ((knownFlags_ & GameInfoFlags::UNCOMPRESSED_SIZE) && (knownFlags_ & GameInfoFlags::SIZE)) { char temp[256]; - snprintf(temp, sizeof(temp), "%s: %s", ga->T_cstr("Game"), NiceSizeFormat(info->gameSizeOnDisk).c_str()); - if (info->gameSizeUncompressed != info->gameSizeOnDisk) { + snprintf(temp, sizeof(temp), "%s: %s", ga->T_cstr("Game"), NiceSizeFormat(info_->gameSizeOnDisk).c_str()); + if (info_->gameSizeUncompressed != info_->gameSizeOnDisk) { size_t len = strlen(temp); - snprintf(temp + len, sizeof(temp) - len, " (%s: %s)", ga->T_cstr("Uncompressed"), NiceSizeFormat(info->gameSizeUncompressed).c_str()); + snprintf(temp + len, sizeof(temp) - len, " (%s: %s)", ga->T_cstr("Uncompressed"), NiceSizeFormat(info_->gameSizeUncompressed).c_str()); } TextView *tvGameSize = mainGameInfo->Add(new TextView(temp, ALIGN_LEFT, true, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT))); tvGameSize->SetShadow(true); - - if (info->saveDataSize > 0) { - snprintf(temp, sizeof(temp), "%s: %s", ga->T_cstr("SaveData"), NiceSizeFormat(info->saveDataSize).c_str()); + + if (info_->saveDataSize > 0) { + snprintf(temp, sizeof(temp), "%s: %s", ga->T_cstr("SaveData"), NiceSizeFormat(info_->saveDataSize).c_str()); TextView *tvSaveDataSize = infoLayout->Add(new TextView(temp, ALIGN_LEFT, true, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT))); tvSaveDataSize->SetShadow(true); } - if (info->installDataSize > 0) { - snprintf(temp, sizeof(temp), "%s: %1.2f %s", ga->T_cstr("InstallData"), (float)(info->installDataSize) / 1024.f / 1024.f, ga->T_cstr("MB")); + if (info_->installDataSize > 0) { + snprintf(temp, sizeof(temp), "%s: %1.2f %s", ga->T_cstr("InstallData"), (float)(info_->installDataSize) / 1024.f / 1024.f, ga->T_cstr("MB")); TextView *tvInstallDataSize = infoLayout->Add(new TextView(temp, ALIGN_LEFT, true, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT))); tvInstallDataSize->SetShadow(true); } @@ -203,7 +215,7 @@ void GameScreen::CreateViews() { infoLayout->Add(new TextView(gamePath_.ToVisualString(), ALIGN_LEFT | FLAG_WRAP_TEXT, true, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)))->SetShadow(true); std::string str; - if (g_Config.TimeTracker().GetPlayedTimeString(info->id, &str)) { + if (g_Config.TimeTracker().GetPlayedTimeString(info_->id, &str)) { TextView *tvPlayTime = infoLayout->Add(new TextView(str, ALIGN_LEFT, true, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT))); tvPlayTime->SetShadow(true); tvPlayTime->SetText(str); @@ -222,7 +234,7 @@ void GameScreen::CreateViews() { tvCRC->SetShadow(true); if (System_GetPropertyBool(SYSPROP_HAS_TEXT_CLIPBOARD)) { - Button *tvCRCCopy = crcHoriz->Add(new Button(di->T("Copy to clipboard"), new LinearLayoutParams(0.0, Gravity::G_VCENTER))); + Choice *tvCRCCopy = crcHoriz->Add(new Choice(ImageID("I_FILE_COPY"), new LinearLayoutParams(0.0, Gravity::G_VCENTER))); tvCRCCopy->OnClick.Add([this](UI::EventParams &) { u32 crc = Reporting::RetrieveCRC(gamePath_); char buffer[16]; @@ -235,9 +247,9 @@ void GameScreen::CreateViews() { // Let's check the CRC in the game database, looking up the ID and also matching the crc. std::vector dbInfos; - if ((hasFlags & GameInfoFlags::PARAM_SFO) && g_gameDB.GetGameInfos(info->id_version, &dbInfos)) { + if ((knownFlags_ & GameInfoFlags::PARAM_SFO) && g_gameDB.GetGameInfos(info_->id_version, &dbInfos)) { bool found = false; - for (auto &dbInfo : dbInfos) { + for (const auto &dbInfo : dbInfos) { if (dbInfo.crc == crcVal) { found = true; } @@ -248,17 +260,15 @@ void GameScreen::CreateViews() { } } } else if (!isHomebrew_) { - GameDBInfo dbInfo; - std::vector dbInfos; - if ((hasFlags & GameInfoFlags::PARAM_SFO) && !g_gameDB.GetGameInfos(info->id_version, &dbInfos)) { + if ((knownFlags_ & GameInfoFlags::PARAM_SFO) && !inGameDB) { // tvVerified_->SetText(ga->T("Game ID unknown - not in the ReDump database")); // tvVerified_->SetVisibility(UI::V_VISIBLE); // tvVerified_->SetLevel(NoticeLevel::WARN); - } else if ((hasFlags & GameInfoFlags::UNCOMPRESSED_SIZE) && info->gameSizeUncompressed != 0) { // don't do this check if info still pending + } else if ((knownFlags_ & GameInfoFlags::UNCOMPRESSED_SIZE) && info_->gameSizeUncompressed != 0) { // don't do this check if info_ still pending bool found = false; for (auto &dbInfo : dbInfos) { // TODO: Doesn't take CSO/CHD into account. - if (info->gameSizeUncompressed == dbInfo.size) { + if (info_->gameSizeUncompressed == dbInfo.size) { found = true; } } @@ -266,7 +276,7 @@ void GameScreen::CreateViews() { // tvVerified_->SetText(ga->T("File size incorrect, bad or modified ISO")); // tvVerified_->SetVisibility(UI::V_VISIBLE); // tvVerified_->SetLevel(NoticeLevel::ERROR); - // INFO_LOG(Log::Loader, "File size %d not matching game DB", (int)info->gameSizeUncompressed); + // INFO_LOG(Log::Loader, "File size %d not matching game DB", (int)info_->gameSizeUncompressed); } NoticeView *tvVerified = infoLayout->Add(new NoticeView(NoticeLevel::INFO, ga->T("Click \"Calculate CRC\" to verify ISO"), "", new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT))); @@ -280,8 +290,8 @@ void GameScreen::CreateViews() { tvVerified->SetVisibility(UI::V_GONE); tvVerified->SetSquishy(true); - // Show plugin info, if any. Later might add checkboxes. - auto plugins = HLEPlugins::FindPlugins(info->id, g_Config.sLanguageIni); + // Show plugin info_, if any. Later might add checkboxes. + auto plugins = HLEPlugins::FindPlugins(info_->id, g_Config.sLanguageIni); if (!plugins.empty()) { auto sy = GetI18NCategory(I18NCat::SYSTEM); infoLayout->Add(new TextView(sy->T("Plugins"), ALIGN_LEFT, true)); @@ -290,24 +300,37 @@ void GameScreen::CreateViews() { } } - ViewGroup *rightColumn = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(300, FILL_PARENT, actionMenuMargins)); - root_->Add(rightColumn); + if (portrait) { + parent->Add(new Choice(ga->T("Play"), ImageID("I_PLAY")))->OnClick.Handle(this, &GameScreen::OnPlay); + } +} + +void GameScreen::CreateSettingsViews(UI::ViewGroup *rightColumn) { + using namespace UI; + + auto di = GetI18NCategory(I18NCat::DIALOG); + auto ga = GetI18NCategory(I18NCat::GAME); + + const bool fileTypeSupportCRC = FileTypeSupportsCRC(info_->fileType); + const bool portrait = GetDeviceOrientation() == DeviceOrientation::Portrait; LinearLayout *rightColumnItems = new LinearLayout(ORIENT_VERTICAL); rightColumnItems->SetSpacing(0.0f); rightColumn->Add(rightColumnItems); - if (!inGame_) { - rightColumnItems->Add(new Choice(ga->T("Play")))->OnClick.Handle(this, &GameScreen::OnPlay); + if (!inGame_ && !portrait) { + rightColumnItems->Add(new Choice(ga->T("Play"), ImageID("I_PLAY")))->OnClick.Handle(this, &GameScreen::OnPlay); } - if (!info->id.empty()) { - Choice *btnGameSettings = rightColumnItems->Add(new Choice(ga->T("Game Settings"))); - btnGameSettings->OnClick.Handle(this, &GameScreen::OnGameSettings); - if (inGame_) - btnGameSettings->SetEnabled(false); + if (!info_->id.empty()) { + if (info_->hasConfig) { + // Only show the Game Settings button here if the game has a config. Always showing it + // is confusing since it'll just control the global settings. + Choice *btnGameSettings = rightColumnItems->Add(new Choice(ga->T("Game Settings"), ImageID("I_GEAR"))); + btnGameSettings->OnClick.Handle(this, &GameScreen::OnGameSettings); + if (inGame_) + btnGameSettings->SetEnabled(false); - if (info->hasConfig) { Choice *btnDeleteGameConfig = rightColumnItems->Add(new Choice(ga->T("Delete Game Config"))); btnDeleteGameConfig->OnClick.Handle(this, &GameScreen::OnDeleteConfig); if (inGame_) @@ -320,7 +343,7 @@ void GameScreen::CreateViews() { } } - if (info->saveDataSize) { + if (info_->saveDataSize) { Choice *btnDeleteSaveData = new Choice(ga->T("Delete Save Data")); rightColumnItems->Add(btnDeleteSaveData)->OnClick.Handle(this, &GameScreen::OnDeleteSaveData); } @@ -332,12 +355,12 @@ void GameScreen::CreateViews() { deleteChoice->SetEnabled(false); } - if ((hasFlags & GameInfoFlags::PARAM_SFO) && System_GetPropertyBool(SYSPROP_CAN_CREATE_SHORTCUT)) { + if ((knownFlags_ & GameInfoFlags::PARAM_SFO) && System_GetPropertyBool(SYSPROP_CAN_CREATE_SHORTCUT)) { rightColumnItems->Add(new Choice(ga->T("Create Shortcut")))->OnClick.Add([this](UI::EventParams &e) { GameInfoFlags hasFlags; - std::shared_ptr info = g_gameInfoCache->GetInfo(NULL, gamePath_, GameInfoFlags::PARAM_SFO, &hasFlags); + std::shared_ptr info_ = g_gameInfoCache->GetInfo(NULL, gamePath_, GameInfoFlags::PARAM_SFO, &hasFlags); if (hasFlags & GameInfoFlags::PARAM_SFO) { - System_CreateGameShortcut(gamePath_, info->GetTitle()); + System_CreateGameShortcut(gamePath_, info_->GetTitle()); g_OSD.Show(OSDType::MESSAGE_SUCCESS, GetI18NCategory(I18NCat::DIALOG)->T("Desktop shortcut created"), 2.0f); } }); @@ -353,7 +376,7 @@ void GameScreen::CreateViews() { } if (System_GetPropertyBool(SYSPROP_CAN_SHOW_FILE)) { - rightColumnItems->Add(new Choice(di->T("Show in folder")))->OnClick.Add([this](UI::EventParams &e) { + rightColumnItems->Add(new Choice(di->T("Show in folder"), ImageID("I_FOLDER")))->OnClick.Add([this](UI::EventParams &e) { System_ShowFileInFolder(gamePath_); }); } @@ -363,12 +386,12 @@ void GameScreen::CreateViews() { rightColumnItems->Add(new Choice(pa->T("Cheats")))->OnClick.Handle(this, &GameScreen::OnCwCheat); } - if (info->pic1.texture) { + if (info_->pic1.texture) { Choice *btnSetBackground = rightColumnItems->Add(new Choice(ga->T("Use UI background"))); btnSetBackground->OnClick.Handle(this, &GameScreen::OnSetBackground); } - isHomebrew_ = info && info->region == GameRegion::HOMEBREW; + isHomebrew_ = info_ && info_->region == GameRegion::HOMEBREW; if (fileTypeSupportCRC && !isHomebrew_ && !Reporting::HasCRC(gamePath_) ) { Choice *btnCalcCRC = rightColumnItems->Add(new ChoiceWithValueDisplay(&CRC32string, ga->T("Calculate CRC"), I18NCat::NONE)); btnCalcCRC->OnClick.Add([this](UI::EventParams &) { @@ -379,17 +402,30 @@ void GameScreen::CreateViews() { } void GameScreen::OnCreateConfig(UI::EventParams &e) { - std::shared_ptr info = g_gameInfoCache->GetInfo(nullptr, gamePath_, GameInfoFlags::PARAM_SFO); - if (!info->Ready(GameInfoFlags::PARAM_SFO)) { + if (!info_->Ready(GameInfoFlags::PARAM_SFO)) { return; } - g_Config.CreateGameConfig(info->id); - g_Config.SaveGameConfig(info->id, info->GetTitle()); - info->hasConfig = true; + g_Config.CreateGameConfig(info_->id); + g_Config.SaveGameConfig(info_->id, info_->GetTitle()); + info_->hasConfig = true; screenManager()->topScreen()->RecreateViews(); } +std::string_view GameScreen::GetTitle() const { + if (knownFlags_ & GameInfoFlags::PARAM_SFO) { + std::vector dbInfos; + const bool inGameDB = g_gameDB.GetGameInfos(info_->id_version, &dbInfos); + if (inGameDB) { + titleCache_ = dbInfos[0].title; + } else { + titleCache_ = std::string(info_->GetTitle()); + } + } + + return titleCache_; +} + void GameScreen::OnDeleteConfig(UI::EventParams &e) { auto di = GetI18NCategory(I18NCat::DIALOG); const bool trashAvailable = System_GetPropertyBool(SYSPROP_HAS_TRASH_BIN); @@ -466,20 +502,20 @@ void GameScreen::OnPlay(UI::EventParams &e) { } void GameScreen::OnGameSettings(UI::EventParams &e) { - std::shared_ptr info = g_gameInfoCache->GetInfo(NULL, gamePath_, GameInfoFlags::PARAM_SFO); - if (info && info->Ready(GameInfoFlags::PARAM_SFO)) { - std::string discID = info->GetParamSFO().GetValueString("DISC_ID"); - if ((discID.empty() || !info->disc_total) && gamePath_.FilePathContainsNoCase("PSP/GAME/")) + std::shared_ptr info_ = g_gameInfoCache->GetInfo(NULL, gamePath_, GameInfoFlags::PARAM_SFO); + if (info_ && info_->Ready(GameInfoFlags::PARAM_SFO)) { + std::string discID = info_->GetParamSFO().GetValueString("DISC_ID"); + if ((discID.empty() || !info_->disc_total) && gamePath_.FilePathContainsNoCase("PSP/GAME/")) discID = g_paramSFO.GenerateFakeID(gamePath_); screenManager()->push(new GameSettingsScreen(gamePath_, discID, true)); } } void GameScreen::OnDeleteSaveData(UI::EventParams &e) { - std::shared_ptr info = g_gameInfoCache->GetInfo(NULL, gamePath_, GameInfoFlags::PARAM_SFO | GameInfoFlags::SIZE); - if (info) { + std::shared_ptr info_ = g_gameInfoCache->GetInfo(NULL, gamePath_, GameInfoFlags::PARAM_SFO | GameInfoFlags::SIZE); + if (info_) { // Check that there's any savedata to delete - if (info->saveDataSize) { + if (info_->saveDataSize) { const bool trashAvailable = System_GetPropertyBool(SYSPROP_HAS_TRASH_BIN); auto di = GetI18NCategory(I18NCat::DIALOG); Path gamePath = gamePath_; @@ -487,10 +523,10 @@ void GameScreen::OnDeleteSaveData(UI::EventParams &e) { new UI::MessagePopupScreen(di->T("Delete"), di->T("DeleteConfirmAll", "Do you really want to delete all\nyour save data for this game?"), trashAvailable ? di->T("Move to trash") : di->T("Delete"), di->T("Cancel"), [gamePath](bool yes) { if (yes) { - std::shared_ptr info = g_gameInfoCache->GetInfo(NULL, gamePath, GameInfoFlags::PARAM_SFO); - info->DeleteAllSaveData(); - info->saveDataSize = 0; - info->installDataSize = 0; + std::shared_ptr info_ = g_gameInfoCache->GetInfo(NULL, gamePath, GameInfoFlags::PARAM_SFO); + info_->DeleteAllSaveData(); + info_->saveDataSize = 0; + info_->installDataSize = 0; } })); } @@ -499,8 +535,8 @@ void GameScreen::OnDeleteSaveData(UI::EventParams &e) { } void GameScreen::OnDeleteGame(UI::EventParams &e) { - std::shared_ptr info = g_gameInfoCache->GetInfo(NULL, gamePath_, GameInfoFlags::PARAM_SFO); - if (info->Ready(GameInfoFlags::PARAM_SFO)) { + std::shared_ptr info_ = g_gameInfoCache->GetInfo(NULL, gamePath_, GameInfoFlags::PARAM_SFO); + if (info_->Ready(GameInfoFlags::PARAM_SFO)) { auto di = GetI18NCategory(I18NCat::DIALOG); auto ga = GetI18NCategory(I18NCat::GAME); std::string prompt; @@ -513,8 +549,8 @@ void GameScreen::OnDeleteGame(UI::EventParams &e) { new UI::MessagePopupScreen(ga->T("Delete Game"), prompt, trashAvailable ? di->T("Move to trash") : di->T("Delete"), di->T("Cancel"), [sm, gamePath](bool yes) { if (yes) { - std::shared_ptr info = g_gameInfoCache->GetInfo(NULL, gamePath, GameInfoFlags::PARAM_SFO); - info->Delete(); + std::shared_ptr info_ = g_gameInfoCache->GetInfo(NULL, gamePath, GameInfoFlags::PARAM_SFO); + info_->Delete(); g_gameInfoCache->Clear(); g_recentFiles.Remove(gamePath.c_str()); sm->switchScreen(new MainScreen()); diff --git a/UI/GameScreen.h b/UI/GameScreen.h index c308171e9c..378e8ab322 100644 --- a/UI/GameScreen.h +++ b/UI/GameScreen.h @@ -22,8 +22,9 @@ #include "UI/BaseScreens.h" #include "Common/UI/UIScreen.h" #include "Common/File/Path.h" - #include "UI/GameInfoCache.h" +#include "UI/SimpleDialogScreen.h" + class NoticeView; @@ -33,7 +34,7 @@ class NoticeView; // Uses GameInfoCache heavily to implement the functionality. // Should possibly merge this with the PauseScreen. -class GameScreen : public UIBaseDialogScreen { +class GameScreen : public UITwoPaneBaseDialogScreen { public: GameScreen(const Path &gamePath, bool inGame); ~GameScreen(); @@ -45,7 +46,10 @@ public: const char *tag() const override { return "Game"; } protected: - void CreateViews() override; + void CreateContentViews(UI::ViewGroup *parent) override; + void CreateSettingsViews(UI::ViewGroup *parent) override; + + std::string_view GetTitle() const override; private: // Event handlers @@ -69,4 +73,7 @@ private: GameInfoFlags knownFlags_ = GameInfoFlags::EMPTY; bool knownHasCRC_ = false; + + std::shared_ptr info_; + mutable std::string titleCache_; }; diff --git a/UI/MainScreen.cpp b/UI/MainScreen.cpp index 91b5eb7a13..4d0ad36001 100644 --- a/UI/MainScreen.cpp +++ b/UI/MainScreen.cpp @@ -515,8 +515,8 @@ void DirButton::Draw(UIContext &dc) { } } -GameBrowser::GameBrowser(int token, const Path &path, BrowseFlags browseFlags, bool *gridStyle, ScreenManager *screenManager, std::string_view lastText, std::string_view lastLink, UI::LayoutParams *layoutParams) - : LinearLayout(ORIENT_VERTICAL, layoutParams), gridStyle_(gridStyle), browseFlags_(browseFlags), lastText_(lastText), lastLink_(lastLink), screenManager_(screenManager), token_(token) { +GameBrowser::GameBrowser(int token, const Path &path, BrowseFlags browseFlags, bool portrait, bool *gridStyle, ScreenManager *screenManager, std::string_view lastText, std::string_view lastLink, UI::LayoutParams *layoutParams) + : LinearLayout(ORIENT_VERTICAL, layoutParams), gridStyle_(gridStyle), browseFlags_(browseFlags), portrait_(portrait), lastText_(lastText), lastLink_(lastLink), screenManager_(screenManager), token_(token) { using namespace UI; path_.SetUserAgent(StringFromFormat("PPSSPP/%s", PPSSPP_GIT_VERSION)); Path memstickRoot = GetSysDirectory(DIRECTORY_MEMSTICK_ROOT); @@ -796,7 +796,7 @@ void GameBrowser::Refresh() { #else if ((browseFlags_ & BrowseFlags::BROWSE) && System_GetPropertyBool(SYSPROP_HAS_FOLDER_BROWSER)) { // Collapse the button title on very small screens (Retroid Pocket) or portrait mode. - std::string_view browseTitle = g_display.pixel_xres <= 550 ? "" : mm->T("Browse"); + std::string_view browseTitle = (g_display.dp_xres <= 550 || portrait_) ? "" : mm->T("Browse"); topBar->Add(new Choice(browseTitle, ImageID("I_FOLDER_OPEN"), new LayoutParams(WRAP_CONTENT, 64.0f)))->OnClick.Handle(this, &GameBrowser::BrowseClick); } if (System_GetPropertyInt(SYSPROP_DEVICE_TYPE) == DEVICE_TYPE_TV) { @@ -1092,8 +1092,10 @@ void MainScreen::CreateRecentTab() { ScrollView *scrollRecentGames = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)); scrollRecentGames->SetTag("MainScreenRecentGames"); + bool portrait = GetDeviceOrientation() == DeviceOrientation::Portrait; + GameBrowser *tabRecentGames = new GameBrowser(GetRequesterToken(), - Path("!RECENT"), BrowseFlags::NONE, &g_Config.bGridView1, screenManager(), "", "", + Path("!RECENT"), BrowseFlags::NONE, portrait, &g_Config.bGridView1, screenManager(), "", "", new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)); scrollRecentGames->Add(tabRecentGames); @@ -1109,10 +1111,12 @@ GameBrowser *MainScreen::CreateBrowserTab(const Path &path, std::string_view tit using namespace UI; auto mm = GetI18NCategory(I18NCat::MAINMENU); + const bool portrait = GetDeviceOrientation() == DeviceOrientation::Portrait; + ScrollView *scrollView = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)); scrollView->SetTag(title); // Re-use title as tag, should be fine. - GameBrowser *gameBrowser = new GameBrowser(GetRequesterToken(), path, browseFlags, bGridView, screenManager(), + GameBrowser *gameBrowser = new GameBrowser(GetRequesterToken(), path, browseFlags, portrait, bGridView, screenManager(), mm->T(howToTitle), howToUri, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)); @@ -1220,7 +1224,7 @@ void MainScreen::CreateViews() { auto mm = GetI18NCategory(I18NCat::MAINMENU); - tabHolder_ = new TabHolder(ORIENT_HORIZONTAL, 64, TabHolderFlags::Default, nullptr, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT, 1.0f)); + tabHolder_ = new TabHolder(ORIENT_HORIZONTAL, 64, TabHolderFlags::Default, nullptr, nullptr, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT, 1.0f)); ViewGroup *leftColumn = tabHolder_; tabHolder_->SetTag("MainScreenGames"); gameBrowsers_.clear(); @@ -1604,7 +1608,9 @@ void UmdReplaceScreen::CreateViews() { auto mm = GetI18NCategory(I18NCat::MAINMENU); auto di = GetI18NCategory(I18NCat::DIALOG); - TabHolder *leftColumn = new TabHolder(ORIENT_HORIZONTAL, 64, TabHolderFlags::Default, nullptr, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT, 1.0)); + const bool portrait = GetDeviceOrientation() == DeviceOrientation::Portrait; + + TabHolder *leftColumn = new TabHolder(ORIENT_HORIZONTAL, 64, TabHolderFlags::Default, nullptr, nullptr, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT, 1.0)); leftColumn->SetTag("UmdReplace"); leftColumn->SetClip(true); @@ -1617,7 +1623,7 @@ void UmdReplaceScreen::CreateViews() { ScrollView *scrollRecentGames = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)); scrollRecentGames->SetTag("UmdReplaceRecentGames"); GameBrowser *tabRecentGames = new GameBrowser(GetRequesterToken(), - Path("!RECENT"), BrowseFlags::NONE, &g_Config.bGridView1, screenManager(), "", "", + Path("!RECENT"), BrowseFlags::NONE, portrait, &g_Config.bGridView1, screenManager(), "", "", new LinearLayoutParams(FILL_PARENT, FILL_PARENT)); scrollRecentGames->Add(tabRecentGames); leftColumn->AddTab(mm->T("Recent"), ImageID::invalid(), scrollRecentGames); @@ -1627,7 +1633,7 @@ void UmdReplaceScreen::CreateViews() { ScrollView *scrollAllGames = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)); scrollAllGames->SetTag("UmdReplaceAllGames"); - GameBrowser *tabAllGames = new GameBrowser(GetRequesterToken(), Path(g_Config.currentDirectory), BrowseFlags::STANDARD, &g_Config.bGridView2, screenManager(), + GameBrowser *tabAllGames = new GameBrowser(GetRequesterToken(), Path(g_Config.currentDirectory), BrowseFlags::STANDARD, portrait, &g_Config.bGridView2, screenManager(), mm->T("How to get games"), "https://www.ppsspp.org/getgames.html", new LinearLayoutParams(FILL_PARENT, FILL_PARENT)); diff --git a/UI/MainScreen.h b/UI/MainScreen.h index 5c9a694ee6..6ba953b195 100644 --- a/UI/MainScreen.h +++ b/UI/MainScreen.h @@ -46,7 +46,7 @@ ENUM_CLASS_BITOPS(BrowseFlags); class GameBrowser : public UI::LinearLayout { public: - GameBrowser(int token, const Path &path, BrowseFlags browseFlags, bool *gridStyle, ScreenManager *screenManager, std::string_view lastText, std::string_view lastLink, UI::LayoutParams *layoutParams = nullptr); + GameBrowser(int token, const Path &path, BrowseFlags browseFlags, bool portrait, bool *gridStyle, ScreenManager *screenManager, std::string_view lastText, std::string_view lastLink, UI::LayoutParams *layoutParams = nullptr); UI::Event OnChoice; UI::Event OnHoldChoice; @@ -114,7 +114,8 @@ private: float lastScale_ = 1.0f; bool lastLayoutWasGrid_ = true; ScreenManager *screenManager_; - int token_; + int token_ = -1; + bool portrait_ = false; }; class RemoteISOBrowseScreen; diff --git a/UI/MiscScreens.cpp b/UI/MiscScreens.cpp index c2197565b6..a5930a742e 100644 --- a/UI/MiscScreens.cpp +++ b/UI/MiscScreens.cpp @@ -508,7 +508,7 @@ std::string_view CreditsScreen::GetTitle() const { return mm->T("About PPSSPP"); } -void CreditsScreen::CreateDialogViews(UI::ViewGroup *root) { +void CreditsScreen::CreateDialogViews(UI::ViewGroup *parent) { using namespace UI; ignoreBottomInset_ = false; @@ -528,18 +528,35 @@ void CreditsScreen::CreateDialogViews(UI::ViewGroup *root) { root_->Add(new ImageView(ImageID("I_ICON"), "", IS_DEFAULT, new AnchorLayoutParams(WRAP_CONTENT, WRAP_CONTENT, 10, 10, NONE, NONE, false)))->SetScale(1.5f); }*/ - root->Add(new CreditsScroller(new LinearLayoutParams(1.0f))); + LinearLayout *left; + LinearLayout *right; + if (portrait) { + parent->Add(new CreditsScroller(new LinearLayoutParams(1.0f))); - LinearLayout *columns = root->Add(new LinearLayout(ORIENT_HORIZONTAL)); + LinearLayout *columns = parent->Add(new LinearLayout(ORIENT_HORIZONTAL)); - LinearLayout *left = columns->Add(new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(250.0f, WRAP_CONTENT, Margins(10)))); - LinearLayout *middle = columns->Add(new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(1.0f))); - LinearLayout *right = columns->Add(new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(250.0f, WRAP_CONTENT, Margins(10)))); + left = columns->Add(new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(250.0f, WRAP_CONTENT, Margins(10)))); + columns->Add(new Spacer(ORIENT_VERTICAL, new LinearLayoutParams(1.0f))); + right = columns->Add(new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(250.0f, WRAP_CONTENT, Margins(10)))); + } else { + LinearLayout *columns = parent->Add(new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(1.0f))); + + left = columns->Add(new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(250.0f, FILL_PARENT, Margins(10)))); + left->Add(new Spacer(0.0f, new LinearLayoutParams(1.0f))); + columns->Add(new CreditsScroller(new LinearLayoutParams(WRAP_CONTENT, FILL_PARENT, 1.0f))); + right = columns->Add(new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(250.0f, FILL_PARENT, Margins(10)))); + right->Add(new Spacer(0.0f, new LinearLayoutParams(1.0f))); + } int rightYOffset = 0; if (!System_GetPropertyBool(SYSPROP_APP_GOLD)) { ScreenManager *sm = screenManager(); - left->Add(new Choice(mm->T("Buy PPSSPP Gold")))->OnClick.Add([sm](UI::EventParams) { + Choice *gold = new Choice(mm->T("Buy PPSSPP Gold")); + gold->SetIcon(ImageID("I_ICON_GOLD"), 0.5f); + gold->SetImageScale(0.6f); // for the left-icon in case of vertical. + gold->SetShine(true); + + left->Add(gold)->OnClick.Add([sm](UI::EventParams) { LaunchBuyGold(sm); }); rightYOffset = 74; diff --git a/UI/MiscViews.cpp b/UI/MiscViews.cpp index 42767595cf..ac0b19952f 100644 --- a/UI/MiscViews.cpp +++ b/UI/MiscViews.cpp @@ -42,7 +42,7 @@ CopyableText::CopyableText(ImageID imageID, std::string_view text, UI::LinearLay }); } -TopBar::TopBar(const UIContext &ctx, bool usePortraitLayout, std::string_view title, UI::LayoutParams *layoutParams) : UI::LinearLayout(ORIENT_HORIZONTAL, layoutParams) { +TopBar::TopBar(const UIContext &ctx, TopBarFlags flags, std::string_view title, UI::LayoutParams *layoutParams) : UI::LinearLayout(ORIENT_HORIZONTAL, layoutParams), flags_(flags) { using namespace UI; SetSpacing(10.0f); if (!layoutParams) { @@ -55,9 +55,6 @@ TopBar::TopBar(const UIContext &ctx, bool usePortraitLayout, std::string_view ti backButton_ ->OnClick.Add([](UI::EventParams &e) { e.bubbleResult = DR_BACK; }); - if (!usePortraitLayout) { - backButton_->SetText(dlg->T("Back")); - } if (!title.empty()) { TextView *titleView = Add(new TextView(title, ALIGN_VCENTER | FLAG_WRAP_TEXT, false, new LinearLayoutParams(1.0f, Gravity::G_VCENTER))); @@ -66,6 +63,13 @@ TopBar::TopBar(const UIContext &ctx, bool usePortraitLayout, std::string_view ti // If using HCENTER, to balance the centering, add a spacer on the right. // Add(new Spacer(50.0f)); } + + if (flags & TopBarFlags::ContextMenuButton) { + Choice *menuButton = Add(new Choice(ImageID("I_THREE_DOTS"), new LinearLayoutParams(ITEM_HEIGHT, ITEM_HEIGHT))); + menuButton->OnClick.Add([this](UI::EventParams &e) { + this->OnContextMenuClick.Trigger(e); + }); + } } SettingInfoMessage::SettingInfoMessage(int align, float cutOffY, UI::AnchorLayoutParams *lp) diff --git a/UI/MiscViews.h b/UI/MiscViews.h index 5ffa67261d..a479bce166 100644 --- a/UI/MiscViews.h +++ b/UI/MiscViews.h @@ -1,5 +1,6 @@ #pragma once +#include "Common/Common.h" #include "Common/UI/View.h" #include "UI/ViewGroup.h" @@ -15,14 +16,24 @@ public: CopyableText(ImageID imageID, std::string_view text, UI::LinearLayoutParams *layoutParams = nullptr); }; +enum class TopBarFlags { + Default = 0, + Portrait = 1, + ContextMenuButton = 2, +}; +ENUM_CLASS_BITOPS(TopBarFlags); + class TopBar : public UI::LinearLayout { public: // The context is needed to get the theme for the background. - TopBar(const UIContext &ctx, bool usePortraitLayout, std::string_view title, UI::LayoutParams *layoutParams = nullptr); + TopBar(const UIContext &ctx, TopBarFlags flags, std::string_view title, UI::LayoutParams *layoutParams = nullptr); UI::View *GetBackButton() const { return backButton_; } + UI::Event OnContextMenuClick; + private: UI::Choice *backButton_ = nullptr; + TopBarFlags flags_ = TopBarFlags::Default; }; class SettingInfoMessage : public UI::LinearLayout { diff --git a/UI/RemoteISOScreen.cpp b/UI/RemoteISOScreen.cpp index 45590a96ca..c12c1a7fd6 100644 --- a/UI/RemoteISOScreen.cpp +++ b/UI/RemoteISOScreen.cpp @@ -592,11 +592,11 @@ void RemoteISOBrowseScreen::CreateViews() { auto di = GetI18NCategory(I18NCat::DIALOG); auto ri = GetI18NCategory(I18NCat::REMOTEISO); - bool portrait = GetDeviceOrientation() == DeviceOrientation::Portrait; + const bool portrait = GetDeviceOrientation() == DeviceOrientation::Portrait; using namespace UI; - TabHolder *leftColumn = new TabHolder(ORIENT_HORIZONTAL, 64, TabHolderFlags::Default, nullptr, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)); + TabHolder *leftColumn = new TabHolder(ORIENT_HORIZONTAL, 64, TabHolderFlags::Default, nullptr, nullptr, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)); tabHolder_ = leftColumn; tabHolder_->SetTag("RemoteGames"); gameBrowsers_.clear(); @@ -606,7 +606,7 @@ void RemoteISOBrowseScreen::CreateViews() { ScrollView *scrollRecentGames = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)); scrollRecentGames->SetTag("RemoteGamesTab"); GameBrowser *tabRemoteGames = new GameBrowser(GetRequesterToken(), - Path(url_), BrowseFlags::NAVIGATE, &g_Config.bGridView1, screenManager(), "", "", + Path(url_), BrowseFlags::NAVIGATE, portrait, &g_Config.bGridView1, screenManager(), "", "", new LinearLayoutParams(FILL_PARENT, FILL_PARENT)); tabRemoteGames->SetHomePath(Path(url_)); diff --git a/UI/SavedataScreen.cpp b/UI/SavedataScreen.cpp index 8c3d1ba4ed..0b1a26ec86 100644 --- a/UI/SavedataScreen.cpp +++ b/UI/SavedataScreen.cpp @@ -431,26 +431,26 @@ void SavedataBrowser::Update() { int n = gameList_->GetNumSubviews(); bool matches = searchFilter_.empty(); for (int i = 0; i < n; ++i) { - SavedataButton *v = static_cast(gameList_->GetViewByIndex(i)); - + View *view = gameList_->GetViewByIndex(i); // Note: might be resetting to empty string. Can do that right away. if (searchFilter_.empty()) { - v->SetVisibility(UI::V_VISIBLE); + view->SetVisibility(UI::V_VISIBLE); continue; } - if (!v->UpdateText()) { + SavedataButton *savedataButton = static_cast(view); + if (!savedataButton->UpdateText()) { // We'll need to wait until the text is loaded. searchPending_ = true; - v->SetVisibility(UI::V_GONE); + view->SetVisibility(UI::V_GONE); continue; } - std::string label = v->DescribeText(); + std::string label = view->DescribeText(); std::transform(label.begin(), label.end(), label.begin(), tolower); bool match = label.find(searchFilter_) != label.npos; matches = matches || match; - v->SetVisibility(match ? UI::V_VISIBLE : UI::V_GONE); + view->SetVisibility(match ? UI::V_VISIBLE : UI::V_GONE); } if (searchingView_) { @@ -671,7 +671,7 @@ void SavedataScreen::CreateTabs() { }); } -void SavedataScreen::CreateExtraButtons(UI::LinearLayout *verticalLayout, int margins) { +void SavedataScreen::CreateExtraButtons(UI::ViewGroup *verticalLayout, int margins) { using namespace UI; if (System_GetPropertyBool(SYSPROP_HAS_TEXT_INPUT_DIALOG)) { auto di = GetI18NCategory(I18NCat::DIALOG); diff --git a/UI/SavedataScreen.h b/UI/SavedataScreen.h index 68fcfd8023..83449ab421 100644 --- a/UI/SavedataScreen.h +++ b/UI/SavedataScreen.h @@ -80,10 +80,10 @@ public: protected: void CreateTabs() override; - void CreateExtraButtons(UI::LinearLayout *verticalLayout, int margins) override; + void CreateExtraButtons(UI::ViewGroup *verticalLayout, int margins) override; bool ShowSearchControls() const override { return false; } - + private: void OnSavedataButtonClick(UI::EventParams &e); void OnSearch(UI::EventParams &e); diff --git a/UI/SimpleDialogScreen.cpp b/UI/SimpleDialogScreen.cpp index de99343d50..a47b9b90ba 100644 --- a/UI/SimpleDialogScreen.cpp +++ b/UI/SimpleDialogScreen.cpp @@ -1,6 +1,7 @@ #include "Common/UI/ScrollView.h" #include "Common/Data/Text/I18n.h" #include "UI/SimpleDialogScreen.h" +#include "Common/UI/PopupScreens.h" #include "UI/MiscViews.h" void UISimpleBaseDialogScreen::CreateViews() { @@ -12,7 +13,7 @@ void UISimpleBaseDialogScreen::CreateViews() { const bool portrait = GetDeviceOrientation() == DeviceOrientation::Portrait; root_ = new LinearLayout(ORIENT_VERTICAL, new LayoutParams(FILL_PARENT, FILL_PARENT)); - root_->Add(new TopBar(*screenManager()->getUIContext(), portrait, GetTitle())); + root_->Add(new TopBar(*screenManager()->getUIContext(), portrait ? TopBarFlags::Portrait : TopBarFlags::Default, GetTitle())); if (canScroll) { ScrollView *scroll = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, FILL_PARENT, 1.0f)); @@ -37,28 +38,55 @@ void UITwoPaneBaseDialogScreen::CreateViews() { // Portrait layout is just a vertical stack. ignoreBottomInset_ = true; LinearLayout *root = new LinearLayout(ORIENT_VERTICAL, new LayoutParams(FILL_PARENT, FILL_PARENT)); - root->Add(new TopBar(*screenManager()->getUIContext(), portrait, GetTitle())); + + TopBarFlags topBarFlags = TopBarFlags::Portrait; + if (flags_ & TwoPaneFlags::SettingsInContextMenu) { + topBarFlags |= TopBarFlags::ContextMenuButton; + } + TopBar *topBar = root->Add(new TopBar(*screenManager()->getUIContext(), topBarFlags, GetTitle())); root->SetSpacing(0); CreateContentViews(root); - ScrollView *settingsScroll = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, FILL_PARENT, 1.0f)); - LinearLayout *settingsPane = new LinearLayout(ORIENT_VERTICAL); - settingsScroll->Add(settingsPane); - CreateSettingsViews(settingsPane); - root->Add(settingsScroll); - + if (flags_ & TwoPaneFlags::SettingsInContextMenu) { + topBar->OnContextMenuClick.Add([this](UI::EventParams &e) { + this->screenManager()->push(new PopupCallbackScreen([this](UI::ViewGroup *parent) { + CreateSettingsViews(parent); + }, nullptr)); + }); + } else { + ScrollView *settingsScroll = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, FILL_PARENT, 1.0f, Margins(8))); + LinearLayout *settingsPane = new LinearLayout(ORIENT_VERTICAL); + settingsScroll->Add(settingsPane); + CreateSettingsViews(settingsPane); + root->Add(settingsScroll); + } root_ = root; } else { ignoreBottomInset_ = false; - LinearLayout *root = new LinearLayout(ORIENT_HORIZONTAL, new LayoutParams(FILL_PARENT, FILL_PARENT)); + LinearLayout *root = new LinearLayout(ORIENT_VERTICAL, new LayoutParams(FILL_PARENT, FILL_PARENT)); + std::string title(GetTitle()); + root->Add(new TopBar(*screenManager()->getUIContext(), portrait ? TopBarFlags::Portrait : TopBarFlags::Default, title)); + root->SetSpacing(0); + + LinearLayout *columns = new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(FILL_PARENT, FILL_PARENT, 1.0f)); + root->Add(columns); + // root_->Add(new TopBar(*screenManager()->getUIContext(), portrait, GetTitle(), new LayoutParams(FILL_PARENT, FILL_PARENT))); - LinearLayout *settingsPane = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(400.0f, FILL_PARENT)); + ScrollView *settingsScroll = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(SettingsWidth(), FILL_PARENT, 0.0f, Margins(8))); + LinearLayout *settingsPane = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT)); settingsPane->SetSpacing(0); CreateSettingsViews(settingsPane); settingsPane->Add(new BorderView(BORDER_BOTTOM, BorderStyle::HEADER_FG, 2.0f, new LayoutParams(FILL_PARENT, 40.0f))); settingsPane->Add(new Choice(di->T("Back"), ImageID("I_NAVIGATE_BACK")))->OnClick.Handle(this, &UIScreen::OnBack); - root->Add(settingsPane); - CreateContentViews(root); + settingsScroll->Add(settingsPane); + + if (flags_ & TwoPaneFlags::SettingsToTheRight) { + CreateContentViews(columns); + columns->Add(settingsScroll); + } else { + columns->Add(settingsScroll); + CreateContentViews(columns); + } root_ = root; } diff --git a/UI/SimpleDialogScreen.h b/UI/SimpleDialogScreen.h index bff9cd6cdf..a7942efaf4 100644 --- a/UI/SimpleDialogScreen.h +++ b/UI/SimpleDialogScreen.h @@ -24,22 +24,31 @@ private: void CreateViews() override; }; +enum class TwoPaneFlags { + Default = 0, + SettingsToTheRight = 1, + SettingsInContextMenu = 2, +}; +ENUM_CLASS_BITOPS(TwoPaneFlags); + // A two-pane version of the above, where settings are meant to go in the settings pane, // and contents in the content pane. Will generate nice layouts for portrait and landscape. // but with a consistent portrait-compatible back button and title. // The settings pane is scrollable while the other is not. class UITwoPaneBaseDialogScreen : public UIBaseDialogScreen { public: - UITwoPaneBaseDialogScreen(const Path &gamePath = Path()) : UIBaseDialogScreen(gamePath) { + UITwoPaneBaseDialogScreen(const Path &gamePath, TwoPaneFlags flags) : UIBaseDialogScreen(gamePath), flags_(flags) { // We need to check CanScroll before we know whether to ignore // bottom inset. Can't do that here, we do it in CreateViews } // Override this, don't override CreateViews. And don't touch root_ directly. - virtual void CreateSettingsViews(UI::LinearLayout *parent) = 0; - virtual void CreateContentViews(UI::LinearLayout *parent) = 0; + virtual void CreateSettingsViews(UI::ViewGroup *parent) = 0; + virtual void CreateContentViews(UI::ViewGroup *parent) = 0; virtual std::string_view GetTitle() const { return ""; } + virtual float SettingsWidth() const { return 350.0f; } private: void CreateViews() override; + TwoPaneFlags flags_ = TwoPaneFlags::Default; }; diff --git a/UI/TabbedDialogScreen.cpp b/UI/TabbedDialogScreen.cpp index 82421b268d..18b378fc03 100644 --- a/UI/TabbedDialogScreen.cpp +++ b/UI/TabbedDialogScreen.cpp @@ -58,16 +58,26 @@ void UITabbedBaseDialogScreen::CreateViews() { if (flags_ & TabDialogFlags::HorizontalOnlyIcons) { tabHolderFlags |= TabHolderFlags::HorizontalOnlyIcons; } - tabHolder_ = new TabHolder(ORIENT_HORIZONTAL, 200, tabHolderFlags, filterNotice_, new LinearLayoutParams(1.0f)); + std::function contextMenu; + if (flags_ & TabDialogFlags::ContextMenuInPortrait) { + contextMenu = [this]() { + this->screenManager()->push(new PopupCallbackScreen([this](UI::ViewGroup *parent) { + CreateExtraButtons(parent, 0); + }, nullptr)); + }; + } + tabHolder_ = new TabHolder(ORIENT_HORIZONTAL, 200, tabHolderFlags, filterNotice_, contextMenu, new LinearLayoutParams(1.0f)); verticalLayout->Add(tabHolder_); - CreateExtraButtons(verticalLayout, 0); + if (!(flags_ & TabDialogFlags::ContextMenuInPortrait)) { + CreateExtraButtons(verticalLayout, 0); + } root_->Add(verticalLayout); } else { TabHolderFlags tabHolderFlags = TabHolderFlags::Default; if (flags_ & TabDialogFlags::VerticalShowIcons) { tabHolderFlags |= TabHolderFlags::VerticalShowIcons; } - tabHolder_ = new TabHolder(ORIENT_VERTICAL, 300, tabHolderFlags, filterNotice_, new AnchorLayoutParams(10, 0, 10, 0)); + tabHolder_ = new TabHolder(ORIENT_VERTICAL, 300, tabHolderFlags, filterNotice_, nullptr, new AnchorLayoutParams(10, 0, 10, 0)); CreateExtraButtons(tabHolder_->Container(), 10); tabHolder_->AddBack(this); root_->Add(tabHolder_); diff --git a/UI/TabbedDialogScreen.h b/UI/TabbedDialogScreen.h index b3089e937d..a61deac397 100644 --- a/UI/TabbedDialogScreen.h +++ b/UI/TabbedDialogScreen.h @@ -20,6 +20,7 @@ enum class TabDialogFlags { HorizontalOnlyIcons = 1, VerticalShowIcons = 2, AddTitles = 4, + ContextMenuInPortrait = 8, }; ENUM_CLASS_BITOPS(TabDialogFlags); @@ -45,9 +46,10 @@ protected: // Load data and define your tabs here. virtual void PreCreateViews() {} virtual void CreateTabs() = 0; - virtual void CreateExtraButtons(UI::LinearLayout *verticalLayout, int margins) {} + virtual void CreateExtraButtons(UI::ViewGroup *verticalLayout, int margins) {} virtual bool ShowSearchControls() const { return true; } virtual void EnsureTabs(); + virtual bool UsePopupMenuInPortrait() const { return false; } virtual bool ForceHorizontalTabs() const { return false; } int GetCurrentTab() const; diff --git a/UI/TiltAnalogSettingsScreen.cpp b/UI/TiltAnalogSettingsScreen.cpp index 13bcb2e9bd..c96962bcb1 100644 --- a/UI/TiltAnalogSettingsScreen.cpp +++ b/UI/TiltAnalogSettingsScreen.cpp @@ -83,12 +83,12 @@ std::string_view TiltAnalogSettingsScreen::GetTitle() const { return co->T("Tilt control setup"); } -void TiltAnalogSettingsScreen::CreateContentViews(UI::LinearLayout *parent) { +void TiltAnalogSettingsScreen::CreateContentViews(UI::ViewGroup *parent) { using namespace UI; CreateCalibrationView(parent, new LinearLayoutParams(300.0f, 300.0f, 1.0f, Gravity::G_CENTER)); } -void TiltAnalogSettingsScreen::CreateSettingsViews(UI::LinearLayout *settings) { +void TiltAnalogSettingsScreen::CreateSettingsViews(UI::ViewGroup *settings) { using namespace UI; auto co = GetI18NCategory(I18NCat::CONTROLS); diff --git a/UI/TiltAnalogSettingsScreen.h b/UI/TiltAnalogSettingsScreen.h index 539a811ed9..a6714bf27a 100644 --- a/UI/TiltAnalogSettingsScreen.h +++ b/UI/TiltAnalogSettingsScreen.h @@ -26,10 +26,10 @@ class GamepadView; class TiltAnalogSettingsScreen : public UITwoPaneBaseDialogScreen { public: - TiltAnalogSettingsScreen(const Path &gamePath) : UITwoPaneBaseDialogScreen(gamePath) {} + TiltAnalogSettingsScreen(const Path &gamePath) : UITwoPaneBaseDialogScreen(gamePath, TwoPaneFlags::Default) {} - void CreateSettingsViews(UI::LinearLayout *parent) override; - void CreateContentViews(UI::LinearLayout *parent) override; + void CreateSettingsViews(UI::ViewGroup *parent) override; + void CreateContentViews(UI::ViewGroup*parent) override; std::string_view GetTitle() const override; void update() override; diff --git a/UI/TouchControlVisibilityScreen.cpp b/UI/TouchControlVisibilityScreen.cpp index a0659c0dd8..29736b756d 100644 --- a/UI/TouchControlVisibilityScreen.cpp +++ b/UI/TouchControlVisibilityScreen.cpp @@ -155,7 +155,7 @@ void RightAnalogMappingScreen::CreateViews() { root_ = new AnchorLayout(new LayoutParams(FILL_PARENT, FILL_PARENT)); Choice *back = new Choice(di->T("Back"), ImageID("I_NAVIGATE_BACK"), new AnchorLayoutParams(leftColumnWidth - 10, WRAP_CONTENT, 10, NONE, NONE, 10)); root_->Add(back)->OnClick.Handle(this, &UIScreen::OnBack); - TabHolder *tabHolder = new TabHolder(ORIENT_VERTICAL, leftColumnWidth, TabHolderFlags::Default, nullptr, new AnchorLayoutParams(10, 0, 10, 0, Centering::None)); + TabHolder *tabHolder = new TabHolder(ORIENT_VERTICAL, leftColumnWidth, TabHolderFlags::Default, nullptr, nullptr, new AnchorLayoutParams(10, 0, 10, 0, Centering::None)); root_->Add(tabHolder); ScrollView *rightPanel = new ScrollView(ORIENT_VERTICAL); tabHolder->AddTab(co->T("Binds"), ImageID::invalid(), rightPanel); diff --git a/UI/UploadScreen.cpp b/UI/UploadScreen.cpp index c8dea29d37..9004d5b021 100644 --- a/UI/UploadScreen.cpp +++ b/UI/UploadScreen.cpp @@ -39,7 +39,7 @@ void UploadScreen::CreateDialogViews(UI::ViewGroup *root) { if (prevRunning_) { container->Add(new TextWithImage(ImageID("I_WIFI"), n->T("With a web browser on the same network, go to:"))); for (const auto &ip : localIPs_) { - std::string url = StringFromFormat("http://%s:%d/upload", ip.c_str(), WebServerPort()); + std::string url = StringFromFormat("%s:%d/upload", ip.c_str(), WebServerPort()); container->Add(new CopyableText(ImageID("I_WEB_BROWSER"), url)); } }