diff --git a/Common/UI/View.h b/Common/UI/View.h index 860b133881..f21f4475fd 100644 --- a/Common/UI/View.h +++ b/Common/UI/View.h @@ -448,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); diff --git a/Common/UI/ViewGroup.cpp b/Common/UI/ViewGroup.cpp index 3f876ace34..0f3bb0f843 100644 --- a/Common/UI/ViewGroup.cpp +++ b/Common/UI/ViewGroup.cpp @@ -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 guard(modifyLock_); for (size_t i = 0; i < views_.size(); i++) { @@ -444,11 +452,57 @@ 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; } } +float GetTargetScore(const Point &target, View *view) { + if (!view->CanBeFocused()) + return 0.0f; + if (view->IsEnabled() == false) + return 0.0f; + if (view->GetVisibility() != V_VISIBLE) + return 0.0f; + + Point viewPos = view->GetBounds().Center(); + float dx = viewPos.x - target.x; + float dy = viewPos.y - target.y; + + float distance = sqrtf(dx * dx + dy * dy); + return 10.0f / std::max(1.0f, distance); +} + +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, v); + if (score > best.score) { + best.score = score; + best.view = v; + } + } + } + for (auto v : views_) { + if (v->IsViewGroup()) { + ViewGroup *vg = static_cast(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_); @@ -792,12 +846,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; } } return ViewGroup::Key(input); @@ -903,6 +951,47 @@ 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(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? + return vg->FindScrollNeighbor(view, targetPos, direction, best); + } + } + + return ViewGroup::FindScrollNeighbor(view, target, direction, best); +} + void ScrollView::PersistData(PersistStatus status, std::string anonId, PersistMap &storage) { ViewGroup::PersistData(status, anonId, storage); diff --git a/Common/UI/ViewGroup.h b/Common/UI/ViewGroup.h index 730f74f10b..5619d2317b 100644 --- a/Common/UI/ViewGroup.h +++ b/Common/UI/ViewGroup.h @@ -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; }