Olympe Engine 2.0
2D Game Engine with ECS Architecture
Loading...
Searching...
No Matches
EntityPrefabRenderer.cpp
Go to the documentation of this file.
3#include "PrefabLoader.h"
4#include "../../system/system_utils.h"
5#include "../../Source/third_party/imgui/imgui.h"
6#include "../Utilities/CustomCanvasEditor.h"
7#include <memory>
8
9namespace Olympe {
10
12 : m_canvas(canvas), m_filePath(""), m_isDirty(false), m_canvasPanelWidth(0.75f)
13{
14 // Initialize component palette with available types
16
17 // NEW: Initialize canvas editor adapter with CustomCanvasEditor for zoom support
18 // Will be created once we have canvas dimensions in first Render() call
19 m_canvasEditor = nullptr; // Deferred initialization
20}
21
25
30
32{
33 // Phase 37 — Render minimap controls toolbar
35
36 // Layout: Canvas (left, ~75%) | Resize Handle | Tabbed Right Panel (right, ~25%)
37 float totalWidth = ImGui::GetContentRegionAvail().x;
38 float handleWidth = 4.0f;
41
42 ImVec2 regionMin = ImGui::GetCursorScreenPos();
43
44 // Render canvas on the left
45 ImGui::BeginChild("EntityPrefabCanvas", ImVec2(canvasWidth, -1.0f), false, ImGuiWindowFlags_NoScrollbar);
46
47 // NEW: Initialize canvas editor on first frame (when we have canvas dimensions)
48 ImVec2 canvasScreenPos = ImGui::GetCursorScreenPos();
49 ImVec2 canvasSize = ImGui::GetContentRegionAvail();
50
51 if (!m_canvasEditor)
52 {
53 // First frame: create CustomCanvasEditor adapter with zoom support
54 m_canvasEditor = std::make_unique<Olympe::CustomCanvasEditor>(
55 "PrefabCanvas",
57 canvasSize,
58 1.0f, // initial zoom
59 0.1f, // min zoom
60 3.0f // max zoom
61 );
62 SYSTEM_LOG << "[EntityPrefabRenderer] CustomCanvasEditor initialized\n";
63
64 // CRITICAL: Pass adapter reference to PrefabCanvas so it can use it!
66
67 // CRITICAL: Initialize minimap visibility and settings
68 m_canvasEditor->SetMinimapVisible(m_minimapVisible);
69 m_canvasEditor->SetMinimapSize(m_minimapSize);
70 m_canvasEditor->SetMinimapPosition(m_minimapPosition);
71 SYSTEM_LOG << "[EntityPrefabRenderer] Minimap initialized (visible=" << m_minimapVisible << ")\n";
72 }
73 else
74 {
75 // Update canvas position/size (may change on window resize)
76 // We can't directly update these, but CustomCanvasEditor stores them as const members
77 // For now, we'll reinitialize if size changes significantly
78 ImVec2 currentSize = ImGui::GetContentRegionAvail();
79 if (currentSize.x != m_canvasEditor->GetCanvasSize().x ||
80 currentSize.y != m_canvasEditor->GetCanvasSize().y)
81 {
82 // CRITICAL FIX: Save state BEFORE destroying old adapter!
83 // If we call GetCanvasOffset() after std::make_unique, m_canvasEditor in PrefabCanvas
84 // still points to the destroyed old adapter, causing a use-after-free crash.
85 float oldZoom = m_canvasEditor->GetZoom();
86 ImVec2 oldPan = m_canvasEditor->GetPan();
87
88 // PHASE 37 FIX: Save minimap state before recreating adapter
89 bool oldMinimapVisible = m_canvasEditor->IsMinimapVisible();
90 float oldMinimapSize = m_canvasEditor->GetMinimapSize();
91 int oldMinimapPosition = m_canvasEditor->GetMinimapPosition();
92
93 // Size changed, reinitialize - old adapter is destroyed here
94 m_canvasEditor = std::make_unique<Olympe::CustomCanvasEditor>(
95 "PrefabCanvas",
98 oldZoom, // preserve zoom (already extracted)
99 0.1f, 3.0f
100 );
101
102 // Restore pan to NEW adapter
103 m_canvasEditor->SetPan(oldPan);
104
105 // PHASE 37 FIX: Restore minimap state to NEW adapter
106 m_canvasEditor->SetMinimapVisible(oldMinimapVisible);
107 m_canvasEditor->SetMinimapSize(oldMinimapSize);
108 m_canvasEditor->SetMinimapPosition(oldMinimapPosition);
109
110 // CRITICAL: Update reference immediately after creation!
112 }
113 }
114
115 // NEW: Use canvas editor BeginRender to handle input
116 m_canvasEditor->BeginRender();
117
119
120 // NEW: Use canvas editor EndRender to finalize
121 m_canvasEditor->EndRender();
122
123 ImGui::EndChild();
124
125 ImVec2 canvasEnd = ImGui::GetCursorScreenPos();
126
127 ImGui::SameLine();
128
129 // Resize handle
130 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.35f, 0.35f, 0.35f, 0.5f));
131 ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.55f, 0.55f, 0.55f, 0.8f));
132 ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.70f, 0.70f, 0.70f, 1.0f));
133 ImGui::Button("##resizeHandle", ImVec2(handleWidth, -1.0f));
134 if (ImGui::IsItemHovered())
135 ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW);
136 if (ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left))
137 {
138 m_canvasPanelWidth += ImGui::GetIO().MouseDelta.x / totalWidth;
139 if (m_canvasPanelWidth < 0.5f) m_canvasPanelWidth = 0.5f;
140 if (m_canvasPanelWidth > 0.9f) m_canvasPanelWidth = 0.9f;
141 }
142 ImGui::PopStyleColor(3);
143
144 ImGui::SameLine();
145
146 // Render right panel with tabs
147 ImGui::BeginChild("RightPanel", ImVec2(rightPanelWidth, -1.0f), true);
149 ImGui::EndChild();
150
151 // After both panels rendered, create an invisible overlay for drag-drop target
152 // This covers the canvas area and accepts drops
155
156 ImGui::SetCursorScreenPos(canvasMin);
157 ImGui::PushClipRect(canvasMin, canvasMax, false);
158 ImGui::Dummy(ImVec2(canvasWidth, canvasEnd.y - canvasMin.y));
159
160 if (ImGui::BeginDragDropTarget())
161 {
162 if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("COMPONENT_TYPE"))
163 {
164 const char* componentName = (const char*)payload->Data;
165
166 // Use the canvas's coordinate transformation with stored canvas position
167 m_canvas.AcceptComponentDropAtScreenPos(componentName, componentName, ImGui::GetMousePos().x, ImGui::GetMousePos().y);
168 }
169 ImGui::EndDragDropTarget();
170 }
171 ImGui::PopClipRect();
172}
173
175{
176 // Tab bar for Component Palette and Property Editor
177 ImGui::TextUnformatted("Right Panel");
178 ImGui::Separator();
179
180 if (ImGui::BeginTabBar("RightPanelTabs", ImGuiTabBarFlags_None))
181 {
182 // Tab 0: Component Palette
183 if (ImGui::BeginTabItem("Components"))
184 {
186 ImGui::EndTabItem();
187 }
188
189 // Tab 1: Property Editor
190 if (ImGui::BeginTabItem("Properties"))
191 {
192 // Connect property panel to selected nodes from canvas
193 const std::vector<NodeId>& selectedNodes = m_canvas.GetDocument()->GetSelectedNodes();
194 if (selectedNodes.size() > 0)
195 {
196 // Select first selected node for property editing
197 m_propertyEditor.SetSelectedNode(selectedNodes[0]);
198 }
199
201 ImGui::EndTabItem();
202 }
203
204 ImGui::EndTabBar();
205 }
206}
207
208bool EntityPrefabRenderer::Load(const std::string& path)
209{
210 m_filePath = path;
211
212 try
213 {
214 // Load JSON file
216 SYSTEM_LOG << "[EntityPrefabRenderer] Loaded JSON from: " << path << "\n";
217
218 // Verify it's an EntityPrefab
219 if (jsonData.contains("blueprintType"))
220 {
221 std::string blueprintType = jsonData["blueprintType"].get<std::string>();
222 SYSTEM_LOG << "[EntityPrefabRenderer] blueprintType: " << blueprintType << "\n";
223 if (blueprintType != "EntityPrefab")
224 {
225 SYSTEM_LOG << "[EntityPrefabRenderer] ERROR: Not an EntityPrefab type\n";
226 return false;
227 }
228 }
229
230 // Get or create document
232 if (document == nullptr)
233 {
234 SYSTEM_LOG << "[EntityPrefabRenderer] ERROR: GetDocument() returned nullptr\n";
235 return false;
236 }
237
238 SYSTEM_LOG << "[EntityPrefabRenderer] Document obtained, loading from file...\n";
239
240 // Load from JSON into document
241 if (!document->LoadFromFile(path))
242 {
243 SYSTEM_LOG << "[EntityPrefabRenderer] ERROR: document->LoadFromFile() failed\n";
244 return false;
245 }
246
247 SYSTEM_LOG << "[EntityPrefabRenderer] Successfully loaded prefab\n";
248 m_isDirty = false;
249 return true;
250 }
251 catch (const std::exception& e)
252 {
253 SYSTEM_LOG << "[EntityPrefabRenderer] EXCEPTION: " << e.what() << "\n";
254 return false;
255 }
256}
257
258bool EntityPrefabRenderer::Save(const std::string& path)
259{
260 std::string savePath = path.empty() ? m_filePath : path;
261
262 if (savePath.empty())
263 {
264 return false;
265 }
266
267 try
268 {
270 if (document == nullptr)
271 {
272 return false;
273 }
274
275 // Save document to file
276 if (!document->SaveToFile(savePath))
277 {
278 return false;
279 }
280
281 m_isDirty = false;
283 return true;
284 }
285 catch (const std::exception&)
286 {
287 return false;
288 }
289}
290
292{
293 // Check both the document's dirty flag and track modifications
295 if (document != nullptr)
296 {
297 return document->IsDirty();
298 }
299 return m_isDirty;
300}
301
303{
304 return "EntityPrefab";
305}
306
308{
309 return m_filePath;
310}
311
312// Phase 35.0: Canvas state management
314{
315 // Capture current canvas pan/zoom from CustomCanvasEditor
316 if (m_canvasEditor)
317 {
318 ImVec2 pan = m_canvasEditor->GetPan();
322 }
323}
324
326{
327 // Restore previously saved canvas pan/zoom
328 if (m_canvasEditor)
329 {
331 m_canvasEditor->SetPan(savedPan);
333 }
334}
335
337{
338 // Return empty for now - can be extended to persist canvas state in JSON files
339 return "";
340}
341
343{
344 // Parse and restore from JSON - can be extended for persistence
345 (void)json;
346}
347
349{
350 // Phase 37 — Minimap toolbar controls
351 ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.15f, 0.15f, 0.15f, 1.0f));
352 ImGui::BeginChild("##ToolbarEntityPrefab", ImVec2(0, 40.0f), true);
353
354 ImGui::AlignTextToFramePadding();
355 ImGui::Text("Minimap:");
356 ImGui::SameLine();
357
358 // Visibility checkbox
359 if (ImGui::Checkbox("##minimap_visible_ep", &m_minimapVisible))
360 {
361 if (m_canvasEditor)
362 m_canvasEditor->SetMinimapVisible(m_minimapVisible);
363 }
364 ImGui::SameLine();
365
366 // Size slider
367 if (ImGui::DragFloat("Size##minimap_ep", &m_minimapSize, 0.01f, 0.05f, 0.5f))
368 {
369 m_minimapSize = std::max(0.05f, std::min(0.5f, m_minimapSize));
370 if (m_canvasEditor)
371 m_canvasEditor->SetMinimapSize(m_minimapSize);
372 }
373 ImGui::SameLine();
374
375 // Position combo
376 const char* positionLabels[] = { "Top-Left", "Top-Right", "Bottom-Left", "Bottom-Right" };
377 if (ImGui::Combo("Position##minimap_ep", &m_minimapPosition, positionLabels, 4, -1))
378 {
379 if (m_canvasEditor)
380 m_canvasEditor->SetMinimapPosition(m_minimapPosition);
381 }
382
383 ImGui::EndChild();
384 ImGui::PopStyleColor();
385}
386
387} // namespace Olympe
ComponentTypeID GetComponentTypeID_Static()
Definition ECS_Entity.h:56
void Render(EntityPrefabGraphDocument *document)
const std::vector< NodeId > & GetSelectedNodes() const
bool Save(const std::string &path) override
Saves the current graph state to disk.
void SetCanvasStateJSON(const std::string &json) override
Restore canvas state from JSON string.
std::unique_ptr< ICanvasEditor > m_canvasEditor
bool IsDirty() const override
Returns true when the graph has unsaved changes.
void Render() override
Renders the graph canvas into the current ImGui child window.
EntityPrefabRenderer(PrefabCanvas &canvas)
ComponentPalettePanel m_componentPalette
bool Load(const std::string &path) override
Loads a graph from a file on disk.
std::string GetCanvasStateJSON() const override
Get canvas state as JSON string for persistence.
void RestoreCanvasState() override
Restore previously saved canvas viewport state Called when tab is reactivated.
void SaveCanvasState() override
Save the current canvas viewport state (pan, zoom, etc.) Called when tab is deactivated.
std::string GetCurrentPath() const override
Returns the last path successfully loaded/saved, or empty string.
std::string GetGraphType() const override
Returns the graph type string, e.g.
struct Olympe::EntityPrefabRenderer::CanvasState m_savedCanvasState
EntityPrefabGraphDocument * GetDocument() const
void AcceptComponentDropAtScreenPos(const std::string &componentType, const std::string &componentName, float screenX, float screenY)
void SetCanvasEditor(ICanvasEditor *canvasEditor)
static json LoadJsonFromFile(const std::string &filePath)
void Render(EntityPrefabGraphDocument *document)
< Provides AssetID and INVALID_ASSET_ID
nlohmann::json json
nlohmann::json json
#define SYSTEM_LOG