Skip to content

Commit dcd2b1f

Browse files
authored
Add actor copy/paste workflow in Hierarchy and Scene View (#734)
1 parent 3fd62fd commit dcd2b1f

5 files changed

Lines changed: 124 additions & 13 deletions

File tree

Sources/OvEditor/include/OvEditor/Core/Context.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66

77
#pragma once
88

9+
#include <cstdint>
910
#include <filesystem>
11+
#include <variant>
1012

1113
#include <OvAudio/Core/AudioEngine.h>
1214
#include <OvCore/ResourceManagement/MaterialManager.h>
@@ -35,6 +37,13 @@ namespace OvEditor::Core
3537
class Context
3638
{
3739
public:
40+
struct ActorCopyBuffer
41+
{
42+
uint64_t guid = 0;
43+
};
44+
45+
using CopyBuffer = std::variant<std::monostate, ActorCopyBuffer>;
46+
3847
/**
3948
* Constructor
4049
* @param p_projectFolder (including the .ovproject file)
@@ -92,5 +101,7 @@ namespace OvEditor::Core
92101
OvWindowing::Settings::WindowSettings windowSettings;
93102

94103
OvTools::Filesystem::IniFile projectSettings;
104+
105+
CopyBuffer copyBuffer;
95106
};
96107
}

Sources/OvEditor/include/OvEditor/Core/EditorActions.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,18 @@ namespace OvEditor::Core
228228
#pragma endregion
229229

230230
#pragma region ACTOR_MANIPULATION
231+
/**
232+
* Copy the given actor in the editor copy buffer
233+
* @param p_actor
234+
*/
235+
void CopyActor(OvCore::ECS::Actor& p_actor);
236+
237+
/**
238+
* Paste the copied actor, optionally as a child of the given parent
239+
* @param p_parent
240+
*/
241+
void PasteActor(OvCore::ECS::Actor* p_parent = nullptr);
242+
231243
/**
232244
* Select an actor and show him in inspector
233245
* @param p_target

Sources/OvEditor/src/OvEditor/Core/Editor.cpp

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,11 +206,40 @@ void OvEditor::Core::Editor::Update(float p_deltaTime)
206206

207207
void OvEditor::Core::Editor::HandleGlobalShortcuts()
208208
{
209+
auto& sceneView = EDITOR_PANEL(SceneView, "Scene View");
210+
auto& hierarchy = EDITOR_PANEL(Hierarchy, "Hierarchy");
211+
const bool isSceneViewFocused = sceneView.IsFocused();
212+
const bool isHierarchyFocused = hierarchy.IsFocused();
213+
209214
// If the [Del] key is pressed while an actor is selected and the Scene View or Hierarchy is focused
210-
if (m_context.inputManager->IsKeyPressed(OvWindowing::Inputs::EKey::KEY_DELETE) && EDITOR_EXEC(IsAnyActorSelected()) && (EDITOR_PANEL(SceneView, "Scene View").IsFocused() || EDITOR_PANEL(Hierarchy, "Hierarchy").IsFocused()))
215+
if (m_context.inputManager->IsKeyPressed(OvWindowing::Inputs::EKey::KEY_DELETE) && EDITOR_EXEC(IsAnyActorSelected()) && (isSceneViewFocused || isHierarchyFocused))
211216
{
212217
EDITOR_EXEC(DestroyActor(EDITOR_EXEC(GetSelectedActor())));
213218
}
219+
220+
const bool isControlPressed =
221+
m_context.inputManager->GetKeyState(OvWindowing::Inputs::EKey::KEY_LEFT_CONTROL) == OvWindowing::Inputs::EKeyState::KEY_DOWN ||
222+
m_context.inputManager->GetKeyState(OvWindowing::Inputs::EKey::KEY_RIGHT_CONTROL) == OvWindowing::Inputs::EKeyState::KEY_DOWN;
223+
224+
if (isControlPressed && (isSceneViewFocused || isHierarchyFocused))
225+
{
226+
if (m_context.inputManager->IsKeyPressed(OvWindowing::Inputs::EKey::KEY_C) && EDITOR_EXEC(IsAnyActorSelected()))
227+
{
228+
EDITOR_EXEC(CopyActor(EDITOR_EXEC(GetSelectedActor())));
229+
}
230+
231+
if (m_context.inputManager->IsKeyPressed(OvWindowing::Inputs::EKey::KEY_V))
232+
{
233+
OvCore::ECS::Actor* parent = nullptr;
234+
235+
if (EDITOR_EXEC(IsAnyActorSelected()))
236+
{
237+
parent = &EDITOR_EXEC(GetSelectedActor());
238+
}
239+
240+
EDITOR_EXEC(PasteActor(parent));
241+
}
242+
}
214243
}
215244

216245
void OvEditor::Core::Editor::UpdateCurrentEditorMode(float p_deltaTime)

Sources/OvEditor/src/OvEditor/Core/EditorActions.cpp

Lines changed: 51 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -800,32 +800,71 @@ void OvEditor::Core::EditorActions::DuplicateActor(OvCore::ECS::Actor & p_toDupl
800800

801801
newActor.SetID(idToUse);
802802
newActor.SetGUID(guidToUse);
803+
auto currentScene = m_context.sceneManager.GetCurrentScene();
803804

804805
if (p_forcedParent)
806+
{
805807
newActor.SetParent(*p_forcedParent);
806-
else
808+
}
809+
else if (newActor.GetParentID() > 0)
807810
{
808-
auto currentScene = m_context.sceneManager.GetCurrentScene();
809-
810-
if (newActor.GetParentID() > 0)
811-
{
812-
if (auto found = currentScene->FindActorByID(newActor.GetParentID()); found)
813-
{
814-
newActor.SetParent(*found);
815-
}
816-
}
811+
if (auto found = currentScene->FindActorByID(newActor.GetParentID()); found)
812+
{
813+
newActor.SetParent(*found);
814+
}
815+
}
817816

818-
const auto uniqueName = FindDuplicatedActorUniqueName(p_toDuplicate, newActor, *currentScene);
819-
newActor.SetName(uniqueName);
817+
if (p_focus || !p_forcedParent)
818+
{
819+
const auto uniqueName = FindDuplicatedActorUniqueName(p_toDuplicate, newActor, *currentScene);
820+
newActor.SetName(uniqueName);
820821
}
821822

822823
if (p_focus)
824+
{
823825
SelectActor(newActor);
826+
}
824827

825828
for (auto& child : p_toDuplicate.GetChildren())
826829
DuplicateActor(*child, &newActor, false);
827830
}
828831

832+
void OvEditor::Core::EditorActions::CopyActor(OvCore::ECS::Actor& p_actor)
833+
{
834+
m_context.copyBuffer = Context::ActorCopyBuffer{
835+
.guid = p_actor.GetGUID()
836+
};
837+
}
838+
839+
void OvEditor::Core::EditorActions::PasteActor(OvCore::ECS::Actor* p_parent)
840+
{
841+
const auto actorCopyBuffer = std::get_if<Context::ActorCopyBuffer>(&m_context.copyBuffer);
842+
if (!actorCopyBuffer)
843+
{
844+
return;
845+
}
846+
847+
const auto currentScene = m_context.sceneManager.GetCurrentScene();
848+
if (!currentScene)
849+
{
850+
return;
851+
}
852+
853+
if (const auto copiedActor = currentScene->FindActorByGUID(actorCopyBuffer->guid))
854+
{
855+
auto* destinationParent = p_parent;
856+
857+
// Pasting on the copied actor itself falls back to its current parent,
858+
// preserving the "duplicate-like" behavior by default.
859+
if (destinationParent && destinationParent->GetGUID() == copiedActor->GetGUID())
860+
{
861+
destinationParent = copiedActor->GetParent();
862+
}
863+
864+
DuplicateActor(*copiedActor, destinationParent, true);
865+
}
866+
}
867+
829868
void OvEditor::Core::EditorActions::SelectActor(OvCore::ECS::Actor& p_target)
830869
{
831870
EDITOR_PANEL(Panels::Inspector, "Inspector").FocusActor(p_target);

Sources/OvEditor/src/OvEditor/Panels/Hierarchy.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,24 @@ class ActorContextualMenu : public OvUI::Plugins::ContextualMenu
5555
EDITOR_EXEC(MoveToTarget(*m_target));
5656
};
5757

58+
auto& copyButton = CreateWidget<OvUI::Widgets::Menu::MenuItem>("Copy");
59+
copyButton.ClickedEvent += [this]
60+
{
61+
EDITOR_EXEC(CopyActor(*m_target));
62+
};
63+
5864
auto& duplicateButton = CreateWidget<OvUI::Widgets::Menu::MenuItem>("Duplicate");
5965
duplicateButton.ClickedEvent += [this]
6066
{
6167
EDITOR_EXEC(DelayAction(EDITOR_BIND(DuplicateActor, std::ref(*m_target), nullptr, true), 0));
6268
};
6369

70+
auto& pasteButton = CreateWidget<OvUI::Widgets::Menu::MenuItem>("Paste");
71+
pasteButton.ClickedEvent += [this]
72+
{
73+
EDITOR_EXEC(DelayAction(EDITOR_BIND(PasteActor, m_target), 0));
74+
};
75+
6476
auto& deleteButton = CreateWidget<OvUI::Widgets::Menu::MenuItem>("Delete");
6577
deleteButton.ClickedEvent += [this]
6678
{
@@ -82,6 +94,14 @@ class ActorContextualMenu : public OvUI::Plugins::ContextualMenu
8294
m_target->SetName(p_newName);
8395
};
8496
}
97+
else
98+
{
99+
auto& pasteButton = CreateWidget<OvUI::Widgets::Menu::MenuItem>("Paste");
100+
pasteButton.ClickedEvent += [this]
101+
{
102+
EDITOR_EXEC(DelayAction(EDITOR_BIND(PasteActor, nullptr), 0));
103+
};
104+
}
85105

86106
auto& createActor = CreateWidget<OvUI::Widgets::Menu::MenuList>("Create...");
87107

0 commit comments

Comments
 (0)