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

823 lines
34 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 "UiInteractableComponent.h"
#include <AzCore/Math/Crc.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/std/sort.h>
#include <LyShine/ISprite.h>
#include <LyShine/UiSerializeHelpers.h>
#include <LyShine/Bus/UiCanvasBus.h>
#include <LyShine/Bus/UiElementBus.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
//! UiInteractableNotificationBus Behavior context handler class
class BehaviorUiInteractableNotificationBusHandler
: public UiInteractableNotificationBus::Handler
, public AZ::BehaviorEBusHandler
{
public:
AZ_EBUS_BEHAVIOR_BINDER(BehaviorUiInteractableNotificationBusHandler, "{BBF912EB-8F45-4869-B1F0-19CDA9D16231}", AZ::SystemAllocator,
OnHoverStart, OnHoverEnd, OnPressed, OnReleased, OnReceivedHoverByNavigatingFromDescendant);
void OnHoverStart() override
{
Call(FN_OnHoverStart);
}
void OnHoverEnd() override
{
Call(FN_OnHoverEnd);
}
void OnPressed() override
{
Call(FN_OnPressed);
}
void OnReleased() override
{
Call(FN_OnReleased);
}
void OnReceivedHoverByNavigatingFromDescendant(AZ::EntityId descendantEntityId) override
{
Call(FN_OnReceivedHoverByNavigatingFromDescendant, descendantEntityId);
}
};
////////////////////////////////////////////////////////////////////////////////////////////////////
UiInteractableComponent::UiInteractableComponent()
: m_isAutoActivationEnabled(false)
, m_isHandlingEvents(true)
, m_isHandlingMultiTouchEvents(true)
, m_isHover(false)
, m_isPressed(false)
, m_pressedPoint(0.0f, 0.0f)
, m_state(UiInteractableStatesInterface::StateNormal)
, m_hoverStartActionCallback(nullptr)
, m_hoverEndActionCallback(nullptr)
, m_pressedActionCallback(nullptr)
, m_releasedActionCallback(nullptr)
{
// Must be called in the same order as the states defined in UiInteractableStatesInterface
m_stateActionManager.AddState(nullptr); // normal state has no state actions
m_stateActionManager.AddState(&m_hoverStateActions);
m_stateActionManager.AddState(&m_pressedStateActions);
m_stateActionManager.AddState(&m_disabledStateActions);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
UiInteractableComponent::~UiInteractableComponent()
{
if (m_isPressed && m_entity)
{
EBUS_EVENT_ID(GetEntityId(), UiInteractableActiveNotificationBus, ActiveCancelled);
m_isPressed = false;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
bool UiInteractableComponent::CanHandleEvent([[maybe_unused]] AZ::Vector2 point)
{
return m_isHandlingEvents;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
bool UiInteractableComponent::HandlePressed(AZ::Vector2 point, bool& shouldStayActive)
{
bool handled = false;
if (m_isHandlingEvents)
{
m_isPressed = true;
m_pressedPoint = point;
shouldStayActive = false;
handled = true;
TriggerPressedAction();
}
return handled;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
bool UiInteractableComponent::HandleReleased([[maybe_unused]] AZ::Vector2 point)
{
m_isPressed = false;
TriggerReleasedAction();
return m_isHandlingEvents;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
bool UiInteractableComponent::HandleMultiTouchPressed(AZ::Vector2 point, int multiTouchIndex)
{
AZ_UNUSED(multiTouchIndex);
bool shouldStayActive = false;
return m_isHandlingMultiTouchEvents && HandlePressed(point, shouldStayActive);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
bool UiInteractableComponent::HandleMultiTouchReleased(AZ::Vector2 point, int multiTouchIndex)
{
AZ_UNUSED(multiTouchIndex);
return m_isHandlingMultiTouchEvents && HandleReleased(point);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
bool UiInteractableComponent::HandleEnterPressed(bool& shouldStayActive)
{
bool handled = false;
if (m_isHandlingEvents)
{
m_isPressed = true;
m_pressedPoint = AZ::Vector2(-1.0f, -1.0f);
shouldStayActive = false;
handled = true;
TriggerPressedAction();
}
return handled;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
bool UiInteractableComponent::HandleEnterReleased()
{
m_isPressed = false;
TriggerReleasedAction();
return m_isHandlingEvents;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiInteractableComponent::InputPositionUpdate(AZ::Vector2 point)
{
if (m_isPressed)
{
AZ::EntityId parentDraggable;
EBUS_EVENT_ID_RESULT(parentDraggable, GetEntityId(), UiElementBus, FindParentInteractableSupportingDrag, point);
if (parentDraggable.IsValid())
{
const float containedDragThreshold = 5.0f;
// offer the parent draggable the chance to become the active interactable
bool handOff = false;
EBUS_EVENT_ID_RESULT(handOff, parentDraggable, UiInteractableBus,
OfferDragHandOff, GetEntityId(), m_pressedPoint, point, containedDragThreshold);
if (handOff)
{
// interaction has been handed off to a container entity
m_isPressed = false;
}
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiInteractableComponent::MultiTouchPositionUpdate(AZ::Vector2 point, int multiTouchIndex)
{
AZ_UNUSED(multiTouchIndex);
if (m_isHandlingMultiTouchEvents)
{
InputPositionUpdate(point);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiInteractableComponent::LostActiveStatus()
{
m_isPressed = false;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiInteractableComponent::HandleHoverStart()
{
m_isHover = true;
TriggerHoverStartAction();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiInteractableComponent::HandleHoverEnd()
{
m_isHover = false;
TriggerHoverEndAction();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiInteractableComponent::HandleReceivedHoverByNavigatingFromDescendant(AZ::EntityId descendantEntityId)
{
TriggerReceivedHoverByNavigatingFromDescendantAction(descendantEntityId);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
bool UiInteractableComponent::IsPressed()
{
return m_isPressed;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
bool UiInteractableComponent::IsHandlingEvents()
{
return m_isHandlingEvents;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiInteractableComponent::SetIsHandlingEvents(bool isHandlingEvents)
{
m_isHandlingEvents = isHandlingEvents;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
bool UiInteractableComponent::IsHandlingMultiTouchEvents()
{
return m_isHandlingMultiTouchEvents;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiInteractableComponent::SetIsHandlingMultiTouchEvents(bool isHandlingMultiTouchEvents)
{
m_isHandlingMultiTouchEvents = isHandlingMultiTouchEvents;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
bool UiInteractableComponent::GetIsAutoActivationEnabled()
{
return m_isAutoActivationEnabled;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiInteractableComponent::SetIsAutoActivationEnabled(bool isEnabled)
{
m_isAutoActivationEnabled = isEnabled;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
const LyShine::ActionName& UiInteractableComponent::GetHoverStartActionName()
{
return m_hoverStartActionName;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiInteractableComponent::SetHoverStartActionName(const LyShine::ActionName& actionName)
{
m_hoverStartActionName = actionName;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
const LyShine::ActionName& UiInteractableComponent::GetHoverEndActionName()
{
return m_hoverEndActionName;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiInteractableComponent::SetHoverEndActionName(const LyShine::ActionName& actionName)
{
m_hoverEndActionName = actionName;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
const LyShine::ActionName& UiInteractableComponent::GetPressedActionName()
{
return m_pressedActionName;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiInteractableComponent::SetPressedActionName(const LyShine::ActionName& actionName)
{
m_pressedActionName = actionName;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
const LyShine::ActionName& UiInteractableComponent::GetReleasedActionName()
{
return m_releasedActionName;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiInteractableComponent::SetReleasedActionName(const LyShine::ActionName& actionName)
{
m_releasedActionName = actionName;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
UiInteractableActionsInterface::OnActionCallback UiInteractableComponent::GetHoverStartActionCallback()
{
return m_hoverStartActionCallback;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiInteractableComponent::SetHoverStartActionCallback(OnActionCallback onActionCallback)
{
m_hoverStartActionCallback = onActionCallback;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
UiInteractableActionsInterface::OnActionCallback UiInteractableComponent::GetHoverEndActionCallback()
{
return m_hoverEndActionCallback;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiInteractableComponent::SetHoverEndActionCallback(OnActionCallback onActionCallback)
{
m_hoverEndActionCallback = onActionCallback;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
UiInteractableActionsInterface::OnActionCallback UiInteractableComponent::GetPressedActionCallback()
{
return m_pressedActionCallback;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiInteractableComponent::SetPressedActionCallback(OnActionCallback onActionCallback)
{
m_pressedActionCallback = onActionCallback;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
UiInteractableActionsInterface::OnActionCallback UiInteractableComponent::GetReleasedActionCallback()
{
return m_releasedActionCallback;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiInteractableComponent::SetReleasedActionCallback(OnActionCallback onActionCallback)
{
m_releasedActionCallback = onActionCallback;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiInteractableComponent::Update(float /* deltaTime */)
{
// This currently happens every frame. Needs optimization to just happen on events
UiInteractableStatesInterface::State state = ComputeInteractableState();
if (state != m_state)
{
m_stateActionManager.ResetAllOverrides();
m_stateActionManager.ApplyStateActions(state);
m_state = state;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiInteractableComponent::OnUiElementFixup(AZ::EntityId canvasEntityId, AZ::EntityId /*parentEntityId*/)
{
bool isElementEnabled = false;
EBUS_EVENT_ID_RESULT(isElementEnabled, GetEntityId(), UiElementBus, GetAreElementAndAncestorsEnabled);
if (isElementEnabled)
{
UiCanvasUpdateNotificationBus::Handler::BusConnect(canvasEntityId);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiInteractableComponent::OnUiElementAndAncestorsEnabledChanged(bool areElementAndAncestorsEnabled)
{
if (areElementAndAncestorsEnabled)
{
AZ::EntityId canvasEntityId;
EBUS_EVENT_ID_RESULT(canvasEntityId, GetEntityId(), UiElementBus, GetCanvasEntityId);
if (canvasEntityId.IsValid())
{
UiCanvasUpdateNotificationBus::Handler::BusConnect(canvasEntityId);
}
}
else
{
UiCanvasUpdateNotificationBus::Handler::BusDisconnect();
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// PUBLIC STATIC MEMBER FUNCTIONS
////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiInteractableComponent::Reflect(AZ::ReflectContext* context)
{
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<UiInteractableComponent, AZ::Component>()
->Version(2, &VersionConverter)
->Field("IsHandlingEvents", &UiInteractableComponent::m_isHandlingEvents)
->Field("IsHandlingMultiTouchEvents", &UiInteractableComponent::m_isHandlingMultiTouchEvents)
->Field("HoverStateActions", &UiInteractableComponent::m_hoverStateActions)
->Field("PressedStateActions", &UiInteractableComponent::m_pressedStateActions)
->Field("DisabledStateActions", &UiInteractableComponent::m_disabledStateActions)
->Field("NavigationSettings", &UiInteractableComponent::m_navigationSettings)
->Field("IsAutoActivationEnabled", &UiInteractableComponent::m_isAutoActivationEnabled)
->Field("HoverStartActionName", &UiInteractableComponent::m_hoverStartActionName)
->Field("HoverEndActionName", &UiInteractableComponent::m_hoverEndActionName)
->Field("PressedActionName", &UiInteractableComponent::m_pressedActionName)
->Field("ReleasedActionName", &UiInteractableComponent::m_releasedActionName);
AZ::EditContext* ec = serializeContext->GetEditContext();
if (ec)
{
auto editInfo = ec->Class<UiInteractableComponent>("Interactable", "Common settings for all interactable components");
editInfo->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AutoExpand, true);
editInfo->DataElement("CheckBox", &UiInteractableComponent::m_isHandlingEvents, "Input enabled",
"When checked, this interactable will handle events.\n"
"When unchecked, this interactable is drawn in the Disabled state.")
->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ_CRC("RefreshEntireTree", 0xefbc823c));
editInfo->DataElement("CheckBox", &UiInteractableComponent::m_isHandlingMultiTouchEvents, "Multi-touch input enabled",
"When checked, this interactable will handle all multi-touch input events.\n"
"When unchecked, this interactable will handle only primary touch input events.\n"
"Will be ignored if the parent UICanvasComponent does not support multi-touch.")
->Attribute(AZ::Edit::Attributes::Visibility, &UiInteractableComponent::IsHandlingEvents);
// Navigation
editInfo->DataElement(0, &UiInteractableComponent::m_navigationSettings, "Navigation",
"How to navigate from this interactbale to the next interactable");
editInfo->DataElement(0, &UiInteractableComponent::m_isAutoActivationEnabled, "Auto activate",
"When checked, this interactable will automatically become active when navigated to with a gamepad/keyboard.\n"
"When unchecked, a button press is required to activate/deactivate this interactable.")
->Attribute(AZ::Edit::Attributes::Visibility, &UiInteractableComponent::IsAutoActivationSupported);
// States Group
{
editInfo->ClassElement(AZ::Edit::ClassElements::Group, "States")
->Attribute(AZ::Edit::Attributes::AutoExpand, true);
editInfo->DataElement(0, &UiInteractableComponent::m_hoverStateActions, "Hover", "The hover/selected state actions")
->Attribute(AZ::Edit::Attributes::AddNotify, &UiInteractableComponent::OnHoverStateActionsChanged);
editInfo->DataElement(0, &UiInteractableComponent::m_pressedStateActions, "Pressed", "The pressed state actions")
->Attribute(AZ::Edit::Attributes::AddNotify, &UiInteractableComponent::OnPressedStateActionsChanged);
editInfo->DataElement(0, &UiInteractableComponent::m_disabledStateActions, "Disabled", "The disabled state actions")
->Attribute(AZ::Edit::Attributes::AddNotify, &UiInteractableComponent::OnDisabledStateActionsChanged);
}
// Actions Group
{
editInfo->ClassElement(AZ::Edit::ClassElements::Group, "Actions")
->Attribute(AZ::Edit::Attributes::AutoExpand, true);
editInfo->DataElement(0, &UiInteractableComponent::m_hoverStartActionName, "Hover start", "Action triggered on hover start");
editInfo->DataElement(0, &UiInteractableComponent::m_hoverEndActionName, "Hover end", "Action triggered on hover end");
editInfo->DataElement(0, &UiInteractableComponent::m_pressedActionName, "Pressed", "Action triggered on press");
editInfo->DataElement(0, &UiInteractableComponent::m_releasedActionName, "Released", "Action triggered on release");
}
}
}
AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context);
if (behaviorContext)
{
behaviorContext->EBus<UiInteractableBus>("UiInteractableBus")
->Event("IsHandlingEvents", &UiInteractableBus::Events::IsHandlingEvents)
->Event("SetIsHandlingEvents", &UiInteractableBus::Events::SetIsHandlingEvents)
->Event("IsHandlingMultiTouchEvents", &UiInteractableBus::Events::IsHandlingMultiTouchEvents)
->Event("SetIsHandlingMultiTouchEvents", &UiInteractableBus::Events::SetIsHandlingMultiTouchEvents)
->Event("GetIsAutoActivationEnabled", &UiInteractableBus::Events::GetIsAutoActivationEnabled)
->Event("SetIsAutoActivationEnabled", &UiInteractableBus::Events::SetIsAutoActivationEnabled);
behaviorContext->EBus<UiInteractableActionsBus>("UiInteractableActionsBus")
->Event("GetHoverStartActionName", &UiInteractableActionsBus::Events::GetHoverStartActionName)
->Event("SetHoverStartActionName", &UiInteractableActionsBus::Events::SetHoverStartActionName)
->Event("GetHoverEndActionName", &UiInteractableActionsBus::Events::GetHoverEndActionName)
->Event("SetHoverEndActionName", &UiInteractableActionsBus::Events::SetHoverEndActionName)
->Event("GetPressedActionName", &UiInteractableActionsBus::Events::GetPressedActionName)
->Event("SetPressedActionName", &UiInteractableActionsBus::Events::SetPressedActionName)
->Event("GetReleasedActionName", &UiInteractableActionsBus::Events::GetReleasedActionName)
->Event("SetReleasedActionName", &UiInteractableActionsBus::Events::SetReleasedActionName);
behaviorContext->Enum<(int)UiInteractableStatesInterface::StateNormal>("eUiInteractableState_Normal")
->Enum<(int)UiInteractableStatesInterface::StateHover>("eUiInteractableState_Hover")
->Enum<(int)UiInteractableStatesInterface::StatePressed>("eUiInteractableState_Pressed")
->Enum<(int)UiInteractableStatesInterface::StateDisabled>("eUiInteractableState_Disabled");
behaviorContext->EBus<UiInteractableStatesBus>("UiInteractableStatesBus")
->Event("GetStateColor", &UiInteractableStatesBus::Events::GetStateColor)
->Event("SetStateColor", &UiInteractableStatesBus::Events::SetStateColor)
->Event("HasStateColor", &UiInteractableStatesBus::Events::HasStateColor)
->Event("GetStateAlpha", &UiInteractableStatesBus::Events::GetStateAlpha)
->Event("SetStateAlpha", &UiInteractableStatesBus::Events::SetStateAlpha)
->Event("HasStateAlpha", &UiInteractableStatesBus::Events::HasStateAlpha)
->Event("GetStateSpritePathname", &UiInteractableStatesBus::Events::GetStateSpritePathname)
->Event("SetStateSpritePathname", &UiInteractableStatesBus::Events::SetStateSpritePathname)
->Event("HasStateSprite", &UiInteractableStatesBus::Events::HasStateSprite)
->Event("GetStateFontPathname", &UiInteractableStatesBus::Events::GetStateFontPathname)
->Event("GetStateFontEffectIndex", &UiInteractableStatesBus::Events::GetStateFontEffectIndex)
->Event("SetStateFont", &UiInteractableStatesBus::Events::SetStateFont)
->Event("HasStateFont", &UiInteractableStatesBus::Events::HasStateFont);
behaviorContext->EBus<UiInteractableNotificationBus>("UiInteractableNotificationBus")
->Handler<BehaviorUiInteractableNotificationBusHandler>();
}
UiInteractableStateAction::Reflect(context);
UiInteractableStateColor::Reflect(context);
UiInteractableStateAlpha::Reflect(context);
UiInteractableStateSprite::Reflect(context);
UiInteractableStateFont::Reflect(context);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// PROTECTED MEMBER FUNCTIONS
////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiInteractableComponent::Init()
{
m_stateActionManager.Init(GetEntityId());
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiInteractableComponent::Activate()
{
m_stateActionManager.Activate();
m_navigationSettings.Activate(m_entity->GetId(), GetNavigableInteractables);
UiInteractableBus::Handler::BusConnect(GetEntityId());
UiInteractableActionsBus::Handler::BusConnect(GetEntityId());
UiElementNotificationBus::Handler::BusConnect(GetEntityId());
// The first time the component is activated the owning canvas will not be known. However if
// the element is fixed up and then we deactivate and reactivate, OnUiElementFixup will
// not get called again. So we need to connect to the UiCanvasUpdateNotificationBus here.
// This assumes than on an element activate it will activate the UiElementComponent before
// this component. We can rely on this because all UI components depend on UiElementService
// as a required service.
AZ::EntityId canvasEntityId;
EBUS_EVENT_ID_RESULT(canvasEntityId, GetEntityId(), UiElementBus, GetCanvasEntityId);
if (canvasEntityId.IsValid())
{
bool isElementEnabled = false;
EBUS_EVENT_ID_RESULT(isElementEnabled, GetEntityId(), UiElementBus, GetAreElementAndAncestorsEnabled);
if (isElementEnabled)
{
UiCanvasUpdateNotificationBus::Handler::BusConnect(canvasEntityId);
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiInteractableComponent::Deactivate()
{
m_stateActionManager.Deactivate();
m_navigationSettings.Deactivate();
UiInteractableBus::Handler::BusDisconnect();
UiCanvasUpdateNotificationBus::Handler::BusDisconnect();
UiElementNotificationBus::Handler::BusDisconnect();
UiInteractableActionsBus::Handler::BusDisconnect();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
UiInteractableStatesInterface::State UiInteractableComponent::ComputeInteractableState()
{
UiInteractableStatesInterface::State state = UiInteractableStatesInterface::StateNormal;
if (!m_isHandlingEvents)
{
// not handling events, use disabled state
state = UiInteractableStatesInterface::StateDisabled;
}
else if (m_isPressed && m_isHover)
{
// We only use the pressed state when the button is pressed AND the mouse is over it
state = UiInteractableStatesInterface::StatePressed;
}
else if (m_isHover || m_isPressed)
{
// we use the hover state for normal hover but also if the button is pressed but
// the mouse is outside the button
state = UiInteractableStatesInterface::StateHover;
}
return state;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiInteractableComponent::OnHoverStateActionsChanged()
{
m_stateActionManager.InitInteractableEntityForStateActions(m_hoverStateActions);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiInteractableComponent::OnPressedStateActionsChanged()
{
m_stateActionManager.InitInteractableEntityForStateActions(m_pressedStateActions);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiInteractableComponent::OnDisabledStateActionsChanged()
{
m_stateActionManager.InitInteractableEntityForStateActions(m_disabledStateActions);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiInteractableComponent::TriggerHoverStartAction()
{
// if a C++ callback is registered for hover start then call it
if (m_hoverStartActionCallback)
{
m_hoverStartActionCallback(GetEntityId());
}
EBUS_EVENT_ID(GetEntityId(), UiInteractableNotificationBus, OnHoverStart);
// Tell any action listeners about the event
if (!m_hoverStartActionName.empty())
{
AZ::EntityId canvasEntityId;
EBUS_EVENT_ID_RESULT(canvasEntityId, GetEntityId(), UiElementBus, GetCanvasEntityId);
EBUS_EVENT_ID(canvasEntityId, UiCanvasNotificationBus, OnAction, GetEntityId(), m_hoverStartActionName);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiInteractableComponent::TriggerHoverEndAction()
{
// if a C++ callback is registered for hover end then call it
if (m_hoverEndActionCallback)
{
m_hoverEndActionCallback(GetEntityId());
}
EBUS_EVENT_ID(GetEntityId(), UiInteractableNotificationBus, OnHoverEnd);
// Tell any action listeners about the event
if (!m_hoverEndActionName.empty())
{
AZ::EntityId canvasEntityId;
EBUS_EVENT_ID_RESULT(canvasEntityId, GetEntityId(), UiElementBus, GetCanvasEntityId);
EBUS_EVENT_ID(canvasEntityId, UiCanvasNotificationBus, OnAction, GetEntityId(), m_hoverEndActionName);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiInteractableComponent::TriggerPressedAction()
{
// if a C++ callback is registered for pressed then call it
if (m_pressedActionCallback)
{
m_pressedActionCallback(GetEntityId());
}
// Queue the event to prevent deletions during the input event
EBUS_QUEUE_EVENT_ID(GetEntityId(), UiInteractableNotificationBus, OnPressed);
// Tell any action listeners about the event
if (!m_pressedActionName.empty())
{
AZ::EntityId canvasEntityId;
EBUS_EVENT_ID_RESULT(canvasEntityId, GetEntityId(), UiElementBus, GetCanvasEntityId);
// Queue the event to prevent deletions during the input event
EBUS_QUEUE_EVENT_ID(canvasEntityId, UiCanvasNotificationBus, OnAction, GetEntityId(), m_pressedActionName);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiInteractableComponent::TriggerReleasedAction()
{
// if a C++ callback is registered for released then call it
if (m_releasedActionCallback)
{
m_releasedActionCallback(GetEntityId());
}
// Queue the event to prevent deletions during the input event
EBUS_QUEUE_EVENT_ID(GetEntityId(), UiInteractableNotificationBus, OnReleased);
// Tell any action listeners about the event
if (!m_releasedActionName.empty())
{
AZ::EntityId canvasEntityId;
EBUS_EVENT_ID_RESULT(canvasEntityId, GetEntityId(), UiElementBus, GetCanvasEntityId);
// Queue the event to prevent deletions during the input event
EBUS_QUEUE_EVENT_ID(canvasEntityId, UiCanvasNotificationBus, OnAction, GetEntityId(), m_releasedActionName);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiInteractableComponent::TriggerReceivedHoverByNavigatingFromDescendantAction(AZ::EntityId descendantEntityId)
{
EBUS_EVENT_ID(GetEntityId(), UiInteractableNotificationBus, OnReceivedHoverByNavigatingFromDescendant, descendantEntityId);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
bool UiInteractableComponent::IsAutoActivationSupported()
{
return false;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// PRIVATE STATIC MEMBER FUNCTIONS
////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////
LyShine::EntityArray UiInteractableComponent::GetNavigableInteractables(AZ::EntityId entityId)
{
// Get a list of all navigable elements
AZ::EntityId canvasEntityId;
EBUS_EVENT_ID_RESULT(canvasEntityId, entityId, UiElementBus, GetCanvasEntityId);
LyShine::EntityArray navigableElements;
EBUS_EVENT_ID(canvasEntityId, UiCanvasBus, FindElements,
[entityId](const AZ::Entity* entity)
{
bool navigable = false;
if (entity->GetId() != entityId)
{
if (UiInteractableBus::FindFirstHandler(entity->GetId()))
{
UiNavigationInterface::NavigationMode navigationMode = UiNavigationInterface::NavigationMode::None;
EBUS_EVENT_ID_RESULT(navigationMode, entity->GetId(), UiNavigationBus, GetNavigationMode);
navigable = (navigationMode != UiNavigationInterface::NavigationMode::None);
}
}
return navigable;
},
navigableElements);
return navigableElements;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
bool UiInteractableComponent::VersionConverter(AZ::SerializeContext& context,
AZ::SerializeContext::DataElementNode& classElement)
{
// conversion from version 1:
// - Need to move the navigation settings into a sub element UiNavigationSettings
if (classElement.GetVersion() <= 1)
{
int navModeIndex = classElement.FindElement(AZ_CRC("NavigationMode"));
int navUpIndex = classElement.FindElement(AZ_CRC("OnUpEntity"));
int navDownIndex = classElement.FindElement(AZ_CRC("OnDownEntity"));
int navLeftIndex = classElement.FindElement(AZ_CRC("OnLeftEntity"));
int navRightIndex = classElement.FindElement(AZ_CRC("OnRightEntity"));
if (navModeIndex == -1 || navUpIndex == -1 || navDownIndex == -1 || navLeftIndex == -1 || navRightIndex == -1)
{
AZ_Error("Serialization", false, "UiInteractableComponent version conversion failed finding navigation fields");
return false;
}
// Add the new UiNavigationSettings node
int navSettingsIndex = classElement.AddElement<UiNavigationSettings>(context, "NavigationSettings");
if (navSettingsIndex == -1)
{
AZ_Error("Serialization", false, "UiInteractableComponent version conversion failed when adding navigation settings");
return false;
}
AZ::SerializeContext::DataElementNode& navSettingsNode = classElement.GetSubElement(navSettingsIndex);
// for each of the fields to move, make a copy of the existing node and add it to the nav settings
AZ::SerializeContext::DataElementNode navModeNode = classElement.GetSubElement(navModeIndex);
navSettingsNode.AddElement(navModeNode);
AZ::SerializeContext::DataElementNode navUpNode = classElement.GetSubElement(navUpIndex);
navSettingsNode.AddElement(navUpNode);
AZ::SerializeContext::DataElementNode navDownNode = classElement.GetSubElement(navDownIndex);
navSettingsNode.AddElement(navDownNode);
AZ::SerializeContext::DataElementNode navLeftNode = classElement.GetSubElement(navLeftIndex);
navSettingsNode.AddElement(navLeftNode);
AZ::SerializeContext::DataElementNode navRightNode = classElement.GetSubElement(navRightIndex);
navSettingsNode.AddElement(navRightNode);
// Remove the old nodes in reverse order since removing an index invalidates all indices after it
classElement.RemoveElement(navRightIndex);
classElement.RemoveElement(navLeftIndex);
classElement.RemoveElement(navDownIndex);
classElement.RemoveElement(navUpIndex);
classElement.RemoveElement(navModeIndex);
}
return true;
}