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.
o3de/Gems/LyShine/Code/Source/UiScrollBarComponent.cpp

1295 lines
46 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 "UiScrollBarComponent.h"
#include "UiNavigationHelpers.h"
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <LyShine/Bus/UiCanvasBus.h>
#include <LyShine/Bus/UiElementBus.h>
#include <LyShine/Bus/UiTransform2dBus.h>
#include <LyShine/Bus/UiTransformBus.h>
#include <ITimer.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
//! UiScrollerNotificationBus Behavior context handler class
class BehaviorUiScrollerNotificationBusHandler
: public UiScrollerNotificationBus::Handler
, public AZ::BehaviorEBusHandler
{
public:
AZ_EBUS_BEHAVIOR_BINDER(BehaviorUiScrollerNotificationBusHandler, "{77A20EE4-EB8D-431A-B4B1-798805801C4D}", AZ::SystemAllocator,
OnScrollerValueChanging, OnScrollerValueChanged);
void OnScrollerValueChanging(float value) override
{
Call(FN_OnScrollerValueChanging, value);
}
void OnScrollerValueChanged(float value) override
{
Call(FN_OnScrollerValueChanged, value);
}
};
////////////////////////////////////////////////////////////////////////////////////////////////////
// PUBLIC MEMBER FUNCTIONS
////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////
UiScrollBarComponent::UiScrollBarComponent()
: m_value(0.0f)
, m_handleSize(0.1f)
, m_minHandlePixelSize(20.0f)
, m_orientation(Orientation::Horizontal)
, m_isDragging(false)
, m_isActive(false)
, m_lastDragPoint(0.0f, 0.0f)
, m_onValueChanged()
, m_onValueChanging()
, m_valueChangedActionName()
, m_valueChangingActionName()
, m_handleEntity()
, m_scrollableEntity()
{
}
////////////////////////////////////////////////////////////////////////////////////////////////////
UiScrollBarComponent::~UiScrollBarComponent()
{
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiScrollBarComponent::Update(float deltaTime)
{
UiInteractableComponent::Update(deltaTime);
if (m_isAutoFadeEnabled)
{
// count down secondsBeforeFade
if (!m_isFading && m_secondsRemainingBeforeFade > 0)
{
m_secondsRemainingBeforeFade -= deltaTime;
if (m_secondsRemainingBeforeFade <= 0)
{
m_isFading = true;
// if m_secondsBeforeFade is below 0, use any leftover time for fading
deltaTime = abs(m_secondsRemainingBeforeFade);
}
}
// calculate fade and set alpha for image components
if (m_isFading && m_isAutoFadeEnabled && m_currFade > 0)
{
float deltaFade = deltaTime * m_fadeSpeed;
m_currFade -= deltaFade;
if (m_currFade < 0)
{
m_currFade = 0;
}
SetImageComponentsAlpha(m_currFade);
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
float UiScrollBarComponent::GetValue()
{
return m_value;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiScrollBarComponent::SetValue(float value)
{
if (m_value != value)
{
DoSetValue(value);
// Reset drag info
if (m_isDragging)
{
ResetDragInfo();
}
DoChangedActions();
NotifyScrollableOnValueChanged();
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
float UiScrollBarComponent::GetHandleSize()
{
return m_handleSize;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiScrollBarComponent::SetHandleSize(float size)
{
m_handleSize = AZ::GetClamp(size, 0.0f, 1.0f);
// Make sure handle size is at least the minimum size
float handleParentLength = GetHandleParentLength();
float minHandleSize = (handleParentLength > 0.0f) ? m_minHandlePixelSize / handleParentLength : 0.0f;
minHandleSize = AZ::GetClamp(minHandleSize, 0.0f, 1.0f);
m_displayedHandleSize = AZ::GetClamp(m_handleSize, minHandleSize, 1.0f);
if (m_handleEntity.IsValid())
{
// Change handle's anchor
UiTransform2dInterface::Anchors anchors;
EBUS_EVENT_ID_RESULT(anchors, m_handleEntity, UiTransform2dBus, GetAnchors);
if (m_orientation == Orientation::Horizontal)
{
anchors.m_right = anchors.m_left + m_displayedHandleSize;
}
else if (m_orientation == Orientation::Vertical)
{
anchors.m_bottom = anchors.m_top + m_displayedHandleSize;
}
else
{
AZ_Assert(false, "unhandled scrollbar orientation");
}
EBUS_EVENT_ID(m_handleEntity, UiTransform2dBus, SetAnchors, anchors, false, false);
// Position handle at the correct location
DoSetValue(m_value);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
float UiScrollBarComponent::GetMinHandlePixelSize()
{
return m_minHandlePixelSize;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiScrollBarComponent::SetMinHandlePixelSize(float size)
{
m_minHandlePixelSize = size;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
bool UiScrollBarComponent::IsAutoFadeEnabled()
{
return m_isAutoFadeEnabled;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiScrollBarComponent::SetAutoFadeEnabled(bool isAutoFadeEnabled)
{
m_isAutoFadeEnabled = isAutoFadeEnabled;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
float UiScrollBarComponent::GetAutoFadeDelay()
{
return m_inactiveSecondsBeforeFade;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiScrollBarComponent::SetAutoFadeDelay(float delay)
{
m_inactiveSecondsBeforeFade = delay;
ResetFade();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
float UiScrollBarComponent::GetAutoFadeSpeed()
{
return m_fadeSpeed;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiScrollBarComponent::SetAutoFadeSpeed(float speed)
{
m_fadeSpeed = speed;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
UiScrollerInterface::Orientation UiScrollBarComponent::GetOrientation()
{
return m_orientation;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiScrollBarComponent::SetOrientation(Orientation orientation)
{
m_orientation = orientation;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
AZ::EntityId UiScrollBarComponent::GetHandleEntity()
{
return m_handleEntity;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiScrollBarComponent::SetHandleEntity(AZ::EntityId entityId)
{
m_handleEntity = entityId;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
AZ::EntityId UiScrollBarComponent::GetScrollableEntity()
{
return m_scrollableEntity;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiScrollBarComponent::SetScrollableEntity(AZ::EntityId entityId)
{
m_scrollableEntity = entityId;
if (m_scrollableEntity.IsValid())
{
UiScrollableToScrollerNotificationBus::Handler::BusConnect(m_scrollableEntity);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
UiScrollBarComponent::ValueChangeCallback UiScrollBarComponent::GetValueChangingCallback()
{
return m_onValueChanging;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiScrollBarComponent::SetValueChangingCallback(ValueChangeCallback onChange)
{
m_onValueChanging = onChange;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
const LyShine::ActionName& UiScrollBarComponent::GetValueChangingActionName()
{
return m_valueChangingActionName;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiScrollBarComponent::SetValueChangingActionName(const LyShine::ActionName& actionName)
{
m_valueChangingActionName = actionName;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
UiScrollBarComponent::ValueChangeCallback UiScrollBarComponent::GetValueChangedCallback()
{
return m_onValueChanged;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiScrollBarComponent::SetValueChangedCallback(ValueChangeCallback onChange)
{
m_onValueChanged = onChange;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
const LyShine::ActionName& UiScrollBarComponent::GetValueChangedActionName()
{
return m_valueChangedActionName;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiScrollBarComponent::SetValueChangedActionName(const LyShine::ActionName& actionName)
{
m_valueChangedActionName = actionName;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiScrollBarComponent::OnValueChangingByScrollable(AZ::Vector2 value)
{
float axisValue = 0.0f;
if (m_orientation == Orientation::Horizontal)
{
axisValue = value.GetX();
}
else if (m_orientation == Orientation::Vertical)
{
axisValue = value.GetY();
}
else
{
AZ_Assert(false, "unhandled scrollbar orientation");
}
if (m_value != axisValue)
{
DoSetValue(axisValue);
// Reset drag info
if (m_isDragging)
{
ResetDragInfo();
}
DoChangingActions();
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiScrollBarComponent::OnValueChangedByScrollable(AZ::Vector2 value)
{
float axisValue = 0.0f;
if (m_orientation == Orientation::Horizontal)
{
axisValue = value.GetX();
}
else if (m_orientation == Orientation::Vertical)
{
axisValue = value.GetY();
}
else
{
AZ_Assert(false, "unhandled scrollbar orientation");
}
if (m_value != axisValue)
{
DoSetValue(axisValue);
// Reset drag info
if (m_isDragging)
{
ResetDragInfo();
}
DoChangedActions();
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiScrollBarComponent::OnScrollableParentToContentRatioChanged(AZ::Vector2 parentToContentRatio)
{
float axisParentToContentRatio = 1.0f;
if (m_orientation == Orientation::Horizontal)
{
axisParentToContentRatio = parentToContentRatio.GetX();
}
else if (m_orientation == Orientation::Vertical)
{
axisParentToContentRatio = parentToContentRatio.GetY();
}
else
{
AZ_Assert(false, "unhandled scrollbar orientation");
}
SetHandleSize(axisParentToContentRatio);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiScrollBarComponent::InGamePostActivate()
{
SetHandleSize(m_handleSize);
ResetFade();
UiImageBus::EventResult(m_initialScrollBarAlpha, GetEntityId(), &UiImageBus::Events::GetAlpha);
UiImageBus::EventResult(m_initialHandleAlpha, m_handleEntity, &UiImageBus::Events::GetAlpha);
// Listen for canvas space rect changes
UiTransformChangeNotificationBus::Handler::BusConnect(GetEntityId());
}
////////////////////////////////////////////////////////////////////////////////////////////////////
bool UiScrollBarComponent::HandlePressed(AZ::Vector2 point, bool& shouldStayActive)
{
bool handled = UiInteractableComponent::HandlePressed(point, shouldStayActive);
if (handled)
{
m_isDragging = false;
m_pressedOnHandle = false;
AZ::Entity* handleParentEntity = nullptr;
EBUS_EVENT_ID_RESULT(handleParentEntity, m_handleEntity, UiElementBus, GetParent);
if (handleParentEntity)
{
// Check where point is relative to handle
LocRelativeToHandle pointLoc = GetLocationRelativeToHandle(point);
m_pressedOnHandle = (pointLoc == LocRelativeToHandle::OnHandle);
if (m_pressedOnHandle)
{
// Store value when press occurred
m_pressedValue = m_value;
// Store pressed position
m_pressedPosAlongAxis = GetPosAlongAxis(point);
}
else
{
// Move handle
m_lastMoveTime = gEnv->pTimer->GetCurrTime(ITimer::ETIMER_UI);
m_moveDelayTime = 0.45f;
MoveHandle(pointLoc);
}
}
}
return handled;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
bool UiScrollBarComponent::HandleReleased([[maybe_unused]] AZ::Vector2 point)
{
if (m_isPressed && m_isHandlingEvents)
{
UiInteractableComponent::TriggerReleasedAction();
DoChangedActions();
NotifyScrollableOnValueChanged();
}
m_isPressed = false;
m_isDragging = false;
m_pressedPoint = AZ::Vector2(0.0f, 0.0f);
return m_isHandlingEvents;
}
/////////////////////////////////////////////////////////////////
bool UiScrollBarComponent::HandleEnterPressed(bool& shouldStayActive)
{
bool handled = UiInteractableComponent::HandleEnterPressed(shouldStayActive);
if (handled)
{
// the scrollbar will stay active after released
shouldStayActive = true;
m_isActive = true;
}
return handled;
}
/////////////////////////////////////////////////////////////////
bool UiScrollBarComponent::HandleAutoActivation()
{
if (!m_isHandlingEvents)
{
return false;
}
m_isActive = true;
return true;
}
/////////////////////////////////////////////////////////////////
bool UiScrollBarComponent::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;
bool moved = false;
const UiNavigationHelpers::Command command = UiNavigationHelpers::MapInputChannelIdToUiNavigationCommand(inputSnapshot.m_channelId, activeModifierKeys);
if (command == UiNavigationHelpers::Command::Up ||
command == UiNavigationHelpers::Command::Down ||
command == UiNavigationHelpers::Command::Left ||
command == UiNavigationHelpers::Command::Right)
{
if ((m_orientation == Orientation::Horizontal) && (command == UiNavigationHelpers::Command::Left || command == UiNavigationHelpers::Command::Right))
{
moved = MoveHandle(command == UiNavigationHelpers::Command::Left ? LocRelativeToHandle::BeforeHandle : LocRelativeToHandle::AfterHandle);
result = true;
}
else if ((m_orientation == Orientation::Vertical) && (command == UiNavigationHelpers::Command::Up || command == UiNavigationHelpers::Command::Down))
{
moved = MoveHandle(command == UiNavigationHelpers::Command::Up ? LocRelativeToHandle::BeforeHandle : LocRelativeToHandle::AfterHandle);
result = true;
}
}
if (moved)
{
DoChangedActions();
NotifyScrollableOnValueChanged();
}
return result;
}
/////////////////////////////////////////////////////////////////
void UiScrollBarComponent::InputPositionUpdate(AZ::Vector2 point)
{
if (m_isPressed)
{
if (m_pressedOnHandle)
{
// if we are not yet in the dragging state do some tests to see if we should be
if (!m_isDragging)
{
bool handOffDone = false;
bool dragDetected = CheckForDragOrHandOffToParent(GetEntityId(), m_pressedPoint, point, 0.0f, handOffDone);
if (dragDetected)
{
if (handOffDone)
{
// the drag was handed off to a parent, this scrollbar is no longer active
m_isPressed = false;
}
else
{
// the drag was valid for this scrollbar, we are now dragging
m_isDragging = true;
m_pressedValue = m_value;
m_pressedPoint = point;
m_pressedPosAlongAxis = GetPosAlongAxis(m_pressedPoint);
}
}
}
// if we are now in the dragging state do the drag of the scrollbar handle
if (m_isDragging)
{
float newValue = m_value;
// Check handle size to see if there is space to scroll
if (m_displayedHandleSize < 1.0f)
{
float handleParentLength = GetHandleParentLength();
if (handleParentLength > 0.0f)
{
float newPosAlongAxis = GetPosAlongAxis(point);
// Calculate drag distance relative to max distance
float dragDistAlongAxis = newPosAlongAxis - m_pressedPosAlongAxis;
float maxDragDistAlongAxis = handleParentLength - (m_displayedHandleSize * handleParentLength);
float valueOffset = dragDistAlongAxis / maxDragDistAlongAxis;
// Update value
newValue = AZ::GetClamp(m_pressedValue + valueOffset, 0.0f, 1.0f);
}
}
m_lastDragPoint = point;
if (newValue != m_value)
{
DoSetValue(newValue);
DoChangingActions();
NotifyScrollableOnValueChanging();
}
}
}
else
{
if (m_handleEntity.IsValid())
{
// Only do something if we're over the interactable
bool isPointInRect = false;
EBUS_EVENT_ID_RESULT(isPointInRect, GetEntityId(), UiTransformBus, IsPointInRect, point);
if (isPointInRect)
{
// Only do something if we're on either side of the handle
LocRelativeToHandle pointLoc = GetLocationRelativeToHandle(point);
if (pointLoc != LocRelativeToHandle::OnHandle)
{
const float currentTime = gEnv->pTimer->GetCurrTime(ITimer::ETIMER_UI);
if (currentTime - m_lastMoveTime > m_moveDelayTime)
{
m_lastMoveTime = currentTime;
m_moveDelayTime = 0.05f;
MoveHandle(pointLoc);
}
}
}
}
}
}
}
/////////////////////////////////////////////////////////////////
bool UiScrollBarComponent::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 UiScrollBarComponent::OfferDragHandOff(AZ::EntityId currentActiveInteractable, AZ::Vector2 startPoint, AZ::Vector2 currentPoint, float dragThreshold)
{
bool handedOffToParent = false;
bool dragDetected = CheckForDragOrHandOffToParent(currentActiveInteractable, startPoint, currentPoint, dragThreshold, handedOffToParent);
if (dragDetected)
{
if (!handedOffToParent)
{
// a drag was detected and it was not handed off to a parent, so this scrollbar is now taking the handoff
m_isPressed = true;
m_pressedValue = m_value;
m_pressedPoint = startPoint;
m_pressedPosAlongAxis = GetPosAlongAxis(m_pressedPoint);
m_isDragging = true;
m_lastDragPoint = m_pressedPoint;
// tell the canvas that this is now the active interactable
EBUS_EVENT_ID(currentActiveInteractable, UiInteractableActiveNotificationBus, ActiveChanged, GetEntityId(), false);
}
}
return dragDetected;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiScrollBarComponent::LostActiveStatus()
{
UiInteractableComponent::LostActiveStatus();
if (m_isDragging)
{
if (m_isHandlingEvents)
{
DoChangedActions();
NotifyScrollableOnValueChanged();
}
m_isDragging = false;
}
m_isActive = false;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiScrollBarComponent::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)
{
SetHandleSize(m_handleSize);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// PROTECTED MEMBER FUNCTIONS
////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiScrollBarComponent::Activate()
{
UiInteractableComponent::Activate();
UiScrollBarBus::Handler::BusConnect(GetEntityId());
UiScrollerBus::Handler::BusConnect(GetEntityId());
UiInitializationBus::Handler::BusConnect(GetEntityId());
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiScrollBarComponent::Deactivate()
{
UiInteractableComponent::Deactivate();
UiScrollBarBus::Handler::BusDisconnect(GetEntityId());
UiScrollerBus::Handler::BusDisconnect(GetEntityId());
UiInitializationBus::Handler::BusDisconnect(GetEntityId());
UiTransformChangeNotificationBus::Handler::BusDisconnect();
if (m_scrollableEntity.IsValid())
{
UiScrollableToScrollerNotificationBus::Handler::BusDisconnect(m_scrollableEntity);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
bool UiScrollBarComponent::IsAutoActivationSupported()
{
return true;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
UiInteractableStatesInterface::State UiScrollBarComponent::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 UiScrollBarComponent::Reflect(AZ::ReflectContext* context)
{
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<UiScrollBarComponent, UiInteractableComponent>()
->Version(1)
// Elements group
->Field("HandleEntity", &UiScrollBarComponent::m_handleEntity)
// Values group
->Field("Orientation", &UiScrollBarComponent::m_orientation)
->Field("Value", &UiScrollBarComponent::m_value)
->Field("HandleSize", &UiScrollBarComponent::m_handleSize)
->Field("MinHandlePixelSize", &UiScrollBarComponent::m_minHandlePixelSize)
// Actions group
->Field("ValueChangingActionName", &UiScrollBarComponent::m_valueChangingActionName)
->Field("ValueChangedActionName", &UiScrollBarComponent::m_valueChangedActionName)
// Visibility group
->Field("IsAutoFadeEnabled", &UiScrollBarComponent::m_isAutoFadeEnabled)
->Field("FadeDelay", &UiScrollBarComponent::m_inactiveSecondsBeforeFade)
->Field("FadeSpeed", &UiScrollBarComponent::m_fadeSpeed);
AZ::EditContext* ec = serializeContext->GetEditContext();
if (ec)
{
auto editInfo = ec->Class<UiScrollBarComponent>("ScrollBar", "An interactable component for scrolling content that is larger than its viewing area.");
editInfo->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Category, "UI")
->Attribute(AZ::Edit::Attributes::Icon, "Editor/Icons/Components/UiScrollBar.png")
->Attribute(AZ::Edit::Attributes::ViewportIcon, "Editor/Icons/Components/Viewport/UiScrollBar.png")
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("UI", 0x27ff46b0))
->Attribute(AZ::Edit::Attributes::AutoExpand, true);
// Elements group
{
editInfo->ClassElement(AZ::Edit::ClassElements::Group, "Elements")
->Attribute(AZ::Edit::Attributes::AutoExpand, true);
editInfo->DataElement(AZ::Edit::UIHandlers::ComboBox, &UiScrollBarComponent::m_handleEntity, "Handle", "The child element that is the sliding handle.")
->Attribute(AZ::Edit::Attributes::EnumValues, &UiScrollBarComponent::PopulateChildEntityList);
}
// Values group
{
editInfo->ClassElement(AZ::Edit::ClassElements::Group, "Values")
->Attribute(AZ::Edit::Attributes::AutoExpand, true);
editInfo->DataElement(AZ::Edit::UIHandlers::ComboBox, &UiScrollBarComponent::m_orientation, "Orientation", "The way the scrollbar should be oriented.")
->EnumAttribute(Orientation::Horizontal, "Horizontal")
->EnumAttribute(Orientation::Vertical, "Vertical");
editInfo->DataElement(0, &UiScrollBarComponent::m_value, "Value", "The initial value of the scrollbar.")
->Attribute(AZ::Edit::Attributes::Step, 0.1f)
->Attribute(AZ::Edit::Attributes::Min, 0.0f)
->Attribute(AZ::Edit::Attributes::Max, 1.0f);
editInfo->DataElement(0, &UiScrollBarComponent::m_handleSize, "Handle size", "The size of the handle relative to the scrollbar.")
->Attribute(AZ::Edit::Attributes::Step, 0.1f)
->Attribute(AZ::Edit::Attributes::Min, 0.0f)
->Attribute(AZ::Edit::Attributes::Max, 1.0f);
editInfo->DataElement(0, &UiScrollBarComponent::m_minHandlePixelSize, "Min handle size", "The minimum size of the handle in pixels.")
->Attribute(AZ::Edit::Attributes::Min, 0.0f);
}
// Actions group
{
editInfo->ClassElement(AZ::Edit::ClassElements::Group, "Actions")
->Attribute(AZ::Edit::Attributes::AutoExpand, true);
editInfo->DataElement(0, &UiScrollBarComponent::m_valueChangingActionName, "Change", "The action triggered while the value is changing.");
editInfo->DataElement(0, &UiScrollBarComponent::m_valueChangedActionName, "End change", "The action triggered when the value is done changing.");
}
// Visibility group
{
editInfo->ClassElement(AZ::Edit::ClassElements::Group, "Fade")
->Attribute(AZ::Edit::Attributes::AutoExpand, true);
editInfo->DataElement(0, &UiScrollBarComponent::m_isAutoFadeEnabled, "Auto Fade When Not In Use", "The scrollbar will automatically fade away when not in use.")
->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ_CRC("RefreshEntireTree", 0xefbc823c));
editInfo->DataElement(0, &UiScrollBarComponent::m_inactiveSecondsBeforeFade, "Fade Delay", "The delay in seconds before the scrollbar will begin to fade.")
->Attribute(AZ::Edit::Attributes::Visibility, &UiScrollBarComponent::m_isAutoFadeEnabled);
editInfo->DataElement(0, &UiScrollBarComponent::m_fadeSpeed, "Fade Speed", "The speed in seconds at which the scrollbar will fade away.")
->Attribute(AZ::Edit::Attributes::Visibility, &UiScrollBarComponent::m_isAutoFadeEnabled);
}
}
}
AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context);
if (behaviorContext)
{
behaviorContext->EBus<UiScrollBarBus>("UiScrollBarBus")
->Event("GetHandleSize", &UiScrollBarBus::Events::GetHandleSize)
->Event("SetHandleSize", &UiScrollBarBus::Events::SetHandleSize)
->Event("GetMinHandlePixelSize", &UiScrollBarBus::Events::GetMinHandlePixelSize)
->Event("SetMinHandlePixelSize", &UiScrollBarBus::Events::SetMinHandlePixelSize)
->Event("GetHandleEntity", &UiScrollBarBus::Events::GetHandleEntity)
->Event("SetHandleEntity", &UiScrollBarBus::Events::SetHandleEntity)
->VirtualProperty("HandleSize", "GetHandleSize", "SetHandleSize")
->VirtualProperty("MinHandlePixelSize", "GetMinHandlePixelSize", "SetMinHandlePixelSize")
->Event("IsAutoFadeEnabled", &UiScrollBarBus::Events::IsAutoFadeEnabled)
->Event("SetAutoFadeEnabled", &UiScrollBarBus::Events::SetAutoFadeEnabled)
->Event("GetAutoFadeDelay", &UiScrollBarBus::Events::GetAutoFadeDelay)
->Event("SetAutoFadeDelay", &UiScrollBarBus::Events::SetAutoFadeDelay)
->Event("GetAutoFadeSpeed", &UiScrollBarBus::Events::GetAutoFadeSpeed)
->Event("SetAutoFadeSpeed", &UiScrollBarBus::Events::SetAutoFadeSpeed)
->VirtualProperty("AutoFadeEnabled", "IsAutoFadeEnabled", "SetAutoFadeEnabled")
->VirtualProperty("AutoFadeDelay", "GetAutoFadeDelay", "SetAutoFadeDelay")
->VirtualProperty("AutoFadeSpeed", "GetAutoFadeSpeed", "SetAutoFadeSpeed");
behaviorContext->Enum<(int)UiScrollerInterface::Orientation::Horizontal>("eUiScrollerOrientation_Horizontal")
->Enum<(int)UiScrollerInterface::Orientation::Vertical>("eUiScrollerOrientation_Vertical");
behaviorContext->EBus<UiScrollerBus>("UiScrollerBus")
->Event("GetValue", &UiScrollerBus::Events::GetValue)
->Event("SetValue", &UiScrollerBus::Events::SetValue)
->Event("GetOrientation", &UiScrollerBus::Events::GetOrientation)
->Event("SetOrientation", &UiScrollerBus::Events::SetOrientation)
->Event("GetValueChangingActionName", &UiScrollerBus::Events::GetValueChangingActionName)
->Event("SetValueChangingActionName", &UiScrollerBus::Events::SetValueChangingActionName)
->Event("GetValueChangedActionName", &UiScrollerBus::Events::GetValueChangedActionName)
->Event("SetValueChangedActionName", &UiScrollerBus::Events::SetValueChangedActionName)
->VirtualProperty("Value", "GetValue", "SetValue");
behaviorContext->EBus<UiScrollerNotificationBus>("UiScrollerNotificationBus")
->Handler<BehaviorUiScrollerNotificationBusHandler>();
behaviorContext->Class<UiScrollBarComponent>()
->RequestBus("UiScrollBarBus")
->RequestBus("UiScrollerBus");
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// PRIVATE MEMBER FUNCTIONS
////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////
UiScrollBarComponent::EntityComboBoxVec UiScrollBarComponent::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;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// calculate how much we have dragged along the axis of the scrollbar
float UiScrollBarComponent::GetValidDragDistanceInPixels(AZ::Vector2 startPoint, AZ::Vector2 endPoint)
{
const float validDragRatio = 0.5f;
// convert the drag vector to local space
AZ::Matrix4x4 transformFromViewport;
EBUS_EVENT_ID(m_entity->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_orientation == Orientation::Horizontal)
{
localDragVec.SetY(0.0f);
}
else if (m_orientation == Orientation::Vertical)
{
localDragVec.SetX(0.0f);
}
// convert back to viewport space
AZ::Matrix4x4 transformToViewport;
EBUS_EVENT_ID(m_entity->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;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
bool UiScrollBarComponent::CheckForDragOrHandOffToParent(AZ::EntityId currentActiveInteractable, AZ::Vector2 startPoint, AZ::Vector2 currentPoint, float childDragThreshold, bool& handOffDone)
{
bool result = false;
AZ::EntityId parentDraggable;
EBUS_EVENT_ID_RESULT(parentDraggable, GetEntityId(), UiElementBus, FindParentInteractableSupportingDrag, startPoint);
// 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 (childDragThreshold > 0.0f)
{
dragThreshold = childDragThreshold;
}
else if (parentDraggable.IsValid())
{
dragThreshold = containedDragThreshold;
}
// calculate how much we have dragged along the axis of the scrollbar
float validDragDistance = GetValidDragDistanceInPixels(startPoint, currentPoint);
if (validDragDistance > dragThreshold)
{
// we dragged above the threshold value along axis of scrollbar
result = true;
}
else if (parentDraggable.IsValid())
{
// offer the parent draggable the chance to become the active interactable
EBUS_EVENT_ID_RESULT(handOffDone, parentDraggable, UiInteractableBus,
OfferDragHandOff, currentActiveInteractable, startPoint, currentPoint, containedDragThreshold);
if (handOffDone)
{
// interaction has been handed off to a container entity
result = true;
}
}
return result;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiScrollBarComponent::DoSetValue(float value)
{
m_value = AZ::GetClamp(value, 0.0f, 1.0f);
if (m_handleEntity.IsValid())
{
// Move handle's anchors
UiTransform2dInterface::Anchors anchors;
EBUS_EVENT_ID_RESULT(anchors, m_handleEntity, UiTransform2dBus, GetAnchors);
if (m_orientation == Orientation::Horizontal)
{
anchors.m_left = (1.0f - m_displayedHandleSize) * m_value;
anchors.m_right = anchors.m_left + m_displayedHandleSize;
}
else if (m_orientation == Orientation::Vertical)
{
anchors.m_top = (1.0f - m_displayedHandleSize) * m_value;
anchors.m_bottom = anchors.m_top + m_displayedHandleSize;
}
else
{
AZ_Assert(false, "unhandled scrollbar orientation");
}
EBUS_EVENT_ID(m_handleEntity, UiTransform2dBus, SetAnchors, anchors, false, false);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiScrollBarComponent::DoChangedActions()
{
if (m_onValueChanged)
{
m_onValueChanged(GetEntityId(), m_value);
}
// Tell any action listeners about the event
if (!m_valueChangedActionName.empty())
{
AZ::EntityId canvasEntityId;
EBUS_EVENT_ID_RESULT(canvasEntityId, GetEntityId(), UiElementBus, GetCanvasEntityId);
EBUS_EVENT_ID(canvasEntityId, UiCanvasNotificationBus, OnAction, GetEntityId(), m_valueChangedActionName);
}
NotifyListenersOnValueChanged();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiScrollBarComponent::DoChangingActions()
{
if (m_onValueChanging)
{
m_onValueChanging(GetEntityId(), m_value);
}
// Tell any action listeners about the event
if (!m_valueChangingActionName.empty())
{
AZ::EntityId canvasEntityId;
EBUS_EVENT_ID_RESULT(canvasEntityId, GetEntityId(), UiElementBus, GetCanvasEntityId);
EBUS_EVENT_ID(canvasEntityId, UiCanvasNotificationBus, OnAction, GetEntityId(), m_valueChangingActionName);
}
ResetFade();
NotifyListenersOnValueChanging();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiScrollBarComponent::NotifyListenersOnValueChanged()
{
EBUS_EVENT_ID(GetEntityId(), UiScrollerNotificationBus, OnScrollerValueChanged, m_value);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiScrollBarComponent::NotifyListenersOnValueChanging()
{
EBUS_EVENT_ID(GetEntityId(), UiScrollerNotificationBus, OnScrollerValueChanging, m_value);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiScrollBarComponent::NotifyScrollableOnValueChanged()
{
EBUS_EVENT_ID(GetEntityId(), UiScrollerToScrollableNotificationBus, OnValueChangedByScroller, m_value);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiScrollBarComponent::NotifyScrollableOnValueChanging()
{
EBUS_EVENT_ID(GetEntityId(), UiScrollerToScrollableNotificationBus, OnValueChangingByScroller, m_value);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
UiScrollBarComponent::LocRelativeToHandle UiScrollBarComponent::GetLocationRelativeToHandle(const AZ::Vector2& point)
{
// get point in the no scale/rotate canvas space
AZ::Matrix4x4 transform;
EBUS_EVENT_ID(m_handleEntity, UiTransformBus, GetTransformFromViewport, transform);
AZ::Vector3 point3(point.GetX(), point.GetY(), 0.0f);
point3 = transform * point3;
// get the rect for this element in the same space
UiTransformInterface::Rect rect;
EBUS_EVENT_ID(m_handleEntity, UiTransformBus, GetCanvasSpaceRectNoScaleRotate, rect);
LocRelativeToHandle pointLoc;
if (m_orientation == Orientation::Horizontal)
{
float left = rect.left;
float right = rect.right;
// allow for "flipped" rects
if (left > right)
{
std::swap(left, right);
}
if (point3.GetX() >= left && point3.GetX() <= right)
{
pointLoc = LocRelativeToHandle::OnHandle;
}
else if (point3.GetX() < left)
{
pointLoc = LocRelativeToHandle::BeforeHandle;
}
else // (point3.GetX() > right)
{
pointLoc = LocRelativeToHandle::AfterHandle;
}
}
else if (m_orientation == Orientation::Vertical)
{
float top = rect.top;
float bottom = rect.bottom;
// allow for "flipped" rects
if (top > bottom)
{
std::swap(top, bottom);
}
if (point3.GetY() >= top && point3.GetY() <= bottom)
{
pointLoc = LocRelativeToHandle::OnHandle;
}
else if (point3.GetY() < top)
{
pointLoc = LocRelativeToHandle::BeforeHandle;
}
else // (point3.GetY() > bottom)
{
pointLoc = LocRelativeToHandle::AfterHandle;
}
}
else
{
AZ_Assert(false, "unhandled scrollbar orientation");
pointLoc = LocRelativeToHandle::OnHandle;
}
return pointLoc;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
float UiScrollBarComponent::GetHandleParentLength()
{
float handleParentLength = 0.0f;
AZ::Entity* handleParentEntity = nullptr;
EBUS_EVENT_ID_RESULT(handleParentEntity, m_handleEntity, UiElementBus, GetParent);
if (handleParentEntity)
{
AZ::Vector2 size;
EBUS_EVENT_ID_RESULT(size, handleParentEntity->GetId(), UiTransformBus, GetCanvasSpaceSizeNoScaleRotate);
if (m_orientation == Orientation::Horizontal)
{
handleParentLength = size.GetX();
}
else if (m_orientation == Orientation::Vertical)
{
handleParentLength = size.GetY();
}
else
{
AZ_Assert(false, "unhandled scrollbar orientation");
}
}
return handleParentLength;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
float UiScrollBarComponent::GetPosAlongAxis(AZ::Vector2 point)
{
float posAlongAxis = 0.0f;
AZ::Entity* handleParentEntity = nullptr;
EBUS_EVENT_ID_RESULT(handleParentEntity, m_handleEntity, UiElementBus, GetParent);
if (handleParentEntity)
{
AZ::Matrix4x4 transform;
EBUS_EVENT_ID(handleParentEntity->GetId(), UiTransformBus, GetTransformFromViewport, transform);
AZ::Vector3 point3(point.GetX(), point.GetY(), 0.0f);
point3 = transform * point3;
if (m_orientation == Orientation::Horizontal)
{
posAlongAxis = point3.GetX();
}
else if (m_orientation == Orientation::Vertical)
{
posAlongAxis = point3.GetY();
}
else
{
AZ_Assert(false, "unhandled scrollbar orientation");
}
}
return posAlongAxis;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
bool UiScrollBarComponent::MoveHandle(LocRelativeToHandle pointLoc)
{
float valueStep = (m_displayedHandleSize < 1.0f) ? m_displayedHandleSize / (1.0f - m_displayedHandleSize) : 0.0f;
float newValue = m_value;
if (pointLoc == LocRelativeToHandle::BeforeHandle)
{
newValue -= valueStep;
}
else if (pointLoc == LocRelativeToHandle::AfterHandle)
{
newValue += valueStep;
}
newValue = AZ::GetClamp(newValue, 0.0f, 1.0f);
if (newValue != m_value)
{
DoSetValue(newValue);
DoChangingActions();
NotifyScrollableOnValueChanging();
return true;
}
return false;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiScrollBarComponent::ResetDragInfo()
{
m_pressedValue = m_value;
m_pressedPoint = m_lastDragPoint;
m_pressedPosAlongAxis = GetPosAlongAxis(m_pressedPoint);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiScrollBarComponent::SetImageComponentsAlpha(float fade)
{
UiImageBus::Event(GetEntityId(), &UiImageBus::Events::SetAlpha, m_initialScrollBarAlpha * fade);
UiImageBus::Event(m_handleEntity, &UiImageBus::Events::SetAlpha, m_initialHandleAlpha * fade);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiScrollBarComponent::ResetFade()
{
m_currFade = 1;
m_secondsRemainingBeforeFade = m_inactiveSecondsBeforeFade;
m_isFading = false;
SetImageComponentsAlpha(m_currFade);
}