mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-11-01 20:18:15 +01:00
ChoiceGroups no longer have hardcoded margins. A new ChoiceGroupList holds them, with padding and spacing that give the same look as before. Settings tabs, developer tools, the two-pane settings columns, the pause menu and the rest put their groups in one, and a few screens adjust its padding and spacing to their layout. Also add the two groups in the remote disc streaming settings that were created but never added, and make the settings search look inside groups: it only checked a tab's direct children, so with ChoiceGroups a filter hid everything. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
368 lines
13 KiB
C++
368 lines
13 KiB
C++
#pragma once
|
|
|
|
#include <cfloat>
|
|
#include <vector>
|
|
#include <set>
|
|
#include <mutex>
|
|
|
|
#include "Common/Math/geom2d.h"
|
|
#include "Common/Input/GestureDetector.h"
|
|
#include "Common/UI/View.h"
|
|
#include "Common/Render/DrawEnum.h"
|
|
|
|
class UIScreen;
|
|
|
|
namespace UI {
|
|
|
|
class AnchorTranslateTween;
|
|
class ScrollView;
|
|
|
|
// For the arrow directions: whether candidate is a better place to move the focus to from origin
|
|
// than best (null if there's nothing yet). originPos is where the move starts across the
|
|
// direction of the move, normally origin's center.
|
|
bool IsBetterFocusCandidate(const Bounds &origin, const Point2D &originPos, const Bounds &candidate, const Bounds *best, FocusMove direction);
|
|
|
|
struct NeighborResult {
|
|
NeighborResult() : view(nullptr), score(0) {}
|
|
NeighborResult(View *v, float s) : view(v), score(s) {}
|
|
View *view;
|
|
float score;
|
|
};
|
|
|
|
class ViewGroup : public View {
|
|
public:
|
|
ViewGroup(LayoutParams *layoutParams = 0) : View(layoutParams) {}
|
|
~ViewGroup();
|
|
|
|
// Pass through external events to children.
|
|
bool Key(const KeyInput &input) override;
|
|
bool Touch(const TouchInput &input) override;
|
|
void Axis(const AxisInput &input) override;
|
|
|
|
// By default, a container will layout to its own bounds.
|
|
void Measure(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert) override = 0;
|
|
void Layout() override = 0;
|
|
void Update() override;
|
|
void Query(float x, float y, std::vector<View *> &list) override;
|
|
|
|
void DeviceLost() override;
|
|
void DeviceRestored(Draw::DrawContext *draw) override;
|
|
|
|
void Draw(UIContext &dc) override;
|
|
|
|
// Takes ownership! DO NOT add a view to multiple parents!
|
|
template <class T>
|
|
T *Add(T *view) {
|
|
views_.push_back(view);
|
|
SetParentOf(view);
|
|
return view;
|
|
}
|
|
template <class T>
|
|
T *Insert(int index, T *view) {
|
|
views_.insert(views_.begin() + index, view);
|
|
SetParentOf(view);
|
|
return view;
|
|
}
|
|
|
|
// Note: This deletes the old view, if found. Returns whether the view was found.
|
|
// If it fails, the newView is deleted.
|
|
bool ReplaceSubview(View *view, View *newView);
|
|
|
|
bool SetFocus(FocusFlags cause) override;
|
|
bool SubviewFocused(View *view) override;
|
|
virtual void RemoveSubview(View *view);
|
|
|
|
void SetDefaultFocusView(View *view) { defaultFocusView_ = view; }
|
|
View *GetDefaultFocusView() { return defaultFocusView_; }
|
|
|
|
// Assumes that layout has taken place.
|
|
NeighborResult FindNeighbor(View *view, FocusMove direction, NeighborResult best);
|
|
virtual NeighborResult FindScrollNeighbor(View *view, const Point2D &target, FocusMove direction, NeighborResult best);
|
|
|
|
// Appends the views Tab/Shift+Tab step through, in the order they were added, depth first -
|
|
// so the order follows the hierarchy rather than the geometry, which is what makes tabbing
|
|
// predictable in a layout that arrow navigation has to guess its way around.
|
|
// Unlike FindNeighbor, this doesn't need layout to have taken place.
|
|
virtual void CollectTabOrder(std::vector<View *> *outViews) const;
|
|
|
|
bool CanBeFocused() const override { return false; }
|
|
bool IsViewGroup() const override { return true; }
|
|
bool ContainsSubview(const View *view) const override;
|
|
int IndexOfSubview(const View *view) const;
|
|
|
|
virtual void SetBG(const Drawable &bg) { bg_ = bg; }
|
|
|
|
void Clear();
|
|
void PersistData(PersistStatus status, std::string anonId, PersistMap &storage) override;
|
|
View *GetViewByIndex(int index) const { return views_[index]; }
|
|
int GetNumSubviews() const { return (int)views_.size(); }
|
|
void SetHasDropShadow(bool has) { hasDropShadow_ = has; }
|
|
void SetDropShadowExpand(float s) { dropShadowExpand_ = s; }
|
|
void SetExclusiveTouch(bool exclusive) { exclusiveTouch_ = exclusive; }
|
|
void SetClickableBackground(bool clickableBackground) { clickableBackground_ = clickableBackground; }
|
|
|
|
void SetClip(bool clip) { clip_ = clip; }
|
|
std::string DescribeLog() const override { return "ViewGroup: " + View::DescribeLog(); }
|
|
std::string DescribeText() const override;
|
|
|
|
void Recurse(std::function<void(View *)> func) override;
|
|
|
|
protected:
|
|
std::string DescribeListUnordered(std::string_view heading) const;
|
|
std::string DescribeListOrdered(std::string_view heading) const;
|
|
|
|
// Anything that adds to views_ without Add or Insert must call this.
|
|
void SetParentOf(View *view) { view->parent_ = this; }
|
|
|
|
std::vector<View *> views_;
|
|
View *defaultFocusView_ = nullptr;
|
|
Drawable bg_;
|
|
float dropShadowExpand_ = 0.0f;
|
|
bool hasDropShadow_ = false;
|
|
bool clickableBackground_ = false;
|
|
bool clip_ = false;
|
|
bool exclusiveTouch_ = false;
|
|
};
|
|
|
|
constexpr float NONE = -FLT_MAX;
|
|
|
|
enum class Centering {
|
|
None = 0,
|
|
Vertical = 1,
|
|
Horizontal = 2,
|
|
Both = 3, // 1 | 2
|
|
};
|
|
ENUM_CLASS_BITOPS(Centering);
|
|
|
|
class AnchorLayoutParams : public LayoutParams {
|
|
public:
|
|
AnchorLayoutParams(Size w, Size h, float l, float t, float r, float b, Centering c = Centering::None)
|
|
: LayoutParams(w, h, LP_ANCHOR), left(l), top(t), right(r), bottom(b), centering(c) {}
|
|
// There's a small hack here to make this behave more intuitively - AnchorLayout ordinarily ignores FILL_PARENT.
|
|
AnchorLayoutParams(Size w, Size h, Centering c = Centering::None)
|
|
: LayoutParams(w, h, LP_ANCHOR), left(0), top(0), right(w == FILL_PARENT ? 0 : NONE), bottom(h == FILL_PARENT ? 0 : NONE), centering(c) {
|
|
}
|
|
AnchorLayoutParams(float l, float t, float r, float b, Centering c = Centering::None)
|
|
: LayoutParams(WRAP_CONTENT, WRAP_CONTENT, LP_ANCHOR), left(l), top(t), right(r), bottom(b), centering(c) {}
|
|
|
|
// These are not bounds, but distances from the container edges.
|
|
// Set to NONE to not attach this edge to the container.
|
|
// If two opposite edges are NONE, centering will happen.
|
|
float left, top, right, bottom;
|
|
Centering centering; // If set, only two "sides" can be set, and they refer to the center, not the edge, of the view being layouted.
|
|
|
|
static LayoutParamsType StaticType() {
|
|
return LP_ANCHOR;
|
|
}
|
|
};
|
|
|
|
class AnchorLayout : public ViewGroup {
|
|
public:
|
|
AnchorLayout(LayoutParams *layoutParams = 0) : ViewGroup(layoutParams) {}
|
|
void Measure(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert) override;
|
|
void Layout() override;
|
|
void Overflow(bool allow) {
|
|
overflow_ = allow;
|
|
}
|
|
std::string DescribeLog() const override { return "AnchorLayout: " + View::DescribeLog(); }
|
|
|
|
private:
|
|
void MeasureViews(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert);
|
|
bool overflow_ = true;
|
|
};
|
|
|
|
class LinearLayoutParams : public LayoutParams {
|
|
public:
|
|
LinearLayoutParams()
|
|
: LayoutParams(LP_LINEAR), weight(0.0f), gravity(Gravity::G_TOPLEFT), hasMargins_(false) {}
|
|
explicit LinearLayoutParams(float wgt, Gravity grav = Gravity::G_TOPLEFT)
|
|
: LayoutParams(LP_LINEAR), weight(wgt), gravity(grav), hasMargins_(false) {}
|
|
LinearLayoutParams(float wgt, const Margins &mgn)
|
|
: LayoutParams(LP_LINEAR), weight(wgt), gravity(Gravity::G_TOPLEFT), margins(mgn), hasMargins_(true) {}
|
|
LinearLayoutParams(float wgt, Gravity grav, const Margins &mgn)
|
|
: LayoutParams(LP_LINEAR), weight(wgt), gravity(grav), margins(mgn), hasMargins_(true) {}
|
|
LinearLayoutParams(Size w, Size h, float wgt = 0.0f, Gravity grav = Gravity::G_TOPLEFT)
|
|
: LayoutParams(w, h, LP_LINEAR), weight(wgt), gravity(grav), margins(0), hasMargins_(false) {}
|
|
LinearLayoutParams(Size w, Size h, float wgt, Gravity grav, const Margins &mgn)
|
|
: 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)
|
|
: LayoutParams(WRAP_CONTENT, WRAP_CONTENT, LP_LINEAR), weight(0.0f), gravity(Gravity::G_TOPLEFT), margins(mgn), hasMargins_(true) {}
|
|
|
|
float weight;
|
|
Gravity gravity;
|
|
Margins margins;
|
|
|
|
bool HasMargins() const { return hasMargins_; }
|
|
|
|
static LayoutParamsType StaticType() {
|
|
return LP_LINEAR;
|
|
}
|
|
|
|
private:
|
|
bool hasMargins_;
|
|
};
|
|
|
|
class LinearLayout : public ViewGroup {
|
|
public:
|
|
LinearLayout(Orientation orientation, LayoutParams *layoutParams = 0)
|
|
: ViewGroup(layoutParams), orientation_(orientation), defaultMargins_(0) {}
|
|
|
|
void Measure(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert) override;
|
|
void Layout() override;
|
|
void SetSpacing(float spacing) {
|
|
spacing_ = spacing;
|
|
}
|
|
// Space between the layout's edges and its children, on top of the children's own margins.
|
|
void SetPadding(const Margins &padding) {
|
|
padding_ = padding;
|
|
}
|
|
// Where the children go along the layout direction when they don't fill it, as a group. Only the
|
|
// gravity's component along the layout direction is used (G_HCENTER or G_RIGHT when horizontal,
|
|
// G_VCENTER or G_BOTTOM when vertical). Each child's own gravity still places it across.
|
|
void SetContentGravity(Gravity gravity) {
|
|
contentGravity_ = gravity;
|
|
}
|
|
|
|
// 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(); }
|
|
|
|
protected:
|
|
Orientation orientation_;
|
|
private:
|
|
Margins defaultMargins_;
|
|
Margins padding_;
|
|
Gravity contentGravity_ = Gravity::G_TOPLEFT;
|
|
float spacing_ = 10.0f;
|
|
};
|
|
|
|
class LinearLayoutList : public LinearLayout {
|
|
public:
|
|
LinearLayoutList(Orientation orientation, LayoutParams *layoutParams = nullptr)
|
|
: LinearLayout(orientation, layoutParams) {
|
|
}
|
|
|
|
// The only difference with a LinearLayout is the DescribeText, which is a list of the items in the layout.
|
|
// Not sure if we really need this.
|
|
std::string DescribeText() const override;
|
|
};
|
|
|
|
// These are pretty vertical LinearLayout, with a title and a roundrect background.
|
|
// They have no margins: put them in a ChoiceGroupList, or another layout that spaces them out.
|
|
class ChoiceGroup : public LinearLayout {
|
|
public:
|
|
ChoiceGroup(std::string_view title, LayoutParams *layoutParams = nullptr);
|
|
|
|
void Draw(UIContext &dc) override;
|
|
|
|
private:
|
|
std::string title_;
|
|
};
|
|
|
|
// A vertical list that spaces out the ChoiceGroups in it and keeps them off its edges.
|
|
class ChoiceGroupList : public LinearLayoutList {
|
|
public:
|
|
ChoiceGroupList(LayoutParams *layoutParams = nullptr);
|
|
};
|
|
|
|
// GridLayout is a little different from the Android layout. This one has fixed size
|
|
// rows and columns, filled row by row. Items are not allowed to deviate from the set sizes.
|
|
struct GridLayoutSettings {
|
|
GridLayoutSettings() : columnWidth(100), rowHeight(50), spacing(5), fillCells(false) {}
|
|
GridLayoutSettings(int colW, int colH, int spac = 5)
|
|
: columnWidth(colW), rowHeight(colH), spacing(spac), fillCells(false) {}
|
|
|
|
int columnWidth;
|
|
int rowHeight;
|
|
int spacing;
|
|
bool fillCells;
|
|
};
|
|
|
|
class GridLayoutParams : public LayoutParams {
|
|
public:
|
|
GridLayoutParams()
|
|
: LayoutParams(LP_GRID), gravity(Gravity::G_CENTER) {}
|
|
explicit GridLayoutParams(Gravity grav)
|
|
: LayoutParams(LP_GRID), gravity(grav) {
|
|
}
|
|
GridLayoutParams(Gravity grav, const Margins &mgn)
|
|
: LayoutParams(LP_GRID), gravity(grav), margins(mgn) {
|
|
}
|
|
|
|
Gravity gravity;
|
|
Margins margins;
|
|
|
|
static LayoutParamsType StaticType() {
|
|
return LP_GRID;
|
|
}
|
|
};
|
|
|
|
class GridLayout : public ViewGroup {
|
|
public:
|
|
GridLayout(GridLayoutSettings settings, LayoutParams *layoutParams = 0);
|
|
|
|
void Measure(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert) override;
|
|
void Layout() override;
|
|
std::string DescribeLog() const override { return "GridLayout: " + View::DescribeLog(); }
|
|
|
|
// Space between the layout's edges and the cells.
|
|
void SetPadding(const Margins &padding) {
|
|
padding_ = padding;
|
|
}
|
|
|
|
private:
|
|
GridLayoutSettings settings_;
|
|
Margins padding_;
|
|
int numColumns_ = 1;
|
|
};
|
|
|
|
class GridLayoutList : public GridLayout {
|
|
public:
|
|
GridLayoutList(GridLayoutSettings settings, LayoutParams *layoutParams = nullptr)
|
|
: GridLayout(settings, layoutParams) {
|
|
}
|
|
|
|
std::string DescribeText() const override;
|
|
};
|
|
|
|
class CollapsibleHeader;
|
|
|
|
class CollapsibleSection : public LinearLayout {
|
|
public:
|
|
CollapsibleSection(std::string_view title, LayoutParams *layoutParams = nullptr);
|
|
|
|
void Update() override;
|
|
|
|
// If you call this at creation, call it AFTER adding the subviews!
|
|
void SetOpen(bool open) {
|
|
_dbg_assert_(open_);
|
|
*open_ = open;
|
|
UpdateVisibility();
|
|
}
|
|
void SetOpenPtr(bool *open) {
|
|
_dbg_assert_(header_);
|
|
_dbg_assert_(open);
|
|
header_->SetOpenPtr(open);
|
|
open_ = open;
|
|
UpdateVisibility();
|
|
}
|
|
CollapsibleHeader *Header() {
|
|
return header_;
|
|
}
|
|
|
|
private:
|
|
void UpdateVisibility();
|
|
bool localOpen_ = true;
|
|
bool *open_ = nullptr;
|
|
CollapsibleHeader *header_ = nullptr;
|
|
};
|
|
|
|
} // namespace UI
|