mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-11-13 09:39:33 +01:00
UI: Add FlowLayout, use it for the game browser toolbar
FlowLayout lays out its children left to right at their natural size and starts a new row when the next one doesn't fit, instead of squeezing them. Weighted children fill what's left of their row. Like LinearLayout, it has SetPadding for space inside its edges. On narrow screens the toolbar buttons used to run off the edge; now they wrap onto a second row. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
bac036a628
commit
db9e1aa30f
3 files changed
+170
-1
No files matched your search
@@ -929,6 +929,130 @@ void ChoiceGroup::Draw(UIContext &dc) {
|
||||
}*/
|
||||
}
|
||||
|
||||
static Margins FlowLayoutMargins(const View *view) {
|
||||
const LinearLayoutParams *lp = view->GetLayoutParams()->As<LinearLayoutParams>();
|
||||
return (lp && lp->HasMargins()) ? lp->margins : Margins(0);
|
||||
}
|
||||
|
||||
static float FlowLayoutWeight(const View *view) {
|
||||
const LinearLayoutParams *lp = view->GetLayoutParams()->As<LinearLayoutParams>();
|
||||
return lp ? lp->weight : 0.0f;
|
||||
}
|
||||
|
||||
void FlowLayout::BuildRows(float maxWidth, std::vector<Row> *rows) const {
|
||||
rows->clear();
|
||||
Row row{};
|
||||
bool rowOpen = false;
|
||||
for (size_t i = 0; i < views_.size(); i++) {
|
||||
const View *view = views_[i];
|
||||
if (view->GetVisibility() == V_GONE)
|
||||
continue;
|
||||
const Margins margins = FlowLayoutMargins(view);
|
||||
const float w = view->GetMeasuredWidth() + margins.horiz();
|
||||
const float h = view->GetMeasuredHeight() + margins.vert();
|
||||
if (rowOpen && row.width + spacing_ + w > maxWidth) {
|
||||
rows->push_back(row);
|
||||
rowOpen = false;
|
||||
}
|
||||
if (!rowOpen) {
|
||||
row = Row{ i, i + 1, w, h, FlowLayoutWeight(view) };
|
||||
rowOpen = true;
|
||||
} else {
|
||||
row.end = i + 1;
|
||||
row.width += spacing_ + w;
|
||||
row.height = std::max(row.height, h);
|
||||
row.weightSum += FlowLayoutWeight(view);
|
||||
}
|
||||
}
|
||||
if (rowOpen) {
|
||||
rows->push_back(row);
|
||||
}
|
||||
}
|
||||
|
||||
void FlowLayout::Measure(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert) {
|
||||
// Without a width limit, everything goes on one row.
|
||||
float maxWidth = INFINITY;
|
||||
if (horiz.type != UNSPECIFIED) {
|
||||
maxWidth = std::max(0.0f, horiz.size - padding_.horiz());
|
||||
}
|
||||
if (layoutParams_->width > 0.0f) {
|
||||
maxWidth = std::min(maxWidth, std::max(0.0f, layoutParams_->width - padding_.horiz()));
|
||||
}
|
||||
|
||||
for (View *view : views_) {
|
||||
if (view->GetVisibility() == V_GONE)
|
||||
continue;
|
||||
const Margins margins = FlowLayoutMargins(view);
|
||||
view->Measure(dc, MeasureSpec(UNSPECIFIED, 0.0f), MeasureSpec(UNSPECIFIED, 0.0f));
|
||||
if (view->GetMeasuredWidth() + margins.horiz() > maxWidth) {
|
||||
// Too wide even for a row of its own, so let it wrap its text or shrink.
|
||||
view->Measure(dc, MeasureSpec(AT_MOST, std::max(0.0f, maxWidth - margins.horiz())), MeasureSpec(UNSPECIFIED, 0.0f));
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Row> rows;
|
||||
BuildRows(maxWidth, &rows);
|
||||
|
||||
float contentWidth = 0.0f;
|
||||
float contentHeight = 0.0f;
|
||||
for (const Row &row : rows) {
|
||||
contentWidth = std::max(contentWidth, row.width);
|
||||
contentHeight += row.height;
|
||||
}
|
||||
if (!rows.empty()) {
|
||||
contentHeight += rowSpacing_ * (rows.size() - 1);
|
||||
}
|
||||
|
||||
MeasureBySpec(layoutParams_->width, contentWidth + padding_.horiz(), horiz, &measuredWidth_);
|
||||
MeasureBySpec(layoutParams_->height, contentHeight + padding_.vert(), vert, &measuredHeight_);
|
||||
}
|
||||
|
||||
void FlowLayout::Layout() {
|
||||
const float maxWidth = bounds_.w - padding_.horiz();
|
||||
std::vector<Row> rows;
|
||||
BuildRows(maxWidth, &rows);
|
||||
|
||||
float y = bounds_.y + padding_.top;
|
||||
for (const Row &row : rows) {
|
||||
const float leftover = std::max(0.0f, maxWidth - row.width);
|
||||
float x = bounds_.x + padding_.left;
|
||||
if (row.weightSum <= 0.0f) {
|
||||
const Gravity rowGravity = (Gravity)((int)rowGravity_ & (int)Gravity::G_HORIZMASK);
|
||||
if (rowGravity == Gravity::G_RIGHT) {
|
||||
x += leftover;
|
||||
} else if (rowGravity == Gravity::G_HCENTER) {
|
||||
x += leftover * 0.5f;
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = row.first; i < row.end; i++) {
|
||||
View *view = views_[i];
|
||||
if (view->GetVisibility() == V_GONE)
|
||||
continue;
|
||||
const LinearLayoutParams *lp = view->GetLayoutParams()->As<LinearLayoutParams>();
|
||||
const Margins margins = FlowLayoutMargins(view);
|
||||
const float weight = FlowLayoutWeight(view);
|
||||
const Gravity gravity = lp ? (Gravity)((int)lp->gravity & (int)Gravity::G_VERTMASK) : Gravity::G_TOP;
|
||||
|
||||
float w = view->GetMeasuredWidth() + margins.horiz();
|
||||
float innerWidth = view->GetMeasuredWidth();
|
||||
if (weight > 0.0f && row.weightSum > 0.0f) {
|
||||
w += leftover * weight / row.weightSum;
|
||||
innerWidth = w - margins.horiz();
|
||||
}
|
||||
|
||||
Bounds itemBounds(x, y, w, row.height);
|
||||
Bounds innerBounds;
|
||||
ApplyGravity(itemBounds, margins, innerWidth, view->GetMeasuredHeight(), gravity, innerBounds);
|
||||
view->SetBounds(innerBounds);
|
||||
view->Layout();
|
||||
|
||||
x += w + spacing_;
|
||||
}
|
||||
y += row.height + rowSpacing_;
|
||||
}
|
||||
}
|
||||
|
||||
void AnchorLayout::Measure(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert) {
|
||||
MeasureBySpec(layoutParams_->width, 0.0f, horiz, &measuredWidth_);
|
||||
MeasureBySpec(layoutParams_->height, 0.0f, vert, &measuredHeight_);
|
||||
|
||||
@@ -283,6 +283,48 @@ public:
|
||||
PopupChoiceGroupList(LayoutParams *layoutParams = nullptr);
|
||||
};
|
||||
|
||||
// Lays out its children left to right at their natural size, starting a new row when the next
|
||||
// one doesn't fit. Takes LinearLayoutParams: the gravity places a child vertically in its row,
|
||||
// and children with weight share whatever space is left in their row.
|
||||
class FlowLayout : public ViewGroup {
|
||||
public:
|
||||
FlowLayout(LayoutParams *layoutParams = nullptr) : ViewGroup(layoutParams) {}
|
||||
|
||||
void Measure(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert) override;
|
||||
void Layout() override;
|
||||
void SetSpacing(float spacing) {
|
||||
spacing_ = spacing;
|
||||
}
|
||||
void SetRowSpacing(float spacing) {
|
||||
rowSpacing_ = spacing;
|
||||
}
|
||||
// Where the children go in a row that has space left over, and no weighted children to take it.
|
||||
// G_LEFT, G_RIGHT or G_HCENTER.
|
||||
void SetRowGravity(Gravity gravity) {
|
||||
rowGravity_ = gravity;
|
||||
}
|
||||
// Space between the layout's edges and its children, on top of the children's own margins.
|
||||
void SetPadding(const Margins &padding) {
|
||||
padding_ = padding;
|
||||
}
|
||||
std::string DescribeLog() const override { return "FlowLayout: " + View::DescribeLog(); }
|
||||
|
||||
private:
|
||||
struct Row {
|
||||
size_t first; // Index into views_.
|
||||
size_t end;
|
||||
float width; // Including margins and spacing.
|
||||
float height;
|
||||
float weightSum;
|
||||
};
|
||||
void BuildRows(float maxWidth, std::vector<Row> *rows) const;
|
||||
|
||||
Margins padding_;
|
||||
float spacing_ = 5.0f;
|
||||
float rowSpacing_ = 5.0f;
|
||||
Gravity rowGravity_ = Gravity::G_LEFT;
|
||||
};
|
||||
|
||||
// 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 {
|
||||
|
||||
+4
-1
@@ -439,7 +439,10 @@ void GameBrowser::Refresh() {
|
||||
gameList_ = nullptr;
|
||||
gameGrid_ = nullptr;
|
||||
|
||||
LinearLayout *topBar = new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT, Margins(0, 12, 8, 0)));
|
||||
// Wraps the buttons onto another row when the screen is too narrow for them.
|
||||
FlowLayout *topBar = new FlowLayout(new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT, Margins(0, 12, 8, 0)));
|
||||
topBar->SetSpacing(10.0f);
|
||||
topBar->SetRowSpacing(10.0f);
|
||||
Add(topBar);
|
||||
|
||||
// Recent isn't a real folder, so it gets no path or refresh.
|
||||
|
||||
Reference in new issue
Block a user