From 357293cd9f8a53bb5c2d912e0bebef6221b2449d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 19 Sep 2021 15:54:01 +0200 Subject: [PATCH] Implement the new MemStick screen experience (rough cut) --- Common/UI/View.cpp | 63 ++++++++++++++++++ Common/UI/View.h | 24 ++++++- UI/MemStickScreen.cpp | 144 +++++++++++++++++++++++++++++++++--------- UI/MemStickScreen.h | 24 +++++-- 4 files changed, 218 insertions(+), 37 deletions(-) diff --git a/Common/UI/View.cpp b/Common/UI/View.cpp index bc9b421fdc..2bf77a4053 100644 --- a/Common/UI/View.cpp +++ b/Common/UI/View.cpp @@ -803,6 +803,69 @@ void Button::Draw(UIContext &dc) { } } +void RadioButton::GetContentDimensions(const UIContext &dc, float &w, float &h) const { + w = 0.0f; + h = 0.0f; + + if (!text_.empty()) { + dc.MeasureText(dc.theme->uiFont, 1.0f, 1.0f, text_.c_str(), &w, &h); + } + + // Add some internal padding to not look totally ugly + w += paddingW_ * 3.0f + radioRadius_ * 2.0f; + h = std::max(h, radioRadius_ * 2) + paddingH_ * 2; +} + +std::string RadioButton::DescribeText() const { + auto u = GetI18NCategory("UI Elements"); + return ReplaceAll(u->T("%1 radio button"), "%1", text_); +} + +void RadioButton::Click() { + Clickable::Click(); + UI::PlayUISound(UI::UISound::CONFIRM); + *value_ = thisButtonValue_; +} + +void RadioButton::Draw(UIContext &dc) { + Style style = dc.theme->buttonStyle; + + bool checked = *value_ == thisButtonValue_; + + if (HasFocus()) style = dc.theme->buttonFocusedStyle; + if (down_) style = dc.theme->buttonDownStyle; + if (!IsEnabled()) style = dc.theme->buttonDisabledStyle; + + DrawBG(dc, style); + + dc.Flush(); + dc.BeginNoTex(); + dc.Draw()->Circle(bounds_.x + paddingW_ + radioRadius_, bounds_.centerY(), radioRadius_, 2.5f, 36, 0, style.fgColor, 1.0f); + if (checked) { + dc.Draw()->FillCircle(bounds_.x + paddingW_ + radioRadius_, bounds_.centerY(), radioInnerRadius_, 36, style.fgColor); + } + dc.Flush(); + dc.Begin(); + + float tw, th; + dc.MeasureText(dc.theme->uiFont, 1.0f, 1.0f, text_.c_str(), &tw, &th); + + if (tw > bounds_.w) { + dc.PushScissor(bounds_); + } + + dc.SetFontStyle(dc.theme->uiFont); + + if (!text_.empty()) { + float textX = bounds_.x + paddingW_ * 2.0f + radioRadius_ * 2.0f; + dc.DrawText(text_.c_str(), textX, bounds_.centerY(), style.fgColor, ALIGN_LEFT | ALIGN_VCENTER); + } + + if (tw > bounds_.w) { + dc.PopScissor(); + } +} + void ImageView::GetContentDimensions(const UIContext &dc, float &w, float &h) const { dc.Draw()->GetAtlas()->measureImage(atlasImage_, &w, &h); // TODO: involve sizemode diff --git a/Common/UI/View.h b/Common/UI/View.h index 8f8cbd4167..136468ebed 100644 --- a/Common/UI/View.h +++ b/Common/UI/View.h @@ -522,6 +522,8 @@ protected: bool down_ = false; }; +// TODO: Very similar to Choice, should probably merge them. +// Right now more flexible image support though. class Button : public Clickable { public: Button(const std::string &text, LayoutParams *layoutParams = 0) @@ -544,12 +546,10 @@ public: void SetIgnoreText(bool ignore) { ignoreText_ = ignore; } - // Needed an extra small button... void SetScale(float f) { scale_ = f; } - private: Style style_; std::string text_; @@ -560,6 +560,26 @@ private: bool ignoreText_ = false; }; +class RadioButton : public Clickable { +public: + RadioButton(int *value, int thisButtonValue, const std::string &text, LayoutParams *layoutParams = 0) + : Clickable(layoutParams), value_(value), thisButtonValue_(thisButtonValue), text_(text) {} + void Click() override; + void Draw(UIContext &dc) override; + void GetContentDimensions(const UIContext &dc, float &w, float &h) const override; + std::string DescribeText() const override; + +private: + int *value_; + int thisButtonValue_; + std::string text_; + const float paddingW_ = 8; + const float paddingH_ = 4; + + const float radioRadius_ = 16.0f; + const float radioInnerRadius_ = 8.0f; +}; + class Slider : public Clickable { public: Slider(int *value, int minValue, int maxValue, LayoutParams *layoutParams = 0) diff --git a/UI/MemStickScreen.cpp b/UI/MemStickScreen.cpp index de4c62a7ac..eb28639a7d 100644 --- a/UI/MemStickScreen.cpp +++ b/UI/MemStickScreen.cpp @@ -102,6 +102,36 @@ static std::string FormatSpaceString(int64_t space) { MemStickScreen::MemStickScreen(bool initialSetup) : initialSetup_(initialSetup) { +#if PPSSPP_PLATFORM(ANDROID) + // Let's only offer the browse-for-folder choice on Android 10 or later. + // Earlier versions often don't really have working folder browsers. + storageBrowserWorking_ = System_GetPropertyInt(SYSPROP_SYSTEMVERSION) >= 29; +#else + // For testing UI only + storageBrowserWorking_ = true; +#endif + + if (initialSetup_) { + // Preselect current choice. + if (System_GetPropertyBool(SYSPROP_ANDROID_SCOPED_STORAGE)) { + choice_ = CHOICE_BROWSE_FOLDER; + } else { + WARN_LOG(SYSTEM, "Scoped storage not enabled - shouldn't be in MemStickScreen at initial setup"); + choice_ = CHOICE_STORAGE_ROOT; + // Shouldn't really be here in initial setup. + } + } else { + // Detect the current choice, so it's preselected in the UI. + if (g_Config.memStickDirectory == Path(g_extFilesDir)) { + choice_ = CHOICE_PRIVATE_DIRECTORY; + } else if (g_Config.memStickDirectory == Path(g_externalDir)) { + choice_ = CHOICE_STORAGE_ROOT; + } else if (storageBrowserWorking_) { + choice_ = CHOICE_BROWSE_FOLDER; + } else { + choice_ = CHOICE_SET_MANUAL; + } + } } void MemStickScreen::CreateViews() { @@ -138,43 +168,72 @@ void MemStickScreen::CreateViews() { // TODO: Gonna need a scroll view. #if PPSSPP_PLATFORM(ANDROID) if (!System_GetPropertyBool(SYSPROP_ANDROID_SCOPED_STORAGE)) { - leftColumn->Add(new Button(iz->T("Use PSP folder at root of storage")))->OnClick.Handle(this, &MemStickScreen::OnUseStorageRoot); - leftColumn->Add(new TextView(iz->T("DataWillStay", "Data will stay even if you uninstall PPSSPP.")))->SetBullet(true); - leftColumn->Add(new TextView(iz->T("DataCanBeShared", "Data can be shared between PPSSPP regular/Gold.")))->SetBullet(true); - leftColumn->Add(new TextView(iz->T("EasyUSBAccess", "Easy USB access")))->SetBullet(true); + leftColumn->Add(new RadioButton(&choice_, CHOICE_STORAGE_ROOT, iz->T("Use PSP folder at root of storage")))->OnClick.Handle(this, &MemStickScreen::OnChoiceClick); } - - // Let's only offer the browse-for-folder choice on Android 10 or later. - // Earlier versions often don't really have working folder browsers. - bool storageBrowserWorking = System_GetPropertyInt(SYSPROP_SYSTEMVERSION) >= 29; -#else - // For testing UI only - bool storageBrowserWorking = true; #endif - if (storageBrowserWorking) { - leftColumn->Add(new Button(iz->T("Create or Choose a PSP folder"), ImageID("I_FOLDER_OPEN")))->OnClick.Handle(this, &MemStickScreen::OnBrowse); - leftColumn->Add(new TextView(iz->T("DataWillStay", "Data will stay even if you uninstall PPSSPP.")))->SetBullet(true); - leftColumn->Add(new TextView(iz->T("DataCanBeShared", "Data can be shared between PPSSPP regular/Gold.")))->SetBullet(true); - leftColumn->Add(new TextView(iz->T("EasyUSBAccess", "Easy USB access")))->SetBullet(true); + if (storageBrowserWorking_) { + //ImageID("I_FOLDER_OPEN") + leftColumn->Add(new RadioButton(&choice_, CHOICE_BROWSE_FOLDER, iz->T("Create or Choose a PSP folder")))->OnClick.Handle(this, &MemStickScreen::OnChoiceClick); + // TODO: Show current folder here if we have one set. } else { - leftColumn->Add(new Button(iz->T("Manually specify PSP folder")))->OnClick.Handle(this, &MemStickScreen::OnSetFolderManually); + leftColumn->Add(new RadioButton(&choice_, CHOICE_SET_MANUAL, iz->T("Manually specify PSP folder")))->OnClick.Handle(this, &MemStickScreen::OnChoiceClick); leftColumn->Add(new TextView(iz->T("DataWillStay", "Data will stay even if you uninstall PPSSPP.")))->SetBullet(true); leftColumn->Add(new TextView(iz->T("DataCanBeShared", "Data can be shared between PPSSPP regular/Gold.")))->SetBullet(true); + // TODO: Show current folder here if we have one set. + } + if (!g_Config.memStickDirectory.empty()) { + leftColumn->Add(new TextView(StringFromFormat(" %s: %s", iz->T("Current PSP Data Folder"), g_Config.memStickDirectory.ToVisualString().c_str()), ALIGN_LEFT, false)); } - leftColumn->Add(new Button(iz->T("Skip for now - use App Private Directory"), ImageID("I_WARNING")))->OnClick.Handle(this, &MemStickScreen::OnUseInternalStorage); - // Consider https://www.compart.com/en/unicode/U+26A0 (unicode warning sign?)? or a graphic? - leftColumn->Add(new TextView(iz->T("DataWillBeLostOnUninstall", "Warning! Data will be lost when you uninstall PPSSPP!")))->SetBullet(true); - leftColumn->Add(new TextView(iz->T("DataCannotBeShared", "Data CANNOT be shared between PPSSPP regular/Gold!")))->SetBullet(true); -#if GOLD - leftColumn->Add(new TextView(iz->T("USBAccessThroughGold", "USB access through Android/data/org.ppsspp.ppssppgold/files")))->SetBullet(true); -#else - leftColumn->Add(new TextView(iz->T("USBAccessThrough", "USB access through Android/data/org.ppsspp.ppsspp/files")))->SetBullet(true); -#endif + leftColumn->Add(new RadioButton(&choice_, CHOICE_PRIVATE_DIRECTORY, iz->T("Skip for now - use App Private Directory")))->OnClick.Handle(this, &MemStickScreen::OnChoiceClick); leftColumn->Add(new Spacer(new LinearLayoutParams(FILL_PARENT, 12.0f, 0.0f))); + const char *confirmButtonText = nullptr; + ImageID confirmButtonImage = ImageID::invalid(); + switch (choice_) { + case CHOICE_STORAGE_ROOT: + // Old school choice + leftColumn->Add(new TextView(iz->T("DataWillStay", "Data will stay even if you uninstall PPSSPP.")))->SetBullet(true); + leftColumn->Add(new TextView(iz->T("DataCanBeShared", "Data can be shared between PPSSPP regular/Gold.")))->SetBullet(true); + leftColumn->Add(new TextView(iz->T("EasyUSBAccess", "Easy USB access")))->SetBullet(true); + + confirmButtonText = di->T("Confirm"); + break; + case CHOICE_BROWSE_FOLDER: + leftColumn->Add(new TextView(iz->T("DataWillStay", "Data will stay even if you uninstall PPSSPP.")))->SetBullet(true); + leftColumn->Add(new TextView(iz->T("DataCanBeShared", "Data can be shared between PPSSPP regular/Gold.")))->SetBullet(true); + leftColumn->Add(new TextView(iz->T("EasyUSBAccess", "Easy USB access")))->SetBullet(true); + + confirmButtonText = di->T("Browse"); + confirmButtonImage = ImageID("I_FOLDER_OPEN"); + break; + case CHOICE_PRIVATE_DIRECTORY: + // Consider https://www.compart.com/en/unicode/U+26A0 (unicode warning sign?)? or a graphic? + leftColumn->Add(new TextView(iz->T("DataWillBeLostOnUninstall", "Warning! Data will be lost when you uninstall PPSSPP!")))->SetBullet(true); + leftColumn->Add(new TextView(iz->T("DataCannotBeShared", "Data CANNOT be shared between PPSSPP regular/Gold!")))->SetBullet(true); +#if GOLD + leftColumn->Add(new TextView(iz->T("USBAccessThroughGold", "USB access through Android/data/org.ppsspp.ppssppgold/files")))->SetBullet(true); +#else + leftColumn->Add(new TextView(iz->T("USBAccessThrough", "USB access through Android/data/org.ppsspp.ppsspp/files")))->SetBullet(true); +#endif + confirmButtonText = di->T("Confirm"); + confirmButtonImage = ImageID("I_WARNING"); + break; + case CHOICE_SET_MANUAL: + default: + leftColumn->Add(new TextView(iz->T("EasyUSBAccess", "Easy USB access")))->SetBullet(true); + // What more? + + // Should we have a special text here? It'll popup a text window for editing. + confirmButtonText = di->T("Confirm"); + break; + } + + rightColumnItems->Add(new Button(confirmButtonText, confirmButtonImage))->OnClick.Handle(this, &MemStickScreen::OnConfirmClick); + rightColumnItems->Add(new Spacer(new LinearLayoutParams(FILL_PARENT, 12.0f, 0.0f))); + if (!initialSetup_) { rightColumnItems->Add(new Choice(di->T("Back")))->OnClick.Handle(this, &UIScreen::OnBack); } @@ -189,7 +248,30 @@ UI::EventReturn MemStickScreen::OnHelp(UI::EventParams ¶ms) { return UI::EVENT_DONE; } -UI::EventReturn MemStickScreen::OnSetFolderManually(UI::EventParams ¶ms) { +UI::EventReturn MemStickScreen::OnChoiceClick(UI::EventParams ¶ms) { + // Change the confirm button to match the choice, + // and change the text that we show. + RecreateViews(); + + return UI::EVENT_DONE; +} + + +UI::EventReturn MemStickScreen::OnConfirmClick(UI::EventParams ¶ms) { + switch (choice_) { + case CHOICE_SET_MANUAL: + return SetFolderManually(params); + case CHOICE_STORAGE_ROOT: + return UseStorageRoot(params); + case CHOICE_PRIVATE_DIRECTORY: + return UseInternalStorage(params); + case CHOICE_BROWSE_FOLDER: + return Browse(params); + } + return UI::EVENT_DONE; +} + +UI::EventReturn MemStickScreen::SetFolderManually(UI::EventParams ¶ms) { // The old way, from before scoped storage. #if PPSSPP_PLATFORM(ANDROID) auto sy = GetI18NCategory("System"); @@ -256,7 +338,7 @@ UI::EventReturn MemStickScreen::OnSetFolderManually(UI::EventParams ¶ms) { return UI::EVENT_DONE; } -UI::EventReturn MemStickScreen::OnUseInternalStorage(UI::EventParams ¶ms) { +UI::EventReturn MemStickScreen::UseInternalStorage(UI::EventParams ¶ms) { Path pendingMemStickFolder = Path(g_extFilesDir); if (initialSetup_) { @@ -277,7 +359,7 @@ UI::EventReturn MemStickScreen::OnUseInternalStorage(UI::EventParams ¶ms) { return UI::EVENT_DONE; } -UI::EventReturn MemStickScreen::OnUseStorageRoot(UI::EventParams ¶ms) { +UI::EventReturn MemStickScreen::UseStorageRoot(UI::EventParams ¶ms) { Path pendingMemStickFolder = Path(g_externalDir); if (initialSetup_) { @@ -298,7 +380,7 @@ UI::EventReturn MemStickScreen::OnUseStorageRoot(UI::EventParams ¶ms) { return UI::EVENT_DONE; } -UI::EventReturn MemStickScreen::OnBrowse(UI::EventParams ¶ms) { +UI::EventReturn MemStickScreen::Browse(UI::EventParams ¶ms) { System_SendMessage("browse_folder", ""); return UI::EVENT_DONE; } @@ -435,7 +517,7 @@ void ConfirmMemstickMoveScreen::CreateViews() { if (!oldMemstickFolder.empty()) { std::string oldFreeSpaceText = std::string(iz->T("Free space")) + ": " + FormatSpaceString(freeSpaceOld); - rightColumn->Add(new TextView(iz->T("Old PSP Data Folder"), ALIGN_LEFT, false)); + rightColumn->Add(new TextView(iz->T("Current PSP Data Folder"), ALIGN_LEFT, false)); rightColumn->Add(new TextView(oldMemstickFolder.ToVisualString(), ALIGN_LEFT, false)); rightColumn->Add(new TextView(oldFreeSpaceText, ALIGN_LEFT, false)); } diff --git a/UI/MemStickScreen.h b/UI/MemStickScreen.h index 31a765ab4b..ef28904280 100644 --- a/UI/MemStickScreen.h +++ b/UI/MemStickScreen.h @@ -56,16 +56,32 @@ protected: private: // Event handlers - UI::EventReturn OnBrowse(UI::EventParams &e); UI::EventReturn OnHelp(UI::EventParams &e); - UI::EventReturn OnUseInternalStorage(UI::EventParams ¶ms); - UI::EventReturn OnUseStorageRoot(UI::EventParams ¶ms); - UI::EventReturn OnSetFolderManually(UI::EventParams ¶ms); + + // Confirm button sub handlers + UI::EventReturn Browse(UI::EventParams &e); + UI::EventReturn UseInternalStorage(UI::EventParams ¶ms); + UI::EventReturn UseStorageRoot(UI::EventParams ¶ms); + UI::EventReturn SetFolderManually(UI::EventParams ¶ms); + + // Button handlers. + UI::EventReturn OnConfirmClick(UI::EventParams ¶ms); + UI::EventReturn OnChoiceClick(UI::EventParams ¶ms); SettingInfoMessage *settingInfo_ = nullptr; + enum { + CHOICE_BROWSE_FOLDER, + CHOICE_PRIVATE_DIRECTORY, + CHOICE_STORAGE_ROOT, + CHOICE_SET_MANUAL, + }; + bool initialSetup_; + bool storageBrowserWorking_; bool done_ = false; + + int choice_ = 0; }; class ProgressReporter {