From 25992714105b08bc51e873db83dacb9625a44a18 Mon Sep 17 00:00:00 2001 From: Neo Kim Date: Sun, 5 Jul 2015 19:25:00 +0900 Subject: [PATCH] ListView - Add APIs which return the closest item in specific position in current view. --- cocos/ui/UIListView.cpp | 112 ++++++++++++++++++ cocos/ui/UIListView.h | 49 +++++++- .../UIListViewTest/UIListViewTest.cpp | 43 ++++++- 3 files changed, 197 insertions(+), 7 deletions(-) diff --git a/cocos/ui/UIListView.cpp b/cocos/ui/UIListView.cpp index 708963aedc..6bdd39c1cf 100644 --- a/cocos/ui/UIListView.cpp +++ b/cocos/ui/UIListView.cpp @@ -529,6 +529,118 @@ void ListView::interceptTouchEvent(TouchEventType event, Widget *sender, Touch* } } +static Vec2 calculateItemPositionWithAnchor(Widget* item, const Vec2& itemAnchorPoint) +{ + Vec2 origin(item->getLeftBoundary(), item->getBottomBoundary()); + Size size = item->getContentSize(); + return origin + Vec2(size.width * itemAnchorPoint.x, size.height * itemAnchorPoint.y); +} + +static Widget* findClosestItem(const Vec2& targetPosition, const Vector& items, const Vec2& itemAnchorPoint, ssize_t firstIndex, float distanceFromFirst, ssize_t lastIndex, float distanceFromLast) +{ + CCASSERT(firstIndex >= 0 && lastIndex < items.size() && firstIndex <= lastIndex, ""); + if (firstIndex == lastIndex) + { + return items.at(firstIndex); + } + if (lastIndex - firstIndex == 1) + { + if (distanceFromFirst <= distanceFromLast) + { + return items.at(firstIndex); + } + else + { + return items.at(lastIndex); + } + } + + // Binary search + ssize_t midIndex = (firstIndex + lastIndex) / 2; + Vec2 itemPosition = calculateItemPositionWithAnchor(items.at(midIndex), itemAnchorPoint); + float distanceFromMid = (targetPosition - itemPosition).length(); + if (distanceFromFirst <= distanceFromLast) + { + // Left half + return findClosestItem(targetPosition, items, itemAnchorPoint, firstIndex, distanceFromFirst, midIndex, distanceFromMid); + } + else + { + // Right half + return findClosestItem(targetPosition, items, itemAnchorPoint, midIndex, distanceFromMid, lastIndex, distanceFromLast); + } +} + +Widget* ListView::getClosestItemToPosition(const Vec2& targetPosition, const Vec2& itemAnchorPoint) const +{ + if (_items.empty()) + { + return nullptr; + } + + // Find the closest item through binary search + ssize_t firstIndex = 0; + Vec2 firstPosition = calculateItemPositionWithAnchor(_items.at(firstIndex), itemAnchorPoint); + float distanceFromFirst = (targetPosition - firstPosition).length(); + + ssize_t lastIndex = _items.size() - 1; + Vec2 lastPosition = calculateItemPositionWithAnchor(_items.at(lastIndex), itemAnchorPoint); + float distanceFromLast = (targetPosition - lastPosition).length(); + + return findClosestItem(targetPosition, _items, itemAnchorPoint, firstIndex, distanceFromFirst, lastIndex, distanceFromLast); +} + +Widget* ListView::getClosestItemToPositionInCurrentView(const Vec2& positionRatioInView, const Vec2& itemAnchorPoint) const +{ + // Calculate the target position + Size contentSize = getContentSize(); + Vec2 targetPosition = -_innerContainer->getPosition(); + targetPosition.x += contentSize.width * positionRatioInView.x; + targetPosition.y += contentSize.height * positionRatioInView.y; + return getClosestItemToPosition(targetPosition, itemAnchorPoint); +} + +Widget* ListView::getCenterItemInCurrentView() const +{ + return getClosestItemToPositionInCurrentView(Vec2::ANCHOR_MIDDLE); +} + +Widget* ListView::getLeftmostItemInCurrentView() const +{ + if (_direction == Direction::HORIZONTAL) + { + return getClosestItemToPositionInCurrentView(Vec2::ANCHOR_MIDDLE_LEFT); + } + return nullptr; +} + +Widget* ListView::getRightmostItemInCurrentView() const +{ + if (_direction == Direction::HORIZONTAL) + { + return getClosestItemToPositionInCurrentView(Vec2::ANCHOR_MIDDLE_RIGHT); + } + return nullptr; +} + +Widget* ListView::getTopmostItemInCurrentView() const +{ + if (_direction == Direction::VERTICAL) + { + return getClosestItemToPositionInCurrentView(Vec2::ANCHOR_MIDDLE_TOP); + } + return nullptr; +} + +Widget* ListView::getBottommostItemInCurrentView() const +{ + if (_direction == Direction::VERTICAL) + { + return getClosestItemToPositionInCurrentView(Vec2::ANCHOR_MIDDLE_BOTTOM); + } + return nullptr; +} + ssize_t ListView::getCurSelectedIndex() const { return _curSelectedIndex; diff --git a/cocos/ui/UIListView.h b/cocos/ui/UIListView.h index 379cb1b9bd..b60435381a 100644 --- a/cocos/ui/UIListView.h +++ b/cocos/ui/UIListView.h @@ -220,7 +220,54 @@ public: virtual void removeAllChildrenWithCleanup(bool cleanup) override; virtual void removeChild(Node* child, bool cleaup = true) override; - + /** + * @brief Query the closest item to a specific position in inner container. + * + * @param targetPosition Specifies the target position in inner container's coordinates. + * @param itemAnchorPoint Specifies an anchor point of each item for position to calculate distance. + * @return A item instance if list view is not empty. Otherwise, returns null. + */ + Widget* getClosestItemToPosition(const Vec2& targetPosition, const Vec2& itemAnchorPoint = Vec2::ANCHOR_MIDDLE) const; + + /** + * @brief Query the closest item to a specific position in current view. + * + * @param positionRatioInView Specifies the target position with ratio in list view's content size. + * @param itemAnchorPoint Specifies an anchor point of each item for position to calculate distance. + * @return A item instance if list view is not empty. Otherwise, returns null. + */ + Widget* getClosestItemToPositionInCurrentView(const Vec2& positionRatioInView, const Vec2& itemAnchorPoint = Vec2::ANCHOR_MIDDLE) const; + + /** + * @brief Query the center item + * @return A item instance. + */ + Widget* getCenterItemInCurrentView() const; + + /** + * @brief Query the leftmost item in horizontal list + * @return A item instance. + */ + Widget* getLeftmostItemInCurrentView() const; + + /** + * @brief Query the rightmost item in horizontal list + * @return A item instance. + */ + Widget* getRightmostItemInCurrentView() const; + + /** + * @brief Query the topmost item in horizontal list + * @return A item instance. + */ + Widget* getTopmostItemInCurrentView() const; + + /** + * @brief Query the bottommost item in horizontal list + * @return A item instance. + */ + Widget* getBottommostItemInCurrentView() const; + /** * @brief Query current selected widget's idnex. * diff --git a/tests/cpp-tests/Classes/UITest/CocoStudioGUITest/UIListViewTest/UIListViewTest.cpp b/tests/cpp-tests/Classes/UITest/CocoStudioGUITest/UIListViewTest/UIListViewTest.cpp index 036bc37e26..687c0f6245 100644 --- a/tests/cpp-tests/Classes/UITest/CocoStudioGUITest/UIListViewTest/UIListViewTest.cpp +++ b/tests/cpp-tests/Classes/UITest/CocoStudioGUITest/UIListViewTest/UIListViewTest.cpp @@ -67,10 +67,7 @@ bool UIListViewTest_Vertical::init() listView->setBackGroundImage("cocosui/green_edit.png"); listView->setBackGroundImageScale9Enabled(true); listView->setContentSize(Size(240, 130)); - listView->setPosition(Vec2((widgetSize.width - backgroundSize.width) / 2.0f + - (backgroundSize.width - listView->getContentSize().width) / 2.0f, - (widgetSize.height - backgroundSize.height) / 2.0f + - (backgroundSize.height - listView->getContentSize().height) / 2.0f)); + listView->setPosition(Vec2((widgetSize - listView->getContentSize()) / 2.0f)); listView->addEventListener((ui::ListView::ccListViewCallback)CC_CALLBACK_2(UIListViewTest_Vertical::selectedItemEvent, this)); listView->addEventListener((ui::ListView::ccScrollViewCallback)CC_CALLBACK_2(UIListViewTest_Vertical::selectedItemEventScrollView,this)); listView->setScrollBarPositionFromCorner(Vec2(7, 7)); @@ -84,8 +81,7 @@ bool UIListViewTest_Vertical::init() Layout* default_item = Layout::create(); default_item->setTouchEnabled(true); default_item->setContentSize(default_button->getContentSize()); - default_button->setPosition(Vec2(default_item->getContentSize().width / 2.0f, - default_item->getContentSize().height / 2.0f)); + default_button->setPosition(Vec2(default_item->getContentSize() / 2.0f)); default_item->addChild(default_button); // set model @@ -166,6 +162,41 @@ bool UIListViewTest_Vertical::init() // set items margin listView->setItemsMargin(2.0f); + // Show the indexes of items on each boundary. + { + float position = 75; + // Labels + Text* labels[3]; + labels[0] = Text::create(" ", "fonts/Marker Felt.ttf", 12); + labels[0]->setAnchorPoint(Vec2::ANCHOR_MIDDLE); + labels[0]->setPosition(_uiLayer->getContentSize() / 2 + Size(0, position)); + _uiLayer->addChild(labels[0]); + labels[1] = Text::create(" ", "fonts/Marker Felt.ttf", 12); + labels[1]->setAnchorPoint(Vec2::ANCHOR_MIDDLE); + labels[1]->setPosition(_uiLayer->getContentSize() / 2 + Size(140, 0)); + _uiLayer->addChild(labels[1]); + labels[2] = Text::create(" ", "fonts/Marker Felt.ttf", 12); + labels[2]->setAnchorPoint(Vec2::ANCHOR_MIDDLE); + labels[2]->setPosition(_uiLayer->getContentSize() / 2 + Size(0, -position)); + _uiLayer->addChild(labels[2]); + + // Callback + listView->ScrollView::addEventListener([labels](Ref* ref, ScrollView::EventType eventType) { + ListView* listView = dynamic_cast(ref); + if(listView == nullptr || eventType != ScrollView::EventType::CONTAINER_MOVED) + { + return; + } + auto bottom = listView->getBottommostItemInCurrentView(); + auto center = listView->getCenterItemInCurrentView(); + auto top = listView->getTopmostItemInCurrentView(); + + labels[0]->setString(StringUtils::format("Top index=%zd", listView->getIndex(top))); + labels[1]->setString(StringUtils::format("Center\nindex=%zd", listView->getIndex(center))); + labels[2]->setString(StringUtils::format("Bottom index=%zd", listView->getIndex(bottom))); + }); + } + return true; }