#include "stdafx.h" #include "V8DataModel/ScrollingFrame.h" #include "v8datamodel/GuiService.h" #include "V8DataModel/UserInputService.h" #include "v8datamodel/GamepadService.h" #include "V8DataModel/TextBox.h" #define category_Scrolling "Scrolling" #define WHEEL_SCROLL_DELTA 10 #define MAX_SCROLL_WHEEL_SCROLL_PERCENT 0.10f #define MIN_SCROLLBAR_SIZE 1.0f #define REALLY_BIG_SCROLL_DELTA 10000000.0f #define MAX_MULTIPLY_TIME_MSEC 250.0f #define DECELERATION_RATE 0.9f #define TOUCH_INERTIA_RESET_TIME_MSEC 500.0f #define GAMEPAD_THUMBSTICK_WAIT_PERIOD_MSEC 300.0f #define GAMEPAD_SCROLL_CONSTANT 80.0f DYNAMIC_FASTFLAGVARIABLE(LimitScrollWheelMaxToHalfWindowSize, false) DYNAMIC_FASTFLAGVARIABLE(FixRotatedHorizontalScrollBar, false) namespace RBX { const char* const sScrollingFrame = "ScrollingFrame"; const Reflection::PropDescriptor prop_scrollingEnabled("ScrollingEnabled", category_Scrolling, &ScrollingFrame::getScrollingEnabled, &ScrollingFrame::setScrollingEnabled); const Reflection::PropDescriptor prop_canvasSize("CanvasSize", category_Scrolling, &ScrollingFrame::getCanvasSize, &ScrollingFrame::setCanvasSize); const Reflection::PropDescriptor prop_canvasPosition("CanvasPosition", category_Scrolling, &ScrollingFrame::getCanvasPosition, &ScrollingFrame::luaSetCanvasPosition); const Reflection::PropDescriptor prop_absWindowSize("AbsoluteWindowSize", category_Scrolling, &ScrollingFrame::getAbsoluteWindowSize, NULL); const Reflection::PropDescriptor prop_scrollBarThickness("ScrollBarThickness", category_Scrolling, &ScrollingFrame::getScrollBarThickness, &ScrollingFrame::setScrollBarThickness); const Reflection::PropDescriptor prop_topImage("TopImage", category_Scrolling, &ScrollingFrame::getTopImage, &ScrollingFrame::setTopImage); const Reflection::PropDescriptor prop_midImage("MidImage", category_Scrolling, &ScrollingFrame::getMidImage, &ScrollingFrame::setMidImage); const Reflection::PropDescriptor prop_bottomImage("BottomImage", category_Scrolling, &ScrollingFrame::getBottomImage, &ScrollingFrame::setBottomImage); ScrollingFrame::ScrollingFrame() : DescribedCreatable(sScrollingFrame, false) , IStepped(StepType_Render) , scrollDragging(false) , verticalBarSize(Rect2D()) , horizontalBarSize(Rect2D()) , scrollingDirection(Y) , topImage(TextureId("rbxasset://textures/ui/Scroll/scroll-top.png")) , midImage(TextureId("rbxasset://textures/ui/Scroll/scroll-middle.png")) , bottomImage(TextureId("rbxasset://textures/ui/Scroll/scroll-bottom.png")) , scrollingEnabled(true) , canvasSize(0,0,2,0) , canvasPosition(0,0) , scrollBarThickness(12) , needsRecalculate(true) , scrollDeltaMultiplier(1) , isLeftMouseDown(false) , touchInputPositions(4) , sampledVelocity(0,0) , lastGamepadScrollDelta(0,0) { selectable = true; clipping = true; } void ScrollingFrame::onServiceProvider(ServiceProvider* oldProvider, ServiceProvider* newProvider) { if (oldProvider) { inputEndedConnection.disconnect(); guiServicePropertyChangedConnection.disconnect(); selectionGainedConnection.disconnect(); selectionLostConnection.disconnect(); } Super::onServiceProvider(oldProvider, newProvider); onServiceProviderIStepped(oldProvider, newProvider); if (newProvider) { wheelDownTimer.reset(); wheelUpTimer.reset(); if(UserInputService* inputService = RBX::ServiceProvider::find(newProvider)) { inputEndedConnection = inputService->coreInputEndedEvent.connect(boost::bind(&ScrollingFrame::globalInputEnded,this,_1)); } if(GuiService* guiService = RBX::ServiceProvider::find(newProvider)) { guiServicePropertyChangedConnection = guiService->propertyChangedSignal.connect(boost::bind(&ScrollingFrame::selectedGuiObjectChanged, this, _1)); } selectionGainedConnection = selectionGainedEvent.connect(boost::bind(&ScrollingFrame::selectionGained, this)); selectionLostConnection = selectionLostEvent.connect(boost::bind(&ScrollingFrame::selectionLost, this)); } } void ScrollingFrame::selectionLost() { lastGamepadScrollDelta = Vector2::zero(); } void ScrollingFrame::selectionGained() { selectionTimer.reset(); } void ScrollingFrame::selectedGuiObjectChanged(const RBX::Reflection::PropertyDescriptor* desc) { GuiService* guiService = RBX::ServiceProvider::find(this); if (!guiService) { return; } if (!scrollingEnabled) { return; } RBX::GuiObject* selectedObject = NULL; if (*desc == GuiService::prop_selectedGuiObject) { selectedObject = guiService->getSelectedGuiObjectLua(); } else if(*desc == GuiService::prop_selectedCoreGuiObject) { selectedObject = guiService->getSelectedCoreGuiObjectLua(); } if (!selectedObject) { return; } if (!selectedObject->isDescendantOf(this)) { return; } // make sure selected object is visible in some capacity const Rect2D selectedObjectRect = selectedObject->getRect2D(); const Rect2D visibleRect = getVisibleRect2D(); Vector2 scrollDelta = Vector2::zero(); if (selectedObjectRect.height() <= visibleRect.height()) { if (selectedObjectRect.y1() > visibleRect.y1()) { scrollDelta.y = selectedObjectRect.y1() - visibleRect.y1(); } else if (selectedObjectRect.y0() < visibleRect.y0()) { scrollDelta.y = selectedObjectRect.y0() - visibleRect.y0(); } } else { if (selectedObjectRect.y0() >= visibleRect.y1() ) { scrollDelta.y = selectedObjectRect.height(); } else if (selectedObjectRect.y1() <= visibleRect.y0()) { scrollDelta.y = -selectedObjectRect.height(); } } if (selectedObjectRect.width() <= visibleRect.width()) { if (selectedObjectRect.x1() > visibleRect.x1()) { scrollDelta.x = selectedObjectRect.x1() - visibleRect.x1(); } else if (selectedObjectRect.x0() < visibleRect.x0()) { scrollDelta.x = selectedObjectRect.x0() - visibleRect.x0(); } } else { if (selectedObjectRect.x0() >= visibleRect.x1() ) { scrollDelta.x = selectedObjectRect.width(); } else if (selectedObjectRect.x1() <= visibleRect.x0()) { scrollDelta.x = -selectedObjectRect.width(); } } if (scrollDelta != Vector2::zero()) { Vector2 scrollPercent = scrollDelta/getCanvasRect().wh(); setCanvasPosition( Vector2(canvasPosition.x + (getCanvasRect().width() * scrollPercent.x), canvasPosition.y + (getCanvasRect().height() * scrollPercent.y)), false); } } void ScrollingFrame::onPropertyChanged(const Reflection::PropertyDescriptor& descriptor) { Super::onPropertyChanged(descriptor); if(descriptor == GuiBase2d::prop_AbsoluteSize || descriptor == GuiBase2d::prop_AbsolutePosition || descriptor == prop_canvasSize || descriptor == prop_canvasPosition || descriptor == prop_scrollBarThickness) { resizeChildren(); needsRecalculate = true; } } Vector2 ScrollingFrame::getAbsoluteWindowSize() const { return getVisibleRect2D().wh(); } Rect2D ScrollingFrame::getVisibleRect2D() const { Rect2D rect = getRect2D(); if (canScrollHorizontal()) { rect = Rect2D::xywh(rect.x0y0(), Vector2(rect.width(), rect.height() - scrollBarThickness)); } if (canScrollVertical()) { rect = Rect2D::xywh(rect.x0y0(), Vector2(rect.width() - scrollBarThickness, rect.height())); } return rect; } void ScrollingFrame::setScrollingEnabled(bool value) { if (value != scrollingEnabled) { scrollingEnabled = value; raisePropertyChanged(prop_scrollingEnabled); } } void ScrollingFrame::setCanvasSize(UDim2 value) { if (value != canvasSize) { canvasSize = value; raisePropertyChanged(prop_canvasSize); } } void ScrollingFrame::luaSetCanvasPosition(Vector2 value) { setCanvasPosition(value); } Vector2 ScrollingFrame::getMaxSize() const { Vector2 maxSize = getCanvasRectLocal().wh() - getRect2D().wh(); maxSize += (canScrollHorizontal() && canScrollVertical()) ? Vector2(scrollBarThickness, scrollBarThickness) : Vector2::zero(); return maxSize; } Vector2 ScrollingFrame::calculateCanvasPositionClamped(Vector2 desiredValue) const { // make sure we aren't setting anything to NaN or some other invalid value if (RBX::Math::isNanInf(desiredValue.x)) { desiredValue.x = canvasPosition.x; } if (RBX::Math::isNanInf(desiredValue.y)) { desiredValue.y = canvasPosition.y; } Vector2 maxSize = getCanvasRectLocal().wh() - getRect2D().wh(); maxSize += (canScrollHorizontal() && canScrollVertical()) ? Vector2(scrollBarThickness, scrollBarThickness) : Vector2::zero(); return G3D::clamp(desiredValue, Vector2::zero(), maxSize.max(Vector2::zero())); } void ScrollingFrame::setCanvasPosition(Vector2 value, bool printWarnings) { if (value == canvasPosition) { return; } Vector2 newValue = calculateCanvasPositionClamped(value); if (newValue != value && printWarnings) { RBX::StandardOut::singleton()->printf(RBX::MESSAGE_WARNING, "ScrollingFrame.CanvasPosition set to invalid position (%f, %f), clamping to position (%f, %f).", value.x, value.y, newValue.x, newValue.y); } if (newValue != canvasPosition) { canvasPosition = newValue; raisePropertyChanged(prop_canvasPosition); } } void ScrollingFrame::setScrollBarThickness(int value) { int newValue = std::max(0, value); if (newValue != value) { RBX::StandardOut::singleton()->printf(RBX::MESSAGE_WARNING, "ScrollingFrame.ScrollBarThickness set to negative value, clamping value to 0."); } if (newValue != scrollBarThickness) { scrollBarThickness = newValue; raisePropertyChanged(prop_scrollBarThickness); } } void ScrollingFrame::setTopImage(TextureId value) { if (value != topImage) { topImage = value; raisePropertyChanged(prop_topImage); } } void ScrollingFrame::setMidImage(TextureId value) { if (value != midImage) { midImage = value; raisePropertyChanged(prop_midImage); } } void ScrollingFrame::setBottomImage(TextureId value) { if (value != bottomImage) { bottomImage = value; raisePropertyChanged(prop_bottomImage); } } Rect2D ScrollingFrame::getCanvasRectLocal() const { Rect2D myScrollRect; if(const GuiBase2d* guiParent = Instance::fastDynamicCast(getParent())) { myScrollRect = guiParent->getRect2D(); } else { myScrollRect = getRect2D(); } float width = (myScrollRect.width() * canvasSize.x.scale) + canvasSize.x.offset; float height = (myScrollRect.height() * canvasSize.y.scale) + canvasSize.y.offset; // make sure there is a valid size for these width = std::max(getRect2D().width(), width); height = std::max(getRect2D().height(), height); const Vector2 rectPos = getRect2D().x0y0(); return Rect2D::xywh(rectPos.x - canvasPosition.x, rectPos.y - canvasPosition.y, width, height); } Rect2D ScrollingFrame::getCanvasRect() const { Rect2D canvasLocalRect = getCanvasRectLocal(); Vector2 localPos = canvasLocalRect.x0y0(); return Rect2D::xywh(localPos, canvasLocalRect.wh()); } void ScrollingFrame::handleResize(const Rect2D& viewport, bool force) { Super::handleResize(viewport,force); resizeChildren(); } void ScrollingFrame::stepScrollInertia(const Stepped& event) { if(sampledVelocity != Vector2::zero() && touchInput == shared_ptr()) { Vector2 movement = sampledVelocity * event.gameStep; handleInputDrag(lastInputPosition + movement); sampledVelocity *= DECELERATION_RATE; if (sampledVelocity.length() <= 1.0f) { sampledVelocity = Vector2::zero(); scrollDragging = false; } } } void ScrollingFrame::stepGamepadScroll(const Stepped& event) { Vector2 scrollDelta = lastGamepadScrollDelta * event.gameStep * GAMEPAD_SCROLL_CONSTANT; if (scrollDelta != Vector2::zero()) { if (!scrollByPosition(scrollDelta)) { if (GamepadService* gamepadService = RBX::ServiceProvider::create(this)) { gamepadService->trySelectGuiObject(-lastGamepadScrollDelta.direction()); lastGamepadScrollDelta = Vector2::zero(); } } } } void ScrollingFrame::onStepped(const Stepped& event) { stepScrollInertia(event); stepGamepadScroll(event); recalculateScroll(); } Rect2D ScrollingFrame::getClippedRect() { Rect2D clippedRect = Super::getClippedRect(); clippedRect = Rect2D::xywh( clippedRect.x0(), clippedRect.y0(), canScrollVertical() ? (clippedRect.width() - scrollBarThickness - 1) : clippedRect.width(), canScrollHorizontal() ? (clippedRect.height() - scrollBarThickness - 1) : clippedRect.height() ); return clippedRect; } Vector2 ScrollingFrame::getCanvasScrollingSize() const { Vector2 scrollingSize = getCanvasRectLocal().wh() - getRect2D().wh(); scrollingSize = scrollingSize.clamp(Vector2::zero(),Vector2::inf()); return ( scrollingSize + Vector2(canScrollVertical() ? scrollBarThickness : 0, canScrollHorizontal() ? scrollBarThickness : 0) ); } Vector2 ScrollingFrame::getFrameScrollingSize() const { const bool noCorner = (!canScrollHorizontal() || !canScrollVertical()); return (getRect2D().wh() - getVerticalScrollBarSize() - getHorizontalScrollBarSize() + (noCorner ? Vector2(scrollBarThickness,scrollBarThickness) : Vector2::zero())); } Vector2 ScrollingFrame::getVerticalScrollBarSize() const { const float minScrollBarLength = getMinScrollBarSize(); const bool hasCorner = (canScrollHorizontal() && canScrollVertical()); const float rectHeight = getRect2D().height(); float verticalSize = (hasCorner ? (-scrollBarThickness+1) : 0) + (rectHeight / getCanvasRectLocal().height()) * rectHeight; verticalSize = std::max(minScrollBarLength, verticalSize); return Vector2(scrollBarThickness, verticalSize); } Vector2 ScrollingFrame::getHorizontalScrollBarSize() const { const float minScrollBarLength = getMinScrollBarSize(); const bool hasCorner = (canScrollHorizontal() && canScrollVertical()); const float rectWidth = getRect2D().width(); float horizontalSize = (hasCorner ? (-scrollBarThickness+1) : 0) + (rectWidth / getCanvasRectLocal().width()) * rectWidth; horizontalSize = std::max(minScrollBarLength, horizontalSize); return Vector2(horizontalSize, scrollBarThickness); } float ScrollingFrame::getMinScrollBarSize() const { return MIN_SCROLLBAR_SIZE + (scrollBarThickness * 2); } void ScrollingFrame::recalculateScroll() { if (!needsRecalculate) { return; } needsRecalculate = false; const Rect2D myRect = getRect2D(); const Vector2 scrollSize = getFrameScrollingSize(); const Vector2 canvasSize = getCanvasScrollingSize(); const Vector2 canvasPos = calculateCanvasPositionClamped(getCanvasPosition()); setCanvasPosition(canvasPos, false); //////////////////////////////////////////////////////////////////////////////////////////// // Vertical Calculation const float yPercentageLocation = (canvasSize.y > 0.0f) ? canvasPos.y/canvasSize.y : 0.0f; const float yPositionVertical = myRect.y0() + (scrollSize.y * yPercentageLocation); const Vector2 sizeVertical = getVerticalScrollBarSize(); const Vector2 positionVertical = Vector2(myRect.x1() - sizeVertical.x, yPositionVertical); verticalBarSize = Rect2D::xywh(positionVertical, sizeVertical); ////////////////////////////////////////////////////////////////////////////////////////// // Horizontal Calculation const float xPercentageLocation = (canvasSize.x > 0.0f) ? canvasPos.x/canvasSize.x : 0.0f; const float xPositionHorizontal = myRect.x0() + (scrollSize.x * xPercentageLocation); const Vector2 sizeHorizontal = getHorizontalScrollBarSize(); const Vector2 positionHorizontal = Vector2(xPositionHorizontal, myRect.y1() - sizeHorizontal.y); horizontalBarSize = Rect2D::xywh(positionHorizontal, sizeHorizontal); } bool ScrollingFrame::scrollByPosition(const Vector2& inputDelta, bool printWarnings) { if (!scrollingEnabled) { return false; } const bool verticalScroll = canScrollVertical(); const bool horizontalScroll = canScrollHorizontal(); if (!verticalScroll && !horizontalScroll) { return false; } const bool bothScrollDir = (verticalScroll && horizontalScroll); const Rect2D myRect = getRect2D(); const Vector2 scrollingFrameSize = getFrameScrollingSize(); Vector2 oldCanvasPosition = canvasPosition; if ( verticalScroll ) { const Rect2D currentVerticalBar = getVerticalBarRect(); Vector2 newVerticalPos = currentVerticalBar.x0y0() - inputDelta; newVerticalPos = newVerticalPos.clamp(Vector2(myRect.x1() - scrollBarThickness, myRect.y0()), Vector2(myRect.x1() - scrollBarThickness, myRect.y1() - currentVerticalBar.height() - (bothScrollDir ? scrollBarThickness : 0))); const float verticalPercent = (scrollingFrameSize.y > 0.0f) ? (newVerticalPos.y - myRect.y0())/scrollingFrameSize.y : 0.0f; setCanvasPosition( Vector2(canvasPosition.x, getCanvasScrollingSize().y * verticalPercent), printWarnings ); } if (horizontalScroll) { const Rect2D currentHorizontalBar = getHorizontalBarRect(); Vector2 newHorizontalPos = currentHorizontalBar.x0y0() - inputDelta; newHorizontalPos = newHorizontalPos.clamp(Vector2(myRect.x0(), myRect.y1() - scrollBarThickness), Vector2(myRect.x1() - currentHorizontalBar.width() - (bothScrollDir ? scrollBarThickness : 0), myRect.y1() - scrollBarThickness)); float horizontalPercent = (scrollingFrameSize.x > 0.0f) ? (newHorizontalPos.x - myRect.x0())/scrollingFrameSize.x : 0.0f; horizontalPercent = G3D::clamp(horizontalPercent,0.0f,1.0f); setCanvasPosition( Vector2(getCanvasScrollingSize().x * horizontalPercent, canvasPosition.y), printWarnings ); } return oldCanvasPosition != canvasPosition; } void ScrollingFrame::resizeChildren() { if (getChildren()) { for (Instances::const_iterator child_itr = getChildren()->begin(); child_itr != getChildren()->end(); ++child_itr) { if (GuiObject* child = Instance::fastDynamicCast(child_itr->get())) { child->checkForResize(); } } } } Vector2 ScrollingFrame::getScrollWheelDelta(bool scrollingDownValue) { Vector2 scrollDelta(0, scrollingDownValue ? WHEEL_SCROLL_DELTA : -WHEEL_SCROLL_DELTA); double lastScrollDelta; if (scrollingDownValue) { lastScrollDelta = wheelDownTimer.delta().msec(); wheelDownTimer.reset(); } else { lastScrollDelta = wheelUpTimer.delta().msec(); wheelUpTimer.reset(); } if (lastScrollDelta <= MAX_MULTIPLY_TIME_MSEC) { scrollDelta *= Vector2(0,scrollDeltaMultiplier); if (scrollDeltaMultiplier == 1) { scrollDeltaMultiplier++; } else { scrollDeltaMultiplier *= 4; } if (DFFlag::LimitScrollWheelMaxToHalfWindowSize) { scrollDeltaMultiplier = std::min(float(scrollDeltaMultiplier), std::min((getVisibleRect2D().height() * 0.5f)/WHEEL_SCROLL_DELTA, (getCanvasRectLocal().height() * MAX_SCROLL_WHEEL_SCROLL_PERCENT)/WHEEL_SCROLL_DELTA)); } else { scrollDeltaMultiplier = std::min(1.0f * scrollDeltaMultiplier, (getCanvasRectLocal().height() * MAX_SCROLL_WHEEL_SCROLL_PERCENT)/WHEEL_SCROLL_DELTA); } } else { scrollDeltaMultiplier = 1; } return scrollDelta; } void ScrollingFrame::scrollDownWheel() { if (canScrollVertical()) { Vector2 scrollDelta = getScrollWheelDelta(true); setCanvasPosition( canvasPosition + scrollDelta, false); lastInputPosition += scrollDelta; } } void ScrollingFrame::scrollUpWheel() { if (canScrollVertical()) { Vector2 scrollDelta = getScrollWheelDelta(false); setCanvasPosition( canvasPosition + scrollDelta, false); lastInputPosition += scrollDelta; } } bool ScrollingFrame::canScrollVertical() const { return ( getCanvasRectLocal().height() > getRect2D().height() ); } bool ScrollingFrame::canScrollHorizontal() const { return ( getCanvasRectLocal().width() > getRect2D().width() ); } bool ScrollingFrame::isInsideDraggingRect(const Vector2& inputPosition) { if (isTouchDevice()) { return true; } if (scrollingDirection == X) { const Rect2D visibleRect = getVisibleRect2D(); if ( (inputPosition.x < visibleRect.x0()) ) { scrollByPosition(Vector2(REALLY_BIG_SCROLL_DELTA,0)); return false; } if( (inputPosition.x > (visibleRect.x0() + visibleRect.width())) ) { scrollByPosition(Vector2(-REALLY_BIG_SCROLL_DELTA,0)); return false; } } else if (scrollingDirection == Y) { const Rect2D visibleRect = getVisibleRect2D(); if ( (inputPosition.y < visibleRect.y0()) ) { scrollByPosition(Vector2(0,REALLY_BIG_SCROLL_DELTA)); return false; } if ( inputPosition.y > (visibleRect.y0() + visibleRect.height()) ) { scrollByPosition(Vector2(0,-REALLY_BIG_SCROLL_DELTA), false); return false; } } return true; } void ScrollingFrame::handleInputDrag(const Vector2& inputPosition, const shared_ptr& event) { if (scrollDragging && inputPosition != lastInputPosition) { if (event && event->isGamepadEvent()) { // don't do dragging rect comparison for gamepad } else if (!isInsideDraggingRect(inputPosition)) { return; } Vector2 dragCoefficient; if (scrollingDirection == XY) { dragCoefficient = Vector2(1,1); } else { dragCoefficient = Vector2( (scrollingDirection == X) ? 1.0f : 0.0f, (scrollingDirection == Y) ? 1.0f : 0.0f); } if (event && event->isGamepadEvent()) { lastGamepadScrollDelta = inputPosition * dragCoefficient; scrollByPosition(lastGamepadScrollDelta); } else { Vector2 inputDelta = inputPosition - lastInputPosition; Vector2 dragDelta = dragCoefficient * inputDelta; if (isTouchDevice()) // scroll by canvasPos if using finger { setCanvasPosition( canvasPosition - dragDelta, false); } else { scrollByPosition(-dragDelta); } } lastInputPosition = inputPosition; } } void ScrollingFrame::handleInputBegin(const Vector2& inputPosition, const ScrollingDirection& dragDirection) { scrollingDirection = dragDirection; lastInputPosition = inputPosition; scrollDragging = true; } bool ScrollingFrame::hasMouse() const { if (RBX::UserInputService* userInputService = RBX::ServiceProvider::find(this)) return userInputService->getMouseEnabled(); return false; } bool ScrollingFrame::isTouchDevice() const { if (RBX::UserInputService* userInputService = RBX::ServiceProvider::find(this)) return userInputService->getTouchEnabled(); return false; } void ScrollingFrame::setScrollingInertia() { sampledVelocity = Vector2::zero(); if (touchDeltaTimer.delta().msec() < TOUCH_INERTIA_RESET_TIME_MSEC) { for (unsigned int i = 0; i < touchInputPositions.size(); ++i) { std::pair currPair = touchInputPositions[i]; sampledVelocity += currPair.first/currPair.second; } sampledVelocity /= touchInputPositions.size(); sampledVelocity = sampledVelocity.clamp(-getCanvasRectLocal().wh(), getCanvasRectLocal().wh()); } } void ScrollingFrame::globalInputEnded(const shared_ptr& event) { if (InputObject* inputObject = Instance::fastDynamicCast(event.get())) { if(touchInput.get() == inputObject) { touchInput.reset(); touchScrolling = false; // input has ended, on touch give some extra scrolling inertia setScrollingInertia(); } if (leftMouseDownInput.get() == inputObject) { leftMouseDownInput.reset(); scrollDragging = false; } } } bool ScrollingFrame::processInputFromDescendant(const shared_ptr& event) { GuiResponse response = process(event); return response.wasSunk(); } bool ScrollingFrame::doProcessMouseEvent(const shared_ptr& event, const bool inputOver) { const bool mouseWheelForward = event->isMouseWheelForward(); const bool mouseWheelBackward = event->isMouseWheelBackward(); const bool eventLeftButtonUp = event->isLeftMouseUpEvent(); const bool eventLeftButtonDown = event->isLeftMouseDownEvent(); const bool wasDownOver = (guiState == Gui::DOWN_OVER); bool sinkEvent = (scrollDragging && eventLeftButtonUp && wasDownOver); if (eventLeftButtonDown) { isLeftMouseDown = true; } if (eventLeftButtonUp) { isLeftMouseDown = false; } if(eventLeftButtonDown) { if (verticalBarSize.contains(Vector2(event->get2DPosition()))) { if (leftMouseDownInput == shared_ptr() && event->getUserInputState() == InputObject::INPUT_STATE_BEGIN) { leftMouseDownInput = shared_from(event.get()); } handleInputBegin(event->get2DPosition(), Y); } else if (horizontalBarSize.contains(Vector2(event->get2DPosition()))) { if (leftMouseDownInput == shared_ptr() && event->getUserInputState() == InputObject::INPUT_STATE_BEGIN) { leftMouseDownInput = shared_from(event.get()); } handleInputBegin(event->get2DPosition(), X); } sinkEvent = scrollDragging; } if(inputOver) { if (eventLeftButtonUp && scrollDragging) {sinkEvent = true;} if (mouseWheelBackward) {sinkEvent = true; scrollDownWheel(); } if (mouseWheelForward) {sinkEvent = true; scrollUpWheel(); } } if (isLeftMouseDown && event->getUserInputType() == InputObject::TYPE_MOUSEMOVEMENT) handleInputDrag(event->get2DPosition()); if (eventLeftButtonUp) scrollDragging = false; return sinkEvent; } bool ScrollingFrame::doProcessTouchEvent(const shared_ptr& event, const bool inputOver) { bool sinkEvent = false; if (inputOver && touchInput == shared_ptr() && event->getUserInputState() == InputObject::INPUT_STATE_BEGIN) { touchInput = shared_from(event.get()); handleInputBegin(event->get2DPosition(), XY); touchInputPositions.clear(); touchDeltaTimer.reset(); sinkEvent = true; } else if(touchInput.get() == event.get()) { if (touchInput->getUserInputState() == InputObject::INPUT_STATE_CHANGE) { Vector2 delta = (event->get2DPosition() - lastInputPosition); if ( delta.length() > 0.0f && (canScrollVertical() || canScrollHorizontal()) ) { touchScrolling = true; std::pair positionDeltaTimePair(delta, (float)touchDeltaTimer.delta().seconds()); touchInputPositions.push_back(positionDeltaTimePair); handleInputDrag(event->get2DPosition()); touchDeltaTimer.reset(); } } sinkEvent = true; } return sinkEvent; } bool ScrollingFrame::canScrollInDirection(Vector2 direction) { Vector2 maxSize = getMaxSize(); return canvasPosition != G3D::clamp(canvasPosition + direction, Vector2::zero(), maxSize.max(Vector2::zero())); } bool ScrollingFrame::doProcessGamepadEvent(const shared_ptr& event, const bool inputOver) { bool sinkEvent = false; GamepadService* gamepadService = RBX::ServiceProvider::find(this); if (!gamepadService) { return sinkEvent; } if ( !gamepadService->isNavigationGamepad(event->getUserInputType()) ) { return sinkEvent; } GuiService* guiService = RBX::ServiceProvider::find(this); if (!guiService) { return sinkEvent; } GuiObject* selectedObject = guiService->getSelectedGuiObject(); if (!selectedObject || selectedObject != this) { return sinkEvent; } if (event->getKeyCode() == SDLK_GAMEPAD_THUMBSTICK1 || event->isDPadEvent()) { if (Instance::fastDynamicCast(selectedObject) && selectedObject->isDescendantOf(this)) { return sinkEvent; } sinkEvent = true; Vector2 scrollingDelta = event->get2DPosition(); if (event->isDPadEvent() && event->getUserInputState() == InputObject::INPUT_STATE_BEGIN) { lastInputPosition = Vector2::zero(); switch (event->getKeyCode()) { case SDLK_GAMEPAD_DPADDOWN: scrollingDelta = Vector2(0,-1.0f); break; case SDLK_GAMEPAD_DPADUP: scrollingDelta = Vector2(0,1.0f); break; case SDLK_GAMEPAD_DPADLEFT: scrollingDelta = Vector2(-1.0f,0); break; case SDLK_GAMEPAD_DPADRIGHT:scrollingDelta = Vector2(1.0f,0); break; default: break; } } else if (event->getKeyCode() == SDLK_GAMEPAD_THUMBSTICK1) { fabs(scrollingDelta.x) > fabs(scrollingDelta.y) ? scrollingDelta.y = 0 : scrollingDelta.x = 0; if ( !canScrollInDirection(Vector2(scrollingDelta.x, -scrollingDelta.y)) ) { lastGamepadScrollDelta = Vector2::zero(); return selectionTimer.delta().msec() <= GAMEPAD_THUMBSTICK_WAIT_PERIOD_MSEC; } } Vector2 maxSize = getMaxSize(); if (maxSize.x == 0.0f) { if (scrollingDelta.x != 0.0f) { sinkEvent = false; scrollingDelta.x = 0.0f; } } if (maxSize.y == 0.0f) { if (scrollingDelta.y != 0.0f) { sinkEvent = false; scrollingDelta.y = 0.0f; } } scrollDragging = scrollingDelta.length() > 0.3f; if (scrollDragging) { scrollingDirection = XY; handleInputDrag(Vector2(-scrollingDelta.x,scrollingDelta.y), event); } else { lastGamepadScrollDelta = Vector2::zero(); } } return sinkEvent; } GuiResponse ScrollingFrame::process(const shared_ptr& event) { if (!scrollingEnabled || event->getUserInputState() == InputObject::INPUT_STATE_CANCEL) { return GuiResponse::notSunk(); } Rect rect(getRect2D()); if( GuiObject* clippingObject = firstAncestorClipping() ) { RBX::Rect2D clippedRect = getRect2D().intersect(clippingObject->getClippedRect()); rect = Rect(clippedRect); } const bool inputOver = rect.pointInRect(event->get2DPosition()); bool sinkEvent = false; if (isTouchDevice() && event->isTouchEvent()) { sinkEvent = doProcessTouchEvent(event, inputOver); } else if (event->isGamepadEvent()) { sinkEvent = doProcessGamepadEvent(event, inputOver); } else if (hasMouse() && event->isMouseEvent()) { sinkEvent = doProcessMouseEvent(event, inputOver); } if (sinkEvent) { return GuiResponse::sunk(); } else { return Super::process(event); } } void ScrollingFrame::renderScrollbarLines(Adorn* adorn, const bool scrollVertical, const bool scrollHorizontal) { Rect2D rect = getRect2D(); Rect2D clippedRect = rect; if( GuiObject* clippingObject = firstAncestorClipping()) { const RBX::Rect2D clipRect = clippingObject->getClippedRect(); clippedRect = clipRect.intersect(rect); } const Rotation2D rotation = getAbsoluteRotation(); const int borderSize = getBorderSizePixel(); // vertical line const float xPosForVerticalLine = rect.x1() - scrollBarThickness; if (scrollVertical && scrollBarThickness > 0 && xPosForVerticalLine < clippedRect.x1()) { const float yBeginPos = std::max(rect.y0(), clippedRect.y0()); const float yEndPos = std::min(rect.y1(), clippedRect.y1()); const Vector2 beginPos = Vector2(xPosForVerticalLine, yBeginPos); const Vector2 endPos = Vector2(xPosForVerticalLine, yEndPos); adorn->rect2d(Rect2D::xyxy(beginPos.x - borderSize, beginPos.y - borderSize, beginPos.x, endPos.y), borderColor, rotation); } // horizontal line const float yPosForHorizonalLine = rect.y1() - scrollBarThickness; if (scrollHorizontal && scrollBarThickness > 0 && yPosForHorizonalLine < clippedRect.y1()) { const float xBeginPos = std::max(rect.x0(), clippedRect.x0()); const float xEndPos = std::min(rect.x1(), clippedRect.x1()); const Vector2 beginPos = Vector2(xBeginPos, yPosForHorizonalLine); const Vector2 endPos = Vector2(xEndPos, yPosForHorizonalLine); adorn->rect2d(Rect2D::xyxy(beginPos.x, beginPos.y - borderSize, endPos.x + borderSize, beginPos.y), borderColor, rotation); } } void ScrollingFrame::renderScrollbarSection(Adorn* adorn, GuiDrawImage& drawImage, const TextureId& image, const char* context, const Rect2D& imageRect, const Rotation2D& rotation) { if (drawImage.setImage(adorn, image, GuiDrawImage::NORMAL, NULL, this, context)) { Vector2 lowerUV(0,0); Vector2 upperUV(1,1); if( GuiObject* clippingObject = firstAncestorClipping()) { const RBX::Rect2D clipRect = clippingObject->getClippedRect(); RBX::Rect2D clippedRect = clipRect.intersect(imageRect); if (rotation != Rotation2D()) { if (DFFlag::FixRotatedHorizontalScrollBar) { Rotation2D reverseRotation(rotation.getAngle().inverse(),imageRect.center()); Vector2 clippedOriginRotated = reverseRotation.rotate(clipRect.x0y0()); Vector2 clippedx1y1Rotated = reverseRotation.rotate(clipRect.x1y1()); RBX::Rect2D clipRectRotated = Rect2D::xyxy(clippedOriginRotated, clippedx1y1Rotated); clippedRect = clipRectRotated.intersect(imageRect); } else { Rotation2D reverseRotation(rotation.getAngle().inverse(),imageRect.center()); Vector2 clippedOriginRotated = reverseRotation.rotate(clippedRect.x0y0()); Vector2 clippedx1y1Rotated = reverseRotation.rotate(clippedRect.x1y1()); clippedRect = Rect2D::xyxy(clippedOriginRotated, clippedx1y1Rotated); } } if(clippedRect.width() != 0) { float uvwidth = upperUV.x - lowerUV.x; lowerUV.x += ( uvwidth * ( clippedRect.x0() - imageRect.x0() ) / imageRect.width() ); upperUV.x += ( uvwidth * ( clippedRect.x1() - imageRect.x1() ) / imageRect.width() ); } if(clippedRect.height() != 0) { float uvheight = upperUV.y - lowerUV.y; lowerUV.y += ( uvheight * ( clippedRect.y0() - imageRect.y0() ) / imageRect.height() ); upperUV.y += ( uvheight * ( clippedRect.y1() - imageRect.y1() ) / imageRect.height() ); } if (clippedRect != RBX::Rect2D()) { drawImage.render2d(adorn, true, clippedRect, lowerUV, upperUV, Color3::white(), rotation, Gui::NOTHING, false); } } else { Rect2D rotatedImageRect = imageRect; if (!rotation.getAngle().empty() && !getAbsoluteRotation().getAngle().empty()) { Vector2 x0y0 = rotation.rotate(imageRect.x0y0()); Vector2 x1y1 = rotation.rotate(imageRect.x1y1()); rotatedImageRect = Rect2D::xyxy(x0y0,x1y1); } drawImage.render2d(adorn, true, rotatedImageRect, lowerUV, upperUV, Color3::white(), getAbsoluteRotation().getAngle().empty() ? rotation : getAbsoluteRotation(), Gui::NOTHING, false); } } } void ScrollingFrame::renderHorizontalScrollBar(Adorn* adorn) { Rect2D lastHorizontalBarSize = getHorizontalBarRect(); ////////////////////////////////////////////////////////////////////////// // Top Image Rect2D topImageRect = Rect2D::xywh(lastHorizontalBarSize.x0y0(), Vector2(scrollBarThickness, lastHorizontalBarSize.height())); renderScrollbarSection(adorn, hTopImage, topImage, ".TopImage", topImageRect, Rotation2D(RotationAngle(-90.0f),topImageRect.center()) ); ////////////////////////////////////////////////////////////////////////// // Mid Image Vector2 midImageRectSize = Vector2(lastHorizontalBarSize.height(), std::max(MIN_SCROLLBAR_SIZE, lastHorizontalBarSize.width() - (scrollBarThickness*2))); Vector2 midImagePosition = lastHorizontalBarSize.center() - Vector2(scrollBarThickness/2, midImageRectSize.y/2); Rect2D midImageRect = Rect2D::xywh(midImagePosition, midImageRectSize); renderScrollbarSection(adorn, hMidImage, midImage, ".MidImage", midImageRect, Rotation2D(RotationAngle(-90.0f),midImageRect.center()) ); ////////////////////////////////////////////////////////////////////////// // Bottom Image Rect2D bottomImageRect = Rect2D::xywh(lastHorizontalBarSize.x0y0() + Vector2(lastHorizontalBarSize.width() - scrollBarThickness, 0), Vector2(scrollBarThickness, lastHorizontalBarSize.height())); renderScrollbarSection(adorn, hBottomImage, bottomImage, ".BottomImage", bottomImageRect, Rotation2D(RotationAngle(-90.0f),bottomImageRect.center()) ); } void ScrollingFrame::renderVerticalScrollBar(Adorn* adorn) { Rect2D lastVerticalBarSize = getVerticalBarRect(); Rect2D topRect = Rect2D::xywh(lastVerticalBarSize.x0y0(), Vector2(scrollBarThickness, scrollBarThickness)); renderScrollbarSection(adorn, vTopImage, topImage, ".TopImage", topRect, Rotation2D()); Rect2D midRect = Rect2D::xywh(topRect.x0y0() + Vector2(0, topRect.height()), Vector2(scrollBarThickness, std::max(MIN_SCROLLBAR_SIZE, lastVerticalBarSize.height() - (scrollBarThickness*2)))); renderScrollbarSection(adorn, vMidImage, midImage, ".MidImage", midRect, Rotation2D()); Rect2D bottomRect = Rect2D::xywh(midRect.x0y0() + Vector2(0, midRect.height()), Vector2(scrollBarThickness, scrollBarThickness)); renderScrollbarSection(adorn, vBottomImage, bottomImage, ".BottomImage", bottomRect, Rotation2D()); } void ScrollingFrame::render2d(Adorn* adorn) { Super::render2d(adorn); const bool scrollVertical = canScrollVertical(); const bool scrollHorizontal = canScrollHorizontal(); renderScrollbarLines(adorn, scrollVertical, scrollHorizontal); // render vertical scrollbar if (scrollVertical && scrollBarThickness > 0) { renderVerticalScrollBar(adorn); } // render horizontal scrollbar if (scrollHorizontal && scrollBarThickness > 0) { renderHorizontalScrollBar(adorn); } } bool checkDescendantsForInteractable(Instance* instance) { if (instance->getChildren()) { Instances::const_iterator end = instance->getChildren()->end(); for (Instances::const_iterator iter = instance->getChildren()->begin(); iter != end; ++iter) { if (Instance::fastDynamicCast((*iter).get()) || Instance::fastDynamicCast((*iter).get())) { if (Instance::fastDynamicCast((*iter).get())) { return true; } else { return checkDescendantsForInteractable((*iter).get()); } } } } return false; } bool ScrollingFrame::hasInteractableDescendants() { return checkDescendantsForInteractable(this); } } // namespace RBX