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/UiSliderComponent.cpp

889 lines
32 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 "UiSliderComponent.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 <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"
#include "UiNavigationHelpers.h"
////////////////////////////////////////////////////////////////////////////////////////////////////
//! UiSliderNotificationBus Behavior context handler class
class UiSliderNotificationBusBehaviorHandler
: public UiSliderNotificationBus::Handler
, public AZ::BehaviorEBusHandler
{
public:
AZ_EBUS_BEHAVIOR_BINDER(UiSliderNotificationBusBehaviorHandler, "{13540E5E-5987-4BD9-AC7A-F771F8AD0206}", AZ::SystemAllocator,
OnSliderValueChanging, OnSliderValueChanged);
void OnSliderValueChanging(float value) override
{
Call(FN_OnSliderValueChanging, value);
}
void OnSliderValueChanged(float value) override
{
Call(FN_OnSliderValueChanged, value);
}
};
////////////////////////////////////////////////////////////////////////////////////////////////////
// PUBLIC MEMBER FUNCTIONS
////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////
UiSliderComponent::UiSliderComponent()
: m_value(0.0f)
, m_minValue(0.0f)
, m_maxValue(100.0f)
, m_stepValue(0.0f)
, m_isDragging(false)
, m_isActive(false)
, m_onValueChanged()
, m_onValueChanging()
, m_valueChangedActionName()
, m_valueChangingActionName()
, m_trackEntity()
, m_fillEntity()
, m_manipulatorEntity()
{
}
////////////////////////////////////////////////////////////////////////////////////////////////////
UiSliderComponent::~UiSliderComponent()
{
}
////////////////////////////////////////////////////////////////////////////////////////////////////
float UiSliderComponent::GetValue()
{
return m_value;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiSliderComponent::SetValue(float value)
{
if (m_minValue < m_maxValue)
{
m_value = AZ::GetClamp(value, m_minValue, m_maxValue);
}
else // ( m_minValue >= m_maxValue )
{
m_value = AZ::GetClamp(value, m_maxValue, m_minValue);
}
if (m_stepValue)
{
m_value += (m_stepValue / 2.0f); ///< Add half the step size to the value before the fmodf so that we round to nearest step rather than flooring to previous step
m_value = m_value - fmodf(m_value, m_stepValue);
}
float valueRange = fabsf(m_maxValue - m_minValue);
float unitValue = valueRange > 0.0f ? (fabsf(m_value - m_minValue) / valueRange) : 0.0f;
if (m_fillEntity.IsValid())
{
// Offsets.
{
UiTransform2dInterface::Offsets offsets;
EBUS_EVENT_ID_RESULT(offsets, m_fillEntity, UiTransform2dBus, GetOffsets);
offsets.m_left = offsets.m_right = 0.0f;
EBUS_EVENT_ID(m_fillEntity, UiTransform2dBus, SetOffsets, offsets);
}
// Anchors.
{
UiTransform2dInterface::Anchors anchors;
EBUS_EVENT_ID_RESULT(anchors, m_fillEntity, UiTransform2dBus, GetAnchors);
anchors.m_left = 0.0f;
anchors.m_right = unitValue;
EBUS_EVENT_ID(m_fillEntity, UiTransform2dBus, SetAnchors, anchors, false, true);
}
}
if (m_manipulatorEntity.IsValid())
{
// Anchors.
{
UiTransform2dInterface::Anchors anchors;
EBUS_EVENT_ID_RESULT(anchors, m_manipulatorEntity, UiTransform2dBus, GetAnchors);
anchors.m_left = anchors.m_right = unitValue;
EBUS_EVENT_ID(m_manipulatorEntity, UiTransform2dBus, SetAnchors, anchors, false, true);
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
float UiSliderComponent::GetMinValue()
{
return m_minValue;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiSliderComponent::SetMinValue(float value)
{
m_minValue = value;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
float UiSliderComponent::GetMaxValue()
{
return m_maxValue;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiSliderComponent::SetMaxValue(float value)
{
m_maxValue = value;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
float UiSliderComponent::GetStepValue()
{
return m_stepValue;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiSliderComponent::SetStepValue(float step)
{
m_stepValue = step;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
UiSliderComponent::ValueChangeCallback UiSliderComponent::GetValueChangingCallback()
{
return m_onValueChanging;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiSliderComponent::SetValueChangingCallback(ValueChangeCallback onChange)
{
m_onValueChanging = onChange;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
const LyShine::ActionName& UiSliderComponent::GetValueChangingActionName()
{
return m_valueChangingActionName;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiSliderComponent::SetValueChangingActionName(const LyShine::ActionName& actionName)
{
m_valueChangingActionName = actionName;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
UiSliderComponent::ValueChangeCallback UiSliderComponent::GetValueChangedCallback()
{
return m_onValueChanged;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiSliderComponent::SetValueChangedCallback(ValueChangeCallback onChange)
{
m_onValueChanged = onChange;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
const LyShine::ActionName& UiSliderComponent::GetValueChangedActionName()
{
return m_valueChangedActionName;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiSliderComponent::SetValueChangedActionName(const LyShine::ActionName& actionName)
{
m_valueChangedActionName = actionName;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiSliderComponent::SetTrackEntity(AZ::EntityId entityId)
{
m_trackEntity = entityId;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
AZ::EntityId UiSliderComponent::GetTrackEntity()
{
return m_trackEntity;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiSliderComponent::SetFillEntity(AZ::EntityId entityId)
{
m_fillEntity = entityId;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
AZ::EntityId UiSliderComponent::GetFillEntity()
{
return m_fillEntity;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiSliderComponent::SetManipulatorEntity(AZ::EntityId entityId)
{
m_manipulatorEntity = entityId;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
AZ::EntityId UiSliderComponent::GetManipulatorEntity()
{
return m_manipulatorEntity;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiSliderComponent::InGamePostActivate()
{
SetValue(m_value);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
bool UiSliderComponent::HandlePressed(AZ::Vector2 point, bool& shouldStayActive)
{
bool handled = UiInteractableComponent::HandlePressed(point, shouldStayActive);
if (handled)
{
m_isDragging = false;
}
return handled;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
bool UiSliderComponent::HandleReleased(AZ::Vector2 point)
{
if (m_isPressed && m_isHandlingEvents)
{
float value = GetValueFromPoint(point);
SetValue(value);
UiInteractableComponent::TriggerReleasedAction();
DoChangedActions();
}
m_isPressed = false;
m_isDragging = false;
m_pressedPoint = AZ::Vector2(0.0f, 0.0f);
return m_isHandlingEvents;
}
/////////////////////////////////////////////////////////////////
bool UiSliderComponent::HandleEnterPressed(bool& shouldStayActive)
{
bool handled = UiInteractableComponent::HandleEnterPressed(shouldStayActive);
if (handled)
{
// the slider will stay active after released
shouldStayActive = true;
m_isActive = true;
}
return handled;
}
/////////////////////////////////////////////////////////////////
bool UiSliderComponent::HandleAutoActivation()
{
if (!m_isHandlingEvents)
{
return false;
}
m_isActive = true;
return true;
}
/////////////////////////////////////////////////////////////////
bool UiSliderComponent::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 (command == UiNavigationHelpers::Command::Up ||
command == UiNavigationHelpers::Command::Down ||
command == UiNavigationHelpers::Command::Left ||
command == UiNavigationHelpers::Command::Right)
{
const float keySteps = 10.0f;
float delta = (m_stepValue != 0.0f) ? m_stepValue : (m_maxValue - m_minValue) / keySteps;
UiTransformInterface::RectPoints points;
EBUS_EVENT_ID(GetEntityId(), UiTransformBus, GetViewportSpacePoints, points);
AZ::Vector2 dir = points.TopRight() - points.TopLeft();
bool isHorizontal = fabs(dir.GetX()) >= fabs(dir.GetY());
bool isVertical = fabs(dir.GetX()) <= fabs(dir.GetY());
float newValue = m_value;
if (isHorizontal && (command == UiNavigationHelpers::Command::Left || command == UiNavigationHelpers::Command::Right))
{
newValue += (command == UiNavigationHelpers::Command::Left ? -delta : delta);
result = true;
}
else if (isVertical && (command == UiNavigationHelpers::Command::Up || command == UiNavigationHelpers::Command::Down))
{
newValue += (command == UiNavigationHelpers::Command::Down ? -delta : delta);
result = true;
}
if (m_minValue < m_maxValue)
{
newValue = AZ::GetClamp(newValue, m_minValue, m_maxValue);
}
else // ( m_minValue >= m_maxValue )
{
newValue = AZ::GetClamp(newValue, m_maxValue, m_minValue);
}
if (newValue != m_value)
{
SetValue(newValue);
AZ::Vector2 point(-1.0f, 1.0f);
DoChangingActions();
DoChangedActions();
}
}
return result;
}
/////////////////////////////////////////////////////////////////
void UiSliderComponent::InputPositionUpdate(AZ::Vector2 point)
{
if (m_isPressed && m_trackEntity.IsValid())
{
// 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 slider is no longer active
m_isPressed = false;
}
else
{
// the drag was valid for this slider, we are now dragging
m_isDragging = true;
}
}
}
// if we are now in the dragging state do the drag of the slider
if (m_isDragging)
{
float value = GetValueFromPoint(point);
SetValue(value);
DoChangingActions();
}
}
}
/////////////////////////////////////////////////////////////////
bool UiSliderComponent::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 UiSliderComponent::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 slider is now taking the handoff
m_isPressed = true;
m_pressedPoint = startPoint;
m_isDragging = true;
// tell the canvas that this is now the active interactable
EBUS_EVENT_ID(currentActiveInteractable, UiInteractableActiveNotificationBus, ActiveChanged, GetEntityId(), false);
}
}
return dragDetected;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiSliderComponent::LostActiveStatus()
{
UiInteractableComponent::LostActiveStatus();
if (m_isDragging)
{
if (m_isHandlingEvents)
{
DoChangedActions();
}
m_isDragging = false;
}
m_isActive = false;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// PROTECTED MEMBER FUNCTIONS
////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiSliderComponent::Activate()
{
UiInteractableComponent::Activate();
UiSliderBus::Handler::BusConnect(GetEntityId());
UiInitializationBus::Handler::BusConnect(GetEntityId());
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiSliderComponent::Deactivate()
{
UiInteractableComponent::Deactivate();
UiSliderBus::Handler::BusDisconnect(GetEntityId());
UiInitializationBus::Handler::BusDisconnect(GetEntityId());
}
////////////////////////////////////////////////////////////////////////////////////////////////////
bool UiSliderComponent::IsAutoActivationSupported()
{
return true;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
UiInteractableStatesInterface::State UiSliderComponent::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 UiSliderComponent::Reflect(AZ::ReflectContext* context)
{
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<UiSliderComponent, UiInteractableComponent>()
->Version(3, &VersionConverter)
// Elements group
->Field("TrackEntity", &UiSliderComponent::m_trackEntity)
->Field("FillEntity", &UiSliderComponent::m_fillEntity)
->Field("ManipulatorEntity", &UiSliderComponent::m_manipulatorEntity)
// Value group
->Field("Value", &UiSliderComponent::m_value)
->Field("MinValue", &UiSliderComponent::m_minValue)
->Field("MaxValue", &UiSliderComponent::m_maxValue)
->Field("StepValue", &UiSliderComponent::m_stepValue)
// Actions group
->Field("ValueChangingActionName", &UiSliderComponent::m_valueChangingActionName)
->Field("ValueChangedActionName", &UiSliderComponent::m_valueChangedActionName);
AZ::EditContext* ec = serializeContext->GetEditContext();
if (ec)
{
auto editInfo = ec->Class<UiSliderComponent>("Slider", "An interactable component for modifying a floating point value with a slider.");
editInfo->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Category, "UI")
->Attribute(AZ::Edit::Attributes::Icon, "Editor/Icons/Components/UiSlider.png")
->Attribute(AZ::Edit::Attributes::ViewportIcon, "Editor/Icons/Components/Viewport/UiSlider.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, &UiSliderComponent::m_trackEntity, "Track", "The child element used to define the range of movement.")
->Attribute(AZ::Edit::Attributes::EnumValues, &UiSliderComponent::PopulateChildEntityList);
editInfo->DataElement(AZ::Edit::UIHandlers::ComboBox, &UiSliderComponent::m_fillEntity, "Fill", "The child element used to show the filled part of the range.")
->Attribute(AZ::Edit::Attributes::EnumValues, &UiSliderComponent::PopulateChildEntityList);
editInfo->DataElement(AZ::Edit::UIHandlers::ComboBox, &UiSliderComponent::m_manipulatorEntity, "Manipulator", "The child element used as a handle.")
->Attribute(AZ::Edit::Attributes::EnumValues, &UiSliderComponent::PopulateChildEntityList);
}
// Value group
{
editInfo->ClassElement(AZ::Edit::ClassElements::Group, "Value")
->Attribute(AZ::Edit::Attributes::AutoExpand, true);
editInfo->DataElement(0, &UiSliderComponent::m_value, "Value", "The initial value of the slider.");
editInfo->DataElement(0, &UiSliderComponent::m_minValue, "Min", "The minimum slider value.");
editInfo->DataElement(0, &UiSliderComponent::m_maxValue, "Max", "The maximum slider value.");
editInfo->DataElement(0, &UiSliderComponent::m_stepValue, "Stepping", "The smallest increment allowed between values. Use zero for no restriction.");
}
// Actions group
{
editInfo->ClassElement(AZ::Edit::ClassElements::Group, "Actions")
->Attribute(AZ::Edit::Attributes::AutoExpand, true);
editInfo->DataElement(0, &UiSliderComponent::m_valueChangingActionName, "Change", "The action triggered while the value is changing.");
editInfo->DataElement(0, &UiSliderComponent::m_valueChangedActionName, "End change", "The action triggered when the value is done changing.");
}
}
}
AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context);
if (behaviorContext)
{
behaviorContext->EBus<UiSliderBus>("UiSliderBus")
->Event("GetValue", &UiSliderBus::Events::GetValue)
->Event("SetValue", &UiSliderBus::Events::SetValue)
->Event("GetMinValue", &UiSliderBus::Events::GetMinValue)
->Event("SetMinValue", &UiSliderBus::Events::SetMinValue)
->Event("GetMaxValue", &UiSliderBus::Events::GetMaxValue)
->Event("SetMaxValue", &UiSliderBus::Events::SetMaxValue)
->Event("GetStepValue", &UiSliderBus::Events::GetStepValue)
->Event("SetStepValue", &UiSliderBus::Events::SetStepValue)
->Event("GetTrackEntity", &UiSliderBus::Events::GetTrackEntity)
->Event("SetTrackEntity", &UiSliderBus::Events::SetTrackEntity)
->Event("GetFillEntity", &UiSliderBus::Events::GetFillEntity)
->Event("SetFillEntity", &UiSliderBus::Events::SetFillEntity)
->Event("GetManipulatorEntity", &UiSliderBus::Events::GetManipulatorEntity)
->Event("SetManipulatorEntity", &UiSliderBus::Events::SetManipulatorEntity)
->Event("GetValueChangingActionName", &UiSliderBus::Events::GetValueChangingActionName)
->Event("SetValueChangingActionName", &UiSliderBus::Events::SetValueChangingActionName)
->Event("GetValueChangedActionName", &UiSliderBus::Events::GetValueChangedActionName)
->Event("SetValueChangedActionName", &UiSliderBus::Events::SetValueChangedActionName)
->VirtualProperty("Value", "GetValue", "SetValue")
->VirtualProperty("MinValue", "GetMinValue", "SetMinValue")
->VirtualProperty("MaxValue", "GetMaxValue", "SetMaxValue")
->VirtualProperty("StepValue", "GetStepValue", "SetStepValue");
behaviorContext->Class<UiSliderComponent>()->RequestBus("UiSliderBus");
behaviorContext->EBus<UiSliderNotificationBus>("UiSliderNotificationBus")
->Handler<UiSliderNotificationBusBehaviorHandler>();
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// PRIVATE MEMBER FUNCTIONS
////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////
UiSliderComponent::EntityComboBoxVec UiSliderComponent::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;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
float UiSliderComponent::GetValueFromPoint(AZ::Vector2 point)
{
// Get m_value from point.
float value = 0.0f;
UiTransformInterface::RectPoints points;
EBUS_EVENT_ID(m_trackEntity, UiTransformBus, GetCanvasSpacePointsNoScaleRotate, points);
// apply scale and rotation to points
EBUS_EVENT_ID(m_trackEntity, UiTransformBus, RotateAndScalePoints, points);
const AZ::Vector2& tl = points.TopLeft();
const AZ::Vector2& tr = points.TopRight();
AZ::Vector2 A = tr - tl;
float magA = A.GetLength();
AZ::Vector2 normA = A.GetNormalized();
AZ::Vector2 B = point - tl;
float magBalongA = B.Dot(normA);
float range = fabsf(m_maxValue - m_minValue);
float unitValue = (magBalongA / magA);
if (m_minValue < m_maxValue)
{
value = m_minValue + (range * unitValue);
}
else // ( m_minValue >= m_maxValue )
{
value = m_minValue - (range * unitValue);
}
return value;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// calculate how much we have dragged along the axis of the slider
float UiSliderComponent::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_trackEntity, UiTransformBus, GetTransformFromViewport, transformFromViewport);
AZ::Vector2 dragVec = endPoint - startPoint;
AZ::Vector3 dragVec3(dragVec.GetX(), dragVec.GetY(), 0.0f);
AZ::Vector3 localDragVec = transformFromViewport.Multiply3x3(dragVec3);
// the slider component only supports drag along the x axis so zero the y axis
localDragVec.SetY(0.0f);
// convert back to viewport space
AZ::Matrix4x4 transformToViewport;
EBUS_EVENT_ID(m_trackEntity, 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 UiSliderComponent::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 slider
float validDragDistance = GetValidDragDistanceInPixels(startPoint, currentPoint);
if (validDragDistance > dragThreshold)
{
// we dragged above the threshold value along axis of slider
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 UiSliderComponent::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);
}
EBUS_EVENT_ID(GetEntityId(), UiSliderNotificationBus, OnSliderValueChanged, m_value);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiSliderComponent::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);
}
EBUS_EVENT_ID(GetEntityId(), UiSliderNotificationBus, OnSliderValueChanging, m_value);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// PRIVATE STATIC MEMBER FUNCTIONS
////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////
bool UiSliderComponent::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;
}
}
return true;
}