#include "Common/UI/View.h" #include "Common/UI/Context.h" #include "Common/Render/DrawBuffer.h" #include "Common/System/Request.h" #include "Common/System/Display.h" #include "Common/TimeUtil.h" #include "Common/Data/Text/I18n.h" #include "Common/Math/curves.h" #include "Common/StringUtils.h" #include "UI/MiscViews.h" #include "UI/GameInfoCache.h" #include "Core/Config.h" TextWithImage::TextWithImage(ImageID imageID, std::string_view text, UI::LinearLayoutParams *layoutParams) : UI::LinearLayout(ORIENT_HORIZONTAL, layoutParams) { using namespace UI; SetSpacing(8.0f); if (!layoutParams) { layoutParams_->width = FILL_PARENT; layoutParams_->height = ITEM_HEIGHT; } if (imageID.isValid()) { Add(new ImageView(imageID, "", UI::IS_DEFAULT, new LinearLayoutParams(0.0f, UI::Gravity::G_VCENTER))); } Add(new TextView(text, new LinearLayoutParams(1.0f, UI::Gravity::G_VCENTER))); } CopyableText::CopyableText(ImageID imageID, std::string_view text, UI::LinearLayoutParams *layoutParams) : UI::LinearLayout(ORIENT_HORIZONTAL, layoutParams) { using namespace UI; SetSpacing(8.0f); if (!layoutParams) { layoutParams_->width = FILL_PARENT; layoutParams_->height = ITEM_HEIGHT; } if (imageID.isValid()) { Add(new ImageView(imageID, "", UI::IS_DEFAULT, new LinearLayoutParams(0.0f, UI::Gravity::G_VCENTER))); } Add(new TextView(text, new LinearLayoutParams(1.0f, UI::Gravity::G_VCENTER)))->SetBig(true); std::string textStr(text); // We need to store the text in the lambda context. Add(new Choice(ImageID("I_FILE_COPY"), new LinearLayoutParams()))->OnClick.Add([textStr](UI::EventParams &) { System_CopyStringToClipboard(textStr); }); } 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) { layoutParams_->width = UI::FILL_PARENT; layoutParams_->height = 64.0f; } auto dlg = GetI18NCategory(I18NCat::DIALOG); backButton_ = Add(new Choice(ImageID("I_NAVIGATE_BACK"), new LinearLayoutParams(ITEM_HEIGHT, ITEM_HEIGHT))); backButton_->OnClick.Add([](UI::EventParams &e) { e.bubbleResult = DR_BACK; }); if (!title.empty()) { TextView *titleView = Add(new TextView(title, ALIGN_VCENTER | FLAG_WRAP_TEXT, false, new LinearLayoutParams(1.0f, Gravity::G_VCENTER))); titleView->SetTextColor(ctx.GetTheme().itemDownStyle.fgColor); titleView->SetBig(true); // 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) : UI::LinearLayout(ORIENT_HORIZONTAL, lp), cutOffY_(cutOffY) { using namespace UI; SetSpacing(0.0f); Add(new Spacer(10.0f)); text_ = Add(new TextView("", align, false, new LinearLayoutParams(1.0, Margins(0, 10)))); Add(new Spacer(10.0f)); } void SettingInfoMessage::Show(std::string_view text, const UI::View *refView) { if (refView) { Bounds b = refView->GetBounds(); const UI::AnchorLayoutParams *lp = GetLayoutParams()->As(); if (lp) { if (cutOffY_ != -1.0f && b.y >= cutOffY_) { ReplaceLayoutParams(new UI::AnchorLayoutParams(lp->width, lp->height, lp->left, 80.0f, lp->right, lp->bottom, lp->centering)); } else { ReplaceLayoutParams(new UI::AnchorLayoutParams(lp->width, lp->height, lp->left, g_display.dp_yres - 80.0f - 40.0f, lp->right, lp->bottom, lp->centering)); } } } if (text_) { text_->SetText(text); } timeShown_ = time_now_d(); } void SettingInfoMessage::Draw(UIContext &dc) { static const double FADE_TIME = 1.0; static const float MAX_ALPHA = 0.9f; // Let's show longer messages for more time (guesstimate at reading speed.) // Note: this will give multibyte characters more time, but they often have shorter words anyway. double timeToShow = std::max(1.5, text_->GetText().size() * 0.05); double sinceShow = time_now_d() - timeShown_; float alpha = MAX_ALPHA; if (timeShown_ == 0.0 || sinceShow > timeToShow + FADE_TIME) { alpha = 0.0f; } else if (sinceShow > timeToShow) { alpha = MAX_ALPHA - MAX_ALPHA * (float)((sinceShow - timeToShow) / FADE_TIME); } UI::Style style = dc.GetTheme().tooltipStyle; if (alpha >= 0.001f) { uint32_t bgColor = alphaMul(style.background.color, alpha); dc.FillRect(UI::Drawable(bgColor), bounds_); } uint32_t textColor = alphaMul(style.fgColor, alpha); text_->SetTextColor(textColor); ViewGroup::Draw(dc); showing_ = sinceShow <= timeToShow; // Don't consider fade time } std::string SettingInfoMessage::GetText() const { return (showing_ && text_) ? text_->GetText() : ""; } void ShinyIcon::Draw(UIContext &dc) { UI::DrawIconShine(dc, bounds_, 1.0f, animated_); UI::ImageView::Draw(dc); } class SimpleGameIconView : public UI::InertView { public: SimpleGameIconView(const Path &gamePath, UI::LayoutParams *layoutParams = 0) : InertView(layoutParams), gamePath_(gamePath) { } void GetContentDimensions(const UIContext &dc, float &w, float &h) const override { h = UI::ITEM_HEIGHT; float aspect = 1.0f; if (textureHeight_ > 0) { aspect = static_cast(textureWidth_) / static_cast(textureHeight_); } w = h * aspect; } void Draw(UIContext &dc) override { std::shared_ptr info = g_gameInfoCache->GetInfo(dc.GetDrawContext(), gamePath_, GameInfoFlags::ICON); if (!info->Ready(GameInfoFlags::ICON) || !info->icon.texture) { return; } Draw::Texture *texture = info->icon.texture; textureWidth_ = texture->Width(); textureHeight_ = texture->Height(); dc.Flush(); dc.GetDrawContext()->BindTexture(0, texture); dc.Draw()->Rect(bounds_.x, bounds_.y, bounds_.w, bounds_.h, 0xFFFFFFFF); dc.Flush(); dc.RebindTexture(); // Draw the gear icon const AtlasImage *gearImage = dc.Draw()->GetAtlas()->getImage(ImageID("I_GEAR_SMALL")); dc.Draw()->DrawImage(ImageID("I_GEAR_SMALL"), bounds_.x + 1, bounds_.y2() - gearImage->h - 1, 1.0f); } std::string DescribeText() const override { return ""; } private: Path gamePath_; int textureWidth_ = 0; int textureHeight_ = 0; }; PaneTitleBar::PaneTitleBar(const Path &gamePath, std::string_view title, const std::string_view settingsCategory, UI::LayoutParams *layoutParams) : UI::LinearLayout(ORIENT_HORIZONTAL, layoutParams), gamePath_(gamePath) { using namespace UI; SetSpacing(10.0f); if (!layoutParams) { layoutParams_->width = UI::FILL_PARENT; layoutParams_->height = ITEM_HEIGHT; } auto dlg = GetI18NCategory(I18NCat::DIALOG); if (!title.empty()) { SimpleTextView *titleView = Add(new SimpleTextView(title, new LinearLayoutParams(0.0f, Gravity::G_VCENTER, Margins(10, 0, 20, 0)))); titleView->SetBig(true); // If using HCENTER, to balance the centering, add a spacer on the right. } Add(new Spacer(10.0f, new LinearLayoutParams(1.0f))); // Now add the game icon. if (!gamePath.empty() && g_Config.IsGameSpecific()) { Add(new SimpleGameIconView(gamePath_, new LinearLayoutParams(WRAP_CONTENT, WRAP_CONTENT))); } if (!settingsCategory.empty()) { std::string settingsUrl; if (settingsCategory[0] == '/') { settingsUrl = join("https://www.ppsspp.org", settingsCategory); } else { settingsUrl = join("https://www.ppsspp.org/docs/settings/", settingsCategory); } Choice *helpButton = Add(new Choice(ImageID("I_INFO"), new LinearLayoutParams())); helpButton->OnClick.Add([settingsUrl](UI::EventParams &) { System_LaunchUrl(LaunchUrlType::BROWSER_URL, settingsUrl); }); } }