mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-11-02 12:32:03 +01:00
270 lines
8.3 KiB
C++
270 lines
8.3 KiB
C++
// Copyright (c) 2012- PPSSPP Project.
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, version 2.0 or later versions.
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License 2.0 for more details.
|
|
|
|
// A copy of the GPL 2.0 should have been included with the program.
|
|
// If not, see http://www.gnu.org/licenses/
|
|
|
|
// Official git repository and contact information can be found at
|
|
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
|
|
|
|
|
#pragma once
|
|
|
|
#include <algorithm>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <string_view>
|
|
|
|
#include "Common/File/Path.h"
|
|
#include "Common/TimeUtil.h"
|
|
#include "Common/UI/View.h"
|
|
#include "Common/UI/Root.h"
|
|
#include "Core/Config.h"
|
|
#include "Core/ConfigValues.h"
|
|
#include "UI/MiscViews.h"
|
|
|
|
class GameInfo;
|
|
|
|
// Icon dimensions in the large list, and its padding around the icon.
|
|
static constexpr float LARGE_LIST_ICON_W = 144.0f;
|
|
static constexpr float LARGE_LIST_ICON_H = 80.0f;
|
|
static constexpr float LARGE_LIST_PAD = 6.0f;
|
|
static constexpr float LARGE_LIST_LEFT_PAD = 11.0f;
|
|
static constexpr float LARGE_LIST_INFO_SIZE = 64.0f;
|
|
|
|
enum class GameButtonType {
|
|
Game,
|
|
Other,
|
|
Dir,
|
|
};
|
|
|
|
class GameButton : public UI::Clickable {
|
|
public:
|
|
GameButton(GameButtonType type, const Path &gamePath, GameBrowserViewMode viewMode, UI::LayoutParams *layoutParams = nullptr)
|
|
: UI::Clickable(layoutParams), type_(type), viewMode_(viewMode), gamePath_(gamePath) {}
|
|
|
|
void Draw(UIContext &dc) override;
|
|
std::string DescribeText() const override;
|
|
std::string SearchAlias() const override {
|
|
// Without the extension, or searching for "iso" would match every ISO. Only three or more
|
|
// letters counts as one, so a folder like "Game v1.2" keeps its version number.
|
|
std::string filename = gamePath_.GetFilename();
|
|
const std::string ext = gamePath_.GetFileExtension();
|
|
if (ext.size() >= 4 && ext.size() <= filename.size() && std::all_of(ext.begin() + 1, ext.end(), [](char c) { return c >= 'a' && c <= 'z'; })) {
|
|
filename.resize(filename.size() - ext.size());
|
|
}
|
|
return filename;
|
|
}
|
|
void GetContentDimensions(const UIContext &dc, float &w, float &h) const override;
|
|
|
|
bool IsHoldEnabled() const {
|
|
return type_ == GameButtonType::Game;
|
|
}
|
|
bool Touch(const TouchInput &input) override {
|
|
hovering_ = bounds_.Contains(input.x, input.y);
|
|
if (HasInfoButton() && TouchInfoButton(input)) {
|
|
return true;
|
|
}
|
|
const bool retval = UI::Clickable::Touch(input);
|
|
if (hovering_ && (input.flags & TouchInputFlags::DOWN)) {
|
|
holdStart_ = time_now_d();
|
|
}
|
|
if (input.flags & TouchInputFlags::UP) {
|
|
holdStart_ = 0;
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
bool Key(const KeyInput &key) override {
|
|
bool showInfo = false;
|
|
|
|
if (HasFocus() && UI::IsInfoKey(key)) {
|
|
// If it's the button that's mapped to triangle, then show the info.
|
|
if (key.flags & KeyInputFlags::DOWN) {
|
|
infoKeyPressed_ = true;
|
|
}
|
|
if ((key.flags & KeyInputFlags::UP) && infoKeyPressed_) {
|
|
showInfo = true;
|
|
infoKeyPressed_ = false;
|
|
}
|
|
} else if (hovering_ && key.deviceId == DEVICE_ID_MOUSE && key.keyCode == NKCODE_EXT_MOUSEBUTTON_2) {
|
|
// If it's the right mouse button, and it's not otherwise mapped, show the info also.
|
|
if (key.flags & KeyInputFlags::DOWN) {
|
|
showInfoPressed_ = true;
|
|
}
|
|
if ((key.flags & KeyInputFlags::UP) && showInfoPressed_) {
|
|
showInfo = true;
|
|
showInfoPressed_ = false;
|
|
}
|
|
}
|
|
|
|
if (showInfo) {
|
|
TriggerOnHoldClick();
|
|
return true;
|
|
}
|
|
|
|
if (infoSelected_ && HasFocus() && UI::IsAcceptKey(key)) {
|
|
if (key.flags & KeyInputFlags::DOWN) {
|
|
infoButtonDown_ = true;
|
|
}
|
|
if ((key.flags & KeyInputFlags::UP) && infoButtonDown_) {
|
|
infoButtonDown_ = false;
|
|
TriggerOnHoldClick();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Holding the accept button works like a long press, so a gamepad can do it too.
|
|
if (IsHoldEnabled() && HasFocus() && UI::IsAcceptKey(key) && !(key.flags & KeyInputFlags::IS_REPEAT)) {
|
|
if (key.flags & KeyInputFlags::DOWN) {
|
|
holdStart_ = time_now_d();
|
|
}
|
|
if (key.flags & KeyInputFlags::UP) {
|
|
holdStart_ = 0.0;
|
|
}
|
|
}
|
|
|
|
return Clickable::Key(key);
|
|
}
|
|
|
|
void Update() override {
|
|
// Hold button for 1.5 seconds to launch the game options
|
|
if (IsHoldEnabled() && holdStart_ != 0.0 && holdStart_ < time_now_d() - 1.5) {
|
|
TriggerOnHoldClick();
|
|
}
|
|
}
|
|
|
|
void FocusChanged(UI::FocusFlags focusFlags) override {
|
|
UI::Clickable::FocusChanged(focusFlags);
|
|
if (focusFlags & UI::FocusFlags::LOST_FOCUS) {
|
|
// Don't let a held accept button carry over from a button we've left.
|
|
holdStart_ = 0.0;
|
|
// Moving to another game selects the game, not its info button.
|
|
infoSelected_ = false;
|
|
infoButtonDown_ = false;
|
|
}
|
|
TriggerOnHighlight(focusFlags);
|
|
}
|
|
|
|
// Left and right move between the game and its info button, so up and down still go from game to game.
|
|
bool CanMoveFocus(UI::FocusMove dir) const override {
|
|
if (!HasInfoButton()) {
|
|
return true;
|
|
}
|
|
// We handle these moves ourselves, so MoveFocus won't play the sound for them.
|
|
if (dir == UI::FocusMove::RIGHT && !infoSelected_) {
|
|
infoSelected_ = true;
|
|
UI::PlayUISound(UI::UISound::SELECT);
|
|
return false;
|
|
}
|
|
if (dir == UI::FocusMove::LEFT && infoSelected_) {
|
|
infoSelected_ = false;
|
|
UI::PlayUISound(UI::UISound::SELECT);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void DeviceLost() override {
|
|
if (video_) {
|
|
video_->DeviceLost();
|
|
}
|
|
}
|
|
|
|
// Previously in DirButton
|
|
|
|
// This only determines if the little pin icon is shown.
|
|
void SetPinned(bool pin) { pinned_ = pin; }
|
|
bool IsPinned() const { return pinned_; }
|
|
|
|
void SetAsAbsolute() { absolute_ = true; }
|
|
bool NavigateAsAbsolute() const { return absolute_; }
|
|
|
|
const Path &GetPath() const { return gamePath_; }
|
|
|
|
UI::Event OnHoldClick;
|
|
UI::Event OnHighlight;
|
|
|
|
private:
|
|
void TriggerOnHoldClick() {
|
|
holdStart_ = 0.0;
|
|
UI::EventParams e{};
|
|
e.v = this;
|
|
e.s = gamePath_.ToString();
|
|
down_ = false;
|
|
OnHoldClick.Trigger(e);
|
|
}
|
|
void TriggerOnHighlight(UI::FocusFlags focusFlags) {
|
|
UI::EventParams e{};
|
|
e.v = this;
|
|
e.s = gamePath_.ToString();
|
|
e.a = (u32)focusFlags;
|
|
OnHighlight.Trigger(e);
|
|
}
|
|
|
|
void UpdateVideo(const std::shared_ptr<GameInfo> &ginfo, bool wantVideo);
|
|
|
|
// The large list has room for an info button on each line, doing the same as a long press.
|
|
bool HasInfoButton() const {
|
|
return viewMode_ == GameBrowserViewMode::LARGE_LIST && type_ == GameButtonType::Game;
|
|
}
|
|
Bounds InfoButtonBounds() const {
|
|
return Bounds(bounds_.x2() - LARGE_LIST_INFO_SIZE - LARGE_LIST_PAD, bounds_.centerY() - LARGE_LIST_INFO_SIZE * 0.5f, LARGE_LIST_INFO_SIZE, LARGE_LIST_INFO_SIZE);
|
|
}
|
|
// Returns true if the info button took the input.
|
|
bool TouchInfoButton(const TouchInput &input) {
|
|
if ((input.flags & TouchInputFlags::MOUSE) && (input.buttons & 1) == 0) {
|
|
return false;
|
|
}
|
|
const bool inside = InfoButtonBounds().Contains(input.x, input.y);
|
|
if (input.flags & TouchInputFlags::DOWN) {
|
|
infoButtonDown_ = inside;
|
|
}
|
|
if (!infoButtonDown_) {
|
|
return false;
|
|
}
|
|
if (input.flags & TouchInputFlags::UP) {
|
|
infoButtonDown_ = false;
|
|
if (inside && !(input.flags & TouchInputFlags::CANCEL)) {
|
|
TriggerOnHoldClick();
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
const GameBrowserViewMode viewMode_;
|
|
const Path gamePath_;
|
|
std::string title_;
|
|
|
|
// The ICON1.PMF video, played in place of the icon in the large list, like the XMB does.
|
|
std::unique_ptr<PMFVideoTexture> video_;
|
|
Instant videoStart_;
|
|
|
|
double holdStart_ = 0.0;
|
|
bool holdEnabled_ = true;
|
|
bool showInfoPressed_ = false;
|
|
bool infoKeyPressed_ = false;
|
|
bool hovering_ = false;
|
|
bool infoButtonDown_ = false;
|
|
// The info button has the keyboard focus, rather than the game. Changed from CanMoveFocus, hence mutable.
|
|
mutable bool infoSelected_ = false;
|
|
|
|
// Previously in DirButton
|
|
bool pinned_ = false;
|
|
bool absolute_ = false;
|
|
|
|
GameButtonType type_;
|
|
};
|
|
|
|
// Also used for the folder buttons.
|
|
void DrawIconWithTextGrid(UIContext &dc, ImageID image, std::string_view text, const Bounds &bounds, const UI::Style &style);
|
|
void DrawIconWithTextList(UIContext &dc, ImageID image, std::string_view text, const Bounds &bounds, GameBrowserViewMode viewMode, const UI::Style &style, std::string_view rightText = {});
|