mirror of
https://github.com/hrydgard/ppsspp.git
synced 2025-04-02 11:01:50 -04:00
Merge pull request #14705 from unknownbrackets/ui-scroll
Move focus when scrolling with page up/down or home/end
This commit is contained in:
commit
624b094ff5
4 changed files with 147 additions and 21 deletions
|
@ -195,11 +195,23 @@ static std::set<HeldKey> heldKeys;
|
|||
const double repeatDelay = 15 * (1.0 / 60.0f); // 15 frames like before.
|
||||
const double repeatInterval = 5 * (1.0 / 60.0f); // 5 frames like before.
|
||||
|
||||
bool IsScrollKey(const KeyInput &input) {
|
||||
switch (input.keyCode) {
|
||||
case NKCODE_PAGE_UP:
|
||||
case NKCODE_PAGE_DOWN:
|
||||
case NKCODE_MOVE_HOME:
|
||||
case NKCODE_MOVE_END:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool KeyEvent(const KeyInput &key, ViewGroup *root) {
|
||||
bool retval = false;
|
||||
// Ignore repeats for focus moves.
|
||||
if ((key.flags & (KEY_DOWN | KEY_IS_REPEAT)) == KEY_DOWN) {
|
||||
if (IsDPadKey(key)) {
|
||||
if (IsDPadKey(key) || IsScrollKey(key)) {
|
||||
// Let's only repeat DPAD initially.
|
||||
HeldKey hk;
|
||||
hk.key = key.keyCode;
|
||||
|
@ -399,6 +411,10 @@ void UpdateViewHierarchy(ViewGroup *root) {
|
|||
case NKCODE_DPAD_RIGHT: MoveFocus(root, FOCUS_RIGHT); break;
|
||||
case NKCODE_DPAD_UP: MoveFocus(root, FOCUS_UP); break;
|
||||
case NKCODE_DPAD_DOWN: MoveFocus(root, FOCUS_DOWN); break;
|
||||
case NKCODE_PAGE_UP: MoveFocus(root, FOCUS_PREV_PAGE); break;
|
||||
case NKCODE_PAGE_DOWN: MoveFocus(root, FOCUS_NEXT_PAGE); break;
|
||||
case NKCODE_MOVE_HOME: MoveFocus(root, FOCUS_FIRST); break;
|
||||
case NKCODE_MOVE_END: MoveFocus(root, FOCUS_LAST); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -128,6 +128,10 @@ enum FocusDirection {
|
|||
FOCUS_RIGHT,
|
||||
FOCUS_NEXT,
|
||||
FOCUS_PREV,
|
||||
FOCUS_FIRST,
|
||||
FOCUS_LAST,
|
||||
FOCUS_PREV_PAGE,
|
||||
FOCUS_NEXT_PAGE,
|
||||
};
|
||||
|
||||
enum {
|
||||
|
@ -177,6 +181,10 @@ inline FocusDirection Opposite(FocusDirection d) {
|
|||
case FOCUS_RIGHT: return FOCUS_LEFT;
|
||||
case FOCUS_PREV: return FOCUS_NEXT;
|
||||
case FOCUS_NEXT: return FOCUS_PREV;
|
||||
case FOCUS_FIRST: return FOCUS_LAST;
|
||||
case FOCUS_LAST: return FOCUS_FIRST;
|
||||
case FOCUS_PREV_PAGE: return FOCUS_NEXT_PAGE;
|
||||
case FOCUS_NEXT_PAGE: return FOCUS_PREV_PAGE;
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
@ -440,6 +448,7 @@ public:
|
|||
|
||||
// Fake RTTI
|
||||
virtual bool IsViewGroup() const { return false; }
|
||||
virtual bool ContainsSubview(const View *view) const { return false; }
|
||||
|
||||
Point GetFocusPosition(FocusDirection dir);
|
||||
|
||||
|
|
|
@ -57,6 +57,14 @@ void ViewGroup::RemoveSubview(View *view) {
|
|||
}
|
||||
}
|
||||
|
||||
bool ViewGroup::ContainsSubview(const View *view) const {
|
||||
for (const View *subview : views_) {
|
||||
if (subview == view || subview->ContainsSubview(view))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ViewGroup::Clear() {
|
||||
std::lock_guard<std::mutex> guard(modifyLock_);
|
||||
for (size_t i = 0; i < views_.size(); i++) {
|
||||
|
@ -272,7 +280,7 @@ static float VerticalOverlap(const Bounds &a, const Bounds &b) {
|
|||
return std::min(1.0f, overlap / std::min(a.h, b.h));
|
||||
}
|
||||
|
||||
float GetDirectionScore(View *origin, View *destination, FocusDirection direction) {
|
||||
float GetTargetScore(const Point &originPos, int originIndex, View *origin, View *destination, FocusDirection direction) {
|
||||
// Skip labels and things like that.
|
||||
if (!destination->CanBeFocused())
|
||||
return 0.0f;
|
||||
|
@ -281,7 +289,6 @@ float GetDirectionScore(View *origin, View *destination, FocusDirection directio
|
|||
if (destination->GetVisibility() != V_VISIBLE)
|
||||
return 0.0f;
|
||||
|
||||
Point originPos = origin->GetFocusPosition(direction);
|
||||
Point destPos = destination->GetFocusPosition(Opposite(direction));
|
||||
|
||||
float dx = destPos.x - originPos.x;
|
||||
|
@ -297,8 +304,10 @@ float GetDirectionScore(View *origin, View *destination, FocusDirection directio
|
|||
float horizOverlap = HorizontalOverlap(origin->GetBounds(), destination->GetBounds());
|
||||
float vertOverlap = VerticalOverlap(origin->GetBounds(), destination->GetBounds());
|
||||
if (horizOverlap == 1.0f && vertOverlap == 1.0f) {
|
||||
INFO_LOG(SYSTEM, "Contain overlap");
|
||||
return 0.0;
|
||||
if (direction != FOCUS_PREV_PAGE && direction != FOCUS_NEXT_PAGE) {
|
||||
INFO_LOG(SYSTEM, "Contain overlap");
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
float originSize = 0.0f;
|
||||
switch (direction) {
|
||||
|
@ -332,6 +341,25 @@ float GetDirectionScore(View *origin, View *destination, FocusDirection directio
|
|||
}
|
||||
vertical = true;
|
||||
break;
|
||||
case FOCUS_FIRST:
|
||||
if (originIndex == -1)
|
||||
return 0.0f;
|
||||
if (dirX > 0.0f || dirY > 0.0f)
|
||||
return 0.0f;
|
||||
// More distance is good.
|
||||
return distance;
|
||||
case FOCUS_LAST:
|
||||
if (originIndex == -1)
|
||||
return 0.0f;
|
||||
if (dirX < 0.0f || dirY < 0.0f)
|
||||
return 0.0f;
|
||||
// More distance is good.
|
||||
return distance;
|
||||
case FOCUS_PREV_PAGE:
|
||||
case FOCUS_NEXT_PAGE:
|
||||
// Not always, but let's go with the bonus on height.
|
||||
vertical = true;
|
||||
break;
|
||||
case FOCUS_PREV:
|
||||
case FOCUS_NEXT:
|
||||
ERROR_LOG(SYSTEM, "Invalid focus direction");
|
||||
|
@ -363,6 +391,11 @@ float GetDirectionScore(View *origin, View *destination, FocusDirection directio
|
|||
return 10.0f / std::max(1.0f, distance - distanceBonus) + overlap;
|
||||
}
|
||||
|
||||
float GetDirectionScore(int originIndex, View *origin, View *destination, FocusDirection direction) {
|
||||
Point originPos = origin->GetFocusPosition(direction);
|
||||
return GetTargetScore(originPos, originIndex, origin, destination, direction);
|
||||
}
|
||||
|
||||
NeighborResult ViewGroup::FindNeighbor(View *view, FocusDirection direction, NeighborResult result) {
|
||||
if (!IsEnabled())
|
||||
return result;
|
||||
|
@ -398,13 +431,15 @@ NeighborResult ViewGroup::FindNeighbor(View *view, FocusDirection direction, Nei
|
|||
case FOCUS_LEFT:
|
||||
case FOCUS_RIGHT:
|
||||
case FOCUS_DOWN:
|
||||
case FOCUS_FIRST:
|
||||
case FOCUS_LAST:
|
||||
{
|
||||
// First, try the child views themselves as candidates
|
||||
for (size_t i = 0; i < views_.size(); i++) {
|
||||
if (views_[i] == view)
|
||||
continue;
|
||||
|
||||
float score = GetDirectionScore(view, views_[i], direction);
|
||||
float score = GetDirectionScore(num, view, views_[i], direction);
|
||||
if (score > result.score) {
|
||||
result.score = score;
|
||||
result.view = views_[i];
|
||||
|
@ -428,11 +463,41 @@ NeighborResult ViewGroup::FindNeighbor(View *view, FocusDirection direction, Nei
|
|||
return result;
|
||||
}
|
||||
|
||||
case FOCUS_PREV_PAGE:
|
||||
case FOCUS_NEXT_PAGE:
|
||||
return FindScrollNeighbor(view, Point(INFINITY, INFINITY), direction, result);
|
||||
|
||||
default:
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
NeighborResult ViewGroup::FindScrollNeighbor(View *view, const Point &target, FocusDirection direction, NeighborResult best) {
|
||||
if (!IsEnabled())
|
||||
return best;
|
||||
if (GetVisibility() != V_VISIBLE)
|
||||
return best;
|
||||
|
||||
if (target.x < INFINITY && target.y < INFINITY) {
|
||||
for (auto v : views_) {
|
||||
// Note: we consider the origin itself, which might already be the best option.
|
||||
float score = GetTargetScore(target, -1, view, v, direction);
|
||||
if (score > best.score) {
|
||||
best.score = score;
|
||||
best.view = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (auto v : views_) {
|
||||
if (v->IsViewGroup()) {
|
||||
ViewGroup *vg = static_cast<ViewGroup *>(v);
|
||||
if (vg)
|
||||
best = vg->FindScrollNeighbor(view, target, direction, best);
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
// TODO: This code needs some cleanup/restructuring...
|
||||
void LinearLayout::Measure(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert) {
|
||||
MeasureBySpec(layoutParams_->width, 0.0f, horiz, &measuredWidth_);
|
||||
|
@ -776,19 +841,6 @@ bool ScrollView::Key(const KeyInput &input) {
|
|||
case NKCODE_EXT_MOUSEWHEEL_DOWN:
|
||||
ScrollRelative(250);
|
||||
break;
|
||||
case NKCODE_PAGE_DOWN:
|
||||
ScrollRelative((orientation_ == ORIENT_VERTICAL ? bounds_.h : bounds_.w) - 50);
|
||||
break;
|
||||
case NKCODE_PAGE_UP:
|
||||
ScrollRelative(-(orientation_ == ORIENT_VERTICAL ? bounds_.h : bounds_.w) + 50);
|
||||
break;
|
||||
case NKCODE_MOVE_HOME:
|
||||
ScrollTo(0);
|
||||
break;
|
||||
case NKCODE_MOVE_END:
|
||||
if (views_.size())
|
||||
ScrollTo(orientation_ == ORIENT_VERTICAL ? views_[0]->GetBounds().h : views_[0]->GetBounds().w);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return ViewGroup::Key(input);
|
||||
|
@ -894,6 +946,51 @@ bool ScrollView::SubviewFocused(View *view) {
|
|||
return true;
|
||||
}
|
||||
|
||||
NeighborResult ScrollView::FindScrollNeighbor(View *view, const Point &target, FocusDirection direction, NeighborResult best) {
|
||||
if (ContainsSubview(view) && views_[0]->IsViewGroup()) {
|
||||
ViewGroup *vg = static_cast<ViewGroup *>(views_[0]);
|
||||
int found = -1;
|
||||
for (int i = 0, n = vg->GetNumSubviews(); i < n; ++i) {
|
||||
View *child = vg->GetViewByIndex(i);
|
||||
if (child == view || child->ContainsSubview(view)) {
|
||||
found = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Okay, the previously focused view is inside this.
|
||||
if (found != -1) {
|
||||
float mult = 0.0f;
|
||||
switch (direction) {
|
||||
case FOCUS_PREV_PAGE:
|
||||
mult = -1.0f;
|
||||
break;
|
||||
case FOCUS_NEXT_PAGE:
|
||||
mult = 1.0f;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Okay, now where is our ideal target?
|
||||
Point targetPos = view->GetBounds().Center();
|
||||
if (orientation_ == ORIENT_VERTICAL)
|
||||
targetPos.y += mult * bounds_.h;
|
||||
else
|
||||
targetPos.x += mult * bounds_.x;
|
||||
|
||||
// Okay, which subview is closest to that?
|
||||
best = vg->FindScrollNeighbor(view, targetPos, direction, best);
|
||||
// Avoid reselecting the same view.
|
||||
if (best.view == view)
|
||||
best.view = nullptr;
|
||||
return best;
|
||||
}
|
||||
}
|
||||
|
||||
return ViewGroup::FindScrollNeighbor(view, target, direction, best);
|
||||
}
|
||||
|
||||
void ScrollView::PersistData(PersistStatus status, std::string anonId, PersistMap &storage) {
|
||||
ViewGroup::PersistData(status, anonId, storage);
|
||||
|
||||
|
|
|
@ -63,9 +63,11 @@ public:
|
|||
|
||||
// Assumes that layout has taken place.
|
||||
NeighborResult FindNeighbor(View *view, FocusDirection direction, NeighborResult best);
|
||||
virtual NeighborResult FindScrollNeighbor(View *view, const Point &target, FocusDirection direction, NeighborResult best);
|
||||
|
||||
virtual bool CanBeFocused() const override { return false; }
|
||||
virtual bool IsViewGroup() const override { return true; }
|
||||
bool CanBeFocused() const override { return false; }
|
||||
bool IsViewGroup() const override { return true; }
|
||||
bool ContainsSubview(const View *view) const override;
|
||||
|
||||
virtual void SetBG(const Drawable &bg) { bg_ = bg; }
|
||||
|
||||
|
@ -268,6 +270,8 @@ public:
|
|||
void PersistData(PersistStatus status, std::string anonId, PersistMap &storage) override;
|
||||
void SetVisibility(Visibility visibility) override;
|
||||
|
||||
NeighborResult FindScrollNeighbor(View *view, const Point &target, FocusDirection direction, NeighborResult best) override;
|
||||
|
||||
// Quick hack to prevent scrolling to top in some lists
|
||||
void SetScrollToTop(bool t) { scrollToTopOnSizeChange_ = t; }
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue