Merge pull request #21006 from hrydgard/new-game-screen

Rework the game screen to work better in portrait mode
This commit is contained in:
Henrik Rydgård authored and GitHub committed 2025-11-15 22:03:57 +01:00
commit fa3dc9a3a0
32 files changed
+440 -192

No files matched your search

+8
View File
@@ -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;
+7
View File
@@ -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) {
+16 -1
View File
@@ -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<bool> enabled_;
};
class PopupCallbackScreen : public PopupScreen {
public:
PopupCallbackScreen(std::function<void(UI::ViewGroup *)> 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<void(UI::ViewGroup *)> createViews_;
};
// Reads and writes value to determine the current selection.
class PopupMultiChoice : public AbstractChoiceWithValueDisplay {
public:
+32 -4
View File
@@ -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.
+1
View File
@@ -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!
+8 -1
View File
@@ -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<void()> 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));
+1 -1
View File
@@ -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<void()> contextMenuCallback, LayoutParams *layoutParams);
template <class T>
T *AddTab(std::string_view title, ImageID imageId, T *tabContents) {
+3
View File
@@ -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);
};
+9 -1
View File
@@ -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<bool()> 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)
+2
View File
@@ -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)
+40 -2
View File
@@ -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 {
+5 -5
View File
@@ -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);
+4 -4
View File
@@ -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:
+1 -1
View File
@@ -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));
+143 -107
View File
@@ -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<GameInfo> 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<GameDBInfo> 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<GameDBInfo> 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<GameDBInfo> 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<GameInfo> info = g_gameInfoCache->GetInfo(NULL, gamePath_, GameInfoFlags::PARAM_SFO, &hasFlags);
std::shared_ptr<GameInfo> 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<GameInfo> 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<GameDBInfo> 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<GameInfo> 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<GameInfo> 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<GameInfo> info = g_gameInfoCache->GetInfo(NULL, gamePath_, GameInfoFlags::PARAM_SFO | GameInfoFlags::SIZE);
if (info) {
std::shared_ptr<GameInfo> 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<GameInfo> info = g_gameInfoCache->GetInfo(NULL, gamePath, GameInfoFlags::PARAM_SFO);
info->DeleteAllSaveData();
info->saveDataSize = 0;
info->installDataSize = 0;
std::shared_ptr<GameInfo> 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<GameInfo> info = g_gameInfoCache->GetInfo(NULL, gamePath_, GameInfoFlags::PARAM_SFO);
if (info->Ready(GameInfoFlags::PARAM_SFO)) {
std::shared_ptr<GameInfo> 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<GameInfo> info = g_gameInfoCache->GetInfo(NULL, gamePath, GameInfoFlags::PARAM_SFO);
info->Delete();
std::shared_ptr<GameInfo> info_ = g_gameInfoCache->GetInfo(NULL, gamePath, GameInfoFlags::PARAM_SFO);
info_->Delete();
g_gameInfoCache->Clear();
g_recentFiles.Remove(gamePath.c_str());
sm->switchScreen(new MainScreen());
+10 -3
View File
@@ -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<GameInfo> info_;
mutable std::string titleCache_;
};
+15 -9
View File
@@ -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));
+3 -2
View File
@@ -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;
+24 -7
View File
@@ -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;
+8 -4
View File
@@ -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)
+12 -1
View File
@@ -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 {
+3 -3
View File
@@ -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_));
+8 -8
View File
@@ -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<SavedataButton *>(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<SavedataButton *>(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);
+2 -2
View File
@@ -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);
+40 -12
View File
@@ -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<UIScreen>(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;
}
+12 -3
View File
@@ -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;
};
+13 -3
View File
@@ -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<void()> 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_);
+3 -1
View File
@@ -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;
+2 -2
View File
@@ -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);
+3 -3
View File
@@ -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;
+1 -1
View File
@@ -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<UIScreen>(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);
+1 -1
View File
@@ -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));
}
}