mirror of
https://github.com/copyrighttxt/watrbx-game-engine.git
synced 2026-09-04 20:57:49 +00:00
1307 lines
40 KiB
C++
1307 lines
40 KiB
C++
#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<ScrollingFrame, bool> prop_scrollingEnabled("ScrollingEnabled", category_Scrolling, &ScrollingFrame::getScrollingEnabled, &ScrollingFrame::setScrollingEnabled);
|
|
|
|
const Reflection::PropDescriptor<ScrollingFrame, UDim2> prop_canvasSize("CanvasSize", category_Scrolling, &ScrollingFrame::getCanvasSize, &ScrollingFrame::setCanvasSize);
|
|
const Reflection::PropDescriptor<ScrollingFrame, Vector2> prop_canvasPosition("CanvasPosition", category_Scrolling, &ScrollingFrame::getCanvasPosition, &ScrollingFrame::luaSetCanvasPosition);
|
|
|
|
const Reflection::PropDescriptor<ScrollingFrame, Vector2> prop_absWindowSize("AbsoluteWindowSize", category_Scrolling, &ScrollingFrame::getAbsoluteWindowSize, NULL);
|
|
|
|
const Reflection::PropDescriptor<ScrollingFrame, int> prop_scrollBarThickness("ScrollBarThickness", category_Scrolling, &ScrollingFrame::getScrollBarThickness, &ScrollingFrame::setScrollBarThickness);
|
|
|
|
const Reflection::PropDescriptor<ScrollingFrame, TextureId> prop_topImage("TopImage", category_Scrolling, &ScrollingFrame::getTopImage, &ScrollingFrame::setTopImage);
|
|
const Reflection::PropDescriptor<ScrollingFrame, TextureId> prop_midImage("MidImage", category_Scrolling, &ScrollingFrame::getMidImage, &ScrollingFrame::setMidImage);
|
|
const Reflection::PropDescriptor<ScrollingFrame, TextureId> prop_bottomImage("BottomImage", category_Scrolling, &ScrollingFrame::getBottomImage, &ScrollingFrame::setBottomImage);
|
|
|
|
ScrollingFrame::ScrollingFrame()
|
|
: DescribedCreatable<ScrollingFrame, GuiObject, sScrollingFrame>(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<UserInputService>(newProvider))
|
|
{
|
|
inputEndedConnection = inputService->coreInputEndedEvent.connect(boost::bind(&ScrollingFrame::globalInputEnded,this,_1));
|
|
}
|
|
|
|
if(GuiService* guiService = RBX::ServiceProvider::find<GuiService>(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<GuiService>(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<GuiBase2d>(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<InputObject>())
|
|
{
|
|
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<GamepadService>(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<GuiObject>(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<InputObject>& 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<RBX::UserInputService>(this))
|
|
return userInputService->getMouseEnabled();
|
|
|
|
return false;
|
|
}
|
|
|
|
bool ScrollingFrame::isTouchDevice() const
|
|
{
|
|
if (RBX::UserInputService* userInputService = RBX::ServiceProvider::find<RBX::UserInputService>(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<Vector2, float> currPair = touchInputPositions[i];
|
|
sampledVelocity += currPair.first/currPair.second;
|
|
}
|
|
sampledVelocity /= touchInputPositions.size();
|
|
sampledVelocity = sampledVelocity.clamp(-getCanvasRectLocal().wh(), getCanvasRectLocal().wh());
|
|
}
|
|
}
|
|
|
|
void ScrollingFrame::globalInputEnded(const shared_ptr<Instance>& event)
|
|
{
|
|
if (InputObject* inputObject = Instance::fastDynamicCast<InputObject>(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<InputObject>& event)
|
|
{
|
|
GuiResponse response = process(event);
|
|
return response.wasSunk();
|
|
}
|
|
|
|
bool ScrollingFrame::doProcessMouseEvent(const shared_ptr<InputObject>& 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<InputObject>() && 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<InputObject>() && 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<InputObject>& event, const bool inputOver)
|
|
{
|
|
bool sinkEvent = false;
|
|
|
|
if (inputOver && touchInput == shared_ptr<InputObject>() && 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<Vector2, float> 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<InputObject>& event, const bool inputOver)
|
|
{
|
|
bool sinkEvent = false;
|
|
|
|
GamepadService* gamepadService = RBX::ServiceProvider::find<GamepadService>(this);
|
|
|
|
if (!gamepadService)
|
|
{
|
|
return sinkEvent;
|
|
}
|
|
|
|
if ( !gamepadService->isNavigationGamepad(event->getUserInputType()) )
|
|
{
|
|
return sinkEvent;
|
|
}
|
|
|
|
GuiService* guiService = RBX::ServiceProvider::find<GuiService>(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<GuiButton>(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<InputObject>& 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<GuiButton>((*iter).get()) || Instance::fastDynamicCast<TextBox>((*iter).get()))
|
|
{
|
|
if (Instance::fastDynamicCast<GuiObject>((*iter).get()))
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return checkDescendantsForInteractable((*iter).get());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool ScrollingFrame::hasInteractableDescendants()
|
|
{
|
|
return checkDescendantsForInteractable(this);
|
|
}
|
|
} // namespace RBX
|