Olympe Engine 2.0
2D Game Engine with ECS Architecture
Loading...
Searching...
No Matches
AssetBrowser.cpp
Go to the documentation of this file.
1/*
2 * Olympe Blueprint Editor - Asset Browser Implementation
3 * Frontend component that uses BlueprintEditor backend for asset data
4 */
5
6#include "AssetBrowser.h"
7#include "BlueprintEditor.h"
10#include "../third_party/imgui/imgui.h"
11#include <algorithm>
12#include <iostream>
13
14namespace Olympe
15{
17 : m_TypeFilterSelection(0)
18 {
19 m_SearchBuffer[0] = '\0';
20 m_AvailableTypes = {"All", "EntityBlueprint", "BehaviorTree", "Prefab", "Trigger", "FX", "Sound"};
21 }
22
26
28 {
29 // Set the root path in the backend
31
32 std::cout << "AssetBrowser: Initialized with root path: " << assetsRootPath << std::endl;
33 }
34
36 {
37 // Delegate to backend
39
40 std::cout << "AssetBrowser: Refreshed asset tree from backend" << std::endl;
41 }
42
43 bool AssetBrowser::PassesFilter(const std::shared_ptr<AssetNode>& node) const
44 {
45 // Directories always pass
46 if (node->isDirectory)
47 return true;
48
49 // Apply type filter
51 {
53 if (node->type != selectedType)
54 return false;
55 }
56
57 // Apply search filter
58 if (m_Filter.searchQuery.empty())
59 return true;
60
61 // Case-insensitive search in filename
62 std::string lowerName = node->name;
63 std::string lowerQuery = m_Filter.searchQuery;
64 std::transform(lowerName.begin(), lowerName.end(), lowerName.begin(), ::tolower);
65 std::transform(lowerQuery.begin(), lowerQuery.end(), lowerQuery.begin(), ::tolower);
66
67 return lowerName.find(lowerQuery) != std::string::npos;
68 }
69
71 {
72 // Search box
73 ImGui::SetNextItemWidth(200.0f);
74 if (ImGui::InputText("##search", m_SearchBuffer, sizeof(m_SearchBuffer)))
75 {
77 }
78
79 ImGui::SameLine();
80 ImGui::Text("Search");
81
82 // Type filter combo
83 ImGui::SameLine(0.0f, 20.0f);
84 ImGui::SetNextItemWidth(150.0f);
85 if (ImGui::BeginCombo("##typefilter", m_AvailableTypes[m_TypeFilterSelection].c_str()))
86 {
87 for (int i = 0; i < (int)m_AvailableTypes.size(); i++)
88 {
90 if (ImGui::Selectable(m_AvailableTypes[i].c_str(), is_selected))
92 }
93 ImGui::EndCombo();
94 }
95
96 ImGui::SameLine();
97 ImGui::Text("Type Filter");
98
99 // Refresh button
100 ImGui::SameLine(0.0f, 20.0f);
101 if (ImGui::Button("Refresh"))
102 {
103 Refresh();
104 }
105
106 ImGui::Separator();
107 }
108
109 void AssetBrowser::RenderTreeNode(const std::shared_ptr<AssetNode>& node)
110 {
111 if (!node)
112 return;
113
114 // Skip if doesn't pass filter
115 if (!PassesFilter(node))
116 {
117 // But still check children for directories
118 if (node->isDirectory)
119 {
120 for (const auto& child : node->children)
122 }
123 return;
124 }
125
127
128 if (node->fullPath == m_SelectedAssetPath)
130
131 if (!node->isDirectory)
133
134 std::string label = node->name;
135 if (!node->isDirectory && !node->type.empty())
136 label += " [" + node->type + "]";
137
138 bool node_open = ImGui::TreeNodeEx((void*)(intptr_t)node.get(), flags, "%s", label.c_str());
139
140 // Handle selection (single click)
141 if (ImGui::IsItemClicked())
142 {
143 if (!node->isDirectory)
144 {
145 m_SelectedAssetPath = node->fullPath;
147 std::cout << "AssetBrowser: Selected asset: " << m_SelectedAssetPath << std::endl;
148 }
149 }
150
151 // Handle double-click to open
152 if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0))
153 {
154 if (!node->isDirectory)
155 {
156 std::cout << "AssetBrowser: Double-clicked asset: " << node->fullPath << std::endl;
157
158 // Check if this is a BehaviorTree or HFSM - open in Node Graph Editor
159 if (node->type == "BehaviorTree" || node->type == "HFSM")
160 {
161 std::cout << "AssetBrowser: Opening " << node->type << " in Node Graph Editor" << std::endl;
163 }
164 // Otherwise, use the legacy callback if set (for EntityBlueprint, etc.)
165 else if (m_OnAssetOpen)
166 {
167 std::cout << "AssetBrowser: Opening asset via callback" << std::endl;
168 m_OnAssetOpen(node->fullPath);
169 }
170 }
171 }
172
173 // Tooltip for double-click action
174 if (ImGui::IsItemHovered())
175 {
176 if (node->type == "BehaviorTree" || node->type == "HFSM")
177 {
178 ImGui::SetTooltip("Double-click to open in Node Graph Editor");
179 }
180 else if (!node->isDirectory)
181 {
182 ImGui::SetTooltip("Double-click to open");
183 }
184 }
185
186 if (node_open)
187 {
188 if (node->isDirectory)
189 {
190 for (const auto& child : node->children)
192 }
193 ImGui::TreePop();
194 }
195 }
196
198 {
199 if (ImGui::Begin("Asset Browser"))
200 {
201 // ===== USE TABS TO SEPARATE FILES AND RUNTIME ENTITIES =====
202 if (ImGui::BeginTabBar("AssetBrowserTabs"))
203 {
204 // ===== TAB 1: Blueprint Files =====
205 if (ImGui::BeginTabItem("Blueprint Files"))
206 {
208
209 ImGui::Separator();
210
211 // Get asset tree from backend
213
214 if (rootNode)
215 {
216 // Render the tree starting from children (skip root "Blueprints" node)
217 for (const auto& child : rootNode->children)
219 }
220 else
221 {
222 // Check if backend has an error
224 {
225 ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f),
226 "Error: %s", BlueprintEditor::Get().GetLastError().c_str());
227 }
228 else
229 {
230 ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f),
231 "No blueprint files found.");
232 }
233 }
234
235 ImGui::EndTabItem();
236 }
237
238 // ===== TAB 2: Runtime Entities =====
239 if (ImGui::BeginTabItem("Runtime Entities"))
240 {
241 ImGui::Text("Active Entities: %zu", BlueprintEditor::Get().GetRuntimeEntityCount());
242 ImGui::Separator();
243
245
246 ImGui::EndTabItem();
247 }
248
249 // ===== TAB 3: Node Palette =====
250 if (ImGui::BeginTabItem("Nodes"))
251 {
253 ImGui::EndTabItem();
254 }
255
256 ImGui::EndTabBar();
257 }
258 }
259 ImGui::End();
260 }
261
263 {
264 // Get runtime entities from BlueprintEditor backend
265 const auto& entities = BlueprintEditor::Get().GetRuntimeEntities();
266
267 if (entities.empty())
268 {
269 ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "No runtime entities.");
270 ImGui::TextWrapped("Create entities with World::CreateEntity() to see them here.");
271 return;
272 }
273
274 // Use EntityInspectorManager to get entity names and info
276 if (!inspector.IsInitialized())
277 {
278 ImGui::TextColored(ImVec4(1.0f, 0.7f, 0.0f, 1.0f),
279 "Inspector not initialized.");
280 return;
281 }
282
283 // Get current selection
285
286 // Render each entity as a selectable item
287 ImGui::BeginChild("RuntimeEntitiesScroll", ImVec2(0, 200), true);
288
289 for (uint64_t entityId : entities)
290 {
291 bool isSelected = (selectedEntity == entityId);
292
293 // Get entity info from inspector
294 EntityInfo info = inspector.GetEntityInfo(entityId);
295 std::string displayName = info.name;
296 if (displayName.empty())
297 {
298 displayName = "Entity_" + std::to_string(entityId);
299 }
300
301 // Add component count badge
302 displayName += " (" + std::to_string(info.componentTypes.size()) + " comp)";
303
304 // Selectable item
305 if (ImGui::Selectable(displayName.c_str(), isSelected))
306 {
308 }
309
310 // Tooltip on hover
311 if (ImGui::IsItemHovered())
312 {
313 ImGui::BeginTooltip();
314 ImGui::Text("Entity ID: %llu", entityId);
315 ImGui::Text("Components: %zu", info.componentTypes.size());
316 if (!info.componentTypes.empty())
317 {
318 ImGui::Separator();
319 for (const auto& compType : info.componentTypes)
320 {
321 ImGui::BulletText("%s", compType.c_str());
322 }
323 }
324 ImGui::EndTooltip();
325 }
326 }
327
328 ImGui::EndChild();
329 }
330
332 {
333 ImGui::TextColored(ImVec4(0.5f, 1.0f, 0.5f, 1.0f),
334 "Drag nodes to the graph to add them");
335 ImGui::Separator();
336
337 // ===== Composite Nodes =====
338 if (ImGui::CollapsingHeader("Composites", ImGuiTreeNodeFlags_DefaultOpen))
339 {
340 const char* compositeNodes[] = {"Sequence", "Selector"};
341 const char* compositeTooltips[] = {
342 "Executes children in order until one fails",
343 "Executes children in order until one succeeds"
344 };
345
346 for (int i = 0; i < 2; i++)
347 {
348 ImGui::Selectable(compositeNodes[i]);
349 if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None))
350 {
351 const char* nodeType = compositeNodes[i];
352 ImGui::SetDragDropPayload("NODE_TYPE", nodeType, strlen(nodeType) + 1);
353 ImGui::Text("%s", compositeNodes[i]);
354 ImGui::EndDragDropSource();
355 }
356
357 if (ImGui::IsItemHovered())
358 {
359 ImGui::SetTooltip("%s", compositeTooltips[i]);
360 }
361 }
362 }
363
364 // ===== Action Nodes =====
365 if (ImGui::CollapsingHeader("Actions", ImGuiTreeNodeFlags_DefaultOpen))
366 {
368
369 if (actionTypes.empty())
370 {
371 ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f),
372 "No actions available");
373 }
374 else
375 {
376 for (const auto& actionType : actionTypes)
377 {
378 ImGui::Selectable(actionType.c_str());
379 if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None))
380 {
381 std::string payload = "Action:" + actionType;
382 ImGui::SetDragDropPayload("NODE_TYPE", payload.c_str(), payload.size() + 1);
383 ImGui::Text("%s", actionType.c_str());
384 ImGui::EndDragDropSource();
385 }
386
387 // Show tooltip with action info
389 if (actionDef && ImGui::IsItemHovered())
390 {
391 ImGui::BeginTooltip();
392 ImGui::TextColored(ImVec4(0.5f, 1.0f, 0.5f, 1.0f), "%s", actionDef->name.c_str());
393 if (!actionDef->description.empty())
394 {
395 ImGui::TextWrapped("%s", actionDef->description.c_str());
396 }
397 if (!actionDef->parameters.empty())
398 {
399 ImGui::Separator();
400 ImGui::Text("Parameters:");
401 for (const auto& param : actionDef->parameters)
402 {
403 ImGui::BulletText("%s: %s %s",
404 param.name.c_str(),
405 param.type.c_str(),
406 param.required ? "(required)" : "");
407 }
408 }
409 ImGui::EndTooltip();
410 }
411 }
412 }
413 }
414
415 // ===== Condition Nodes =====
416 if (ImGui::CollapsingHeader("Conditions", ImGuiTreeNodeFlags_DefaultOpen))
417 {
419
420 if (conditionTypes.empty())
421 {
422 ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f),
423 "No conditions available");
424 }
425 else
426 {
427 for (const auto& conditionType : conditionTypes)
428 {
429 ImGui::Selectable(conditionType.c_str());
430 if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None))
431 {
432 std::string payload = "Condition:" + conditionType;
433 ImGui::SetDragDropPayload("NODE_TYPE", payload.c_str(), payload.size() + 1);
434 ImGui::Text("%s", conditionType.c_str());
435 ImGui::EndDragDropSource();
436 }
437
438 // Show tooltip with condition info
440 if (conditionDef && ImGui::IsItemHovered())
441 {
442 ImGui::BeginTooltip();
443 ImGui::TextColored(ImVec4(0.5f, 1.0f, 0.5f, 1.0f), "%s", conditionDef->name.c_str());
444 if (!conditionDef->description.empty())
445 {
446 ImGui::TextWrapped("%s", conditionDef->description.c_str());
447 }
448 ImGui::EndTooltip();
449 }
450 }
451 }
452 }
453
454 // ===== Decorator Nodes =====
455 if (ImGui::CollapsingHeader("Decorators"))
456 {
458
459 if (decoratorTypes.empty())
460 {
461 ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f),
462 "No decorators available");
463 }
464 else
465 {
466 for (const auto& decoratorType : decoratorTypes)
467 {
468 ImGui::Selectable(decoratorType.c_str());
469 if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None))
470 {
471 std::string payload = "Decorator:" + decoratorType;
472 ImGui::SetDragDropPayload("NODE_TYPE", payload.c_str(), payload.size() + 1);
473 ImGui::Text("%s", decoratorType.c_str());
474 ImGui::EndDragDropSource();
475 }
476
477 // Show tooltip with decorator info
479 if (decoratorDef && ImGui::IsItemHovered())
480 {
481 ImGui::BeginTooltip();
482 ImGui::TextColored(ImVec4(0.5f, 1.0f, 0.5f, 1.0f), "%s", decoratorDef->name.c_str());
483 if (!decoratorDef->description.empty())
484 {
485 ImGui::TextWrapped("%s", decoratorDef->description.c_str());
486 }
487 ImGui::EndTooltip();
488 }
489 }
490 }
491 }
492
493 ImGui::Separator();
494 ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f),
495 "Tip: Drag & drop nodes onto the graph canvas");
496 }
497
499 {
500 return m_SelectedAssetPath;
501 }
502
504 {
505 return !m_SelectedAssetPath.empty();
506 }
507
508 void AssetBrowser::SetAssetOpenCallback(std::function<void(const std::string&)> callback)
509 {
511 }
512}
ComponentTypeID GetComponentTypeID_Static()
Definition ECS_Entity.h:56
bool HasSelection() const
void RenderTreeNode(const std::shared_ptr< AssetNode > &node)
std::vector< std::string > m_AvailableTypes
std::function< void(const std::string &)> m_OnAssetOpen
std::string m_SelectedAssetPath
void Initialize(const std::string &assetsRootPath)
void SetAssetOpenCallback(std::function< void(const std::string &)> callback)
bool PassesFilter(const std::shared_ptr< AssetNode > &node) const
std::string GetSelectedAssetPath() const
std::shared_ptr< AssetNode > GetAssetTree() const
const std::vector< uint64_t > & GetRuntimeEntities() const
void SetAssetRootPath(const std::string &path)
void SelectAsset(const std::string &assetPath)
void SetSelectedEntity(uint64_t entityId)
uint64_t GetSelectedEntity() const
void OpenGraphInEditor(const std::string &assetPath)
static BlueprintEditor & Get()
static EntityInspectorManager & Get()
const CatalogType * FindActionType(const std::string &id) const
std::vector< std::string > GetDecoratorTypes() const
std::vector< std::string > GetActionTypes() const
static EnumCatalogManager & Get()
const CatalogType * FindDecoratorType(const std::string &id) const
std::vector< std::string > GetConditionTypes() const
const CatalogType * FindConditionType(const std::string &id) const
std::string searchQuery