diff --git a/Common/CMakeLists.txt b/Common/CMakeLists.txt index 9603342672..f86cffe35a 100644 --- a/Common/CMakeLists.txt +++ b/Common/CMakeLists.txt @@ -278,6 +278,7 @@ add_library(Common STATIC Render/TextureAtlas.h Render/DrawBuffer.cpp Render/DrawBuffer.h + Render/DrawEnum.h Render/ManagedTexture.cpp Render/ManagedTexture.h Render/Text/Font.h diff --git a/Common/Common.vcxproj b/Common/Common.vcxproj index 4169ccbafc..f618df14f5 100644 --- a/Common/Common.vcxproj +++ b/Common/Common.vcxproj @@ -495,6 +495,7 @@ + @@ -1133,4 +1134,4 @@ - + \ No newline at end of file diff --git a/Common/Common.vcxproj.filters b/Common/Common.vcxproj.filters index 8780958f1d..ee58c22799 100644 --- a/Common/Common.vcxproj.filters +++ b/Common/Common.vcxproj.filters @@ -818,6 +818,9 @@ UI + + Render + @@ -1709,4 +1712,4 @@ Data\Collections - + \ No newline at end of file diff --git a/Common/Render/DrawBuffer.h b/Common/Render/DrawBuffer.h index 1bd6751e91..a5ffbf4c4e 100644 --- a/Common/Render/DrawBuffer.h +++ b/Common/Render/DrawBuffer.h @@ -5,7 +5,6 @@ #include #include #include -#include #include "Common/Common.h" #include "Common/Data/Color/RGBAUtil.h" @@ -14,34 +13,10 @@ #include "Common/Math/lin/matrix4x4.h" #include "Common/Math/math_util.h" #include "Common/GPU/thin3d.h" +#include "Common/Render/DrawEnum.h" struct Atlas; -enum { - ALIGN_LEFT = 0, - ALIGN_RIGHT = 16, - ALIGN_TOP = 0, - ALIGN_BOTTOM = 1, - ALIGN_HCENTER = 4, - ALIGN_VCENTER = 8, - ALIGN_VBASELINE = 32, // text only, possibly not yet working - - ALIGN_CENTER = ALIGN_HCENTER | ALIGN_VCENTER, - ALIGN_TOPLEFT = ALIGN_TOP | ALIGN_LEFT, - ALIGN_TOPRIGHT = ALIGN_TOP | ALIGN_RIGHT, - ALIGN_BOTTOMLEFT = ALIGN_BOTTOM | ALIGN_LEFT, - ALIGN_BOTTOMRIGHT = ALIGN_BOTTOM | ALIGN_RIGHT, - - // For "uncachable" text like debug log. - // Avoids using system font drawing as it's too slow. - // Not actually used here but is reserved for whatever system wraps DrawBuffer. - FLAG_DYNAMIC_ASCII = 2048, - FLAG_NO_PREFIX = 4096, // means to not process ampersands - FLAG_WRAP_TEXT = 8192, - FLAG_ELLIPSIZE_TEXT = 16384, - FLAG_DROP_SHADOW = 32768, // soft shadow under text (system font drawing only) -}; - namespace Draw { class Pipeline; } @@ -53,38 +28,6 @@ struct GradientStop { class TextDrawer; -// Rounded rectangles are drawn from antialiased corner images that UI/UIAtlas.cpp generates -// into the UI atlas, one per radius and style. -enum class RoundRectStyle : uint8_t { - Filled, - OutlineThin, - OutlineMedium, - OutlineThick, - COUNT, -}; - -// Corner radii in UI units, each of which gets an image per style. Other radii scale the nearest one. -// Keep them larger than the thickest outline. -constexpr float ROUND_RECT_RADII[] = { 8.0f, 16.0f, 24.0f, 32.0f }; -constexpr int ROUND_RECT_RADIUS_COUNT = (int)std::size(ROUND_RECT_RADII); -// Outline thickness in UI units, per RoundRectStyle. -constexpr float ROUND_RECT_THICKNESS[(int)RoundRectStyle::COUNT] = { 0.0f, 1.0f, 2.0f, 3.0f }; - -// Which corners of a round rect to round. The others are drawn square. -enum class RoundCorners : uint8_t { - None = 0, - TopLeft = 1, - TopRight = 2, - BottomLeft = 4, - BottomRight = 8, - Top = 3, // TopLeft | TopRight - Bottom = 12, // BottomLeft | BottomRight - Left = 5, // TopLeft | BottomLeft - Right = 10, // TopRight | BottomRight - All = 15, -}; -ENUM_CLASS_BITOPS(RoundCorners); - const char *RoundRectCornerImageName(int radiusIndex, RoundRectStyle style); // The corner image is (center + 1) texels square. The arc is centered on texel corner (center, center), // and the last row and column are the straight edge profile. diff --git a/Common/Render/DrawEnum.h b/Common/Render/DrawEnum.h new file mode 100644 index 0000000000..1a8f1441d5 --- /dev/null +++ b/Common/Render/DrawEnum.h @@ -0,0 +1,61 @@ +#pragma once + +#include +#include "Common/Common.h" + +enum { + ALIGN_LEFT = 0, + ALIGN_RIGHT = 16, + ALIGN_TOP = 0, + ALIGN_BOTTOM = 1, + ALIGN_HCENTER = 4, + ALIGN_VCENTER = 8, + ALIGN_VBASELINE = 32, // text only, possibly not yet working + + ALIGN_CENTER = ALIGN_HCENTER | ALIGN_VCENTER, + ALIGN_TOPLEFT = ALIGN_TOP | ALIGN_LEFT, + ALIGN_TOPRIGHT = ALIGN_TOP | ALIGN_RIGHT, + ALIGN_BOTTOMLEFT = ALIGN_BOTTOM | ALIGN_LEFT, + ALIGN_BOTTOMRIGHT = ALIGN_BOTTOM | ALIGN_RIGHT, + + // For "uncachable" text like debug log. + // Avoids using system font drawing as it's too slow. + // Not actually used here but is reserved for whatever system wraps DrawBuffer. + FLAG_DYNAMIC_ASCII = 2048, + FLAG_NO_PREFIX = 4096, // means to not process ampersands + FLAG_WRAP_TEXT = 8192, + FLAG_ELLIPSIZE_TEXT = 16384, + FLAG_DROP_SHADOW = 32768, // soft shadow under text (system font drawing only) +}; + +// Rounded rectangles are drawn from antialiased corner images that UI/UIAtlas.cpp generates +// into the UI atlas, one per radius and style. +enum class RoundRectStyle : uint8_t { + Filled, + OutlineThin, + OutlineMedium, + OutlineThick, + COUNT, +}; + +// Corner radii in UI units, each of which gets an image per style. Other radii scale the nearest one. +// Keep them larger than the thickest outline. +constexpr float ROUND_RECT_RADII[] = {8.0f, 16.0f, 24.0f, 32.0f}; +constexpr int ROUND_RECT_RADIUS_COUNT = ARRAY_SIZE(ROUND_RECT_RADII); +// Outline thickness in UI units, per RoundRectStyle. +constexpr float ROUND_RECT_THICKNESS[(int)RoundRectStyle::COUNT] = {0.0f, 1.0f, 2.0f, 3.0f}; + +// Which corners of a round rect to round. The others are drawn square. +enum class RoundCorners : uint8_t { + None = 0, + TopLeft = 1, + TopRight = 2, + BottomLeft = 4, + BottomRight = 8, + Top = 3, // TopLeft | TopRight + Bottom = 12, // BottomLeft | BottomRight + Left = 5, // TopLeft | BottomLeft + Right = 10, // TopRight | BottomRight + All = 15, +}; +ENUM_CLASS_BITOPS(RoundCorners); diff --git a/Common/UI/PopupScreens.cpp b/Common/UI/PopupScreens.cpp index b44146eed2..86d5d8acbc 100644 --- a/Common/UI/PopupScreens.cpp +++ b/Common/UI/PopupScreens.cpp @@ -232,6 +232,9 @@ void PopupScreen::CreateViews() { box_->Add(buttonRow); } + + // The popup layout isn't compatible with round corners. + box_->ApplyRoundCorners(RoundCorners::None); } void MessagePopupScreen::CreatePopupContents(UI::ViewGroup *parent) { diff --git a/Common/UI/TabHolder.cpp b/Common/UI/TabHolder.cpp index 32129466d9..ace2b50b77 100644 --- a/Common/UI/TabHolder.cpp +++ b/Common/UI/TabHolder.cpp @@ -81,6 +81,8 @@ void TabHolder::AddTabContents(std::string_view title, ImageID imageId, ViewGrou } else { tabStrip_->AddChoice(title); } + tabStrip_->ApplyRoundCorners(orientation_ == Orientation::ORIENT_HORIZONTAL ? RoundCorners::Left : RoundCorners::Top); + contents_->Add(tabContents); if (tabs_.size() > 1) tabContents->SetVisibility(V_GONE); @@ -101,6 +103,8 @@ void TabHolder::AddTabDeferred(std::string_view title, ImageID imageId, std::fun } else { tabStrip_->AddChoice(title); } + tabStrip_->ApplyRoundCorners(orientation_ == Orientation::ORIENT_HORIZONTAL ? RoundCorners::Left : RoundCorners::Top); + tabTweens_.push_back(nullptr); createFuncs_.push_back(createCb); } diff --git a/Common/UI/View.h b/Common/UI/View.h index 2a779570b1..abff091b42 100644 --- a/Common/UI/View.h +++ b/Common/UI/View.h @@ -24,6 +24,7 @@ #include "Common/Common.h" #include "Common/UI/Screen.h" // for DialogResult +#include "Common/Render/DrawEnum.h" // for RoundCorners #undef small @@ -527,7 +528,7 @@ protected: bool popupStyle_ = false; bool hasAutoResult_ = false; DialogResult autoResult_ = DR_OK; - RoundCorners roundCorners_{}; + RoundCorners roundCorners_ = RoundCorners::All; private: friend class ViewGroup; // Sets parent_. @@ -766,6 +767,7 @@ public: void GetContentDimensionsBySpec(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert, float &w, float &h) const override; void Draw(UIContext &dc) override; + std::string DescribeText() const override; void SetCentered(bool c) { centered_ = c; @@ -783,6 +785,7 @@ public: rightIconFlipH_ = flipH; rightIconImage_ = iconImage; } + // Only useful for things that extend Choice. void SetHideTitle(bool hide) { hideTitle_ = hide; diff --git a/Common/UI/ViewGroup.cpp b/Common/UI/ViewGroup.cpp index dcab250adc..5b90b03333 100644 --- a/Common/UI/ViewGroup.cpp +++ b/Common/UI/ViewGroup.cpp @@ -811,6 +811,29 @@ void LinearLayout::Layout() { } } +// Applies rounding to the corners of the child views, based on their position in the layout and the orientation. Masked by the outerCorners. +// Ignores invisible views. +void LinearLayout::ApplyRoundCorners(RoundCorners outerCorners) { + bool seenVisible = false; + View *lastVisible = nullptr; + for (View *view : views_) { + if (view->GetVisibility() == V_GONE) { + continue; + } + if (!seenVisible) { + // First visible view, round the leading edge. + view->SetRoundCorners((RoundCorners)((u32)(orientation_ == ORIENT_HORIZONTAL ? RoundCorners::Left : RoundCorners::Top) & (u32)outerCorners)); + seenVisible = true; + } else { + view->SetRoundCorners(RoundCorners::None); + } + lastVisible = view; + } + if (lastVisible) { + lastVisible->SetRoundCorners(lastVisible->GetRoundCorners() | (RoundCorners)((u32)(orientation_ == ORIENT_HORIZONTAL ? RoundCorners::Right : RoundCorners::Bottom) & (u32)outerCorners)); + } +} + std::string LinearLayoutList::DescribeText() const { auto u = GetI18NCategory(I18NCat::UI_ELEMENTS); return DescribeListOrdered(u->T("List:")); @@ -831,8 +854,7 @@ void ChoiceGroup::Draw(UIContext &dc) { } if (!finished_) { - views_[0]->SetRoundCorners(RoundCorners::Top); - views_[views_.size() - 1]->SetRoundCorners(views_[views_.size() - 1]->GetRoundCorners() | RoundCorners::Bottom); // Makes it work with a single item. + ApplyRoundCorners(RoundCorners::All); finished_ = true; } diff --git a/Common/UI/ViewGroup.h b/Common/UI/ViewGroup.h index 491c8e16bd..fa4d243145 100644 --- a/Common/UI/ViewGroup.h +++ b/Common/UI/ViewGroup.h @@ -8,6 +8,7 @@ #include "Common/Math/geom2d.h" #include "Common/Input/GestureDetector.h" #include "Common/UI/View.h" +#include "Common/Render/DrawEnum.h" class UIScreen; @@ -226,6 +227,10 @@ public: void SetSpacing(float spacing) { spacing_ = spacing; } + + // Applies rounding to the corners of the child views, based on their position in the layout and the orientation. Masked by the outerCorners. + void ApplyRoundCorners(RoundCorners outerCorners = RoundCorners::All); + std::string DescribeLog() const override { return (orientation_ == ORIENT_HORIZONTAL ? "LinearLayoutHoriz: " : "LinearLayoutVert: ") + View::DescribeLog(); } Margins padding; diff --git a/UI/ControlMappingScreen.cpp b/UI/ControlMappingScreen.cpp index 75681fbf97..a8e33aa725 100644 --- a/UI/ControlMappingScreen.cpp +++ b/UI/ControlMappingScreen.cpp @@ -227,26 +227,31 @@ static const BindingCategory cats[] = { {}, // sentinel }; - void ControlMappingScreen::CreateSettingsViews(UI::ViewGroup *parent) { using namespace UI; auto km = GetI18NCategory(I18NCat::KEYMAPPING); - parent->Add(new Choice(km->T("Clear All")))->OnClick.Add([](UI::EventParams &) { + + ChoiceGroup *resetChoices = parent->Add(new ChoiceGroup("", new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT, Margins(0, 12)))); + + resetChoices->Add(new Choice(km->T("Clear All")))->OnClick.Add([](UI::EventParams &) { KeyMap::ClearAllMappings(); }); - parent->Add(new Choice(km->T("Default All")))->OnClick.Add([](UI::EventParams &) { + resetChoices->Add(new Choice(km->T("Default All")))->OnClick.Add([](UI::EventParams &) { KeyMap::RestoreDefault(); }); + + ChoiceGroup *moreChoices = parent->Add(new ChoiceGroup("", new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT))); + std::string sysName = System_GetProperty(SYSPROP_NAME); // If there's a builtin controller, restore to default should suffice. No need to conf the controller on top. if (!KeyMap::HasBuiltinController(sysName) && KeyMap::GetSeenPads().size()) { - parent->Add(new Choice(km->T("Autoconfigure")))->OnClick.Handle(this, &ControlMappingScreen::OnAutoConfigure); + moreChoices->Add(new Choice(km->T("Autoconfigure")))->OnClick.Handle(this, &ControlMappingScreen::OnAutoConfigure); } - parent->Add(new Choice(km->T("Show PSP")))->OnClick.Add([this](UI::EventParams ¶ms) { + moreChoices->Add(new Choice(km->T("Show PSP")))->OnClick.Add([this](UI::EventParams ¶ms) { screenManager()->push(new VisualMappingScreen(gamePath_)); }); - parent->Add(new CheckBox(&g_Config.bAllowMappingCombos, km->T("Allow combo mappings"))); - parent->Add(new CheckBox(&g_Config.bStrictComboOrder, km->T("Strict combo input order"))); + moreChoices->Add(new CheckBox(&g_Config.bAllowMappingCombos, km->T("Allow combo mappings"))); + moreChoices->Add(new CheckBox(&g_Config.bStrictComboOrder, km->T("Strict combo input order"))); } std::string_view ControlMappingScreen::GetTitle() const { diff --git a/UI/DisplayLayoutScreen.cpp b/UI/DisplayLayoutScreen.cpp index 288f075283..a36f0da446 100644 --- a/UI/DisplayLayoutScreen.cpp +++ b/UI/DisplayLayoutScreen.cpp @@ -324,7 +324,7 @@ void DisplayLayoutScreen::CreateViews() { rightColumn->Add(new ItemHeader(gr->T("PSP display rotation"))); static const char *displayRotation[] = { "Landscape", "Portrait", "Landscape Reversed", "Portrait Reversed" }; - auto rotation = new PopupMultiChoice(&config.iInternalScreenRotation, gr->T("Rotation"), displayRotation, 1, ARRAY_SIZE(displayRotation), I18NCat::CONTROLS, screenManager()); + PopupMultiChoice *rotation = rightColumn->Add(new PopupMultiChoice(&config.iInternalScreenRotation, gr->T("Rotation"), displayRotation, 1, ARRAY_SIZE(displayRotation), I18NCat::CONTROLS, screenManager())); rotation->OnChoice.Add([this](UI::EventParams &) { // Affects the presence of the Stretch checkbox. RecreateViews(); @@ -334,8 +334,6 @@ void DisplayLayoutScreen::CreateViews() { }); rotation->SetHideTitle(true); - - rightColumn->Add(rotation); rightColumn->Add(new CheckBox(&config.bRotateControlsWithScreen, gr->T("Rotate controls")))->SetEnabledFunc([&config]() -> bool { return (!g_Config.bSkipBufferEffects || g_Config.bSoftwareRendering) && config.iInternalScreenRotation != 1; }); diff --git a/UI/GameBrowser.cpp b/UI/GameBrowser.cpp index f21ed1ea52..e9d886fcff 100644 --- a/UI/GameBrowser.cpp +++ b/UI/GameBrowser.cpp @@ -1095,6 +1095,7 @@ void GameBrowser::Refresh() { layoutChoice->AddChoice(ImageID("I_LINES")); layoutChoice->AddChoice(ImageID("I_LARGE_LIST")); layoutChoice->SetSelection((int)lastViewMode_, false); + layoutChoice->ApplyRoundCorners(); layoutChoice->OnChoice.Handle(this, &GameBrowser::LayoutChange); if (!isRecent) { topBar->Add(new Choice(ImageID("I_ROTATE_LEFT"), new LayoutParams(64.0f, 64.0f)))->OnClick.Add([=](UI::EventParams &e) { diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index 2fffa45d70..42cfd23290 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -649,15 +649,17 @@ void GameSettingsScreen::CreateAudioSettings(UI::ViewGroup *audioSettings) { auto ms = GetI18NCategory(I18NCat::MAINSETTINGS); auto di = GetI18NCategory(I18NCat::DIALOG); +#if PPSSPP_PLATFORM(ANDROID) || PPSSPP_PLATFORM(IOS) + ChoiceGroup *systemIntegrationSettings = audioSettings->Add(new ChoiceGroup("")); // Don't need a title. #if PPSSPP_PLATFORM(IOS) - CheckBox *respectSilentMode = audioSettings->Add(new CheckBox(&g_Config.bAudioRespectSilentMode, a->T("Respect silent mode"))); + CheckBox *respectSilentMode = systemIntegrationSettings->Add(new CheckBox(&g_Config.bAudioRespectSilentMode, a->T("Respect silent mode"))); respectSilentMode->OnClick.Add([=](EventParams &e) { System_Notify(SystemNotification::AUDIO_MODE_CHANGED); }); respectSilentMode->SetEnabledPtr(&g_Config.bEnableSound); #endif -#if PPSSPP_PLATFORM(ANDROID) || PPSSPP_PLATFORM(IOS) - CheckBox *mixWithOthers = audioSettings->Add(new CheckBox(&g_Config.bAudioMixWithOthers, a->T("Mix audio with other apps"))); + + CheckBox *mixWithOthers = systemIntegrationSettings->Add(new CheckBox(&g_Config.bAudioMixWithOthers, a->T("Mix audio with other apps"))); mixWithOthers->OnClick.Add([=](EventParams &e) { System_Notify(SystemNotification::AUDIO_MODE_CHANGED); }); @@ -703,7 +705,7 @@ void GameSettingsScreen::CreateAudioSettings(UI::ViewGroup *audioSettings) { g_BackgroundAudio.SFX().Play(UI::UISound::ACHIEVEMENT_UNLOCKED, achievementVolume); }); - ChoiceGroup *uiSoundSettings = audioSettings->Add(new ChoiceGroup(a->T("UI sound"))); + ChoiceGroup *uiSoundSettings = audioSettings->Add(new ChoiceGroup("")); uiSoundSettings->Add(new CheckBox(&g_Config.bUISound, a->T("UI sound"))); PopupSliderChoice *uiVolume = uiSoundSettings->Add(new PopupSliderChoice(&g_Config.iUIVolume, 0, VOLUMEHI_FULL, Config::GetDefaultValueInt(&g_Config.iUIVolume), a->T("UI volume"), screenManager())); diff --git a/UI/SavedataScreen.cpp b/UI/SavedataScreen.cpp index f556633c9d..230874aa7d 100644 --- a/UI/SavedataScreen.cpp +++ b/UI/SavedataScreen.cpp @@ -615,6 +615,7 @@ void SavedataScreen::CreateSavedataTab(UI::ViewGroup *savedata) { sortOption_ = SavedataSortOption(e.a); dataBrowser_->SetSortOption(sortOption_); }); + sortStrip->ApplyRoundCorners(); savedata->Add(sortStrip); dataBrowser_ = savedata->Add(new SavedataBrowser(savedata_dir, new LayoutParams(FILL_PARENT, FILL_PARENT))); @@ -638,6 +639,7 @@ void SavedataScreen::CreateSavestateTab(UI::ViewGroup *savestate) { sortOption_ = SavedataSortOption(e.a); stateBrowser_->SetSortOption(sortOption_); }); + sortStrip->ApplyRoundCorners(); savestate->Add(sortStrip); stateBrowser_ = savestate->Add(new SavedataBrowser(savestate_dir));