/* * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or * its licensors. * * For complete copyright and license terms please see the LICENSE at the root of this * distribution (the "License"). All use of this software is governed by the License, * or, if provided, by the license below or the license accompanying this file. Do not * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * */ #include "LyShine_precompiled.h" #include "UiScrollBarComponent.h" #include "UiNavigationHelpers.h" #include #include #include #include #include #include #include #include //////////////////////////////////////////////////////////////////////////////////////////////////// //! 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) { if (m_scrollableEntity.IsValid() && UiScrollableToScrollerNotificationBus::Handler::BusIsConnected()) { UiScrollableToScrollerNotificationBus::Handler::BusDisconnect(); } m_scrollableEntity = entityId; if (entityId.IsValid()) { UiScrollableToScrollerNotificationBus::Handler::BusConnect(entityId); } } //////////////////////////////////////////////////////////////////////////////////////////////////// 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(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 result = false; 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(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(context); if (serializeContext) { serializeContext->Class() ->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("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(context); if (behaviorContext) { behaviorContext->EBus("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") ->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") ->Handler(); behaviorContext->Class() ->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()), "")); // Get a list of all child elements LyShine::EntityArray matchingElements; EBUS_EVENT_ID(GetEntityId(), UiElementBus, FindDescendantElements, [](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); }