Files
ppsspp/Common/UI/ScrollView.h
T
Henrik RydgårdandClaude Opus 5.5 8e353761c7 UI: Let scroll views extend under the bottom inset when they don't quite reach the edge
A vertical ScrollView that extends into the bottom inset (like a navigation bar) adds
that much space after its contents, so the end can be scrolled out from under it.
It only did that when the view reached the very bottom of the screen, and then added
the whole inset. Since 58fa522d48 inset the content area of dialog screens on
screens over 600 dp, a phone like the S21 Ultra (617 dp wide) got a 1.7 dp inset,
which turned the extra space off. Add exactly the part of the view that lies in the
inset instead, and skip the bottom shadow in that case.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-25 09:41:38 +01:00

174 lines
5.7 KiB
C++

#pragma once
#include "Common/UI/View.h"
#include "Common/UI/ViewGroup.h"
namespace UI {
// A scrollview usually contains just a single child - a linear layout or similar.
class ScrollView : public ViewGroup {
public:
ScrollView(Orientation orientation, LayoutParams *layoutParams = nullptr);
~ScrollView();
void Measure(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert) override;
void Layout() override;
bool Key(const KeyInput &input) override;
bool Touch(const TouchInput &input) override;
void Draw(UIContext &dc) override;
std::string DescribeLog() const override { return "ScrollView: " + View::DescribeLog(); }
void ScrollTo(float newScrollPos);
void ScrollToBottom();
void ScrollRelative(float distance);
bool CanScroll() const;
void Update() override;
void RememberPosition(float *pos) {
rememberPos_ = pos;
ScrollTo(*pos);
}
// Get the last moved scroll view position
static void GetLastScrollPosition(float &x, float &y);
// Override so that we can scroll to the active one after moving the focus.
bool SubviewFocused(View *view) override;
void PersistData(PersistStatus status, std::string anonId, PersistMap &storage) override;
void SetVisibility(Visibility visibility) override;
// If the view is smaller than the scroll view, sets whether to align to the bottom/right instead of the left.
void SetAlignOpposite(bool alignOpposite) {
alignOpposite_ = alignOpposite;
}
void SetShadows(bool shadows) {
shadows_ = shadows;
}
NeighborResult FindScrollNeighbor(View *view, const Point2D &target, FocusMove direction, NeighborResult best) override;
private:
Margins GetMargins() const;
float ChildSize() const; // in the scrolled direction
// How far the view reaches into the bottom inset (like a navigation bar).
float BottomInsetOverlap() const;
float ScrollMax() const;
float HardClampedScrollPos(float pos) const;
// TODO: Don't adjust pull_ within this!
float ClampedScrollPos(float pos);
// The "bob" is the draggable thingy on a scroll bar. Don't know a better name for it.
struct Bob {
bool show;
float thickness;
float size;
float offset;
float scrollMax;
};
Bob ComputeBob() const;
GestureDetector gesture_;
Orientation orientation_;
float scrollPos_ = 0.0f;
float scrollStart_ = 0.0f;
float scrollTarget_ = 0.0f;
int scrollTouchId_ = -1;
bool scrollToTarget_ = false;
float layoutScrollPos_ = 0.0f;
float inertia_ = 0.0f;
float pull_ = 0.0f;
float lastViewSize_ = 0.0f;
float *rememberPos_ = nullptr;
bool alignOpposite_ = false;
bool draggingBob_ = false;
bool mouseHover_ = false;
bool bobHover_ = false; // Mouse is over the strip the bob moves in.
bool shadows_ = true;
// The bob fades out when the scroll view is left alone.
float bobAlpha_ = 1.0f;
double lastScrollActivity_ = 0.0;
double lastBobUpdate_ = 0.0;
float barDragStart_ = 0.0f;
float barDragOffset_ = 0.0f;
static float lastScrollPosX;
static float lastScrollPosY;
};
// Yes, this feels a bit Java-ish...
class ListAdaptor {
public:
virtual ~ListAdaptor() = default;
virtual View *CreateItemView(int index, bool selected, ImageID *optionalImageID) = 0;
virtual int GetNumItems() = 0;
virtual void AddEventCallback(View *view, std::function<void(EventParams &)> callback) {}
virtual std::string GetTitle(int index) const { return ""; }
virtual void SetSelected(int sel) { }
virtual int GetSelected() { return -1; }
virtual void SetDefault(int defaultChoice) { }
};
class ChoiceListAdaptor : public ListAdaptor {
public:
ChoiceListAdaptor(const char *items[], int numItems) : items_(items), numItems_(numItems) {}
View *CreateItemView(int index, bool selected, ImageID *optionalImageID) override;
int GetNumItems() override { return numItems_; }
void AddEventCallback(View *view, std::function<void(EventParams &)> callback) override;
void SetDefault(int defaultChoice) override { default_ = defaultChoice; }
private:
const char **items_;
int numItems_;
int default_ = -1;
};
// The "selected" item is what was previously selected (optional). This items will be drawn differently.
class StringVectorListAdaptor : public ListAdaptor {
public:
StringVectorListAdaptor() : selected_(-1) {}
StringVectorListAdaptor(const std::vector<std::string> &items, int selected = -1) : items_(items), selected_(selected) {}
View *CreateItemView(int index, bool selected, ImageID *optionalImageID) override;
int GetNumItems() override { return (int)items_.size(); }
void AddEventCallback(View *view, std::function<void(EventParams &)> callback) override;
void SetSelected(int sel) override { selected_ = sel; }
std::string GetTitle(int index) const override { return items_[index]; }
int GetSelected() override { return selected_; }
void SetDefault(int defaultChoice) override { default_ = defaultChoice; }
private:
std::vector<std::string> items_;
int selected_;
int default_ = -1;
};
// A list view is a scroll view with autogenerated items.
// In the future, it might be smart and load/unload items as they go, but currently not.
class ListView : public ScrollView {
public:
ListView(ListAdaptor *a, std::set<int> hidden = std::set<int>(), std::map<int, ImageID> icons = std::map<int, ImageID>(), LayoutParams *layoutParams = 0);
int GetSelected() { return adaptor_->GetSelected(); }
void Measure(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert) override;
virtual void SetMaxHeight(float mh) { maxHeight_ = mh; }
Event OnChoice;
std::string DescribeLog() const override { return "ListView: " + View::DescribeLog(); }
std::string DescribeText() const override;
private:
void CreateAllItems();
void OnItemCallback(int num, EventParams &e);
ListAdaptor *adaptor_;
LinearLayout *linLayout_;
float maxHeight_;
std::set<int> hidden_;
std::map<int, ImageID> icons_;
};
} // namespace UI