diff --git a/UI/ControlMappingScreen.cpp b/UI/ControlMappingScreen.cpp index 92c8512cbe..eac5ed1143 100644 --- a/UI/ControlMappingScreen.cpp +++ b/UI/ControlMappingScreen.cpp @@ -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) {}, @@ -535,7 +535,7 @@ std::string_view AnalogCalibrationScreen::GetTitle() const { 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 3dc7711ae6..4741fac0dc 100644 --- a/UI/ControlMappingScreen.h +++ b/UI/ControlMappingScreen.h @@ -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/GameScreen.cpp b/UI/GameScreen.cpp index dbac4757c6..0d66ed988d 100644 --- a/UI/GameScreen.cpp +++ b/UI/GameScreen.cpp @@ -56,7 +56,7 @@ 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) : UITwoPaneBaseDialogScreen(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()); @@ -119,7 +119,7 @@ static bool FileTypeSupportsCRC(IdentifiedFileType fileType) { } } -void GameScreen::CreateContentViews(UI::LinearLayout *parent) { +void GameScreen::CreateContentViews(UI::ViewGroup *parent) { if (!info_) { // Shouldn't happen return; @@ -137,8 +137,13 @@ void GameScreen::CreateContentViews(UI::LinearLayout *parent) { Margins actionMenuMargins(0, 15, 15, 0); - ViewGroup *leftColumn = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(1.0f, Margins(8))); - parent->Add(leftColumn); + ScrollView *leftScroll = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(1.0f, Margins(8))); + + ViewGroup *leftColumn = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)); + + leftScroll->Add(leftColumn); + + parent->Add(leftScroll); const bool fileTypeSupportCRC = FileTypeSupportsCRC(info_->fileType); @@ -295,21 +300,25 @@ void GameScreen::CreateContentViews(UI::LinearLayout *parent) { } } + if (portrait) { + parent->Add(new Choice(ga->T("Play"), ImageID("I_PLAY")))->OnClick.Handle(this, &GameScreen::OnPlay); + } } -void GameScreen::CreateSettingsViews(UI::LinearLayout *rightColumn) { +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_) { + if (!inGame_ && !portrait) { rightColumnItems->Add(new Choice(ga->T("Play"), ImageID("I_PLAY")))->OnClick.Handle(this, &GameScreen::OnPlay); } @@ -317,7 +326,7 @@ void GameScreen::CreateSettingsViews(UI::LinearLayout *rightColumn) { 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"))); + Choice *btnGameSettings = rightColumnItems->Add(new Choice(ga->T("Game Settings"), ImageID("I_GEAR"))); btnGameSettings->OnClick.Handle(this, &GameScreen::OnGameSettings); if (inGame_) btnGameSettings->SetEnabled(false); diff --git a/UI/GameScreen.h b/UI/GameScreen.h index 012efd7bfa..378e8ab322 100644 --- a/UI/GameScreen.h +++ b/UI/GameScreen.h @@ -46,10 +46,9 @@ public: const char *tag() const override { return "Game"; } protected: - void CreateContentViews(UI::LinearLayout *parent) override; - void CreateSettingsViews(UI::LinearLayout *parent) override; + void CreateContentViews(UI::ViewGroup *parent) override; + void CreateSettingsViews(UI::ViewGroup *parent) override; - bool SettingsToTheRight() const override { return true; } std::string_view GetTitle() const override; private: diff --git a/UI/MiscViews.cpp b/UI/MiscViews.cpp index d361828cf7..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,10 +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) { - // Not really liking this.. - // 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))); @@ -67,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/SimpleDialogScreen.cpp b/UI/SimpleDialogScreen.cpp index 35824cd964..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,22 +38,34 @@ 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, Margins(8))); - 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_VERTICAL, new LayoutParams(FILL_PARENT, FILL_PARENT)); std::string title(GetTitle()); - root->Add(new TopBar(*screenManager()->getUIContext(), portrait, title)); + 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)); @@ -67,7 +80,7 @@ void UITwoPaneBaseDialogScreen::CreateViews() { settingsPane->Add(new Choice(di->T("Back"), ImageID("I_NAVIGATE_BACK")))->OnClick.Handle(this, &UIScreen::OnBack); settingsScroll->Add(settingsPane); - if (SettingsToTheRight()) { + if (flags_ & TwoPaneFlags::SettingsToTheRight) { CreateContentViews(columns); columns->Add(settingsScroll); } else { diff --git a/UI/SimpleDialogScreen.h b/UI/SimpleDialogScreen.h index ace3b4158f..a7942efaf4 100644 --- a/UI/SimpleDialogScreen.h +++ b/UI/SimpleDialogScreen.h @@ -24,25 +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; } - virtual bool SettingsToTheRight() const { return false; } - private: void CreateViews() override; + TwoPaneFlags flags_ = TwoPaneFlags::Default; }; 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;