You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2510 lines
99 KiB
C++
2510 lines
99 KiB
C++
/*
|
|
* Copyright (c) Contributors to the Open 3D Engine Project.
|
|
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
*
|
|
*/
|
|
#include "UiScrollBoxComponent.h"
|
|
#include "Sprite.h"
|
|
|
|
#include <AzCore/Component/TickBus.h>
|
|
#include <AzCore/Serialization/SerializeContext.h>
|
|
#include <AzCore/Serialization/EditContext.h>
|
|
#include <AzCore/RTTI/BehaviorContext.h>
|
|
#include <AzCore/std/sort.h>
|
|
|
|
#include <LyShine/Bus/UiCanvasBus.h>
|
|
#include <LyShine/Bus/UiElementBus.h>
|
|
#include <LyShine/Bus/UiTransform2dBus.h>
|
|
#include <LyShine/Bus/UiTransformBus.h>
|
|
#include <LyShine/Bus/UiVisualBus.h>
|
|
|
|
#include <LyShine/ISprite.h>
|
|
#include <LyShine/UiSerializeHelpers.h>
|
|
#include "UiSerialize.h"
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//! UiScrollBoxNotificationBus Behavior context handler class
|
|
class BehaviorUiScrollBoxNotificationBusHandler
|
|
: public UiScrollBoxNotificationBus::Handler
|
|
, public AZ::BehaviorEBusHandler
|
|
{
|
|
public:
|
|
AZ_EBUS_BEHAVIOR_BINDER(BehaviorUiScrollBoxNotificationBusHandler, "{15CA0E45-F673-4E18-922F-D9DB1272CFEA}", AZ::SystemAllocator,
|
|
OnScrollOffsetChanging, OnScrollOffsetChanged);
|
|
|
|
void OnScrollOffsetChanging(AZ::Vector2 value) override
|
|
{
|
|
Call(FN_OnScrollOffsetChanging, value);
|
|
}
|
|
|
|
void OnScrollOffsetChanged(AZ::Vector2 value) override
|
|
{
|
|
Call(FN_OnScrollOffsetChanged, value);
|
|
}
|
|
};
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//! UiScrollableNotificationBus Behavior context handler class
|
|
class BehaviorUiScrollableNotificationBusHandler
|
|
: public UiScrollableNotificationBus::Handler
|
|
, public AZ::BehaviorEBusHandler
|
|
{
|
|
public:
|
|
AZ_EBUS_BEHAVIOR_BINDER(BehaviorUiScrollableNotificationBusHandler, "{7F130E59-778C-4951-BB62-B2E57E530BC0}", AZ::SystemAllocator,
|
|
OnScrollableValueChanging, OnScrollableValueChanged);
|
|
|
|
void OnScrollableValueChanging(AZ::Vector2 value) override
|
|
{
|
|
Call(FN_OnScrollableValueChanging, value);
|
|
}
|
|
|
|
void OnScrollableValueChanged(AZ::Vector2 value) override
|
|
{
|
|
Call(FN_OnScrollableValueChanged, value);
|
|
}
|
|
};
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// PUBLIC MEMBER FUNCTIONS
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
UiScrollBoxComponent::UiScrollBoxComponent()
|
|
: m_scrollOffset(0.0f, 0.0f)
|
|
, m_isHorizontalScrollingEnabled(true)
|
|
, m_isVerticalScrollingEnabled(false)
|
|
, m_isScrollingConstrained(true)
|
|
, m_snapMode(SnapMode::None)
|
|
, m_snapGrid(10.0f, 10.0f)
|
|
, m_hScrollBarVisibility(ScrollBarVisibility::AlwaysShow)
|
|
, m_vScrollBarVisibility(ScrollBarVisibility::AlwaysShow)
|
|
, m_contentEntity()
|
|
, m_hScrollBarEntity()
|
|
, m_vScrollBarEntity()
|
|
, m_onScrollOffsetChanged()
|
|
, m_onScrollOffsetChanging()
|
|
, m_scrollOffsetChangedActionName()
|
|
, m_scrollOffsetChangingActionName()
|
|
, m_isDragging(false)
|
|
, m_isActive(false)
|
|
, m_pressedScrollOffset(0.0f, 0.0f)
|
|
, m_lastDragPoint(0.0f, 0.0f)
|
|
{
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
UiScrollBoxComponent::~UiScrollBoxComponent()
|
|
{
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
AZ::Vector2 UiScrollBoxComponent::GetScrollOffset()
|
|
{
|
|
return m_scrollOffset;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiScrollBoxComponent::SetScrollOffset(AZ::Vector2 scrollOffset)
|
|
{
|
|
if (m_isScrollingConstrained)
|
|
{
|
|
AZ::Entity* contentParentEntity = nullptr;
|
|
EBUS_EVENT_ID_RESULT(contentParentEntity, m_contentEntity, UiElementBus, GetParent);
|
|
|
|
if (contentParentEntity)
|
|
{
|
|
scrollOffset = ConstrainOffset(scrollOffset, contentParentEntity);
|
|
}
|
|
}
|
|
|
|
if (scrollOffset != m_scrollOffset)
|
|
{
|
|
DoSetScrollOffset(scrollOffset);
|
|
|
|
// Reset drag info
|
|
if (m_isDragging)
|
|
{
|
|
m_pressedScrollOffset = m_scrollOffset;
|
|
m_pressedPoint = m_lastDragPoint;
|
|
}
|
|
|
|
NotifyScrollersOnValueChanged();
|
|
|
|
DoChangedActions();
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
AZ::Vector2 UiScrollBoxComponent::GetNormalizedScrollValue()
|
|
{
|
|
AZ::Vector2 normalizedScrollValueOut(0.0f, 0.0f);
|
|
ScrollOffsetToNormalizedScrollValue(m_scrollOffset, normalizedScrollValueOut);
|
|
|
|
return normalizedScrollValueOut;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiScrollBoxComponent::ChangeContentSizeAndScrollOffset(AZ::Vector2 contentSize, AZ::Vector2 scrollOffset)
|
|
{
|
|
if (m_contentEntity.IsValid())
|
|
{
|
|
AZ::Vector2 prevScrollOffset = m_scrollOffset;
|
|
|
|
// Get current content size
|
|
AZ::Vector2 prevContentSize(0.0f, 0.0f);
|
|
EBUS_EVENT_ID_RESULT(prevContentSize, m_contentEntity, UiTransformBus, GetCanvasSpaceSizeNoScaleRotate);
|
|
|
|
// Resize content element
|
|
if (prevContentSize != contentSize)
|
|
{
|
|
UiTransform2dInterface::Offsets offsets;
|
|
EBUS_EVENT_ID_RESULT(offsets, m_contentEntity, UiTransform2dBus, GetOffsets);
|
|
|
|
AZ::Vector2 pivot;
|
|
EBUS_EVENT_ID_RESULT(pivot, m_contentEntity, UiTransformBus, GetPivot);
|
|
|
|
AZ::Vector2 sizeDiff = contentSize - prevContentSize;
|
|
|
|
if (sizeDiff.GetX() != 0.0f)
|
|
{
|
|
offsets.m_left -= sizeDiff.GetX() * pivot.GetX();
|
|
offsets.m_right += sizeDiff.GetX() * (1.0f - pivot.GetX());
|
|
}
|
|
if (sizeDiff.GetY() != 0.0f)
|
|
{
|
|
offsets.m_top -= sizeDiff.GetY() * pivot.GetY();
|
|
offsets.m_bottom += sizeDiff.GetY() * (1.0f - pivot.GetY());
|
|
}
|
|
|
|
EBUS_EVENT_ID(m_contentEntity, UiTransform2dBus, SetOffsets, offsets);
|
|
}
|
|
|
|
// Adjust scroll offset
|
|
if (m_scrollOffset != scrollOffset)
|
|
{
|
|
DoSetScrollOffset(scrollOffset);
|
|
}
|
|
|
|
// Reset drag info
|
|
if (m_isDragging)
|
|
{
|
|
m_pressedScrollOffset = m_scrollOffset;
|
|
m_pressedPoint = m_lastDragPoint;
|
|
}
|
|
|
|
// Handle content size change which also handles snapping/constraining
|
|
if (prevContentSize != contentSize)
|
|
{
|
|
ContentOrParentSizeChanged();
|
|
}
|
|
else
|
|
{
|
|
if (prevScrollOffset != m_scrollOffset)
|
|
{
|
|
NotifyScrollersOnValueChanged();
|
|
}
|
|
|
|
if (DoSnap())
|
|
{
|
|
// Reset drag info
|
|
if (m_isDragging)
|
|
{
|
|
m_pressedScrollOffset = m_scrollOffset;
|
|
m_pressedPoint = m_lastDragPoint;
|
|
}
|
|
|
|
NotifyScrollersOnValueChanged();
|
|
|
|
DoChangedActions();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
bool UiScrollBoxComponent::HasHorizontalContentToScroll()
|
|
{
|
|
bool hasContentToScroll = false;
|
|
|
|
if (!m_isHorizontalScrollingEnabled)
|
|
{
|
|
hasContentToScroll = false;
|
|
}
|
|
else if (!m_isScrollingConstrained)
|
|
{
|
|
hasContentToScroll = true;
|
|
}
|
|
else
|
|
{
|
|
if (m_hScrollBarEntity.IsValid() && (m_hScrollBarVisibility != ScrollBarVisibility::AlwaysShow))
|
|
{
|
|
bool isEnabled = false;
|
|
EBUS_EVENT_ID_RESULT(isEnabled, m_hScrollBarEntity, UiElementBus, IsEnabled);
|
|
hasContentToScroll = isEnabled;
|
|
}
|
|
else
|
|
{
|
|
AZ::Entity* contentParentEntity = nullptr;
|
|
EBUS_EVENT_ID_RESULT(contentParentEntity, m_contentEntity, UiElementBus, GetParent);
|
|
if (contentParentEntity)
|
|
{
|
|
// Get content parent's size
|
|
AZ::Vector2 parentSize;
|
|
EBUS_EVENT_ID_RESULT(parentSize, contentParentEntity->GetId(), UiTransformBus, GetCanvasSpaceSizeNoScaleRotate);
|
|
|
|
// Get content size
|
|
UiTransformInterface::Rect contentRect = GetAxisAlignedContentRect();
|
|
AZ::Vector2 contentSize = contentRect.GetSize();
|
|
|
|
hasContentToScroll = contentSize.GetX() > parentSize.GetX();
|
|
}
|
|
}
|
|
}
|
|
|
|
return hasContentToScroll;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
bool UiScrollBoxComponent::HasVerticalContentToScroll()
|
|
{
|
|
bool hasContentToScroll = false;
|
|
|
|
if (!m_isVerticalScrollingEnabled)
|
|
{
|
|
hasContentToScroll = false;
|
|
}
|
|
else if (!m_isScrollingConstrained)
|
|
{
|
|
hasContentToScroll = true;
|
|
}
|
|
else
|
|
{
|
|
if (m_vScrollBarEntity.IsValid() && (m_vScrollBarVisibility != ScrollBarVisibility::AlwaysShow))
|
|
{
|
|
bool isEnabled = false;
|
|
EBUS_EVENT_ID_RESULT(isEnabled, m_vScrollBarEntity, UiElementBus, IsEnabled);
|
|
hasContentToScroll = isEnabled;
|
|
}
|
|
else
|
|
{
|
|
AZ::Entity* contentParentEntity = nullptr;
|
|
EBUS_EVENT_ID_RESULT(contentParentEntity, m_contentEntity, UiElementBus, GetParent);
|
|
if (contentParentEntity)
|
|
{
|
|
// Get content parent's size
|
|
AZ::Vector2 parentSize;
|
|
EBUS_EVENT_ID_RESULT(parentSize, contentParentEntity->GetId(), UiTransformBus, GetCanvasSpaceSizeNoScaleRotate);
|
|
|
|
// Get content size
|
|
UiTransformInterface::Rect contentRect = GetAxisAlignedContentRect();
|
|
AZ::Vector2 contentSize = contentRect.GetSize();
|
|
|
|
hasContentToScroll = contentSize.GetY() > parentSize.GetY();
|
|
}
|
|
}
|
|
}
|
|
|
|
return hasContentToScroll;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
bool UiScrollBoxComponent::GetIsHorizontalScrollingEnabled()
|
|
{
|
|
return m_isHorizontalScrollingEnabled;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiScrollBoxComponent::SetIsHorizontalScrollingEnabled(bool isEnabled)
|
|
{
|
|
m_isHorizontalScrollingEnabled = isEnabled;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
bool UiScrollBoxComponent::GetIsVerticalScrollingEnabled()
|
|
{
|
|
return m_isVerticalScrollingEnabled;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiScrollBoxComponent::SetIsVerticalScrollingEnabled(bool isEnabled)
|
|
{
|
|
m_isVerticalScrollingEnabled = isEnabled;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
bool UiScrollBoxComponent::GetIsScrollingConstrained()
|
|
{
|
|
return m_isScrollingConstrained;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiScrollBoxComponent::SetIsScrollingConstrained(bool isConstrained)
|
|
{
|
|
m_isScrollingConstrained = isConstrained;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
UiScrollBoxInterface::SnapMode UiScrollBoxComponent::GetSnapMode()
|
|
{
|
|
return m_snapMode;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiScrollBoxComponent::SetSnapMode(SnapMode snapMode)
|
|
{
|
|
m_snapMode = snapMode;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
AZ::Vector2 UiScrollBoxComponent::GetSnapGrid()
|
|
{
|
|
return m_snapGrid;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiScrollBoxComponent::SetSnapGrid(AZ::Vector2 snapGrid)
|
|
{
|
|
m_snapGrid = snapGrid;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
UiScrollBoxInterface::ScrollBarVisibility UiScrollBoxComponent::GetHorizontalScrollBarVisibility()
|
|
{
|
|
return m_hScrollBarVisibility;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiScrollBoxComponent::SetHorizontalScrollBarVisibility(ScrollBarVisibility visibility)
|
|
{
|
|
m_hScrollBarVisibility = visibility;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
UiScrollBoxInterface::ScrollBarVisibility UiScrollBoxComponent::GetVerticalScrollBarVisibility()
|
|
{
|
|
return m_vScrollBarVisibility;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiScrollBoxComponent::SetVerticalScrollBarVisibility(ScrollBarVisibility visibility)
|
|
{
|
|
m_vScrollBarVisibility = visibility;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
UiScrollBoxComponent::ScrollOffsetChangeCallback UiScrollBoxComponent::GetScrollOffsetChangingCallback()
|
|
{
|
|
return m_onScrollOffsetChanging;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiScrollBoxComponent::SetScrollOffsetChangingCallback(ScrollOffsetChangeCallback onChange)
|
|
{
|
|
m_onScrollOffsetChanging = onChange;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
const LyShine::ActionName& UiScrollBoxComponent::GetScrollOffsetChangingActionName()
|
|
{
|
|
return m_scrollOffsetChangingActionName;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiScrollBoxComponent::SetScrollOffsetChangingActionName(const LyShine::ActionName& actionName)
|
|
{
|
|
m_scrollOffsetChangingActionName = actionName;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
UiScrollBoxComponent::ScrollOffsetChangeCallback UiScrollBoxComponent::GetScrollOffsetChangedCallback()
|
|
{
|
|
return m_onScrollOffsetChanged;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiScrollBoxComponent::SetScrollOffsetChangedCallback(ScrollOffsetChangeCallback onChange)
|
|
{
|
|
m_onScrollOffsetChanged = onChange;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
const LyShine::ActionName& UiScrollBoxComponent::GetScrollOffsetChangedActionName()
|
|
{
|
|
return m_scrollOffsetChangedActionName;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiScrollBoxComponent::SetScrollOffsetChangedActionName(const LyShine::ActionName& actionName)
|
|
{
|
|
m_scrollOffsetChangedActionName = actionName;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiScrollBoxComponent::SetContentEntity(AZ::EntityId entityId)
|
|
{
|
|
m_contentEntity = entityId;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
AZ::EntityId UiScrollBoxComponent::GetContentEntity()
|
|
{
|
|
return m_contentEntity;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiScrollBoxComponent::SetHorizontalScrollBarEntity(AZ::EntityId entityId)
|
|
{
|
|
m_hScrollBarEntity = entityId;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
AZ::EntityId UiScrollBoxComponent::GetHorizontalScrollBarEntity()
|
|
{
|
|
return m_hScrollBarEntity;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiScrollBoxComponent::SetVerticalScrollBarEntity(AZ::EntityId entityId)
|
|
{
|
|
m_vScrollBarEntity = entityId;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
AZ::EntityId UiScrollBoxComponent::GetVerticalScrollBarEntity()
|
|
{
|
|
return m_vScrollBarEntity;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
AZ::EntityId UiScrollBoxComponent::FindClosestContentChildElement()
|
|
{
|
|
// if no content entity return an invalid entity id
|
|
if (!m_contentEntity.IsValid())
|
|
{
|
|
return AZ::EntityId();
|
|
}
|
|
|
|
// Iterate over the children of the content element and find the one that has the smallest
|
|
// offset from the content elements anchors to the child's pivot.
|
|
// E.g. if the anchors are the center of the content (the default) and the chilren's pivots
|
|
// are in their centers (the default) then we will find the child whose center is closest
|
|
// to the center of the content element's parent (usually the mask element)
|
|
LyShine::EntityArray children;
|
|
EBUS_EVENT_ID_RESULT(children, m_contentEntity, UiElementBus, GetChildElements);
|
|
|
|
float closestDistSq = FLT_MAX;
|
|
AZ::EntityId closestChild;
|
|
|
|
for (auto child : children)
|
|
{
|
|
AZ::Vector2 scrollOffsetToChild = ComputeCurrentOffsetToChild(child->GetId());
|
|
|
|
float distSq = scrollOffsetToChild.GetLengthSq();
|
|
if (distSq < closestDistSq)
|
|
{
|
|
closestChild = child->GetId();
|
|
closestDistSq = distSq;
|
|
}
|
|
}
|
|
|
|
return closestChild;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
AZ::EntityId UiScrollBoxComponent::FindNextContentChildElement(UiNavigationHelpers::Command command)
|
|
{
|
|
// if no content entity return an invalid entity id
|
|
if (!m_contentEntity.IsValid())
|
|
{
|
|
return AZ::EntityId();
|
|
}
|
|
|
|
// Iterate over the children of the content element and find the one who's pivot is closest to
|
|
// the content element's anchors in the specified direction.
|
|
LyShine::EntityArray children;
|
|
EBUS_EVENT_ID_RESULT(children, m_contentEntity, UiElementBus, GetChildElements);
|
|
|
|
float shortestDist = FLT_MAX;
|
|
float shortestPerpendicularDist = FLT_MAX;
|
|
AZ::EntityId closestChild;
|
|
|
|
for (auto child : children)
|
|
{
|
|
AZ::Vector2 scrollOffsetToChild = ComputeCurrentOffsetToChild(child->GetId());
|
|
|
|
float dist = 0.0f;
|
|
const float epsilon = 0.01f;
|
|
if (command == UiNavigationHelpers::Command::Up)
|
|
{
|
|
dist = scrollOffsetToChild.GetY() < -epsilon ? -scrollOffsetToChild.GetY() : 0.0f;
|
|
}
|
|
else if (command == UiNavigationHelpers::Command::Down)
|
|
{
|
|
dist = scrollOffsetToChild.GetY() > epsilon ? scrollOffsetToChild.GetY() : 0.0f;
|
|
}
|
|
else if (command == UiNavigationHelpers::Command::Left)
|
|
{
|
|
dist = scrollOffsetToChild.GetX() < -epsilon ? -scrollOffsetToChild.GetX() : 0.0f;
|
|
}
|
|
else if (command == UiNavigationHelpers::Command::Right)
|
|
{
|
|
dist = scrollOffsetToChild.GetX() > epsilon ? scrollOffsetToChild.GetX() : 0.0f;
|
|
}
|
|
|
|
if (dist > 0.0f)
|
|
{
|
|
if (dist < shortestDist)
|
|
{
|
|
shortestDist = dist;
|
|
shortestPerpendicularDist = fabs((command == UiNavigationHelpers::Command::Up || command == UiNavigationHelpers::Command::Down) ? scrollOffsetToChild.GetX() : scrollOffsetToChild.GetY());
|
|
closestChild = child->GetId();
|
|
}
|
|
else if (dist == shortestDist)
|
|
{
|
|
float perpDist = fabs((command == UiNavigationHelpers::Command::Up || command == UiNavigationHelpers::Command::Down) ? scrollOffsetToChild.GetX() : scrollOffsetToChild.GetY());
|
|
if (perpDist < shortestPerpendicularDist)
|
|
{
|
|
shortestPerpendicularDist = perpDist;
|
|
closestChild = child->GetId();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return closestChild;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
bool UiScrollBoxComponent::GetScrollableParentToContentRatio(AZ::Vector2& ratioOut)
|
|
{
|
|
AZ::Entity* contentParentEntity = nullptr;
|
|
EBUS_EVENT_ID_RESULT(contentParentEntity, m_contentEntity, UiElementBus, GetParent);
|
|
if (contentParentEntity)
|
|
{
|
|
AZ::Vector2 parentSize;
|
|
EBUS_EVENT_ID_RESULT(parentSize, contentParentEntity->GetId(), UiTransformBus, GetCanvasSpaceSizeNoScaleRotate);
|
|
|
|
UiTransformInterface::Rect contentRect = GetAxisAlignedContentRect();
|
|
AZ::Vector2 contentSize = contentRect.GetSize();
|
|
|
|
ratioOut.SetX(contentSize.GetX() != 0.0f ? parentSize.GetX() / contentSize.GetX() : 1.0f);
|
|
ratioOut.SetY(contentSize.GetY() != 0.0f ? parentSize.GetY() / contentSize.GetY() : 1.0f);
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiScrollBoxComponent::OnValueChangingByScroller(float value)
|
|
{
|
|
AZ::EntityId scroller = *UiScrollerToScrollableNotificationBus::GetCurrentBusId();
|
|
|
|
AZ::Vector2 newScrollOffsetOut;
|
|
bool result = ScrollerValueToScrollOffsets(scroller, value, newScrollOffsetOut);
|
|
|
|
if (result && m_scrollOffset != newScrollOffsetOut)
|
|
{
|
|
DoSetScrollOffset(newScrollOffsetOut);
|
|
DoChangingActions();
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiScrollBoxComponent::OnValueChangedByScroller(float value)
|
|
{
|
|
AZ::EntityId scroller = *UiScrollerToScrollableNotificationBus::GetCurrentBusId();
|
|
|
|
AZ::Vector2 newScrollOffsetOut;
|
|
bool result = ScrollerValueToScrollOffsets(scroller, value, newScrollOffsetOut);
|
|
|
|
if (result)
|
|
{
|
|
AZ::Vector2 prevScrollOffset = m_scrollOffset;
|
|
|
|
if (m_scrollOffset != newScrollOffsetOut)
|
|
{
|
|
DoSetScrollOffset(newScrollOffsetOut);
|
|
}
|
|
|
|
if (DoSnap())
|
|
{
|
|
// Snapping/constraining caused the scroll offsets to change, so notify scrollers
|
|
NotifyScrollersOnValueChanged();
|
|
}
|
|
|
|
if (m_scrollOffset != prevScrollOffset)
|
|
{
|
|
DoChangedActions();
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiScrollBoxComponent::InGamePostActivate()
|
|
{
|
|
if (m_hScrollBarEntity.IsValid() && m_isHorizontalScrollingEnabled)
|
|
{
|
|
// Set this entity as the scrollable entity of the scroller
|
|
EBUS_EVENT_ID(m_hScrollBarEntity, UiScrollerBus, SetScrollableEntity, GetEntityId());
|
|
|
|
UiScrollerToScrollableNotificationBus::MultiHandler::BusConnect(m_hScrollBarEntity);
|
|
}
|
|
|
|
if (m_vScrollBarEntity.IsValid() && m_isVerticalScrollingEnabled)
|
|
{
|
|
// Set this entity as the scrollable entity of the scroller
|
|
EBUS_EVENT_ID(m_vScrollBarEntity, UiScrollerBus, SetScrollableEntity, GetEntityId());
|
|
|
|
UiScrollerToScrollableNotificationBus::MultiHandler::BusConnect(m_vScrollBarEntity);
|
|
}
|
|
|
|
DoSetScrollOffset(m_scrollOffset);
|
|
|
|
// Setup based on the size of the content and its parent
|
|
ContentOrParentSizeChanged();
|
|
|
|
// Listen for canvas space rect changes from the content entity
|
|
if (m_contentEntity.IsValid())
|
|
{
|
|
UiTransformChangeNotificationBus::MultiHandler::BusConnect(m_contentEntity);
|
|
|
|
// Listen for canvas space rect changes from the content entity's parent
|
|
AZ::Entity* contentParentEntity = nullptr;
|
|
EBUS_EVENT_ID_RESULT(contentParentEntity, m_contentEntity, UiElementBus, GetParent);
|
|
if (contentParentEntity)
|
|
{
|
|
UiTransformChangeNotificationBus::MultiHandler::BusConnect(contentParentEntity->GetId());
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
bool UiScrollBoxComponent::HandlePressed(AZ::Vector2 point, bool& shouldStayActive)
|
|
{
|
|
bool handled = UiInteractableComponent::HandlePressed(point, shouldStayActive);
|
|
|
|
if (handled)
|
|
{
|
|
// clear the dragging flag, we are not dragging until we detect a drag
|
|
m_isDragging = false;
|
|
|
|
// record the scroll offset at the time of the press
|
|
m_pressedScrollOffset = m_scrollOffset;
|
|
}
|
|
|
|
return handled;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
bool UiScrollBoxComponent::HandleReleased([[maybe_unused]] AZ::Vector2 point)
|
|
{
|
|
if (m_isHandlingEvents)
|
|
{
|
|
// handle snapping
|
|
DoSnap();
|
|
|
|
UiInteractableComponent::TriggerReleasedAction();
|
|
|
|
NotifyScrollersOnValueChanged();
|
|
|
|
// NOTE: when we have inertia/rubber-banding these actions should occur when snap is finished
|
|
DoChangedActions();
|
|
}
|
|
|
|
m_isPressed = false;
|
|
m_isDragging = false;
|
|
|
|
return m_isHandlingEvents;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
bool UiScrollBoxComponent::HandleEnterPressed(bool& shouldStayActive)
|
|
{
|
|
bool handled = UiInteractableComponent::HandleEnterPressed(shouldStayActive);
|
|
|
|
if (handled)
|
|
{
|
|
// the scrollbox will stay active after released
|
|
shouldStayActive = true;
|
|
m_isActive = true;
|
|
}
|
|
|
|
return handled;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
bool UiScrollBoxComponent::HandleAutoActivation()
|
|
{
|
|
if (!m_isHandlingEvents)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
m_isActive = true;
|
|
return true;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
bool UiScrollBoxComponent::HandleKeyInputBegan(const AzFramework::InputChannel::Snapshot& inputSnapshot, AzFramework::ModifierKeyMask activeModifierKeys)
|
|
{
|
|
if (!m_isHandlingEvents)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// don't accept key input while in pressed state
|
|
if (m_isPressed)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool result = false;
|
|
|
|
const UiNavigationHelpers::Command command = UiNavigationHelpers::MapInputChannelIdToUiNavigationCommand(inputSnapshot.m_channelId, activeModifierKeys);
|
|
if (m_isHorizontalScrollingEnabled && ((command == UiNavigationHelpers::Command::Left || command == UiNavigationHelpers::Command::Right))
|
|
|| (m_isVerticalScrollingEnabled && (command == UiNavigationHelpers::Command::Up || command == UiNavigationHelpers::Command::Down)))
|
|
{
|
|
AZ::Vector2 newScrollOffset = m_scrollOffset;
|
|
if (m_snapMode == UiScrollBoxInterface::SnapMode::Children)
|
|
{
|
|
AZ::EntityId closestChild = FindNextContentChildElement(command);
|
|
if (closestChild.IsValid())
|
|
{
|
|
// want elastic animation eventually
|
|
AZ::Vector2 deltaToSubtract = ComputeCurrentOffsetToChild(closestChild);
|
|
|
|
// snapping should only move the content in the directions it is allowed to scroll
|
|
if (!m_isHorizontalScrollingEnabled)
|
|
{
|
|
deltaToSubtract.SetX(0.0f);
|
|
}
|
|
else if (!m_isVerticalScrollingEnabled)
|
|
{
|
|
deltaToSubtract.SetY(0.0f);
|
|
}
|
|
|
|
newScrollOffset -= deltaToSubtract;
|
|
|
|
// do constraining
|
|
if (m_isScrollingConstrained)
|
|
{
|
|
AZ::Entity* contentParentEntity = nullptr;
|
|
EBUS_EVENT_ID_RESULT(contentParentEntity, m_contentEntity, UiElementBus, GetParent);
|
|
|
|
newScrollOffset = ConstrainOffset(newScrollOffset, contentParentEntity);
|
|
}
|
|
}
|
|
}
|
|
else if (m_snapMode == UiScrollBoxInterface::SnapMode::Grid)
|
|
{
|
|
if (command == UiNavigationHelpers::Command::Up)
|
|
{
|
|
newScrollOffset.SetY(newScrollOffset.GetY() + m_snapGrid.GetY());
|
|
}
|
|
else if (command == UiNavigationHelpers::Command::Down)
|
|
{
|
|
newScrollOffset.SetY(newScrollOffset.GetY() - m_snapGrid.GetY());
|
|
}
|
|
else if (command == UiNavigationHelpers::Command::Left)
|
|
{
|
|
newScrollOffset.SetX(newScrollOffset.GetX() + m_snapGrid.GetX());
|
|
}
|
|
else if (command == UiNavigationHelpers::Command::Right)
|
|
{
|
|
newScrollOffset.SetX(newScrollOffset.GetX() - m_snapGrid.GetX());
|
|
}
|
|
|
|
if (m_isScrollingConstrained)
|
|
{
|
|
AZ::Entity* contentParentEntity = nullptr;
|
|
EBUS_EVENT_ID_RESULT(contentParentEntity, m_contentEntity, UiElementBus, GetParent);
|
|
|
|
// Only scroll if constraining doesn't change the offset
|
|
AZ::Vector2 constrainedScrollOffset = ConstrainOffset(newScrollOffset, contentParentEntity);
|
|
if (constrainedScrollOffset != newScrollOffset)
|
|
{
|
|
newScrollOffset = m_scrollOffset;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// get content parent's rect in canvas space
|
|
AZ::Entity* contentParentEntity = nullptr;
|
|
EBUS_EVENT_ID_RESULT(contentParentEntity, m_contentEntity, UiElementBus, GetParent);
|
|
|
|
if (contentParentEntity)
|
|
{
|
|
UiTransformInterface::Rect parentRect;
|
|
EBUS_EVENT_ID(contentParentEntity->GetId(), UiTransformBus, GetCanvasSpaceRectNoScaleRotate, parentRect);
|
|
|
|
const float keySteps = 10.0f;
|
|
|
|
if (command == UiNavigationHelpers::Command::Left || command == UiNavigationHelpers::Command::Right)
|
|
{
|
|
float xStep = parentRect.GetSize().GetX() / keySteps;
|
|
newScrollOffset.SetX(newScrollOffset.GetX() + (command == UiNavigationHelpers::Command::Left ? xStep : -xStep));
|
|
}
|
|
else
|
|
{
|
|
float yStep = parentRect.GetSize().GetY() / keySteps;
|
|
newScrollOffset.SetY(newScrollOffset.GetY() + (command == UiNavigationHelpers::Command::Up ? yStep : -yStep));
|
|
}
|
|
|
|
// do constraining
|
|
if (m_isScrollingConstrained)
|
|
{
|
|
newScrollOffset = ConstrainOffset(newScrollOffset, contentParentEntity);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (newScrollOffset != m_scrollOffset)
|
|
{
|
|
DoSetScrollOffset(newScrollOffset);
|
|
|
|
NotifyScrollersOnValueChanged();
|
|
|
|
DoChangingActions();
|
|
|
|
DoChangedActions();
|
|
}
|
|
|
|
result = true;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
void UiScrollBoxComponent::InputPositionUpdate(AZ::Vector2 point)
|
|
{
|
|
if (m_isPressed && m_contentEntity.IsValid())
|
|
{
|
|
if (!m_isDragging)
|
|
{
|
|
CheckForDragOrHandOffToParent(point);
|
|
}
|
|
|
|
if (m_isDragging)
|
|
{
|
|
AZ::Vector2 dragVector = point - m_pressedPoint;
|
|
|
|
AZ::Entity* contentParentEntity = nullptr;
|
|
EBUS_EVENT_ID_RESULT(contentParentEntity, m_contentEntity, UiElementBus, GetParent);
|
|
|
|
AZ::Matrix4x4 transform;
|
|
if (contentParentEntity)
|
|
{
|
|
EBUS_EVENT_ID(contentParentEntity->GetId(), UiTransformBus, GetTransformFromViewport, transform);
|
|
}
|
|
else
|
|
{
|
|
transform = AZ::Matrix4x4::CreateIdentity();
|
|
}
|
|
|
|
// Transform the draw vector from viewport space to the local space of the parent of the content element
|
|
// This means we can do all calculations in unrotated/unscaled space.
|
|
AZ::Vector3 dragVector3(dragVector.GetX(), dragVector.GetY(), 0.0f);
|
|
dragVector3 = transform.Multiply3x3(dragVector3);
|
|
AZ::Vector2 dragVectorInParentSpace(dragVector3.GetX(), dragVector3.GetY());
|
|
|
|
if (!m_isHorizontalScrollingEnabled)
|
|
{
|
|
dragVectorInParentSpace.SetX(0.0f);
|
|
}
|
|
|
|
if (!m_isVerticalScrollingEnabled)
|
|
{
|
|
dragVectorInParentSpace.SetY(0.0f);
|
|
}
|
|
|
|
AZ::Vector2 newScrollOffset = m_pressedScrollOffset + dragVectorInParentSpace;
|
|
|
|
// do constraining
|
|
if (m_isScrollingConstrained)
|
|
{
|
|
newScrollOffset = ConstrainOffset(newScrollOffset, contentParentEntity);
|
|
}
|
|
|
|
m_lastDragPoint = point;
|
|
|
|
if (newScrollOffset != m_scrollOffset)
|
|
{
|
|
DoSetScrollOffset(newScrollOffset);
|
|
|
|
NotifyScrollersOnValueChanging();
|
|
|
|
DoChangingActions();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
bool UiScrollBoxComponent::DoesSupportDragHandOff(AZ::Vector2 startPoint)
|
|
{
|
|
// this component does support hand-off, so long as the start point is in its bounds
|
|
bool isPointInRect = false;
|
|
EBUS_EVENT_ID_RESULT(isPointInRect, GetEntityId(), UiTransformBus, IsPointInRect, startPoint);
|
|
return isPointInRect;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
bool UiScrollBoxComponent::OfferDragHandOff(AZ::EntityId currentActiveInteractable, AZ::Vector2 startPoint, AZ::Vector2 currentPoint, float dragThreshold)
|
|
{
|
|
bool result = false;
|
|
|
|
// This only gets called if this is not already the active interactable, check preconditions
|
|
AZ_Assert(!m_isPressed && !m_isDragging, "ScrollBox is already active");
|
|
|
|
// get transform of content entity
|
|
AZ::Entity* contentParentEntity = nullptr;
|
|
EBUS_EVENT_ID_RESULT(contentParentEntity, m_contentEntity, UiElementBus, GetParent);
|
|
|
|
AZ::Matrix4x4 transform;
|
|
if (contentParentEntity)
|
|
{
|
|
EBUS_EVENT_ID(contentParentEntity->GetId(), UiTransformBus, GetTransformFromViewport, transform);
|
|
}
|
|
else
|
|
{
|
|
transform = AZ::Matrix4x4::CreateIdentity();
|
|
}
|
|
|
|
float validDragDistance = GetValidDragDistanceInPixels(startPoint, currentPoint);
|
|
if (validDragDistance > dragThreshold)
|
|
{
|
|
// share this common code?
|
|
m_isDragging = true;
|
|
m_isPressed = true;
|
|
m_pressedPoint = startPoint;
|
|
m_pressedScrollOffset = m_scrollOffset;
|
|
m_lastDragPoint = m_pressedPoint;
|
|
|
|
// tell the canvas that this is now the active interacatable
|
|
EBUS_EVENT_ID(currentActiveInteractable, UiInteractableActiveNotificationBus, ActiveChanged, GetEntityId(), false);
|
|
result = true;
|
|
}
|
|
else
|
|
{
|
|
// The current drag movement is not over the threshhold to be dragging this interactable
|
|
|
|
// look for a parent interactable that the start point of the drag is inside
|
|
AZ::EntityId interactableContainer;
|
|
EBUS_EVENT_ID_RESULT(interactableContainer, GetEntityId(), UiElementBus, FindParentInteractableSupportingDrag, startPoint);
|
|
|
|
// if there was a parent interactable offer them the opportunity to become the active interactable
|
|
EBUS_EVENT_ID_RESULT(result, interactableContainer, UiInteractableBus,
|
|
OfferDragHandOff, currentActiveInteractable, startPoint, currentPoint, dragThreshold);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiScrollBoxComponent::LostActiveStatus()
|
|
{
|
|
UiInteractableComponent::LostActiveStatus();
|
|
|
|
if (m_isDragging)
|
|
{
|
|
if (m_isHandlingEvents)
|
|
{
|
|
// handle snapping
|
|
DoSnap();
|
|
|
|
NotifyScrollersOnValueChanged();
|
|
|
|
// NOTE: when we have inertia/rubber-banding these actions should occur when snap is finished
|
|
DoChangedActions();
|
|
}
|
|
|
|
m_isDragging = false;
|
|
}
|
|
|
|
m_isActive = false;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiScrollBoxComponent::HandleDescendantReceivedHoverByNavigation(AZ::EntityId descendantEntityId)
|
|
{
|
|
// Check if the content element is an ancestor of the descendant element
|
|
bool isAncestor = false;
|
|
if (m_contentEntity.IsValid())
|
|
{
|
|
EBUS_EVENT_ID_RESULT(isAncestor, descendantEntityId, UiElementBus, IsAncestor, m_contentEntity);
|
|
}
|
|
|
|
if (isAncestor)
|
|
{
|
|
AZ::Vector2 newScrollOffset = m_scrollOffset;
|
|
|
|
if (m_snapMode == UiScrollBoxInterface::SnapMode::Children)
|
|
{
|
|
// Find the descendant's ancestor that's a direct child of the content entity
|
|
AZ::EntityId parent;
|
|
EBUS_EVENT_ID_RESULT(parent, descendantEntityId, UiElementBus, GetParentEntityId);
|
|
while (parent.IsValid())
|
|
{
|
|
if (parent == m_contentEntity)
|
|
{
|
|
break;
|
|
}
|
|
|
|
descendantEntityId = parent;
|
|
parent.SetInvalid();
|
|
EBUS_EVENT_ID_RESULT(parent, descendantEntityId, UiElementBus, GetParentEntityId);
|
|
}
|
|
|
|
if (descendantEntityId.IsValid())
|
|
{
|
|
AZ::Vector2 offset = ComputeCurrentOffsetToChild(descendantEntityId);
|
|
|
|
if (!m_isHorizontalScrollingEnabled)
|
|
{
|
|
offset.SetX(0.0f);
|
|
}
|
|
if (!m_isVerticalScrollingEnabled)
|
|
{
|
|
offset.SetY(0.0f);
|
|
}
|
|
|
|
newScrollOffset = m_scrollOffset - offset;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Check if the descendant element is visible in the viewport area
|
|
AZ::EntityId contentParent;
|
|
EBUS_EVENT_ID_RESULT(contentParent, m_contentEntity, UiElementBus, GetParentEntityId);
|
|
if (contentParent.IsValid())
|
|
{
|
|
UiTransformInterface::Rect contentParentRect;
|
|
AZ::Matrix4x4 transformFromViewport;
|
|
EBUS_EVENT_ID(contentParent, UiTransformBus, GetCanvasSpaceRectNoScaleRotate, contentParentRect);
|
|
EBUS_EVENT_ID(contentParent, UiTransformBus, GetTransformFromViewport, transformFromViewport);
|
|
|
|
UiTransformInterface::RectPoints descendantPoints;
|
|
EBUS_EVENT_ID(descendantEntityId, UiTransformBus, GetViewportSpacePoints, descendantPoints);
|
|
descendantPoints = descendantPoints.Transform(transformFromViewport);
|
|
|
|
UiTransformInterface::Rect descendantRect;
|
|
descendantRect.left = descendantPoints.GetAxisAlignedTopLeft().GetX();
|
|
descendantRect.right = descendantPoints.GetAxisAlignedBottomRight().GetX();
|
|
descendantRect.top = descendantPoints.GetAxisAlignedTopLeft().GetY();
|
|
descendantRect.bottom = descendantPoints.GetAxisAlignedBottomRight().GetY();
|
|
|
|
bool descendantInsideH = (descendantRect.left >= contentParentRect.left &&
|
|
descendantRect.right <= contentParentRect.right);
|
|
bool descendantInsideV = (descendantRect.top >= contentParentRect.top &&
|
|
descendantRect.bottom <= contentParentRect.bottom);
|
|
|
|
if (!descendantInsideH || !descendantInsideV)
|
|
{
|
|
AZ::Vector2 offset(0.0f, 0.0f);
|
|
|
|
// Scroll to make the descendant visible in the viewport area
|
|
if (!descendantInsideH && m_isHorizontalScrollingEnabled)
|
|
{
|
|
float leftOffset = descendantRect.left - contentParentRect.left;
|
|
float rightOffset = descendantRect.right - contentParentRect.right;
|
|
bool shouldOffsetFromLeft = fabs(leftOffset) < fabs(rightOffset);
|
|
offset.SetX(shouldOffsetFromLeft ? leftOffset : rightOffset);
|
|
}
|
|
if (!descendantInsideV && m_isVerticalScrollingEnabled)
|
|
{
|
|
float topOffset = descendantRect.top - contentParentRect.top;
|
|
float bottomOffset = descendantRect.bottom - contentParentRect.bottom;
|
|
bool shouldOffsetFromTop = fabs(topOffset) < fabs(bottomOffset);
|
|
offset.SetY(shouldOffsetFromTop ? topOffset : bottomOffset);
|
|
}
|
|
|
|
newScrollOffset = m_scrollOffset - offset;
|
|
|
|
if (m_snapMode == UiScrollBoxInterface::SnapMode::Grid)
|
|
{
|
|
// Make sure new offset is on the grid
|
|
const float gridEpsilon = 0.00001f;
|
|
|
|
if (m_snapGrid.GetX() >= gridEpsilon && m_isHorizontalScrollingEnabled)
|
|
{
|
|
float gridSteps = newScrollOffset.GetX() / m_snapGrid.GetX();
|
|
float roundedGridSteps = (offset.GetX() < 0.0f) ? ceil(gridSteps) : floor(gridSteps);
|
|
newScrollOffset.SetX(roundedGridSteps * m_snapGrid.GetX());
|
|
}
|
|
|
|
if (m_snapGrid.GetY() >= gridEpsilon && m_isVerticalScrollingEnabled)
|
|
{
|
|
float gridSteps = newScrollOffset.GetY() / m_snapGrid.GetY();
|
|
float roundedGridSteps = (offset.GetY() < 0.0f) ? ceil(gridSteps) : floor(gridSteps);
|
|
newScrollOffset.SetY(roundedGridSteps * m_snapGrid.GetY());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (newScrollOffset != m_scrollOffset)
|
|
{
|
|
SetScrollOffset(newScrollOffset);
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiScrollBoxComponent::OnCanvasSpaceRectChanged([[maybe_unused]] AZ::EntityId entityId, const UiTransformInterface::Rect& oldRect, const UiTransformInterface::Rect& newRect)
|
|
{
|
|
// If old rect equals new rect, size changed due to initialization
|
|
bool sizeChanged = (oldRect == newRect) || (!oldRect.GetSize().IsClose(newRect.GetSize(), 0.05f));
|
|
|
|
if (sizeChanged)
|
|
{
|
|
ContentOrParentSizeChanged();
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// PROTECTED MEMBER FUNCTIONS
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiScrollBoxComponent::Activate()
|
|
{
|
|
UiInteractableComponent::Activate();
|
|
UiScrollBoxBus::Handler::BusConnect(GetEntityId());
|
|
UiScrollableBus::Handler::BusConnect(GetEntityId());
|
|
UiInitializationBus::Handler::BusConnect(GetEntityId());
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiScrollBoxComponent::Deactivate()
|
|
{
|
|
UiInteractableComponent::Deactivate();
|
|
UiScrollBoxBus::Handler::BusDisconnect(GetEntityId());
|
|
UiScrollableBus::Handler::BusDisconnect(GetEntityId());
|
|
UiInitializationBus::Handler::BusDisconnect(GetEntityId());
|
|
UiTransformChangeNotificationBus::MultiHandler::BusDisconnect();
|
|
|
|
if (m_hScrollBarEntity.IsValid() && m_isHorizontalScrollingEnabled)
|
|
{
|
|
UiScrollerToScrollableNotificationBus::MultiHandler::BusDisconnect(m_hScrollBarEntity);
|
|
}
|
|
|
|
if (m_vScrollBarEntity.IsValid() && m_isVerticalScrollingEnabled)
|
|
{
|
|
UiScrollerToScrollableNotificationBus::MultiHandler::BusDisconnect(m_vScrollBarEntity);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
bool UiScrollBoxComponent::IsAutoActivationSupported()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
UiInteractableStatesInterface::State UiScrollBoxComponent::ComputeInteractableState()
|
|
{
|
|
UiInteractableStatesInterface::State state = UiInteractableStatesInterface::StateNormal;
|
|
|
|
if (!m_isHandlingEvents)
|
|
{
|
|
state = UiInteractableStatesInterface::StateDisabled;
|
|
}
|
|
else if (m_isPressed || m_isActive)
|
|
{
|
|
// Use pressed state regardless of mouse position
|
|
state = UiInteractableStatesInterface::StatePressed;
|
|
}
|
|
else if (m_isHover)
|
|
{
|
|
state = UiInteractableStatesInterface::StateHover;
|
|
}
|
|
|
|
return state;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// PROTECTED STATIC MEMBER FUNCTIONS
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void UiScrollBoxComponent::Reflect(AZ::ReflectContext* context)
|
|
{
|
|
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
|
|
|
|
if (serializeContext)
|
|
{
|
|
serializeContext->Class<UiScrollBoxComponent, UiInteractableComponent>()
|
|
->Version(4, &VersionConverter)
|
|
// Content group
|
|
->Field("ContentEntity", &UiScrollBoxComponent::m_contentEntity)
|
|
->Field("ScrollOffset", &UiScrollBoxComponent::m_scrollOffset)
|
|
->Field("ConstrainScrolling", &UiScrollBoxComponent::m_isScrollingConstrained)
|
|
->Field("SnapMode", &UiScrollBoxComponent::m_snapMode)
|
|
->Field("SnapGrid", &UiScrollBoxComponent::m_snapGrid)
|
|
// Horizontal scrolling group
|
|
->Field("AllowHorizSrolling", &UiScrollBoxComponent::m_isHorizontalScrollingEnabled)
|
|
->Field("HScrollBarEntity", &UiScrollBoxComponent::m_hScrollBarEntity)
|
|
->Field("HScrollBarVisibility", &UiScrollBoxComponent::m_hScrollBarVisibility)
|
|
// Vertical scrolling group
|
|
->Field("AllowVertScrolling", &UiScrollBoxComponent::m_isVerticalScrollingEnabled)
|
|
->Field("VScrollBarEntity", &UiScrollBoxComponent::m_vScrollBarEntity)
|
|
->Field("VScrollBarVisibility", &UiScrollBoxComponent::m_vScrollBarVisibility)
|
|
// Actions group
|
|
->Field("ScrollOffsetChangingActionName", &UiScrollBoxComponent::m_scrollOffsetChangingActionName)
|
|
->Field("ScrollOffsetChangedActionName", &UiScrollBoxComponent::m_scrollOffsetChangedActionName);
|
|
|
|
AZ::EditContext* ec = serializeContext->GetEditContext();
|
|
if (ec)
|
|
{
|
|
auto editInfo = ec->Class<UiScrollBoxComponent>("ScrollBox", "An interactable component for scrolling a child element.");
|
|
|
|
editInfo->ClassElement(AZ::Edit::ClassElements::EditorData, "")
|
|
->Attribute(AZ::Edit::Attributes::Category, "UI")
|
|
->Attribute(AZ::Edit::Attributes::Icon, "Editor/Icons/Components/UiScrollBox.png")
|
|
->Attribute(AZ::Edit::Attributes::ViewportIcon, "Editor/Icons/Components/Viewport/UiScrollBox.png")
|
|
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("UI", 0x27ff46b0))
|
|
->Attribute(AZ::Edit::Attributes::AutoExpand, true);
|
|
|
|
// Content group
|
|
{
|
|
editInfo->ClassElement(AZ::Edit::ClassElements::Group, "Content")
|
|
->Attribute(AZ::Edit::Attributes::AutoExpand, true);
|
|
|
|
editInfo->DataElement(AZ::Edit::UIHandlers::ComboBox, &UiScrollBoxComponent::m_contentEntity, "Content element", "The child element that is the scrollable content.")
|
|
->Attribute(AZ::Edit::Attributes::EnumValues, &UiScrollBoxComponent::PopulateChildEntityList);
|
|
editInfo->DataElement(0, &UiScrollBoxComponent::m_scrollOffset, "Initial scroll offset", "The initial offset of the scroll box content.")
|
|
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::Show); // needed because sub-elements are hidden
|
|
editInfo->DataElement(AZ::Edit::UIHandlers::CheckBox, &UiScrollBoxComponent::m_isScrollingConstrained, "Constrain scrolling",
|
|
"Check this box to prevent the content from being scrolled beyond its edges.");
|
|
editInfo->DataElement(AZ::Edit::UIHandlers::ComboBox, &UiScrollBoxComponent::m_snapMode, "Snap",
|
|
"Sets the snapping behavior when the control is released.")
|
|
->EnumAttribute(UiScrollBoxInterface::SnapMode::None, "None")
|
|
->EnumAttribute(UiScrollBoxInterface::SnapMode::Children, "To children")
|
|
->EnumAttribute(UiScrollBoxInterface::SnapMode::Grid, "To grid")
|
|
->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ_CRC("RefreshEntireTree", 0xefbc823c));
|
|
editInfo->DataElement(0, &UiScrollBoxComponent::m_snapGrid, "Grid spacing",
|
|
"The scroll offset will be snapped to multiples of these values.")
|
|
->Attribute(AZ::Edit::Attributes::Visibility, &UiScrollBoxComponent::IsSnapToGrid);
|
|
}
|
|
|
|
// Horizontal scrolling group
|
|
{
|
|
editInfo->ClassElement(AZ::Edit::ClassElements::Group, "Horizontal scrolling")
|
|
->Attribute(AZ::Edit::Attributes::AutoExpand, true);
|
|
|
|
editInfo->DataElement(AZ::Edit::UIHandlers::CheckBox, &UiScrollBoxComponent::m_isHorizontalScrollingEnabled, "Enabled",
|
|
"Check this box to allow the scroll box to be scrolled horizontally.")
|
|
->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ_CRC("RefreshEntireTree", 0xefbc823c));
|
|
|
|
editInfo->DataElement(AZ::Edit::UIHandlers::ComboBox, &UiScrollBoxComponent::m_hScrollBarEntity, "Scrollbar element",
|
|
"The element that is the horizontal scrollbar.")
|
|
->Attribute(AZ::Edit::Attributes::Visibility, &UiScrollBoxComponent::m_isHorizontalScrollingEnabled)
|
|
->Attribute(AZ::Edit::Attributes::EnumValues, &UiScrollBoxComponent::PopulateHScrollBarEntityList);
|
|
|
|
editInfo->DataElement(AZ::Edit::UIHandlers::ComboBox, &UiScrollBoxComponent::m_hScrollBarVisibility, "Scrollbar visibility",
|
|
"Sets visibility behavior of the horizontal scrollbar.")
|
|
->Attribute(AZ::Edit::Attributes::Visibility, &UiScrollBoxComponent::m_isHorizontalScrollingEnabled)
|
|
->EnumAttribute(UiScrollBoxInterface::ScrollBarVisibility::AlwaysShow, "Always visible")
|
|
->EnumAttribute(UiScrollBoxInterface::ScrollBarVisibility::AutoHide, "Auto hide")
|
|
->EnumAttribute(UiScrollBoxInterface::ScrollBarVisibility::AutoHideAndResizeViewport, "Auto hide and resize view area");
|
|
}
|
|
|
|
// Vertical scrolling group
|
|
{
|
|
editInfo->ClassElement(AZ::Edit::ClassElements::Group, "Vertical scrolling")
|
|
->Attribute(AZ::Edit::Attributes::AutoExpand, true);
|
|
|
|
editInfo->DataElement(AZ::Edit::UIHandlers::CheckBox, &UiScrollBoxComponent::m_isVerticalScrollingEnabled, "Enabled",
|
|
"Check this box to allow the scroll box to be scrolled vertically.")
|
|
->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ_CRC("RefreshEntireTree", 0xefbc823c));
|
|
|
|
editInfo->DataElement(AZ::Edit::UIHandlers::ComboBox, &UiScrollBoxComponent::m_vScrollBarEntity, "Scrollbar element",
|
|
"The element that is the vertical scrollbar.")
|
|
->Attribute(AZ::Edit::Attributes::Visibility, &UiScrollBoxComponent::m_isVerticalScrollingEnabled)
|
|
->Attribute(AZ::Edit::Attributes::EnumValues, &UiScrollBoxComponent::PopulateVScrollBarEntityList);
|
|
|
|
editInfo->DataElement(AZ::Edit::UIHandlers::ComboBox, &UiScrollBoxComponent::m_vScrollBarVisibility, "Scrollbar visibility",
|
|
"Sets visibility behavior of the vertical scrollbar.")
|
|
->Attribute(AZ::Edit::Attributes::Visibility, &UiScrollBoxComponent::m_isVerticalScrollingEnabled)
|
|
->EnumAttribute(UiScrollBoxInterface::ScrollBarVisibility::AlwaysShow, "Always visible")
|
|
->EnumAttribute(UiScrollBoxInterface::ScrollBarVisibility::AutoHide, "Auto hide")
|
|
->EnumAttribute(UiScrollBoxInterface::ScrollBarVisibility::AutoHideAndResizeViewport, "Auto hide and resize view area");
|
|
}
|
|
|
|
// Actions group
|
|
{
|
|
editInfo->ClassElement(AZ::Edit::ClassElements::Group, "Actions")
|
|
->Attribute(AZ::Edit::Attributes::AutoExpand, true);
|
|
|
|
editInfo->DataElement(0, &UiScrollBoxComponent::m_scrollOffsetChangingActionName, "Change", "The action triggered while the offset is changing.");
|
|
editInfo->DataElement(0, &UiScrollBoxComponent::m_scrollOffsetChangedActionName, "End change", "The action triggered when the offset is done changing.");
|
|
}
|
|
}
|
|
}
|
|
|
|
AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context);
|
|
if (behaviorContext)
|
|
{
|
|
behaviorContext->EBus<UiScrollBoxBus>("UiScrollBoxBus")
|
|
->Event("GetScrollOffset", &UiScrollBoxBus::Events::GetScrollOffset)
|
|
->Event("SetScrollOffset", &UiScrollBoxBus::Events::SetScrollOffset)
|
|
->Event("GetNormalizedScrollValue", &UiScrollBoxBus::Events::GetNormalizedScrollValue)
|
|
->Event("HasHorizontalContentToScroll", &UiScrollBoxBus::Events::HasHorizontalContentToScroll)
|
|
->Event("HasVerticalContentToScroll", &UiScrollBoxBus::Events::HasVerticalContentToScroll)
|
|
->Event("GetIsHorizontalScrollingEnabled", &UiScrollBoxBus::Events::GetIsHorizontalScrollingEnabled)
|
|
->Event("SetIsHorizontalScrollingEnabled", &UiScrollBoxBus::Events::SetIsHorizontalScrollingEnabled)
|
|
->Event("GetIsVerticalScrollingEnabled", &UiScrollBoxBus::Events::GetIsVerticalScrollingEnabled)
|
|
->Event("SetIsVerticalScrollingEnabled", &UiScrollBoxBus::Events::SetIsVerticalScrollingEnabled)
|
|
->Event("GetIsScrollingConstrained", &UiScrollBoxBus::Events::GetIsScrollingConstrained)
|
|
->Event("SetIsScrollingConstrained", &UiScrollBoxBus::Events::SetIsScrollingConstrained)
|
|
->Event("GetSnapMode", &UiScrollBoxBus::Events::GetSnapMode)
|
|
->Event("SetSnapMode", &UiScrollBoxBus::Events::SetSnapMode)
|
|
->Event("GetSnapGrid", &UiScrollBoxBus::Events::GetSnapGrid)
|
|
->Event("SetSnapGrid", &UiScrollBoxBus::Events::SetSnapGrid)
|
|
->Event("GetHorizontalScrollBarVisibility", &UiScrollBoxBus::Events::GetHorizontalScrollBarVisibility)
|
|
->Event("SetHorizontalScrollBarVisibility", &UiScrollBoxBus::Events::SetHorizontalScrollBarVisibility)
|
|
->Event("GetVerticalScrollBarVisibility", &UiScrollBoxBus::Events::GetVerticalScrollBarVisibility)
|
|
->Event("SetVerticalScrollBarVisibility", &UiScrollBoxBus::Events::SetVerticalScrollBarVisibility)
|
|
->Event("GetScrollOffsetChangingActionName", &UiScrollBoxBus::Events::GetScrollOffsetChangingActionName)
|
|
->Event("SetScrollOffsetChangingActionName", &UiScrollBoxBus::Events::SetScrollOffsetChangingActionName)
|
|
->Event("GetScrollOffsetChangedActionName", &UiScrollBoxBus::Events::GetScrollOffsetChangedActionName)
|
|
->Event("SetScrollOffsetChangedActionName", &UiScrollBoxBus::Events::SetScrollOffsetChangedActionName)
|
|
->Event("GetContentEntity", &UiScrollBoxBus::Events::GetContentEntity)
|
|
->Event("SetContentEntity", &UiScrollBoxBus::Events::SetContentEntity)
|
|
->Event("GetHorizontalScrollBarEntity", &UiScrollBoxBus::Events::GetHorizontalScrollBarEntity)
|
|
->Event("SetHorizontalScrollBarEntity", &UiScrollBoxBus::Events::SetHorizontalScrollBarEntity)
|
|
->Event("GetVerticalScrollBarEntity", &UiScrollBoxBus::Events::GetVerticalScrollBarEntity)
|
|
->Event("SetVerticalScrollBarEntity", &UiScrollBoxBus::Events::SetVerticalScrollBarEntity)
|
|
->Event("FindClosestContentChildElement", &UiScrollBoxBus::Events::FindClosestContentChildElement);
|
|
|
|
behaviorContext->Enum<(int)UiScrollBoxInterface::SnapMode::None>("eUiScrollBoxSnapMode_None")
|
|
->Enum<(int)UiScrollBoxInterface::SnapMode::Children>("eUiScrollBoxSnapMode_Children")
|
|
->Enum<(int)UiScrollBoxInterface::SnapMode::Grid>("eUiScrollBoxSnapMode_Grid")
|
|
->Enum<(int)UiScrollBoxInterface::ScrollBarVisibility::AlwaysShow>("eUiScrollBoxScrollBarVisibility_AlwaysShow")
|
|
->Enum<(int)UiScrollBoxInterface::ScrollBarVisibility::AutoHide>("eUiScrollBoxScrollBarVisibility_AutoHide")
|
|
->Enum<(int)UiScrollBoxInterface::ScrollBarVisibility::AutoHideAndResizeViewport>("eUiScrollBoxScrollBarVisibility_AutoHideAndResizeViewport");
|
|
|
|
behaviorContext->EBus<UiScrollBoxNotificationBus>("UiScrollBoxNotificationBus")
|
|
->Handler<BehaviorUiScrollBoxNotificationBusHandler>();
|
|
|
|
behaviorContext->EBus<UiScrollableNotificationBus>("UiScrollableNotificationBus")
|
|
->Handler<BehaviorUiScrollableNotificationBusHandler>();
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
UiScrollBoxComponent::EntityComboBoxVec UiScrollBoxComponent::PopulateChildEntityList()
|
|
{
|
|
EntityComboBoxVec result;
|
|
|
|
// add a first entry for "None"
|
|
result.push_back(AZStd::make_pair(AZ::EntityId(AZ::EntityId()), "<None>"));
|
|
|
|
// Get a list of all child elements
|
|
LyShine::EntityArray matchingElements;
|
|
EBUS_EVENT_ID(GetEntityId(), UiElementBus, FindDescendantElements,
|
|
[]([[maybe_unused]] const AZ::Entity* entity) { return true; },
|
|
matchingElements);
|
|
|
|
// add their names to the StringList and their IDs to the id list
|
|
for (auto childEntity : matchingElements)
|
|
{
|
|
result.push_back(AZStd::make_pair(AZ::EntityId(childEntity->GetId()), childEntity->GetName()));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
UiScrollBoxComponent::EntityComboBoxVec UiScrollBoxComponent::PopulateHScrollBarEntityList()
|
|
{
|
|
return PopulateScrollBarEntityList(UiScrollerInterface::Orientation::Horizontal);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
UiScrollBoxComponent::EntityComboBoxVec UiScrollBoxComponent::PopulateVScrollBarEntityList()
|
|
{
|
|
return PopulateScrollBarEntityList(UiScrollerInterface::Orientation::Vertical);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
UiScrollBoxComponent::EntityComboBoxVec UiScrollBoxComponent::PopulateScrollBarEntityList(UiScrollerInterface::Orientation orientation)
|
|
{
|
|
EntityComboBoxVec result;
|
|
|
|
// Add a first entry for "None"
|
|
result.push_back(AZStd::make_pair(AZ::EntityId(), "<None>"));
|
|
|
|
// Get a list of all scrollbar elements
|
|
AZ::EntityId canvasEntityId;
|
|
EBUS_EVENT_ID_RESULT(canvasEntityId, GetEntityId(), UiElementBus, GetCanvasEntityId);
|
|
LyShine::EntityArray scrollBarElements;
|
|
EBUS_EVENT_ID(canvasEntityId, UiCanvasBus, FindElements,
|
|
[this, orientation](const AZ::Entity* entity)
|
|
{
|
|
bool isScroller = false;
|
|
if (entity->GetId() != GetEntityId())
|
|
{
|
|
if (UiScrollerBus::FindFirstHandler(entity->GetId()))
|
|
{
|
|
// Check scrollbar's orientation
|
|
UiScrollerInterface::Orientation entityOrientation;
|
|
EBUS_EVENT_ID_RESULT(entityOrientation, entity->GetId(), UiScrollerBus, GetOrientation);
|
|
isScroller = (entityOrientation == orientation);
|
|
}
|
|
}
|
|
return isScroller;
|
|
},
|
|
scrollBarElements);
|
|
|
|
// Sort the elements by name
|
|
AZStd::sort(scrollBarElements.begin(), scrollBarElements.end(),
|
|
[](const AZ::Entity* e1, const AZ::Entity* e2) { return e1->GetName() < e2->GetName(); });
|
|
|
|
// Add their names to the StringList and their IDs to the id list
|
|
for (auto scrollBarEntity : scrollBarElements)
|
|
{
|
|
result.push_back(AZStd::make_pair(scrollBarEntity->GetId(), scrollBarEntity->GetName()));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
bool UiScrollBoxComponent::IsSnapToGrid() const
|
|
{
|
|
return m_snapMode == SnapMode::Grid;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
AZ::Vector2 UiScrollBoxComponent::ConstrainOffset(AZ::Vector2 proposedOffset, AZ::Entity* contentParentEntity)
|
|
{
|
|
AZ::Vector2 newScrollOffset = proposedOffset;
|
|
|
|
if (contentParentEntity)
|
|
{
|
|
// get content parent's rect in canvas space
|
|
UiTransformInterface::Rect parentRect;
|
|
EBUS_EVENT_ID(contentParentEntity->GetId(), UiTransformBus, GetCanvasSpaceRectNoScaleRotate, parentRect);
|
|
|
|
// get content's rect in canvas space
|
|
UiTransformInterface::Rect contentRect = GetAxisAlignedContentRect();
|
|
|
|
AZ::Vector2 latestOffsetDelta = newScrollOffset - m_scrollOffset;
|
|
|
|
// add the requested scroll offset to the content rect to get the proposed position
|
|
// The content has already need moved by the requested offset all but latestOffsetDelta
|
|
contentRect.MoveBy(latestOffsetDelta);
|
|
|
|
if (contentRect.GetWidth() <= parentRect.GetWidth())
|
|
{
|
|
newScrollOffset.SetX(0.0f);
|
|
}
|
|
else if (contentRect.left > parentRect.left)
|
|
{
|
|
newScrollOffset.SetX(newScrollOffset.GetX() - (contentRect.left - parentRect.left));
|
|
}
|
|
else if (contentRect.right < parentRect.right)
|
|
{
|
|
newScrollOffset.SetX(newScrollOffset.GetX() + (parentRect.right - contentRect.right));
|
|
}
|
|
|
|
if (contentRect.GetHeight() <= parentRect.GetHeight())
|
|
{
|
|
newScrollOffset.SetY(0.0f);
|
|
}
|
|
else if (contentRect.top > parentRect.top)
|
|
{
|
|
newScrollOffset.SetY(newScrollOffset.GetY() - (contentRect.top - parentRect.top));
|
|
}
|
|
else if (contentRect.bottom < parentRect.bottom)
|
|
{
|
|
newScrollOffset.SetY(newScrollOffset.GetY() + (parentRect.bottom - contentRect.bottom));
|
|
}
|
|
}
|
|
|
|
return newScrollOffset;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
bool UiScrollBoxComponent::DoSnap()
|
|
{
|
|
AZ::Vector2 newScrollOffset = m_scrollOffset;
|
|
AZ::Vector2 deltaToSubtract(0.0f, 0.0f);
|
|
|
|
if (m_snapMode == UiScrollBoxInterface::SnapMode::Children)
|
|
{
|
|
AZ::EntityId closestChild = FindClosestContentChildElement();
|
|
|
|
if (closestChild.IsValid())
|
|
{
|
|
// want elastic animation eventually
|
|
deltaToSubtract = ComputeCurrentOffsetToChild(closestChild);
|
|
}
|
|
}
|
|
else if (m_snapMode == UiScrollBoxInterface::SnapMode::Grid)
|
|
{
|
|
deltaToSubtract = ComputeCurrentOffsetFromGrid();
|
|
}
|
|
|
|
// snapping should only move the content in the directions it is allowed to scroll
|
|
if (!m_isHorizontalScrollingEnabled)
|
|
{
|
|
deltaToSubtract.SetX(0.0f);
|
|
}
|
|
if (!m_isVerticalScrollingEnabled)
|
|
{
|
|
deltaToSubtract.SetY(0.0f);
|
|
}
|
|
|
|
newScrollOffset = m_scrollOffset - deltaToSubtract;
|
|
|
|
if (m_isScrollingConstrained)
|
|
{
|
|
AZ::Entity* contentParentEntity = nullptr;
|
|
EBUS_EVENT_ID_RESULT(contentParentEntity, m_contentEntity, UiElementBus, GetParent);
|
|
newScrollOffset = ConstrainOffset(newScrollOffset, contentParentEntity);
|
|
}
|
|
|
|
if (newScrollOffset != m_scrollOffset)
|
|
{
|
|
DoSetScrollOffset(newScrollOffset);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
AZ::Vector2 UiScrollBoxComponent::ComputeCurrentOffsetToChild(AZ::EntityId child)
|
|
{
|
|
// Get the position of the child element's pivot in canvas space
|
|
AZ::Vector2 childPivotPosition;
|
|
EBUS_EVENT_ID_RESULT(childPivotPosition, child, UiTransformBus, GetCanvasSpacePivot);
|
|
|
|
AZ::Vector2 anchorCenter = ComputeContentAnchorCenterInCanvasSpace();
|
|
|
|
// offset is the distance from the content anchors to the current child pivot position
|
|
// (given the current scroll offset)
|
|
AZ::Vector2 offsetToChild = childPivotPosition - anchorCenter;
|
|
|
|
AZ::Entity* contentParentEntity = nullptr;
|
|
EBUS_EVENT_ID_RESULT(contentParentEntity, m_contentEntity, UiElementBus, GetParent);
|
|
|
|
AZ::Matrix4x4 transform;
|
|
if (contentParentEntity)
|
|
{
|
|
EBUS_EVENT_ID(contentParentEntity->GetId(), UiTransformBus, GetTransformFromCanvasSpace, transform);
|
|
}
|
|
else
|
|
{
|
|
transform = AZ::Matrix4x4::CreateIdentity();
|
|
}
|
|
|
|
// Transform the offset from canvas space to the local space of the parent of the content element
|
|
AZ::Vector3 offsetToChild3(offsetToChild.GetX(), offsetToChild.GetY(), 0.0f);
|
|
offsetToChild3 = transform.Multiply3x3(offsetToChild3);
|
|
offsetToChild = AZ::Vector2(offsetToChild3.GetX(), offsetToChild3.GetY());
|
|
|
|
return offsetToChild;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
AZ::Vector2 UiScrollBoxComponent::ComputeCurrentOffsetFromGrid()
|
|
{
|
|
// offset is the delta to add to subtract from m_scrollOffset to put it on the grid
|
|
AZ::Vector2 offsetToGrid;
|
|
offsetToGrid.SetX(ComputeOffsetOfValueFromGrid(m_scrollOffset.GetX(), m_snapGrid.GetX()));
|
|
offsetToGrid.SetY(ComputeOffsetOfValueFromGrid(m_scrollOffset.GetY(), m_snapGrid.GetY()));
|
|
return offsetToGrid;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
AZ::Vector2 UiScrollBoxComponent::ComputeContentAnchorCenterInCanvasSpace() const
|
|
{
|
|
// Get the position of the content elements anchors in canvas space
|
|
AZ::Entity* contentParentEntity = nullptr;
|
|
EBUS_EVENT_ID_RESULT(contentParentEntity, m_contentEntity, UiElementBus, GetParent);
|
|
|
|
if (!contentParentEntity)
|
|
{
|
|
return AZ::Vector2(0.0f, 0.0f);
|
|
}
|
|
|
|
// get content parent's rect in canvas space
|
|
UiTransformInterface::Rect parentRect;
|
|
EBUS_EVENT_ID(contentParentEntity->GetId(), UiTransformBus, GetCanvasSpaceRectNoScaleRotate, parentRect);
|
|
|
|
// Get the content anchor center in canvas space
|
|
UiTransform2dInterface::Anchors anchors;
|
|
EBUS_EVENT_ID_RESULT(anchors, m_contentEntity, UiTransform2dBus, GetAnchors);
|
|
|
|
UiTransformInterface::Rect anchorRect;
|
|
anchorRect.left = parentRect.left + anchors.m_left * parentRect.GetWidth();
|
|
anchorRect.right = parentRect.left + anchors.m_right * parentRect.GetWidth();
|
|
anchorRect.top = parentRect.top + anchors.m_top * parentRect.GetHeight();
|
|
anchorRect.bottom = parentRect.top + anchors.m_bottom * parentRect.GetHeight();
|
|
|
|
AZ::Vector2 anchorCenter = anchorRect.GetCenter();
|
|
|
|
AZ::Matrix4x4 transformToCanvasSpace;
|
|
EBUS_EVENT_ID(contentParentEntity->GetId(), UiTransformBus, GetTransformToCanvasSpace, transformToCanvasSpace);
|
|
AZ::Vector3 anchorCenter3(anchorCenter.GetX(), anchorCenter.GetY(), 0.0f);
|
|
anchorCenter3 = transformToCanvasSpace * anchorCenter3;
|
|
anchorCenter = AZ::Vector2(anchorCenter3.GetX(), anchorCenter3.GetY());
|
|
|
|
return anchorCenter;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
float UiScrollBoxComponent::ComputeOffsetOfValueFromGrid(float value, float gridStep)
|
|
{
|
|
const float gridEpsilon = 0.00001f;
|
|
|
|
// compute offset to round to nearest point on grid
|
|
float offsetFromGrid = 0.0f;
|
|
if (gridStep >= gridEpsilon)
|
|
{
|
|
float roundedGridStep = roundf(value / gridStep);
|
|
float targetValue = roundedGridStep * gridStep;
|
|
offsetFromGrid = value - targetValue;
|
|
}
|
|
|
|
return offsetFromGrid;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// calculate how much we have dragged along the dragable axes of the ScrollBox
|
|
float UiScrollBoxComponent::GetValidDragDistanceInPixels(AZ::Vector2 startPoint, AZ::Vector2 endPoint)
|
|
{
|
|
const float validDragRatio = 0.5f;
|
|
|
|
AZ::Entity* contentParentEntity = nullptr;
|
|
EBUS_EVENT_ID_RESULT(contentParentEntity, m_contentEntity, UiElementBus, GetParent);
|
|
|
|
if (!contentParentEntity)
|
|
{
|
|
return 0.0f;
|
|
}
|
|
|
|
// convert the drag vector to local space
|
|
AZ::Matrix4x4 transformFromViewport;
|
|
EBUS_EVENT_ID(contentParentEntity->GetId(), UiTransformBus, GetTransformFromViewport, transformFromViewport);
|
|
AZ::Vector2 dragVec = endPoint - startPoint;
|
|
AZ::Vector3 dragVec3(dragVec.GetX(), dragVec.GetY(), 0.0f);
|
|
AZ::Vector3 localDragVec = transformFromViewport.Multiply3x3(dragVec3);
|
|
|
|
// constrain to the allowed movement directions
|
|
if (!m_isHorizontalScrollingEnabled)
|
|
{
|
|
localDragVec.SetX(0.0f);
|
|
}
|
|
if (!m_isVerticalScrollingEnabled)
|
|
{
|
|
localDragVec.SetY(0.0f);
|
|
}
|
|
|
|
// convert back to viewport space
|
|
AZ::Matrix4x4 transformToViewport;
|
|
EBUS_EVENT_ID(contentParentEntity->GetId(), UiTransformBus, GetTransformToViewport, transformToViewport);
|
|
AZ::Vector3 validDragVec = transformToViewport.Multiply3x3(localDragVec);
|
|
|
|
float validDistance = validDragVec.GetLengthSq();
|
|
float totalDistance = dragVec.GetLengthSq();
|
|
|
|
// if they are not dragging mostly in a valid direction then ignore the drag
|
|
if (validDistance / totalDistance < validDragRatio)
|
|
{
|
|
validDistance = 0.0f;
|
|
}
|
|
|
|
// return the valid drag distance
|
|
return validDistance;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiScrollBoxComponent::CheckForDragOrHandOffToParent(AZ::Vector2 point)
|
|
{
|
|
AZ::EntityId parentDraggable;
|
|
EBUS_EVENT_ID_RESULT(parentDraggable, GetEntityId(), UiElementBus, FindParentInteractableSupportingDrag, point);
|
|
|
|
// if this interactable is inside another interactable that supports drag then we use
|
|
// a threshold value before starting a drag on this interactable
|
|
const float normalDragThreshold = 0.0f;
|
|
const float containedDragThreshold = 5.0f;
|
|
|
|
float dragThreshold = normalDragThreshold;
|
|
if (parentDraggable.IsValid())
|
|
{
|
|
dragThreshold = containedDragThreshold;
|
|
}
|
|
|
|
// calculate how much we have dragged in a valid direction
|
|
float validDragDistance = GetValidDragDistanceInPixels(m_pressedPoint, point);
|
|
if (validDragDistance > dragThreshold)
|
|
{
|
|
// we dragged above the threshold value along axis of slider
|
|
m_isDragging = true;
|
|
}
|
|
else if (parentDraggable.IsValid())
|
|
{
|
|
// offer the parent draggable the chance to become the active interactable
|
|
bool handOff = false;
|
|
EBUS_EVENT_ID_RESULT(handOff, parentDraggable, UiInteractableBus,
|
|
OfferDragHandOff, GetEntityId(), m_pressedPoint, point, containedDragThreshold);
|
|
|
|
if (handOff)
|
|
{
|
|
// interaction has been handed off to a container entity
|
|
m_isPressed = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiScrollBoxComponent::DoSetScrollOffset(AZ::Vector2 scrollOffset)
|
|
{
|
|
m_scrollOffset = scrollOffset;
|
|
|
|
if (m_contentEntity.IsValid())
|
|
{
|
|
// The scrollOffset is the distance from the content element's anchors to its pivot
|
|
// Given the scrollOffset we adjust the offsets to make this so.
|
|
UiTransform2dInterface::Offsets offsets;
|
|
EBUS_EVENT_ID_RESULT(offsets, m_contentEntity, UiTransform2dBus, GetOffsets);
|
|
|
|
AZ::Vector2 pivot;
|
|
EBUS_EVENT_ID_RESULT(pivot, m_contentEntity, UiTransformBus, GetPivot);
|
|
|
|
float width = offsets.m_right - offsets.m_left;
|
|
float height = offsets.m_bottom - offsets.m_top;
|
|
|
|
offsets.m_left = scrollOffset.GetX() - width * pivot.GetX();
|
|
offsets.m_right = offsets.m_left + width;
|
|
offsets.m_top = scrollOffset.GetY() - height * pivot.GetY();
|
|
offsets.m_bottom = offsets.m_top + height;
|
|
|
|
EBUS_EVENT_ID(m_contentEntity, UiTransform2dBus, SetOffsets, offsets);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiScrollBoxComponent::DoChangedActions()
|
|
{
|
|
if (m_onScrollOffsetChanged)
|
|
{
|
|
m_onScrollOffsetChanged(GetEntityId(), m_scrollOffset);
|
|
}
|
|
|
|
// Tell any action listeners about the event
|
|
if (!m_scrollOffsetChangedActionName.empty())
|
|
{
|
|
AZ::EntityId canvasEntityId;
|
|
EBUS_EVENT_ID_RESULT(canvasEntityId, GetEntityId(), UiElementBus, GetCanvasEntityId);
|
|
EBUS_EVENT_ID(canvasEntityId, UiCanvasNotificationBus, OnAction, GetEntityId(), m_scrollOffsetChangedActionName);
|
|
}
|
|
|
|
NotifyListenersOnScrollOffsetChanged();
|
|
|
|
NotifyListenersOnScrollValueChanged();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiScrollBoxComponent::DoChangingActions()
|
|
{
|
|
if (m_onScrollOffsetChanging)
|
|
{
|
|
m_onScrollOffsetChanging(GetEntityId(), m_scrollOffset);
|
|
}
|
|
|
|
// Tell any action listeners about the event
|
|
if (!m_scrollOffsetChangingActionName.empty())
|
|
{
|
|
AZ::EntityId canvasEntityId;
|
|
EBUS_EVENT_ID_RESULT(canvasEntityId, GetEntityId(), UiElementBus, GetCanvasEntityId);
|
|
EBUS_EVENT_ID(canvasEntityId, UiCanvasNotificationBus, OnAction, GetEntityId(), m_scrollOffsetChangingActionName);
|
|
}
|
|
|
|
NotifyListenersOnScrollOffsetChanging();
|
|
|
|
NotifyListenersOnScrollValueChanging();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiScrollBoxComponent::NotifyScrollersOnValueChanged()
|
|
{
|
|
AZ::Vector2 normalizedScrollValueOut;
|
|
bool result = ScrollOffsetToNormalizedScrollValue(m_scrollOffset, normalizedScrollValueOut);
|
|
|
|
if (result)
|
|
{
|
|
EBUS_EVENT_ID(GetEntityId(), UiScrollableToScrollerNotificationBus, OnValueChangedByScrollable, normalizedScrollValueOut);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiScrollBoxComponent::NotifyScrollersOnValueChanging()
|
|
{
|
|
AZ::Vector2 normalizedScrollValueOut;
|
|
bool result = ScrollOffsetToNormalizedScrollValue(m_scrollOffset, normalizedScrollValueOut);
|
|
|
|
if (result)
|
|
{
|
|
EBUS_EVENT_ID(GetEntityId(), UiScrollableToScrollerNotificationBus, OnValueChangingByScrollable, normalizedScrollValueOut);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiScrollBoxComponent::NotifyListenersOnScrollValueChanged()
|
|
{
|
|
AZ::Vector2 normalizedScrollValueOut;
|
|
bool result = ScrollOffsetToNormalizedScrollValue(m_scrollOffset, normalizedScrollValueOut);
|
|
|
|
if (result)
|
|
{
|
|
EBUS_EVENT_ID(GetEntityId(), UiScrollableNotificationBus, OnScrollableValueChanged, normalizedScrollValueOut);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiScrollBoxComponent::NotifyListenersOnScrollValueChanging()
|
|
{
|
|
AZ::Vector2 normalizedScrollValueOut;
|
|
bool result = ScrollOffsetToNormalizedScrollValue(m_scrollOffset, normalizedScrollValueOut);
|
|
|
|
if (result)
|
|
{
|
|
EBUS_EVENT_ID(GetEntityId(), UiScrollableNotificationBus, OnScrollableValueChanging, normalizedScrollValueOut);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiScrollBoxComponent::NotifyListenersOnScrollOffsetChanged()
|
|
{
|
|
EBUS_EVENT_ID(GetEntityId(), UiScrollBoxNotificationBus, OnScrollOffsetChanged, m_scrollOffset);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiScrollBoxComponent::NotifyListenersOnScrollOffsetChanging()
|
|
{
|
|
EBUS_EVENT_ID(GetEntityId(), UiScrollBoxNotificationBus, OnScrollOffsetChanging, m_scrollOffset);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
UiTransformInterface::Rect UiScrollBoxComponent::GetAxisAlignedContentRect()
|
|
{
|
|
UiTransformInterface::RectPoints points;
|
|
EBUS_EVENT_ID(m_contentEntity, UiTransformBus, GetCanvasSpacePointsNoScaleRotate, points);
|
|
|
|
AZ::Matrix4x4 transform;
|
|
EBUS_EVENT_ID(m_contentEntity, UiTransformBus, GetLocalTransform, transform);
|
|
|
|
points = points.Transform(transform);
|
|
|
|
UiTransformInterface::Rect rect;
|
|
rect.left = points.GetAxisAlignedTopLeft().GetX();
|
|
rect.right = points.GetAxisAlignedBottomRight().GetX();
|
|
rect.top = points.GetAxisAlignedTopLeft().GetY();
|
|
rect.bottom = points.GetAxisAlignedBottomRight().GetY();
|
|
|
|
return rect;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
bool UiScrollBoxComponent::ScrollOffsetToNormalizedScrollValue(AZ::Vector2 scrollOffset, AZ::Vector2& normalizedScrollValueOut)
|
|
{
|
|
AZ::Entity* contentParentEntity = nullptr;
|
|
EBUS_EVENT_ID_RESULT(contentParentEntity, m_contentEntity, UiElementBus, GetParent);
|
|
if (contentParentEntity)
|
|
{
|
|
UiTransformInterface::Rect parentRect;
|
|
EBUS_EVENT_ID(contentParentEntity->GetId(), UiTransformBus, GetCanvasSpaceRectNoScaleRotate, parentRect);
|
|
|
|
UiTransformInterface::Rect contentRect = GetAxisAlignedContentRect();
|
|
|
|
if (contentRect.GetWidth() <= parentRect.GetWidth())
|
|
{
|
|
normalizedScrollValueOut.SetX(0.0f);
|
|
}
|
|
else
|
|
{
|
|
float minScrollOffset = scrollOffset.GetX() - (contentRect.left - parentRect.left);
|
|
float maxScrollOffset = scrollOffset.GetX() - (contentRect.right - parentRect.right);
|
|
normalizedScrollValueOut.SetX((scrollOffset.GetX() - minScrollOffset) / (maxScrollOffset - minScrollOffset));
|
|
}
|
|
|
|
if (contentRect.GetHeight() <= parentRect.GetHeight())
|
|
{
|
|
normalizedScrollValueOut.SetY(0.0f);
|
|
}
|
|
else
|
|
{
|
|
float minScrollOffset = scrollOffset.GetY() - (contentRect.top - parentRect.top);
|
|
float maxScrollOffset = scrollOffset.GetY() - (contentRect.bottom - parentRect.bottom);
|
|
normalizedScrollValueOut.SetY((scrollOffset.GetY() - minScrollOffset) / (maxScrollOffset - minScrollOffset));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
bool UiScrollBoxComponent::NormalizedScrollValueToScrollOffset(UiScrollerInterface::Orientation orientation, float normalizedScrollValue, float& scrollOffsetOut)
|
|
{
|
|
if (orientation == UiScrollerInterface::Orientation::Horizontal || orientation == UiScrollerInterface::Orientation::Vertical)
|
|
{
|
|
AZ::Entity* contentParentEntity = nullptr;
|
|
EBUS_EVENT_ID_RESULT(contentParentEntity, m_contentEntity, UiElementBus, GetParent);
|
|
if (contentParentEntity)
|
|
{
|
|
UiTransformInterface::Rect parentRect;
|
|
EBUS_EVENT_ID(contentParentEntity->GetId(), UiTransformBus, GetCanvasSpaceRectNoScaleRotate, parentRect);
|
|
|
|
UiTransformInterface::Rect contentRect = GetAxisAlignedContentRect();
|
|
|
|
if (orientation == UiScrollerInterface::Orientation::Horizontal)
|
|
{
|
|
if (contentRect.GetWidth() <= parentRect.GetWidth())
|
|
{
|
|
if (m_isScrollingConstrained)
|
|
{
|
|
scrollOffsetOut = 0.0f;
|
|
}
|
|
else
|
|
{
|
|
scrollOffsetOut = m_scrollOffset.GetX();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
float minScrollOffset = m_scrollOffset.GetX() - (contentRect.left - parentRect.left);
|
|
float maxScrollOffset = m_scrollOffset.GetX() - (contentRect.right - parentRect.right);
|
|
scrollOffsetOut = minScrollOffset + (maxScrollOffset - minScrollOffset) * normalizedScrollValue;
|
|
}
|
|
}
|
|
else // orientation == UiScrollerInterface::Orientation::Vertical
|
|
{
|
|
if (contentRect.GetHeight() <= parentRect.GetHeight())
|
|
{
|
|
if (m_isScrollingConstrained)
|
|
{
|
|
scrollOffsetOut = 0.0f;
|
|
}
|
|
else
|
|
{
|
|
scrollOffsetOut = m_scrollOffset.GetY();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
float minScrollOffset = m_scrollOffset.GetY() - (contentRect.top - parentRect.top);
|
|
float maxScrollOffset = m_scrollOffset.GetY() - (contentRect.bottom - parentRect.bottom);
|
|
scrollOffsetOut = minScrollOffset + (maxScrollOffset - minScrollOffset) * normalizedScrollValue;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
bool UiScrollBoxComponent::ScrollerValueToScrollOffsets(AZ::EntityId scroller, float scrollerValue, AZ::Vector2& scrollOffsetsOut)
|
|
{
|
|
if (((scroller == m_hScrollBarEntity) && m_isHorizontalScrollingEnabled)
|
|
|| ((scroller == m_vScrollBarEntity) && m_isVerticalScrollingEnabled))
|
|
{
|
|
float scrollOffsetOut;
|
|
UiScrollerInterface::Orientation orientation = (scroller == m_hScrollBarEntity) ? UiScrollerInterface::Orientation::Horizontal : UiScrollerInterface::Orientation::Vertical;
|
|
bool result = NormalizedScrollValueToScrollOffset(orientation, scrollerValue, scrollOffsetOut);
|
|
|
|
if (result)
|
|
{
|
|
scrollOffsetsOut = m_scrollOffset;
|
|
if (orientation == UiScrollerInterface::Orientation::Horizontal)
|
|
{
|
|
scrollOffsetsOut.SetX(scrollOffsetOut);
|
|
}
|
|
else // orientation == UiScrollerInterface::Orientation::Vertical
|
|
{
|
|
scrollOffsetsOut.SetY(scrollOffsetOut);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
bool UiScrollBoxComponent::IsVerticalScrollBarOnRight()
|
|
{
|
|
// Check if vertical scrollbar is on the right of the content's parent
|
|
|
|
AZ::Entity* contentParentEntity = nullptr;
|
|
EBUS_EVENT_ID_RESULT(contentParentEntity, m_contentEntity, UiElementBus, GetParent);
|
|
if (contentParentEntity)
|
|
{
|
|
// Get content parent rect in canvas space
|
|
UiTransformInterface::Rect parentRect;
|
|
EBUS_EVENT_ID(contentParentEntity->GetId(), UiTransformBus, GetCanvasSpaceRectNoScaleRotate, parentRect);
|
|
|
|
// Get vertical scrollbar rect in canvas space
|
|
UiTransformInterface::Rect vScrollBarRect;
|
|
EBUS_EVENT_ID(m_vScrollBarEntity, UiTransformBus, GetCanvasSpaceRectNoScaleRotate, vScrollBarRect);
|
|
|
|
return (vScrollBarRect.GetCenter().GetX() > parentRect.GetCenter().GetX());
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
bool UiScrollBoxComponent::IsHorizontalScrollBarOnBottom()
|
|
{
|
|
// Check if horizontal scrollbar is on the bottom of the content's parent
|
|
|
|
AZ::Entity* contentParentEntity = nullptr;
|
|
EBUS_EVENT_ID_RESULT(contentParentEntity, m_contentEntity, UiElementBus, GetParent);
|
|
if (contentParentEntity)
|
|
{
|
|
// Get content parent rect in canvas space
|
|
UiTransformInterface::Rect parentRect;
|
|
EBUS_EVENT_ID(contentParentEntity->GetId(), UiTransformBus, GetCanvasSpaceRectNoScaleRotate, parentRect);
|
|
|
|
// Get horizontal scrollbar rect in canvas space
|
|
UiTransformInterface::Rect hScrollBarRect;
|
|
EBUS_EVENT_ID(m_hScrollBarEntity, UiTransformBus, GetCanvasSpaceRectNoScaleRotate, hScrollBarRect);
|
|
|
|
return (hScrollBarRect.GetCenter().GetY() > parentRect.GetCenter().GetY());
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiScrollBoxComponent::UpdateScrollBarVisiblity()
|
|
{
|
|
bool updateHorizontalScrollBar = m_hScrollBarEntity.IsValid() && m_isHorizontalScrollingEnabled && (m_hScrollBarVisibility != ScrollBarVisibility::AlwaysShow);
|
|
bool updateVerticalScrollBar = m_vScrollBarEntity.IsValid() && m_isVerticalScrollingEnabled && (m_vScrollBarVisibility != ScrollBarVisibility::AlwaysShow);
|
|
|
|
if (updateHorizontalScrollBar || updateVerticalScrollBar)
|
|
{
|
|
// Set scrollbar visibility based on whether there is scrollable content
|
|
|
|
AZ::Entity* contentParentEntity = nullptr;
|
|
EBUS_EVENT_ID_RESULT(contentParentEntity, m_contentEntity, UiElementBus, GetParent);
|
|
if (contentParentEntity)
|
|
{
|
|
bool showHScrollBar = true;
|
|
bool showVScrollBar = true;
|
|
|
|
// Get content parent's size
|
|
AZ::Vector2 parentSize;
|
|
EBUS_EVENT_ID_RESULT(parentSize, contentParentEntity->GetId(), UiTransformBus, GetCanvasSpaceSizeNoScaleRotate);
|
|
|
|
// Get content size
|
|
UiTransformInterface::Rect contentRect = GetAxisAlignedContentRect();
|
|
AZ::Vector2 contentSize = contentRect.GetSize();
|
|
|
|
// First check if none of the hideable scrollbars are needed
|
|
bool needHScrollBar = false;
|
|
bool needVScrollBar = false;
|
|
if (updateHorizontalScrollBar)
|
|
{
|
|
needHScrollBar = (contentSize.GetX() > parentSize.GetX());
|
|
}
|
|
if (updateVerticalScrollBar)
|
|
{
|
|
needVScrollBar = (contentSize.GetY() > parentSize.GetY());
|
|
}
|
|
|
|
if (!needHScrollBar && !needVScrollBar)
|
|
{
|
|
showHScrollBar = false;
|
|
showVScrollBar = false;
|
|
}
|
|
else
|
|
{
|
|
// Next, check if only a horizontal scrollbar is needed
|
|
AZ::Vector2 supposedParentSize = parentSize;
|
|
|
|
if (updateHorizontalScrollBar && (m_hScrollBarVisibility == ScrollBarVisibility::AutoHideAndResizeViewport))
|
|
{
|
|
// Get height of horizontal scrollbar
|
|
AZ::Vector2 hScrollBarSize;
|
|
EBUS_EVENT_ID_RESULT(hScrollBarSize, m_hScrollBarEntity, UiTransformBus, GetCanvasSpaceSizeNoScaleRotate);
|
|
float hScrollBarHeight = hScrollBarSize.GetY();
|
|
|
|
supposedParentSize.SetY(supposedParentSize.GetY() - hScrollBarHeight);
|
|
}
|
|
|
|
if (contentSize.GetY() <= supposedParentSize.GetY() && contentSize.GetX() > supposedParentSize.GetX())
|
|
{
|
|
showHScrollBar = true;
|
|
showVScrollBar = false;
|
|
}
|
|
else
|
|
{
|
|
// Next, check if only a vertical scrollbar is needed
|
|
supposedParentSize = parentSize;
|
|
|
|
if (updateVerticalScrollBar && (m_vScrollBarVisibility == ScrollBarVisibility::AutoHideAndResizeViewport))
|
|
{
|
|
// Get width of vertical scrollbar
|
|
AZ::Vector2 vScrollBarSize;
|
|
EBUS_EVENT_ID_RESULT(vScrollBarSize, m_vScrollBarEntity, UiTransformBus, GetCanvasSpaceSizeNoScaleRotate);
|
|
float vScrollBarWidth = vScrollBarSize.GetX();
|
|
|
|
supposedParentSize.SetX(supposedParentSize.GetX() - vScrollBarWidth);
|
|
}
|
|
|
|
if (contentSize.GetX() <= supposedParentSize.GetX() && contentSize.GetY() > supposedParentSize.GetY())
|
|
{
|
|
showHScrollBar = false;
|
|
showVScrollBar = true;
|
|
}
|
|
else
|
|
{
|
|
// Both scrollbars are needed
|
|
showHScrollBar = true;
|
|
showVScrollBar = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Set enabled property on the scrollbars
|
|
if (updateHorizontalScrollBar)
|
|
{
|
|
EBUS_EVENT_ID(m_hScrollBarEntity, UiElementBus, SetIsEnabled, showHScrollBar);
|
|
}
|
|
if (updateVerticalScrollBar)
|
|
{
|
|
EBUS_EVENT_ID(m_vScrollBarEntity, UiElementBus, SetIsEnabled, showVScrollBar);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiScrollBoxComponent::UpdateScrollBarAnchorsAndOffsets()
|
|
{
|
|
// Set scrollbar anchors and offsets based on the other scrollbar's visibility
|
|
|
|
if (m_hScrollBarEntity.IsValid() && m_isHorizontalScrollingEnabled && (m_hScrollBarVisibility != ScrollBarVisibility::AlwaysShow))
|
|
{
|
|
// Set anchors
|
|
UiTransform2dInterface::Anchors anchors;
|
|
EBUS_EVENT_ID_RESULT(anchors, m_hScrollBarEntity, UiTransform2dBus, GetAnchors);
|
|
anchors.m_left = 0.0f;
|
|
anchors.m_right = 1.0f;
|
|
EBUS_EVENT_ID(m_hScrollBarEntity, UiTransform2dBus, SetAnchors, anchors, false, false);
|
|
|
|
// Set offsets
|
|
|
|
UiTransform2dInterface::Offsets offsets;
|
|
EBUS_EVENT_ID_RESULT(offsets, m_hScrollBarEntity, UiTransform2dBus, GetOffsets);
|
|
|
|
bool isVScrollBarEnabled = false;
|
|
if (m_vScrollBarEntity.IsValid() && m_isVerticalScrollingEnabled)
|
|
{
|
|
EBUS_EVENT_ID_RESULT(isVScrollBarEnabled, m_vScrollBarEntity, UiElementBus, IsEnabled);
|
|
}
|
|
|
|
if (isVScrollBarEnabled)
|
|
{
|
|
// Get width of vertical scrollbar
|
|
AZ::Vector2 vScrollBarSize;
|
|
EBUS_EVENT_ID_RESULT(vScrollBarSize, m_vScrollBarEntity, UiTransformBus, GetCanvasSpaceSizeNoScaleRotate);
|
|
|
|
if (IsVerticalScrollBarOnRight())
|
|
{
|
|
offsets.m_left = 0.0f;
|
|
offsets.m_right = -vScrollBarSize.GetX();
|
|
}
|
|
else
|
|
{
|
|
offsets.m_left = vScrollBarSize.GetX();
|
|
offsets.m_right = 0.0f;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
offsets.m_left = 0.0f;
|
|
offsets.m_right = 0.0f;
|
|
}
|
|
|
|
EBUS_EVENT_ID(m_hScrollBarEntity, UiTransform2dBus, SetOffsets, offsets);
|
|
}
|
|
|
|
if (m_vScrollBarEntity.IsValid() && m_isVerticalScrollingEnabled && (m_vScrollBarVisibility != ScrollBarVisibility::AlwaysShow))
|
|
{
|
|
// Set anchors
|
|
UiTransform2dInterface::Anchors anchors;
|
|
EBUS_EVENT_ID_RESULT(anchors, m_vScrollBarEntity, UiTransform2dBus, GetAnchors);
|
|
anchors.m_top = 0.0f;
|
|
anchors.m_bottom = 1.0f;
|
|
EBUS_EVENT_ID(m_vScrollBarEntity, UiTransform2dBus, SetAnchors, anchors, false, false);
|
|
|
|
// Set offsets
|
|
|
|
UiTransform2dInterface::Offsets offsets;
|
|
EBUS_EVENT_ID_RESULT(offsets, m_vScrollBarEntity, UiTransform2dBus, GetOffsets);
|
|
|
|
bool isHScrollBarEnabled = false;
|
|
if (m_hScrollBarEntity.IsValid() && m_isHorizontalScrollingEnabled)
|
|
{
|
|
EBUS_EVENT_ID_RESULT(isHScrollBarEnabled, m_hScrollBarEntity, UiElementBus, IsEnabled);
|
|
}
|
|
|
|
if (isHScrollBarEnabled)
|
|
{
|
|
// Get height of horizontal scrollbar
|
|
AZ::Vector2 hScrollBarSize;
|
|
EBUS_EVENT_ID_RESULT(hScrollBarSize, m_hScrollBarEntity, UiTransformBus, GetCanvasSpaceSizeNoScaleRotate);
|
|
|
|
if (IsHorizontalScrollBarOnBottom())
|
|
{
|
|
offsets.m_top = 0.0f;
|
|
offsets.m_bottom = -hScrollBarSize.GetY();
|
|
}
|
|
else
|
|
{
|
|
offsets.m_top = hScrollBarSize.GetY();
|
|
offsets.m_bottom = 0.0f;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
offsets.m_top = 0.0f;
|
|
offsets.m_bottom = 0.0f;
|
|
}
|
|
|
|
EBUS_EVENT_ID(m_vScrollBarEntity, UiTransform2dBus, SetOffsets, offsets);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiScrollBoxComponent::UpdateContentParentOffsets(bool checkScrollBarVisibility)
|
|
{
|
|
if ((m_hScrollBarEntity.IsValid() && m_isHorizontalScrollingEnabled && (m_hScrollBarVisibility == ScrollBarVisibility::AutoHideAndResizeViewport))
|
|
|| (m_vScrollBarEntity.IsValid() && m_isVerticalScrollingEnabled && (m_vScrollBarVisibility == ScrollBarVisibility::AutoHideAndResizeViewport)))
|
|
{
|
|
// Set content parent offsets based on scrollbar visibility
|
|
|
|
AZ::Entity* contentParentEntity = nullptr;
|
|
EBUS_EVENT_ID_RESULT(contentParentEntity, m_contentEntity, UiElementBus, GetParent);
|
|
if (contentParentEntity)
|
|
{
|
|
UiTransform2dInterface::Offsets offsets;
|
|
EBUS_EVENT_ID_RESULT(offsets, contentParentEntity->GetId(), UiTransform2dBus, GetOffsets);
|
|
|
|
if (m_hScrollBarEntity.IsValid() && m_isHorizontalScrollingEnabled && (m_hScrollBarVisibility == ScrollBarVisibility::AutoHideAndResizeViewport))
|
|
{
|
|
bool isHScrollBarEnabled = false;
|
|
if (checkScrollBarVisibility)
|
|
{
|
|
EBUS_EVENT_ID_RESULT(isHScrollBarEnabled, m_hScrollBarEntity, UiElementBus, IsEnabled);
|
|
}
|
|
|
|
if (isHScrollBarEnabled)
|
|
{
|
|
// Get height of horizontal scrollbar
|
|
AZ::Vector2 hScrollBarSize;
|
|
EBUS_EVENT_ID_RESULT(hScrollBarSize, m_hScrollBarEntity, UiTransformBus, GetCanvasSpaceSizeNoScaleRotate);
|
|
|
|
if (IsHorizontalScrollBarOnBottom())
|
|
{
|
|
offsets.m_top = 0.0f;
|
|
offsets.m_bottom = -hScrollBarSize.GetY();
|
|
}
|
|
else
|
|
{
|
|
offsets.m_top = hScrollBarSize.GetY();
|
|
offsets.m_bottom = 0.0f;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
offsets.m_top = 0.0f;
|
|
offsets.m_bottom = 0.0f;
|
|
}
|
|
}
|
|
|
|
if (m_vScrollBarEntity.IsValid() && m_isVerticalScrollingEnabled && (m_vScrollBarVisibility == ScrollBarVisibility::AutoHideAndResizeViewport))
|
|
{
|
|
bool isVScrollBarEnabled = false;
|
|
if (checkScrollBarVisibility)
|
|
{
|
|
EBUS_EVENT_ID_RESULT(isVScrollBarEnabled, m_vScrollBarEntity, UiElementBus, IsEnabled);
|
|
}
|
|
|
|
if (isVScrollBarEnabled)
|
|
{
|
|
// Get width of vertical scrollbar
|
|
AZ::Vector2 vScrollBarSize;
|
|
EBUS_EVENT_ID_RESULT(vScrollBarSize, m_vScrollBarEntity, UiTransformBus, GetCanvasSpaceSizeNoScaleRotate);
|
|
|
|
if (IsVerticalScrollBarOnRight())
|
|
{
|
|
offsets.m_left = 0.0f;
|
|
offsets.m_right = -vScrollBarSize.GetX();
|
|
}
|
|
else
|
|
{
|
|
offsets.m_left = vScrollBarSize.GetX();
|
|
offsets.m_right = 0.0f;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
offsets.m_left = 0.0f;
|
|
offsets.m_right = 0.0f;
|
|
}
|
|
}
|
|
|
|
EBUS_EVENT_ID(contentParentEntity->GetId(), UiTransform2dBus, SetOffsets, offsets);
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiScrollBoxComponent::ContentOrParentSizeChanged()
|
|
{
|
|
// Initialize content parent offsets if they are being controlled by scrollbar visibility behavior.
|
|
// Offsets are initialized as if scrollbars are not visible
|
|
UpdateContentParentOffsets(false);
|
|
|
|
// Set whether scrollbars are visible based on scrollbar visibility behavior, content size and the size of its parent
|
|
UpdateScrollBarVisiblity();
|
|
|
|
// Set scrollbar anchors and offsets based on scrollbar visibility behavior and whether the other scrollbar is visible
|
|
UpdateScrollBarAnchorsAndOffsets();
|
|
|
|
// Set content parent offsets based on scrollbar visibility behavior and whether scrollbars are visible
|
|
UpdateContentParentOffsets(true);
|
|
|
|
// Notify listeners of ratio change between content size and the size of its parent
|
|
AZ::Vector2 parentToContentRatio;
|
|
bool result = GetScrollableParentToContentRatio(parentToContentRatio);
|
|
if (result)
|
|
{
|
|
EBUS_EVENT_ID(GetEntityId(), UiScrollableToScrollerNotificationBus, OnScrollableParentToContentRatioChanged, parentToContentRatio);
|
|
}
|
|
|
|
if (DoSnap())
|
|
{
|
|
// Reset drag info
|
|
if (m_isDragging)
|
|
{
|
|
m_pressedScrollOffset = m_scrollOffset;
|
|
m_pressedPoint = m_lastDragPoint;
|
|
}
|
|
|
|
NotifyScrollersOnValueChanged();
|
|
|
|
DoChangedActions();
|
|
}
|
|
else
|
|
{
|
|
NotifyScrollersOnValueChanged();
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// PRIVATE STATIC MEMBER FUNCTIONS
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
bool UiScrollBoxComponent::VersionConverter(AZ::SerializeContext& context,
|
|
AZ::SerializeContext::DataElementNode& classElement)
|
|
{
|
|
// conversion from version 1 to 2:
|
|
// - Need to convert AZStd::string sprites to AzFramework::SimpleAssetReference<LmbrCentral::TextureAsset>
|
|
if (classElement.GetVersion() < 2)
|
|
{
|
|
if (!LyShine::ConvertSubElementFromAzStringToAssetRef<LmbrCentral::TextureAsset>(context, classElement, "SelectedSprite"))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!LyShine::ConvertSubElementFromAzStringToAssetRef<LmbrCentral::TextureAsset>(context, classElement, "DisabledSprite"))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Conversion from version 2 to 3:
|
|
if (classElement.GetVersion() < 3)
|
|
{
|
|
// find the base class (AZ::Component)
|
|
// NOTE: in very old versions there may not be a base class because the base class was not serialized
|
|
int componentBaseClassIndex = classElement.FindElement(AZ_CRC("BaseClass1", 0xd4925735));
|
|
|
|
// If there was a base class, make a copy and remove it
|
|
AZ::SerializeContext::DataElementNode componentBaseClassNode;
|
|
if (componentBaseClassIndex != -1)
|
|
{
|
|
// make a local copy of the component base class node
|
|
componentBaseClassNode = classElement.GetSubElement(componentBaseClassIndex);
|
|
|
|
// remove the component base class from the button
|
|
classElement.RemoveElement(componentBaseClassIndex);
|
|
}
|
|
|
|
// Add a new base class (UiInteractableComponent)
|
|
int interactableBaseClassIndex = classElement.AddElement<UiInteractableComponent>(context, "BaseClass1");
|
|
AZ::SerializeContext::DataElementNode& interactableBaseClassNode = classElement.GetSubElement(interactableBaseClassIndex);
|
|
|
|
// if there was previously a base class...
|
|
if (componentBaseClassIndex != -1)
|
|
{
|
|
// copy the component base class into the new interactable base class
|
|
// Since AZ::Component is now the base class of UiInteractableComponent
|
|
interactableBaseClassNode.AddElement(componentBaseClassNode);
|
|
}
|
|
|
|
// Move the selected/hover state to the base class
|
|
if (!UiSerialize::MoveToInteractableStateActions(context, classElement, "HoverStateActions",
|
|
"SelectedColor", "SelectedAlpha", "SelectedSprite"))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Move the disabled state to the base class
|
|
if (!UiSerialize::MoveToInteractableStateActions(context, classElement, "DisabledStateActions",
|
|
"DisabledColor", "DisabledAlpha", "DisabledSprite"))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Conversion from version 3 to 4:
|
|
// - Need to convert Vec2 to AZ::Vector2
|
|
if (classElement.GetVersion() < 4)
|
|
{
|
|
if (!LyShine::ConvertSubElementFromVec2ToVector2(context, classElement, "ScrollOffset"))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!LyShine::ConvertSubElementFromVec2ToVector2(context, classElement, "SnapGrid"))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|