Initial commit

This commit is contained in:
alexpete
2021-03-05 11:26:34 -08:00
commit a10351f38d
27091 changed files with 5521199 additions and 0 deletions
@@ -0,0 +1,521 @@
/*
* 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 "ComponentModeCollection.h"
#include <AzToolsFramework/Commands/ComponentModeCommand.h>
#include <AzCore/std/smart_ptr/make_shared.h>
namespace AzToolsFramework
{
namespace ComponentModeFramework
{
AZ_CLASS_ALLOCATOR_IMPL(ComponentModeCollection, AZ::SystemAllocator, 0)
static const char* const s_nextActiveComponentModeTitle = "Edit Next";
static const char* const s_previousActiveComponentModeTitle = "Edit Previous";
static const char* const s_nextActiveComponentModeDesc = "Move to the next component";
static const char* const s_prevActiveComponentModeDesc = "Move to the previous component";
static const char* const s_leaveCompoenentModeTitle = "Done";
static const char* const s_leaveCompoenentModeDesc = "Return to normal viewport editing";
static const char* const s_enteringComponentModeUndoRedoDesc = "Editing Component";
static const char* const s_leavingComponentModeUndoRedoDesc = "Stopped Editing Component";
/// Predicate to search for a Component in list of Entities and Component Modes.
struct EntityAndComponentModePred
{
explicit EntityAndComponentModePred(
const AZ::EntityComponentIdPair& entityComponentIdPair)
: m_entityComponentIdPair(entityComponentIdPair) {}
bool operator()(const EntityAndComponentMode& entityAndComponentMode) const
{
// find the entity and specific component in this mode
return entityAndComponentMode.m_entityId == m_entityComponentIdPair.GetEntityId()
&& entityAndComponentMode.m_componentMode->GetComponentId() == m_entityComponentIdPair.GetComponentId();
}
private:
AZ::EntityComponentIdPair m_entityComponentIdPair;
};
/// Predicate to search for contained Component in list of Entities and Component Mode Builders.
struct EntityAndComponentModeBuildersPred
{
explicit EntityAndComponentModeBuildersPred(
const AZ::EntityComponentIdPair& entityComponentIdPair)
: m_entityComponentIdPair(entityComponentIdPair) {}
bool operator()(const EntityAndComponentModeBuilders& entityAndComponentModeBuilders) const
{
// find the entity this builder is associated with
if (entityAndComponentModeBuilders.m_entityId == m_entityComponentIdPair.GetEntityId())
{
// find the specific component on the entity this builder is associated with
for (const ComponentModeBuilder& componentModeBuilder : entityAndComponentModeBuilders.m_componentModeBuilders)
{
if (m_entityComponentIdPair.GetComponentId() == componentModeBuilder.m_componentId)
{
return true;
}
}
}
return false;
}
private:
AZ::EntityComponentIdPair m_entityComponentIdPair;
};
// struct to hold ActionOverride and duplicate m_uri (unique identifier)
struct BoundOverrideActions
{
AZ::Crc32 m_uri;
ActionOverride m_actionOverride;
};
// add all actions, use uri as key, override action with matching key
static void SetBoundActions(
const AZStd::vector<ActionOverride>& actions, AZStd::vector<BoundOverrideActions>& boundActions)
{
for (const auto& action : actions)
{
// check if we already have an action with this uri
auto actionIt = AZStd::find_if(boundActions.begin(), boundActions.end(),
[action](const BoundOverrideActions& boundActionOverride)
{
return action.m_uri == boundActionOverride.m_actionOverride.m_uri;
});
// if we do not already have an action with this uri, store it
if (actionIt == boundActions.end())
{
boundActions.push_back({ action.m_uri, action });
}
else
{
// if we do already have an action with this uri, check if the new and existing
// action both have valid entity ids, and if so, if they match - if the entity
// and component ids are valid and match, keep this action and the previous one
// (so add/push_back), otherwise we want to override it.
if (action.m_entityIdComponentPair.GetEntityId().IsValid() &&
actionIt->m_actionOverride.m_entityIdComponentPair.GetEntityId().IsValid() &&
action.m_entityIdComponentPair != actionIt->m_actionOverride.m_entityIdComponentPair)
{
boundActions.push_back({ action.m_uri, action });
}
else
{
// overwrite existing action if uri already exists
actionIt->m_actionOverride = action;
}
}
}
};
void ComponentModeCollection::AddComponentMode(
const AZ::EntityComponentIdPair& entityComponentIdPair, const AZ::Uuid componentType,
const ComponentModeFactoryFunction& componentModeBuilder)
{
// check if we already have a ComponentMode for this component type
const auto componentTypeIt = AZStd::find(
m_activeComponentTypes.begin(), m_activeComponentTypes.end(), componentType);
// if not, store it to notify other system what types of components are in ComponentMode
if (componentTypeIt == m_activeComponentTypes.end())
{
m_activeComponentTypes.push_back(componentType);
}
// see if we already have a ComponentModeBuilder for the specific component on this entity
const auto builderEntityIt = AZStd::find_if(
m_entitiesAndComponentModeBuilders.begin(), m_entitiesAndComponentModeBuilders.end(),
EntityAndComponentModeBuildersPred(entityComponentIdPair));
// if we do not have a ComponentModeBuilder, create the ComponentMode from the builder, store it,
// and also store the builder to be later recorded for the undo/redo step
if (builderEntityIt == m_entitiesAndComponentModeBuilders.end())
{
// see if we are already storing a ComponentMode for this entity
const auto entityWithComponentMode = AZStd::find_if(
m_entitiesAndComponentModes.begin(), m_entitiesAndComponentModes.end(),
[entityComponentIdPair](const EntityAndComponentMode& entityAndComponentMode)
{
return entityAndComponentMode.m_entityId == entityComponentIdPair.GetEntityId();
});
// we do not already have a component mode for this entity
if (entityWithComponentMode == m_entitiesAndComponentModes.end())
{
// instantiate the component mode from the builder
m_entitiesAndComponentModes.emplace_back(entityComponentIdPair.GetEntityId(), componentModeBuilder());
}
// see if we are already storing a ComponentModeBuilder for this entity
const auto entityWithComponentBuilder = AZStd::find_if(
m_entitiesAndComponentModeBuilders.begin(), m_entitiesAndComponentModeBuilders.end(),
[entityComponentIdPair](const EntityAndComponentModeBuilders& entityAndComponentModeBuilder)
{
return entityAndComponentModeBuilder.m_entityId == entityComponentIdPair.GetEntityId();
});
// if we are, add the new ComponentModeBuilder to this entities list of ComponentModeBuilders
if (entityWithComponentBuilder != m_entitiesAndComponentModeBuilders.end())
{
entityWithComponentBuilder->m_componentModeBuilders.push_back(
ComponentModeBuilder(entityComponentIdPair.GetComponentId(), componentType, componentModeBuilder));
// if any of the already instantiated Component Modes are the same type as the one we're
// adding now, make sure we instantiate it as well
if (AZStd::any_of(m_entitiesAndComponentModes.begin(), m_entitiesAndComponentModes.end(),
[componentType](const EntityAndComponentMode& entityAndComponentMode)
{
return entityAndComponentMode.m_componentMode->GetComponentType() == componentType;
}))
{
m_entitiesAndComponentModes.emplace_back(
entityComponentIdPair.GetEntityId(), componentModeBuilder());
}
}
else
{
// otherwise create new EntityAndComponentModeBuilder entry
m_entitiesAndComponentModeBuilders.emplace_back(
entityComponentIdPair.GetEntityId(), AZStd::vector<ComponentModeBuilder>(
1, ComponentModeBuilder(entityComponentIdPair.GetComponentId(), componentType, componentModeBuilder)));
}
// notify the ComponentModeCollection ComponentModes are being added
// (we are transitioning to editor-wide ComponentMode)
m_adding = true;
}
}
void ComponentModeCollection::BeginComponentMode()
{
m_selectedComponentModeIndex = 0;
m_componentMode = true;
m_adding = false;
// notify listeners the editor has entered ComponentMode - listeners may
// wish to modify state to indicate this (e.g. appearance, functionality etc.)
EditorComponentModeNotificationBus::Event(
GetEntityContextId(), &EditorComponentModeNotifications::EnteredComponentMode,
m_activeComponentTypes);
// enable actions for the first/primary ComponentMode
// note: if multiple ComponentModes are activated at the same time, actions
// are not available together, the 'active' mode will bind its actions one at a time
if (!m_entitiesAndComponentModes.empty())
{
RefreshActions();
}
// if entering ComponentMode not as an undo/redo step (an action was
// taken to initiate), record it as an undo step
if (!UndoRedoOperationInProgress())
{
ScopedUndoBatch undoBatch(s_enteringComponentModeUndoRedoDesc);
auto componentModeCommand = AZStd::make_unique<ComponentModeCommand>(
ComponentModeCommand::Transition::Enter, AZStd::string(s_enteringComponentModeUndoRedoDesc),
m_entitiesAndComponentModeBuilders);
// componentModeCommand managed by undoBatch
componentModeCommand->SetParent(undoBatch.GetUndoBatch());
componentModeCommand.release();
}
}
void ComponentModeCollection::AddOtherSelectedEntityModes()
{
for (const auto& componentType : m_activeComponentTypes)
{
ComponentModeDelegateRequestBus::Broadcast(
&ComponentModeDelegateRequests::AddComponentModeOfType,
componentType);
}
}
bool ComponentModeCollection::AddedToComponentMode(
const AZ::EntityComponentIdPair& entityComponentIdPair, const AZ::Uuid& componentType)
{
// if we already have a builder for this entity, we will have
// created a mode from it, find it for the component id on this entity
const auto modeEntityIt = AZStd::find_if(
m_entitiesAndComponentModes.begin(), m_entitiesAndComponentModes.end(),
EntityAndComponentModePred(entityComponentIdPair));
if (modeEntityIt == m_entitiesAndComponentModes.end())
{
return false;
}
return componentType == modeEntityIt->m_componentMode->GetComponentType();
}
void ComponentModeCollection::EndComponentMode()
{
if (!UndoRedoOperationInProgress())
{
ScopedUndoBatch undoBatch(s_leavingComponentModeUndoRedoDesc);
auto componentModeCommand = AZStd::make_unique<ComponentModeCommand>(
ComponentModeCommand::Transition::Leave, AZStd::string(s_leavingComponentModeUndoRedoDesc),
m_entitiesAndComponentModeBuilders);
// componentModeCommand managed by undoBatch
componentModeCommand->SetParent(undoBatch.GetUndoBatch());
componentModeCommand.release();
}
// notify listeners the editor has left ComponentMode - listeners may
// wish to modify state to indicate this (e.g. appearance, functionality etc.)
EditorComponentModeNotificationBus::Event(
GetEntityContextId(),
&EditorComponentModeNotifications::LeftComponentMode,
m_activeComponentTypes);
// clear stored modes and builders for this ComponentMode
// TLDR: avoid 'use after free' error
// note: it is important to use pop_back() here to remove elements one at a
// time as opposed to clear() because there is a possibility a callback in a
// ComponentMode destructor may query state in ComponentModeCollection and the
// internal owned ComponentMode instance may have been destroyed while iterating.
while (!m_entitiesAndComponentModes.empty())
{
m_entitiesAndComponentModes.pop_back();
}
m_entitiesAndComponentModeBuilders.clear();
m_activeComponentTypes.clear();
m_componentMode = false;
m_selectedComponentModeIndex = 0;
}
void ComponentModeCollection::Refresh(const AZ::EntityComponentIdPair& entityComponentIdPair)
{
for (auto& entityAndComponentModes : m_entitiesAndComponentModes)
{
if (entityAndComponentModes.m_entityId == entityComponentIdPair.GetEntityId() &&
entityAndComponentModes.m_componentMode->GetComponentId() == entityComponentIdPair.GetComponentId())
{
entityAndComponentModes.m_componentMode->Refresh();
}
}
}
bool ComponentModeCollection::SelectNextActiveComponentMode()
{
const AZ::Uuid previousComponentType =
m_activeComponentTypes[m_selectedComponentModeIndex];
m_selectedComponentModeIndex =
(m_selectedComponentModeIndex + 1) % m_activeComponentTypes.size();
return ActiveComponentModeChanged(previousComponentType);
}
bool ComponentModeCollection::SelectPreviousActiveComponentMode()
{
const AZ::Uuid previousComponentType =
m_activeComponentTypes[m_selectedComponentModeIndex];
m_selectedComponentModeIndex =
(m_selectedComponentModeIndex + m_activeComponentTypes.size() - 1) % m_activeComponentTypes.size();
return ActiveComponentModeChanged(previousComponentType);
}
bool ComponentModeCollection::SelectActiveComponentMode(const AZ::Uuid& componentType)
{
// is this ComponentMode in the active set
const auto it = AZStd::find_if(m_activeComponentTypes.begin(), m_activeComponentTypes.end(),
[componentType](const AZ::Uuid& activeComponentType)
{
return componentType == activeComponentType;
});
if (it != m_activeComponentTypes.end())
{
const AZ::Uuid previousComponentType =
m_activeComponentTypes[m_selectedComponentModeIndex];
// calculate the selected index
m_selectedComponentModeIndex = it - m_activeComponentTypes.begin();
return ActiveComponentModeChanged(previousComponentType);
}
return false;
}
AZ::Uuid ComponentModeCollection::ActiveComponentMode() const
{
return m_activeComponentTypes[m_selectedComponentModeIndex];
}
bool ComponentModeCollection::ComponentModeInstantiated(const AZ::EntityComponentIdPair& entityComponentIdPair) const
{
// search through all instantiated Component Modes to see if we have one
// matching the requested Entity/Component pair
return AZStd::any_of(m_entitiesAndComponentModes.begin(), m_entitiesAndComponentModes.end(),
[entityComponentIdPair](const EntityAndComponentMode& entityAndComponentMode)
{
return entityAndComponentMode.m_entityId == entityComponentIdPair.GetEntityId() &&
entityAndComponentMode.m_componentMode->GetComponentId() == entityComponentIdPair.GetComponentId();
});
}
bool ComponentModeCollection::HasMultipleComponentTypes() const
{
return m_activeComponentTypes.size() > 1;
}
bool ComponentModeCollection::ActiveComponentModeChanged(const AZ::Uuid& previousComponentType)
{
if (m_activeComponentTypes[m_selectedComponentModeIndex] != previousComponentType)
{
// for each entity and its 'active' Component Mode
for (auto& componentMode : m_entitiesAndComponentModes)
{
// find the builders for this entity and component
const auto builderEntityIt = AZStd::find_if(
m_entitiesAndComponentModeBuilders.begin(), m_entitiesAndComponentModeBuilders.end(),
EntityAndComponentModeBuildersPred(AZ::EntityComponentIdPair(
componentMode.m_entityId, componentMode.m_componentMode->GetComponentId())));
// find the builder for the active component type
const auto& componentModeBuilder = AZStd::find_if(
builderEntityIt->m_componentModeBuilders.begin(),
builderEntityIt->m_componentModeBuilders.end(),
[this](const ComponentModeBuilder& componentModeBuilder)
{
return componentModeBuilder.m_componentType == m_activeComponentTypes[m_selectedComponentModeIndex];
});
// replace the current component mode by invoking the builder
// for the new 'active' component mode
componentMode.m_componentMode = componentModeBuilder->m_componentModeBuilder();
}
RefreshActions();
if (m_selectedComponentModeIndex < m_activeComponentTypes.size())
{
// notify other systems ComponentMode actions have changed
EditorComponentModeNotificationBus::Event(
GetEntityContextId(), &EditorComponentModeNotifications::ActiveComponentModeChanged,
m_activeComponentTypes[m_selectedComponentModeIndex]);
return true;
}
}
return false;
}
void ComponentModeCollection::RefreshActions()
{
// update actions for new component type
if (m_selectedComponentModeIndex < m_activeComponentTypes.size())
{
AZStd::vector<ActionOverride> allActions;
// iterate over all entities and their active Component Mode, populate actions for the new mode
for (auto& entityAndComponentMode : m_entitiesAndComponentModes)
{
// build actions based on current state
const auto actions = entityAndComponentMode.m_componentMode->PopulateActions();
allActions.insert(allActions.end(), actions.begin(), actions.end());
}
// we only want to show cycle options if we have multiple Component Modes active
AZStd::vector<ActionOverride> baseActions;
if (HasMultipleComponentTypes())
{
// cycle to next 'selected' ComponentMode actions
const ActionOverride nextComponentModeAction = ActionOverride()
.SetUri(s_nextComponentMode)
.SetKeySequence(QKeySequence(Qt::Key_Tab))
.SetTitle(s_nextActiveComponentModeTitle)
.SetTip(s_nextActiveComponentModeDesc)
.SetCallback([]()
{
ComponentModeSystemRequestBus::Broadcast(
&ComponentModeSystemRequests::SelectNextActiveComponentMode);
});
// cycle to previous 'selected' ComponentMode actions
const ActionOverride previousComponentModeAction = ActionOverride()
.SetUri(s_previousComponentMode)
.SetKeySequence(QKeySequence(Qt::SHIFT + Qt::Key_Tab))
.SetTitle(s_previousActiveComponentModeTitle)
.SetTip(s_prevActiveComponentModeDesc)
.SetCallback([]()
{
ComponentModeSystemRequestBus::Broadcast(
&ComponentModeSystemRequests::SelectPreviousActiveComponentMode);
});
baseActions = { nextComponentModeAction, previousComponentModeAction };
}
// default 'back' action to end ComponentMode
const ActionOverride backAction = ActionOverride()
.SetUri(s_backAction)
.SetKeySequence(QKeySequence(Qt::Key_Escape))
.SetTitle(s_leaveCompoenentModeTitle)
.SetTip(s_leaveCompoenentModeDesc)
.SetCallback([]()
{
ComponentModeSystemRequestBus::Broadcast(
&ComponentModeSystemRequests::EndComponentMode);
});
const size_t backActionInsertPosition = baseActions.size();
// always provide back action to leave Component Mode
baseActions.push_back(backAction);
// always insert 'escape' and 'cycle' actions at the beginning of the ComponentMode edit menu
// note: this is so the 'back' action (will be overridden in SetBoundActions if it is re-used,
// e.g. for deselecting a vertex)
allActions.insert(allActions.begin(), baseActions.begin(), baseActions.end());
// build list of currently bound actions
AZStd::vector<BoundOverrideActions> boundActions;
SetBoundActions(allActions, boundActions);
// rotate the 'back' action to the end of the vector so it always
// appear as the last item in the edit menu
AZStd::rotate(
boundActions.begin() + backActionInsertPosition,
boundActions.begin() + backActionInsertPosition + 1,
boundActions.end());
// clear any existing actions from a previous ComponentMode
ActionOverrideRequestBus::Event(
GetEntityContextId(), &ActionOverrideRequests::ClearActionOverrides);
// add all bound actions (to the ActionManager)
for (const auto& action : boundActions)
{
ActionOverrideRequestBus::Event(
GetEntityContextId(), &ActionOverrideRequests::AddActionOverride,
action.m_actionOverride);
}
}
}
} // namespace ComponentModeFramework
} // namespace AzToolsFramework
@@ -0,0 +1,110 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/std/containers/vector.h>
#include <AzToolsFramework/ComponentMode/ComponentModeViewportUi.h>
#include <AzToolsFramework/ComponentMode/EditorComponentModeBus.h>
namespace AzToolsFramework
{
class EditorMetricsEventsBusTraits;
namespace ComponentModeFramework
{
/// Manages all individual ComponentModes for a single instance of Editor wide ComponentMode.
class ComponentModeCollection
{
public:
AZ_CLASS_ALLOCATOR_DECL
/// @cond
ComponentModeCollection() = default;
~ComponentModeCollection() = default;
ComponentModeCollection(const ComponentModeCollection&) = delete;
ComponentModeCollection& operator=(const ComponentModeCollection&) = delete;
/// @endcond
/// Add a ComponentMode for a given Component type on the EntityId specified.
/// An \ref AZ::EntityComponentIdPair is provided so the individual Component on
/// the Entity can be addressed if there are more than one of the same type.
void AddComponentMode(
const AZ::EntityComponentIdPair& entityComponentIdPair, AZ::Uuid componentType,
const ComponentModeFactoryFunction& componentModeBuilder);
/// Refresh (update) all active ComponentModes for the specified Entity and Component Id pair.
void Refresh(const AZ::EntityComponentIdPair& entityComponentIdPair);
/// Begin Editor-wide ComponentMode.
/// Notify other systems a ComponentMode is starting and transition the Editor to the correct state.
void BeginComponentMode();
/// End Editor-wide ComponentMode
/// Leave all active ComponentModes and move the Editor back to its normal state.
void EndComponentMode();
/// Ensure entire Entity selection is moved to ComponentMode.
/// For all other selected entities, if they have a matching component that has just entered
/// ComponentMode, add them too (duplicates will not be added - handled by AddComponentMode)
void AddOtherSelectedEntityModes();
/// Return is the Editor-wide ComponentMode state active.
bool InComponentMode() const { return m_componentMode; }
/// Are ComponentModes in the process of being added.
/// Used to determine if other selected entities with the same Component type should also be added.
bool ModesAdded() const { return m_adding; }
/// Return if this Entity and its Component are currently in ComponentMode.
bool AddedToComponentMode(
const AZ::EntityComponentIdPair& entityComponentIdPair, const AZ::Uuid& componentType);
/// Move to the next active ComponentMode so the Actions for that mode become available (it is now 'selected').
bool SelectNextActiveComponentMode();
/// Move to the previous active ComponentMode so the Actions for that mode become available (it is now 'selected').
bool SelectPreviousActiveComponentMode();
/// Pick a specific ComponentMode for a Component (by directly selecting a Component in the EntityInspector - is it now 'selected').
bool SelectActiveComponentMode(const AZ::Uuid& componentType);
/// Return the Uuid of the Component Type that is currently active in Component Mode.
AZ::Uuid ActiveComponentMode() const;
/// Return if the ComponentMode for this specific Entity/Component pair is instantiated.
bool ComponentModeInstantiated(const AZ::EntityComponentIdPair& entityComponentIdPair) const;
/// Return if there is more than one Component type in Component Mode.
bool HasMultipleComponentTypes() const;
/// Refresh Actions (shortcuts) for the 'selected' ComponentMode.
void RefreshActions();
/// Populate Viewport UI elements to use for this ComponentMode.
/// Called once each time a ComponentMode is added.
void PopulateViewportUi();
private:
// Internal helper used by Select[|Prev|Next]ActiveComponentMode
bool ActiveComponentModeChanged(const AZ::Uuid& previousComponentType);
AZStd::vector<AZ::Uuid> m_activeComponentTypes; ///< What types of ComponentMode are currently active.
AZStd::vector<ComponentModeViewportUi> m_viewportUiHandlers; ///< Viewport UI handlers for each ComponentMode.
AZStd::vector<EntityAndComponentMode> m_entitiesAndComponentModes; ///< The active ComponentModes (one per Entity).
AZStd::vector<EntityAndComponentModeBuilders> m_entitiesAndComponentModeBuilders; ///< Factory functions to re-create specific modes
///< tied to a particular Entity (for undo/redo).
size_t m_selectedComponentModeIndex = 0; ///< Index into the array of active ComponentModes, current index is 'selected' ComponentMode.
bool m_adding = false; ///< Are we currently adding individual ComponentModes to the Editor wide ComponentMode.
bool m_componentMode = false; ///< Editor (global) ComponentMode flag - is ComponentMode active or not.
};
} // namespace ComponentModeFramework
} // namespace AzToolsFramework
@@ -0,0 +1,387 @@
/*
* 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 "ComponentModeDelegate.h"
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzToolsFramework/Application/ToolsApplication.h>
#include <AzToolsFramework/Entity/EditorEntityHelpers.h>
#include <AzToolsFramework/Viewport/ViewportMessages.h>
namespace AzToolsFramework
{
namespace ComponentModeFramework
{
namespace Internal
{
struct EditorComponentModeNotificationBusHandler final
: public EditorComponentModeNotificationBus::Handler
, public AZ::BehaviorEBusHandler
{
AZ_EBUS_BEHAVIOR_BINDER(EditorComponentModeNotificationBusHandler, "{AD2F4204-0913-4FC9-9A10-492538F60C70}", AZ::SystemAllocator,
EnteredComponentMode, LeftComponentMode, ActiveComponentModeChanged);
void EnteredComponentMode(const AZStd::vector<AZ::Uuid>& componentTypes) override
{
Call(FN_EnteredComponentMode, componentTypes);
}
void LeftComponentMode(const AZStd::vector<AZ::Uuid>& componentTypes) override
{
Call(FN_LeftComponentMode, componentTypes);
}
void ActiveComponentModeChanged(const AZ::Uuid& componentType) override
{
Call(FN_ActiveComponentModeChanged, componentType);
}
};
}
static const char* const s_componentModeEnterDescription =
"In this mode, you can only edit properties for this component. "
"All other components on the entity are locked.";
static const char* const s_componentModeLeaveDescription =
"Return to normal viewport editing";
// was the double click on the component or off it (select/deselect)
enum class DoubleClickOutcome
{
OnComponent,
OffComponent,
None,
};
static bool EnterComponentModeButtonVisible()
{
return !InComponentMode();
}
static bool LeaveComponentModeButtonVisible()
{
return InComponentMode();
}
static DoubleClickOutcome DoubleClickedComponent(
const ViewportInteraction::MouseInteractionEvent& mouseInteraction,
EditorComponentSelectionRequestsBus::Handler* editorComponentSelection)
{
if (mouseInteraction.m_mouseEvent == ViewportInteraction::MouseEvent::DoubleClick &&
mouseInteraction.m_mouseInteraction.m_mouseButtons.Left())
{
if (editorComponentSelection)
{
float distance;
if (editorComponentSelection->EditorSelectionIntersectRayViewport(
{ mouseInteraction.m_mouseInteraction.m_interactionId.m_viewportId },
mouseInteraction.m_mouseInteraction.m_mousePick.m_rayOrigin,
mouseInteraction.m_mouseInteraction.m_mousePick.m_rayDirection,
distance))
{
return DoubleClickOutcome::OnComponent;
}
}
return DoubleClickOutcome::OffComponent;
}
return DoubleClickOutcome::None;
}
static bool ShouldDetectEnterLeaveComponentMode(
const ViewportInteraction::MouseInteractionEvent& mouseInteraction)
{
return mouseInteraction.m_mouseInteraction.m_mouseButtons.Left() &&
mouseInteraction.m_mouseEvent == ViewportInteraction::MouseEvent::DoubleClick;
}
static bool EditorRequestingGame()
{
bool requestingGame = false;
AzToolsFramework::EditorEntityContextRequestBus::BroadcastResult(
requestingGame, &AzToolsFramework::EditorEntityContextRequestBus::Events::IsEditorRequestingGame);
return requestingGame;
}
static bool EntityHasPendingComponents(const AZ::EntityId entityId)
{
AZ::Entity::ComponentArrayType pendingComponents;
EditorPendingCompositionRequestBus::Event(
entityId, &EditorPendingCompositionRequestBus::Events::GetPendingComponents,
pendingComponents);
return !pendingComponents.empty();
}
static bool CanEnterComponentMode(const AZ::EntityId entityId)
{
// if the editor is transitioning to game mode or if any entities in the selection are not
// selectable (invisible/locked) or if any components are in a pending state (a conflict is
// present), make it impossible to enter Component Mode
return !EditorRequestingGame() && IsSelectableInViewport(entityId) && !EntityHasPendingComponents(entityId);
}
void ComponentModeDelegate::Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<ComponentModeDelegate>()
->Version(1)
;
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
{
editContext->Class<ComponentModeDelegate>(
"Component Mode", "Provides advanced editing of Components.")
->UIElement(AZ::Edit::UIHandlers::Button, "", s_componentModeEnterDescription)
->Attribute(AZ::Edit::Attributes::ChangeNotify, &ComponentModeDelegate::OnComponentModeEnterButtonPressed)
->Attribute(AZ::Edit::Attributes::ButtonText, "Edit")
->Attribute(AZ::Edit::Attributes::Visibility, &EnterComponentModeButtonVisible)
//->Attribute(AZ::Edit::Attributes::AcceptsMultiEdit, true) // disable temporarily until editor updates are integrated
->Attribute(AZ::Edit::Attributes::ReadOnly, &ComponentModeDelegate::ComponentModeButtonInactive)
->UIElement(AZ::Edit::UIHandlers::Button, "", s_componentModeLeaveDescription)
->Attribute(AZ::Edit::Attributes::ChangeNotify, &ComponentModeDelegate::OnComponentModeLeaveButtonPressed)
->Attribute(AZ::Edit::Attributes::ButtonText, "Done")
->Attribute(AZ::Edit::Attributes::Visibility, &LeaveComponentModeButtonVisible)
//->Attribute(AZ::Edit::Attributes::AcceptsMultiEdit, true) // disable temporarily until editor fixes are integrated
;
}
}
if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->EBus<ComponentModeSystemRequestBus>("ComponentModeSystemRequestBus")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
->Attribute(AZ::Script::Attributes::Category, "Editor")
->Attribute(AZ::Script::Attributes::Module, "editor")
->Event("EnterComponentMode", &ComponentModeSystemRequests::AddSelectedComponentModesOfType)
->Event("EndComponentMode", &ComponentModeSystemRequests::EndComponentMode)
;
behaviorContext->EBus<EditorComponentModeNotificationBus>("EditorComponentModeNotificationBus")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
->Attribute(AZ::Script::Attributes::Category, "Editor")
->Attribute(AZ::Script::Attributes::Module, "editor")
->Handler<Internal::EditorComponentModeNotificationBusHandler>()
->Event("EnteredComponentMode", &EditorComponentModeNotifications::EnteredComponentMode)
->Event("LeftComponentMode", &EditorComponentModeNotifications::LeftComponentMode)
->Event("ActiveComponentModeChanged", &EditorComponentModeNotifications::ActiveComponentModeChanged)
;
}
}
bool ComponentModeDelegate::AddedToComponentMode()
{
bool addedToComponentMode = false;
ComponentModeSystemRequestBus::BroadcastResult(
addedToComponentMode, &ComponentModeSystemRequests::AddedToComponentMode,
m_entityComponentIdPair, m_componentType);
return addedToComponentMode;
}
void ComponentModeDelegate::SetAddComponentModeCallback(
const AZStd::function<void(const AZ::EntityComponentIdPair&)>& addComponentModeCallback)
{
m_addComponentMode = addComponentModeCallback;
}
void ComponentModeDelegate::OnComponentModeEnterButtonPressed()
{
// ensure we aren't already in ComponentMode and are not also attempting to enter game mode
if (!InComponentMode() && !EditorRequestingGame())
{
// move all selected components into ComponentMode
ComponentModeSystemRequestBus::Broadcast(
&ComponentModeSystemRequests::AddSelectedComponentModesOfType,
m_componentType);
}
}
void ComponentModeDelegate::OnComponentModeLeaveButtonPressed()
{
if (InComponentMode())
{
// move the editor out of ComponentMode
ComponentModeSystemRequestBus::Broadcast(
&ComponentModeSystemRequests::EndComponentMode);
}
}
void ComponentModeDelegate::AddComponentMode()
{
if (m_addComponentMode)
{
m_addComponentMode(m_entityComponentIdPair);
}
}
void ComponentModeDelegate::ConnectInternal(
const AZ::EntityComponentIdPair& entityComponentIdPair, const AZ::Uuid componentType,
EditorComponentSelectionRequestsBus::Handler* handler)
{
m_handler = handler; // could be null
m_entityComponentIdPair = entityComponentIdPair;
m_componentType = componentType;
EntitySelectionEvents::Bus::Handler::BusConnect(entityComponentIdPair.GetEntityId());
EditorEntityVisibilityNotificationBus::Handler::BusConnect(entityComponentIdPair.GetEntityId());
EditorEntityLockComponentNotificationBus::Handler::BusConnect(entityComponentIdPair.GetEntityId());
}
void ComponentModeDelegate::Disconnect()
{
EditorEntityLockComponentNotificationBus::Handler::BusDisconnect();
EditorEntityVisibilityNotificationBus::Handler::BusDisconnect();
EntitySelectionEvents::Bus::Handler::BusDisconnect();
}
void ComponentModeDelegate::OnSelected()
{
ComponentModeDelegateRequestBus::Handler::BusConnect(m_entityComponentIdPair);
}
void ComponentModeDelegate::OnDeselected()
{
ComponentModeDelegateRequestBus::Handler::BusDisconnect();
}
bool ComponentModeDelegate::DetectEnterComponentModeInteraction(
const ViewportInteraction::MouseInteractionEvent& mouseInteraction)
{
if (!ShouldDetectEnterLeaveComponentMode(mouseInteraction) ||
DoubleClickedComponent(mouseInteraction, m_handler) != DoubleClickOutcome::OnComponent)
{
return false;
}
if (!CanEnterComponentMode(m_entityComponentIdPair.GetEntityId()))
{
// note: return true here to indicate attempted to enter component mode,
// we do not want the attempted double-click to deselect the entity
return true;
}
EntityIdList entityIds;
AzToolsFramework::ToolsApplicationRequestBus::BroadcastResult(
entityIds, &AzToolsFramework::ToolsApplicationRequests::GetSelectedEntities);
AZStd::vector<AZ::Uuid> componentTypes;
AZ::Entity::ComponentArrayType components;
// reserve small initial buffer for common case
components.reserve(8);
componentTypes.reserve(8);
// build a list of all components on each entity in the current selection
for (AZ::EntityId entityId : entityIds)
{
components.clear();
// get all components related to the entity
GetAllComponentsForEntity(GetEntity(entityId), components);
RemoveHiddenComponents(components);
AZStd::transform(
components.begin(), components.end(), AZStd::back_inserter(componentTypes),
[](const AZ::Component* component) { return component->GetUnderlyingComponentType(); });
}
// count how many components of our type are in the selection
const size_t componentCount =
AZStd::count_if(componentTypes.begin(), componentTypes.end(),
[this](const AZ::Uuid& componentType) { return componentType == m_componentType; });
// if the count matches the entity selection size, we know each entity has a
// component of that type, and so it will be displaying in the Entity Outliner
// if this is the case we know it is safe to enter ComponentMode
if (componentCount == entityIds.size())
{
AddComponentMode();
}
// we still want to notify the outside world an attempt was made to enter
// ComponentMode - ComponentModeCollection::ModesAdded() must be called
// to determine if ComponentMode was actually entered
return true;
}
bool ComponentModeDelegate::DetectLeaveComponentModeInteraction(
const ViewportInteraction::MouseInteractionEvent& mouseInteraction)
{
if (ShouldDetectEnterLeaveComponentMode(mouseInteraction))
{
if (DoubleClickedComponent(mouseInteraction, m_handler) == DoubleClickOutcome::OffComponent)
{
ComponentModeSystemRequestBus::Broadcast(
&ComponentModeSystemRequests::EndComponentMode);
return true;
}
}
return false;
}
void ComponentModeDelegate::AddComponentModeOfType(const AZ::Uuid componentType)
{
if (m_componentType == componentType)
{
AddComponentMode();
}
}
bool CouldBeginComponentModeWithEntity(const AZ::EntityId entityId)
{
EntityIdList selectedEntityIds;
ToolsApplicationRequests::Bus::BroadcastResult(
selectedEntityIds, &ToolsApplicationRequests::GetSelectedEntities);
// handles both having no entities selected and when an entity inspector is
// pinned on an entity that's not selected and a different entity is selected
bool canBegin = AZStd::find(
selectedEntityIds.cbegin(), selectedEntityIds.cend(), entityId) != selectedEntityIds.cend();
if (canBegin)
{
for (auto&& selectedEntityId : selectedEntityIds)
{
if (!CanEnterComponentMode(selectedEntityId))
{
canBegin = false;
break;
}
}
}
return canBegin;
}
bool ComponentModeDelegate::ComponentModeButtonInactive() const
{
return !CouldBeginComponentModeWithEntity(m_entityComponentIdPair.GetEntityId());
}
void ComponentModeDelegate::OnEntityVisibilityChanged(bool /*visibility*/)
{
ToolsApplicationNotificationBus::Broadcast(
&ToolsApplicationNotificationBus::Events::InvalidatePropertyDisplay, Refresh_AttributesAndValues);
}
void ComponentModeDelegate::OnEntityLockChanged(bool /*locked*/)
{
ToolsApplicationNotificationBus::Broadcast(
&ToolsApplicationNotificationBus::Events::InvalidatePropertyDisplay, Refresh_AttributesAndValues);
}
} // namespace ComponentModeFramework
} // namespace AzToolsFramework
@@ -0,0 +1,147 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/Memory/SystemAllocator.h>
#include <AzToolsFramework/API/ComponentEntitySelectionBus.h>
#include <AzToolsFramework/ComponentMode/EditorComponentModeBus.h>
#include <AzToolsFramework/ToolsComponents/EditorLockComponentBus.h>
#include <AzToolsFramework/ToolsComponents/EditorVisibilityBus.h>
namespace AzToolsFramework
{
namespace ComponentModeFramework
{
/// Utility factory function to create a ComponentModeBuilder for a specific EditorComponent
template<typename EditorComponentType, typename EditorComponentModeType>
ComponentModeBuilder CreateComponentModeBuilder(const AZ::EntityComponentIdPair& entityComponentIdPair)
{
const auto componentModeBuilderFunc = [entityComponentIdPair]()
{
return AZStd::make_unique<EditorComponentModeType>(
entityComponentIdPair, AZ::AzTypeInfo<EditorComponentType>::Uuid());
};
return ComponentModeBuilder(
entityComponentIdPair.GetComponentId(), AZ::AzTypeInfo<EditorComponentType>::Uuid(), componentModeBuilderFunc);
}
/// Helper to provide ComponentMode button in the Entity Inspector and double click
/// handling in the viewport for entering/exiting ComponentMode.
class ComponentModeDelegate
: private ComponentModeDelegateRequestBus::Handler
, private EntitySelectionEvents::Bus::Handler
, private EditorEntityVisibilityNotificationBus::Handler
, private EditorEntityLockComponentNotificationBus::Handler
{
public:
/// @cond
AZ_CLASS_ALLOCATOR(ComponentModeDelegate, AZ::SystemAllocator, 0);
AZ_RTTI(ComponentModeDelegate, "{635B28F0-601A-43D2-A42A-02C4A88CD9C2}");
static void Reflect(AZ::ReflectContext* context);
/// @endcond
/// Connect the ComponentModeDelegate to listen for Editor selection events.
/// Editor Component must call Connect (or variant of Connect), usually in Component::Activate, and
/// Disconnect, most likely in Component::Deactivate.
template<typename EditorComponentType>
void Connect(
const AZ::EntityComponentIdPair& entityComponentIdPair, EditorComponentSelectionRequestsBus::Handler* handler)
{
ConnectInternal(entityComponentIdPair, AZ::AzTypeInfo<EditorComponentType>::Uuid(), handler);
}
/// Connect the ComponentModeDelegate to listen for Editor selection events and
/// simultaneously add a single concrete ComponentMode (common case utility).
template<typename EditorComponentType, typename EditorComponentModeType>
void ConnectWithSingleComponentMode(
const AZ::EntityComponentIdPair& entityComponentIdPair, EditorComponentSelectionRequestsBus::Handler* handler)
{
Connect<EditorComponentType>(entityComponentIdPair, handler);
IndividualComponentMode<EditorComponentType, EditorComponentModeType>();
}
/// Disconnect the ComponentModeDelegate to stop listening for Editor selection events.
void Disconnect();
/// Has this specific ComponentModeDelegate (for a specific Entity and Component)
/// been added to ComponentMode.
bool AddedToComponentMode();
/// The function to call when this ComponentModeDelegate detects an event to enter ComponentMode.
void SetAddComponentModeCallback(
const AZStd::function<void(const AZ::EntityComponentIdPair&)>& addComponentModeCallback);
private:
void ConnectInternal(
const AZ::EntityComponentIdPair& entityComponentIdPair, AZ::Uuid componentType,
EditorComponentSelectionRequestsBus::Handler* handler);
/// Utility function for the common case of creating a single ComponentMode for a Component.
template<typename EditorComponentType, typename EditorComponentModeType>
void IndividualComponentMode()
{
SetAddComponentModeCallback([](const AZ::EntityComponentIdPair& entityComponentIdPair)
{
const auto componentModeBuilder =
CreateComponentModeBuilder<EditorComponentType, EditorComponentModeType>(entityComponentIdPair);
const auto entityAndComponentModeBuilder =
EntityAndComponentModeBuilders(entityComponentIdPair.GetEntityId(), componentModeBuilder);
ComponentModeSystemRequestBus::Broadcast(
&ComponentModeSystemRequests::AddComponentModes, entityAndComponentModeBuilder);
});
}
void AddComponentMode();
// EntitySelectionEvents
void OnSelected() override;
void OnDeselected() override;
// ComponentModeDelegateRequestBus
bool DetectEnterComponentModeInteraction(
const ViewportInteraction::MouseInteractionEvent& mouseInteraction) override;
bool DetectLeaveComponentModeInteraction(
const ViewportInteraction::MouseInteractionEvent& mouseInteraction) override;
void AddComponentModeOfType(AZ::Uuid componentType) override;
// EditorEntityVisibilityNotificationBus
void OnEntityVisibilityChanged(bool visibility) override;
// EditorEntityLockComponentNotificationBus
void OnEntityLockChanged(bool locked) override;
/// Is the ComponentMode button active/operational.
/// It will not be if the entity with this component is either locked or hidden.
/// It will also not be active if the Entity is pinned but not selected.
bool ComponentModeButtonInactive() const;
AZ::Uuid m_componentType; ///< The type of component entering ComponentMode.
AZ::EntityComponentIdPair m_entityComponentIdPair; ///< The Entity and Component Id this ComponentMode is bound to.
EditorComponentSelectionRequestsBus::Handler* m_handler = nullptr; /**< Selection handler (used for double clicking
* on a component to enter ComponentMode). */
AZStd::function<void(const AZ::EntityComponentIdPair&)> m_addComponentMode; ///< Callback to add ComponentMode for this component.
/// ComponentMode Button
void OnComponentModeEnterButtonPressed();
void OnComponentModeLeaveButtonPressed();
};
/// If this Entity had a Component supporting a ComponentMode, would
/// it be possible for it to enter it given the current Editor state.
bool CouldBeginComponentModeWithEntity(AZ::EntityId entityId);
} // namespace ComponentModeFramework
} // namespace AzToolsFramework
@@ -0,0 +1,119 @@
/*
* 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 "ComponentModeViewportUi.h"
#include <AzToolsFramework/Entity/EditorEntityHelpers.h>
#include <AzToolsFramework/ViewportUi/ViewportUiRequestBus.h>
#include <QString>
#include <QLabel>
#include <QWidget>
#include <QLayout>
namespace AzToolsFramework
{
namespace ComponentModeFramework
{
ComponentModeViewportUi::ComponentModeViewportUi(AZ::Uuid uuid)
: m_componentType(uuid)
{
ComponentModeViewportUiRequestBus::Handler::BusConnect(uuid);
}
ComponentModeViewportUi::~ComponentModeViewportUi()
{
ComponentModeViewportUiRequestBus::Handler::BusDisconnect();
}
void ComponentModeViewportUi::RegisterViewportElementGroup(
const AZ::EntityComponentIdPair& entityComponentId,
const AZStd::vector<ViewportUi::ClusterId>& clusterIds)
{
if (clusterIds.empty())
{
return;
}
// check if a widget is already registered with the entity component id pair
// and that the registered widget has not been deleted
if (auto widgetMapEntry = m_entityComponentWidgetMap.find(entityComponentId);
widgetMapEntry != m_entityComponentWidgetMap.end()
&& !widgetMapEntry->second.empty())
{
return;
}
m_entityComponentWidgetMap.insert({ entityComponentId, clusterIds });
}
void ComponentModeViewportUi::ShowActiveViewportElements()
{
ViewportUi::ViewportUiRequestBus::Event(
ViewportUi::DefaultViewportId,
&ViewportUi::ViewportUiRequestBus::Events::SetClusterGroupVisible,
m_activeViewportIds, true);
}
void ComponentModeViewportUi::HideActiveViewportElements()
{
ViewportUi::ViewportUiRequestBus::Event(
ViewportUi::DefaultViewportId,
&ViewportUi::ViewportUiRequestBus::Events::SetClusterGroupVisible,
m_activeViewportIds, false);
}
void ComponentModeViewportUi::SetComponentModeViewportUiActive(bool active)
{
// hide all the elements when deactivating
if (!active)
{
if (!m_active)
{
return;
}
HideActiveViewportElements();
m_active = active;
return;
}
// broadcast to all other component mode handlers that they should not be active
// as only one can be active at a time
ComponentModeViewportUiRequestBus::Broadcast(
&ComponentModeViewportUiRequestBus::Events::SetComponentModeViewportUiActive, false);
// once all other handlers are inactive, activate just this handler
m_active = active;
if (const auto activeViewportIds = m_entityComponentWidgetMap.find(m_activeEntityComponentId);
activeViewportIds != m_entityComponentWidgetMap.end())
{
// set all of the newly active viewport ids to visible
m_activeViewportIds = activeViewportIds->second;
ShowActiveViewportElements();
}
}
void ComponentModeViewportUi::SetViewportUiActiveEntityComponentId(
const AZ::EntityComponentIdPair& entityComponentId)
{
if (m_activeEntityComponentId != entityComponentId)
{
HideActiveViewportElements();
}
m_activeEntityComponentId = entityComponentId;
SetComponentModeViewportUiActive(true);
}
} // namespace ComponentModeFramework
} // namespace AzToolsFramework
@@ -0,0 +1,60 @@
/*
* 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.
*
*/
#pragma once
#include "ComponentModeViewportUiRequestBus.h"
#include <AzToolsFramework/ComponentMode/EditorComponentModeBus.h>
#include <AzToolsFramework/Viewport/ActionBus.h>
namespace AzToolsFramework
{
namespace ComponentModeFramework
{
//! Handles adding Viewport UI widgets from a specific ComponentMode onto the render viewport UI.
class ComponentModeViewportUi
: public ComponentModeViewportUiRequestBus::Handler
{
public:
explicit ComponentModeViewportUi(AZ::Uuid uuid);
ComponentModeViewportUi(ComponentModeViewportUi&&) = default;
ComponentModeViewportUi& operator=(ComponentModeViewportUi&&) = default;
~ComponentModeViewportUi();
//! ComponentModeViewportUiRequestBus ...
void RegisterViewportElementGroup(
const AZ::EntityComponentIdPair& entityComponentId,
const AZStd::vector<ViewportUi::ClusterId>& clusterIds) override;
void SetComponentModeViewportUiActive(bool active) override;
void SetViewportUiActiveEntityComponentId(const AZ::EntityComponentIdPair& entityComponentId) override;
//! The underlying Component type for this ComponentMode.
AZ::Uuid GetComponentType() const { return m_componentType; }
private:
//! Show all active widgets to display on the viewport.
void ShowActiveViewportElements();
//! Hide all active widgets displaying on the viewport.
void HideActiveViewportElements();
using EntityComponentWidgetMapping = AZStd::unordered_map<AZ::EntityComponentIdPair, AZStd::vector<ViewportUi::ClusterId>>;
EntityComponentWidgetMapping m_entityComponentWidgetMap; //!< Map of all EntityComponentIdPairs to their respective widgets.
AZ::EntityComponentIdPair m_activeEntityComponentId; //!< The current active entityComponentId to display on the viewport.
AZStd::vector<ViewportUi::ClusterId> m_activeViewportIds; //!< Ids of all active viewport widgets displaying on the viewport.
bool m_active = false; //!< Whether this ComponentMode is active and should display on the viewport.
AZ::Uuid m_componentType; //!< The component type that this handler is connected to.
};
} // namespace ComponentModeFramework
} // namespace AzToolsFramework
@@ -0,0 +1,48 @@
/*
* 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.
*
*/
#pragma once
#include <AzToolsFramework/ViewportUi/ViewportUiRequestBus.h>
#include <AzCore/Component/ComponentBus.h>
#include <QString>
#include <QPointer>
#include <QWidget>
namespace AzToolsFramework
{
namespace ComponentModeFramework
{
//! Bus for interacting with the widget that handles Viewport UI for component mode.
class ComponentModeViewportUiRequests
: public AZ::EBusTraits
{
public:
using BusIdType = AZ::Uuid;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
//! Set the given clusterIds to visible in the Viewport UI.
virtual void RegisterViewportElementGroup(
const AZ::EntityComponentIdPair& entityComponentIdPair,
const AZStd::vector<ViewportUi::ClusterId>& clusterIds) = 0;
//! Set this handler to be the active (on display) handler that will display in the Viewport UI.
virtual void SetComponentModeViewportUiActive(bool) = 0;
//! Set this handler to display for this specific EntityComponentIdPair only.
virtual void SetViewportUiActiveEntityComponentId(const AZ::EntityComponentIdPair& entityComponentId) = 0;
};
//! Type to inherit to implement ComponentModeSystemRequests.
using ComponentModeViewportUiRequestBus = AZ::EBus<ComponentModeViewportUiRequests>;
} // namespace ComponentModeFramework
} // namespace AzToolsFramework
@@ -0,0 +1,94 @@
/*
* 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 "EditorBaseComponentMode.h"
#include "ComponentModeViewportUiRequestBus.h"
#include <AzToolsFramework/Entity/EditorEntityHelpers.h>
namespace AzToolsFramework
{
namespace ComponentModeFramework
{
AZ_CLASS_ALLOCATOR_IMPL(EditorBaseComponentMode, AZ::SystemAllocator, 0)
EditorBaseComponentMode::EditorBaseComponentMode(
const AZ::EntityComponentIdPair& entityComponentIdPair, const AZ::Uuid componentType)
: m_entityComponentIdPair(entityComponentIdPair), m_componentType(componentType)
{
const AZ::EntityId entityId = entityComponentIdPair.GetEntityId();
AZ_Assert(entityId.IsValid(), "Attempting to create a Component Mode with an invalid EntityId");
if (const AZ::Entity* entity = AzToolsFramework::GetEntity(entityId))
{
AZ_Assert(entity->GetState() == AZ::Entity::State::Active,
"Attempting to create a Component Mode for an Entity which is not currently active. "
"Its current state is %u", entity->GetState());
}
ComponentModeRequestBus::Handler::BusConnect(m_entityComponentIdPair);
ToolsApplicationNotificationBus::Handler::BusConnect();
}
EditorBaseComponentMode::~EditorBaseComponentMode()
{
ToolsApplicationNotificationBus::Handler::BusDisconnect();
ComponentModeRequestBus::Handler::BusDisconnect();
}
void EditorBaseComponentMode::AfterUndoRedo()
{
Refresh();
}
AZStd::vector<ViewportUi::ClusterId> EditorBaseComponentMode::PopulateViewportUi()
{
auto elementIdsToDisplay = PopulateViewportUiImpl();
// register all of the elements in ComponentModeViewportUi system with corresponding EntityComponentIdPair
ComponentModeViewportUiRequestBus::Event(
GetComponentType(), &ComponentModeViewportUiRequestBus::Events::RegisterViewportElementGroup,
GetEntityComponentIdPair(), elementIdsToDisplay);
// create the component mode border with the specific name for this component mode
ViewportUi::ViewportUiRequestBus::Event(
ViewportUi::DefaultViewportId, &ViewportUi::ViewportUiRequestBus::Events::CreateComponentModeBorder,
GetComponentModeName());
// set the EntityComponentId for this ComponentMode to active in the ComponentModeViewportUi system
ComponentModeViewportUiRequestBus::Event(
GetComponentType(), &ComponentModeViewportUiRequestBus::Events::SetViewportUiActiveEntityComponentId,
GetEntityComponentIdPair());
return elementIdsToDisplay;
}
AZStd::vector<ViewportUi::ClusterId> EditorBaseComponentMode::PopulateViewportUiImpl()
{
return AZStd::vector<ViewportUi::ClusterId>();
}
AZStd::vector<ActionOverride> EditorBaseComponentMode::PopulateActionsImpl()
{
return AZStd::vector<ActionOverride>{};
}
AZStd::vector<ActionOverride> EditorBaseComponentMode::PopulateActions()
{
return PopulateActionsImpl();
}
void EditorBaseComponentMode::PostHandleMouseInteraction()
{
ComponentModeViewportUiRequestBus::Event(
GetComponentType(),
&ComponentModeViewportUiRequestBus::Events::SetViewportUiActiveEntityComponentId,
GetEntityComponentIdPair());
}
} // namespace ComponentModeFramework
} // namespace AzToolsFramework
@@ -0,0 +1,82 @@
/*
* 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.
*
*/
#pragma once
#include <AzToolsFramework/ComponentMode/EditorComponentModeBus.h>
#include <AzToolsFramework/Viewport/ActionBus.h>
namespace AzToolsFramework
{
namespace ComponentModeFramework
{
/// Abstract class to be inherited from by concrete ComponentModes.
/// Exposes ComponentMode interface and handles some useful common
/// functionality all ComponentModes require.
class EditorBaseComponentMode
: public ComponentModeRequestBus::Handler
, private ToolsApplicationNotificationBus::Handler
{
public:
AZ_CLASS_ALLOCATOR_DECL
/// @cond
EditorBaseComponentMode(
const AZ::EntityComponentIdPair& entityComponentIdPair, AZ::Uuid componentType);
EditorBaseComponentMode(EditorBaseComponentMode&&) = default;
EditorBaseComponentMode& operator=(EditorBaseComponentMode&&) = default;
virtual ~EditorBaseComponentMode();
/// @endcond
/// ComponentMode interface - populate actions for this ComponentMode.
/// When PopulateActions is called, if a second action override is found with the
/// same key, it should override the existing action if one already exists.
/// (e.g. The 'escape' key will first deselect a vertex, then leave ComponentMode if
/// an action is added to deselect a vertex when one is selected)
/// @attention More specific actions come later in the ordering when they are added.
AZStd::vector<ActionOverride> PopulateActions() final;
/// Populate the Viewport UI widget for this ComponentMode.
AZStd::vector<ViewportUi::ClusterId> PopulateViewportUi() final;
/// ComponentModeRequestBus ...
void PostHandleMouseInteraction() final;
protected:
/// The EntityId this ComponentMode instance is associated with.
AZ::EntityId GetEntityId() const { return m_entityComponentIdPair.GetEntityId(); }
/// The combined Entity and Component Id to uniquely identify a specific Component on a given Entity.
/// Note: This is required when more than one Component of the same type can exists on an Entity at a time.
AZ::EntityComponentIdPair GetEntityComponentIdPair() const { return m_entityComponentIdPair; }
/// The ComponentId this ComponentMode instance is associated with.
AZ::ComponentId GetComponentId() const final { return m_entityComponentIdPair.GetComponentId(); }
/// The underlying Component type for this ComponentMode.
AZ::Uuid GetComponentType() const final { return m_componentType; }
/// EditorBaseComponentMode interface
/// @see To be overridden by derived ComponentModes
virtual AZStd::vector<ViewportUi::ClusterId> PopulateViewportUiImpl();
private:
/// EditorBaseComponentMode interface
/// @see To be overridden by derived ComponentModes
virtual AZStd::vector<ActionOverride> PopulateActionsImpl();
// ToolsApplicationNotificationBus
void AfterUndoRedo() override;
AZ::EntityComponentIdPair m_entityComponentIdPair; ///< Entity and Component Id associated with this ComponentMode.
AZ::Uuid m_componentType; ///< The underlying type of the Component this ComponentMode is for.
};
} // namespace ComponentModeFramework
} // namespace AzToolsFramework
@@ -0,0 +1,309 @@
/*
* 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.
*
*/
#pragma once
#include <AzToolsFramework/Viewport/ViewportMessages.h>
#include <AzToolsFramework/Viewport/ActionBus.h>
#include <AzToolsFramework/ViewportUi/ViewportUiRequestBus.h>
namespace AzToolsFramework
{
/// Encompasses all types responsible for providing ComponentMode.
/// ComponentModeFramework comprises a set of types responsible for providing hooks
/// to edit Components in the Viewport. It is also responsible for handling how
/// the Editor transitions in and out of ComponentMode.
namespace ComponentModeFramework
{
/// Foundational interface for any Component wishing to implement ComponentMode.
/// Components wishing to implement ComponentMode should inherit from
/// EditorBaseComponentMode, not ComponentMode directly.
class ComponentMode
{
public:
/// @cond
ComponentMode() = default;
ComponentMode(ComponentMode&) = delete;
ComponentMode& operator=(ComponentMode&) = delete;
ComponentMode(ComponentMode&&) = default;
ComponentMode& operator=(ComponentMode&&) = default;
virtual ~ComponentMode() = default;
/// @endcond
/// The type of the underlying Component this mode is for.
virtual AZ::Uuid GetComponentType() const = 0;
/// The Id of the underlying Component this mode is associated with.
virtual AZ::ComponentId GetComponentId() const = 0;
/// Notify ComponentMode that something external to it has
/// changed and its state should be updated (Manipulators etc).
virtual void Refresh() = 0;
/// Notify the ActionManager which actions should be enabled for this Mode.
virtual AZStd::vector<ActionOverride> PopulateActions() = 0;
/// Add UI elements for this ComponentMode onto the Viewport UI system.
virtual AZStd::vector<ViewportUi::ClusterId> PopulateViewportUi() = 0;
/// The name for the ComponentMode to be displayed.
virtual AZStd::string GetComponentModeName() const { return "Edit Mode"; }
};
/// Alias for builder/factory function that is responsible for creating a new ComponentMode.
using ComponentModeFactoryFunction = AZStd::function<AZStd::unique_ptr<ComponentMode>()>;
/// Holds a function object to create a ComponentMode for a specific type.
struct ComponentModeBuilder
{
AZ::ComponentId m_componentId; ///< The unique Id of the underlying Component.
AZ::Uuid m_componentType; ///< The type of the underlying Component.
ComponentModeFactoryFunction m_componentModeBuilder; ///< Factory function to create a specific ComponentMode.
/// Constructor to bind a Component type to a concrete ComponentMode.
ComponentModeBuilder(
const AZ::ComponentId componentId,
const AZ::Uuid componentType,
const ComponentModeFactoryFunction& componentModeBuilder)
: m_componentId(componentId)
, m_componentType(componentType)
, m_componentModeBuilder(componentModeBuilder) {}
};
/// Encapsulates an Entity and its active ComponentMode.
struct EntityAndComponentMode
{
AZ::EntityId m_entityId; ///< The Entity Id associated with this ComponentMode.
AZStd::unique_ptr<ComponentMode> m_componentMode; ///< The ComponentMode currently active for this Entity.
/// Constructor to bind an EntityId to a ComponentMode.
EntityAndComponentMode(
AZ::EntityId entityId,
AZStd::unique_ptr<ComponentMode> componentMode)
: m_entityId(entityId)
, m_componentMode(AZStd::move(componentMode)) {}
};
/// Encapsulates a series of ComponentModeBuilders with a single Entity.
struct EntityAndComponentModeBuilders
{
AZ::EntityId m_entityId; ///< The Entity Id associated with this ComponentModeBuilders(s).
AZStd::vector<ComponentModeBuilder> m_componentModeBuilders; ///< All ComponentModeBuilders that can create modes for this Entity.
/// Constructor to bind an EntityId to a number of ComponentModeBuilders.
EntityAndComponentModeBuilders(
const AZ::EntityId entityId,
const AZStd::vector<ComponentModeBuilder>& componentModeBuilders)
: m_entityId(entityId)
, m_componentModeBuilders(componentModeBuilders) {}
/// Constructor to bind an EntityId to a single ComponentModeBuilder.
EntityAndComponentModeBuilders(
const AZ::EntityId entityId,
const ComponentModeBuilder& componentModeBuilder)
: m_entityId(entityId)
, m_componentModeBuilders(1, componentModeBuilder) {}
};
/// Bus to control the overall editor ComponentMode state.
class ComponentModeSystemRequests
: public AZ::EBusTraits
{
public:
/// Move the Editor into ComponentMode with a list of Entities and their individual ComponentModes.
virtual void BeginComponentMode(
const AZStd::vector<EntityAndComponentModeBuilders>& entityAndComponentModeBuilders) = 0;
/// Add an Entity and its ComponentModes to an existing or starting up ComponentMode.
virtual void AddComponentModes(
const EntityAndComponentModeBuilders& entityAndComponentModeBuilders) = 0;
/// One single way to leave ComponentMode, will move Editor out of ComponentMode.
virtual void EndComponentMode() = 0;
/// Is the Editor currently in ComponentMode or not.
virtual bool InComponentMode() = 0;
/// If something about the Component/Entity has changed, Refresh
/// can be used to update Manipulator positions etc.
virtual void Refresh(const AZ::EntityComponentIdPair& entityComponentIdPair) = 0;
/// Is this Component type on this entity currently participating
/// in the Editor ComponentMode.
virtual bool AddedToComponentMode(
const AZ::EntityComponentIdPair& entityComponentIdPair, const AZ::Uuid& componentType) = 0;
/// If the user has a multiple selection where each entity in the selection
/// has the same Component on it, move all Components into ComponentMode.
virtual void AddSelectedComponentModesOfType(const AZ::Uuid& componentType) = 0;
/// Move to the next active ComponentMode so the Actions for that mode
/// become available (it is now 'selected').
/// Return true if the mode actually changed - the mode will not change if
/// the componentType requested is the same as the current one.
virtual bool SelectNextActiveComponentMode() = 0;
/// Move to the previous active ComponentMode so the Actions for that mode
/// become available (it is now 'selected').
/// Return true if the mode actually changed - the mode will not change if
/// the componentType requested is the same as the current one.
virtual bool SelectPreviousActiveComponentMode() = 0;
/// Pick a specific ComponentMode for a Component (by directly selecting a
/// Component in the EntityInspector - it is now 'selected').
/// Return true if the mode actually changed - the mode will not change if
/// the componentType requested is the same as the current one.
virtual bool SelectActiveComponentMode(const AZ::Uuid& componentType) = 0;
/// Return the Uuid of the Component Type that is currently active in Component Mode.
virtual AZ::Uuid ActiveComponentMode() = 0;
/// Return if the ComponentMode for this specific Entity/Component pair is instantiated.
virtual bool ComponentModeInstantiated(const AZ::EntityComponentIdPair& entityComponentIdPair) = 0;
/// Return if there are more than one Component type in Component Mode.
/// There may be two dependent Component Modes that are not 'active' at the same time
/// but can be switched between in a Component Mode session (e.g. Tube and Spline Components).
virtual bool HasMultipleComponentTypes() = 0;
/// Refresh Actions (shortcuts) for the 'selected' ComponentMode.
virtual void RefreshActions() = 0;
protected:
~ComponentModeSystemRequests() = default;
};
/// Type to inherit to implement ComponentModeSystemRequests.
using ComponentModeSystemRequestBus = AZ::EBus<ComponentModeSystemRequests>;
/// Bus traits for Individual ComponentMode mouse viewport requests.
class ComponentModeMouseViewportRequests
: public AZ::EBusTraits
{
public:
using BusIdType = AZ::EntityComponentIdPair;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
protected:
~ComponentModeMouseViewportRequests() = default;
};
/// Interface to ComponentModeDelegate - used to detect if a user has double
/// clicked to enter or exit ComponentMode.
class ComponentModeDelegateRequests
{
public:
/// Return true if a mouse event occurred to enter ComponentMode.
virtual bool DetectEnterComponentModeInteraction(
const ViewportInteraction::MouseInteractionEvent& mouseInteraction) = 0;
/// Return true if a mouse event occurred to leave ComponentMode.
virtual bool DetectLeaveComponentModeInteraction(
const ViewportInteraction::MouseInteractionEvent& mouseInteraction) = 0;
/// Attempt to add a ComponentMode for this Delegate if the Component type matches.
virtual void AddComponentModeOfType(AZ::Uuid componentType) = 0;
protected:
~ComponentModeDelegateRequests() = default;
};
/// Type to inherit to implement ComponentModeDelegateRequests.
using ComponentModeDelegateRequestBus = AZ::EBus<ComponentModeDelegateRequests, ComponentModeMouseViewportRequests>;
/// Mouse viewport events to be intercepted by individual ComponentModes.
class ComponentModeRequests
: public ViewportInteraction::MouseViewportRequests
, public ComponentMode
{
public:
/// This function is called internally after a mouse interaction returns true.
/// Implementers of HandleMouseInteraction should not override this function.
virtual void PostHandleMouseInteraction() = 0;
};
/// Type to inherit to implement ComponentModeRequests.
using ComponentModeRequestBus = AZ::EBus<ComponentModeRequests, ComponentModeMouseViewportRequests>;
/// Used to notify other systems when ComponentMode events happen.
class EditorComponentModeNotifications
: public AZ::EBusTraits
{
public:
using BusIdType = AzFramework::EntityContextId;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
/// Called when Editor enters ComponentMode - pass the list of all Component types (usually one).
virtual void EnteredComponentMode(const AZStd::vector<AZ::Uuid>& componentTypes) = 0;
/// Called when Editor leaves ComponentMode - pass the list of all Component types (usually one).
virtual void LeftComponentMode(const AZStd::vector<AZ::Uuid>& componentTypes) = 0;
/// Called when Tab is pressed to cycle the 'selected' ComponentMode (which shortcuts/actions are active).
/// Also called when directly selecting a Component in the EntityOutliner.
virtual void ActiveComponentModeChanged(const AZ::Uuid& /*componentType*/) {}
protected:
~EditorComponentModeNotifications() = default;
};
/// Type to inherit to implement EditorComponentModeNotifications.
using EditorComponentModeNotificationBus = AZ::EBus<EditorComponentModeNotifications>;
/// Helper for EditorComponentModeNotifications to be used
/// as a member instead of inheriting from EBus directly.
class EditorComponentModeNotificationBusImpl
: public EditorComponentModeNotificationBus::Handler
{
public:
/// Set the function to be called when entering ComponentMode.
void SetEnteredComponentModeFunc(
const AZStd::function<void(const AZStd::vector<AZ::Uuid>&)>& enteredComponentModeFunc)
{
m_enteredComponentModeFunc = enteredComponentModeFunc;
}
/// Set the function to be called when leaving ComponentMode.
void SetLeftComponentModeFunc(
const AZStd::function<void(const AZStd::vector<AZ::Uuid>&)>& leftComponentModeFunc)
{
m_leftComponentModeFunc = leftComponentModeFunc;
}
private:
// EditorComponentModeNotificationBus
void EnteredComponentMode(const AZStd::vector<AZ::Uuid>& componentModeTypes) override
{
m_enteredComponentModeFunc(componentModeTypes);
}
void LeftComponentMode(const AZStd::vector<AZ::Uuid>& componentModeTypes) override
{
m_leftComponentModeFunc(componentModeTypes);
}
AZStd::function<void(const AZStd::vector<AZ::Uuid>&)> m_enteredComponentModeFunc; ///< Function to call when entering ComponentMode.
AZStd::function<void(const AZStd::vector<AZ::Uuid>&)> m_leftComponentModeFunc; ///< Function to call when leaving ComponentMode.
};
/// Helper to answer if the Editor is in ComponentMode or not.
inline bool InComponentMode()
{
bool inComponentMode = false;
ComponentModeSystemRequestBus::BroadcastResult(
inComponentMode, &ComponentModeSystemRequests::InComponentMode);
return inComponentMode;
}
} // namespace ComponentModeFramework
} // namespace AzToolsFramework