Olympe Engine 2.0
2D Game Engine with ECS Architecture
Loading...
Searching...
No Matches
ComponentPalettePanel.cpp
Go to the documentation of this file.
2#include "../../Source/third_party/imgui/imgui.h"
3#include "../../system/system_utils.h"
4#include "../../third_party/nlohmann/json.hpp"
5#include <algorithm>
6#include <cstring>
7#include <fstream>
8#include <io.h>
9#include <direct.h>
10
11namespace Olympe
12{
18
20
22 {
23 SYSTEM_LOG << "[ComponentPalettePanel] Initializing...\n";
24
25 // Log current working directory for debugging (Windows only)
26 char cwd[_MAX_PATH];
27 if (_getcwd(cwd, sizeof(cwd)) != nullptr)
28 {
29 SYSTEM_LOG << "[ComponentPalettePanel] Current working directory: " << cwd << "\n";
30 }
31
32 // Try to load from JSON file first (new format: Gamedata/EntityPrefab/ComponentsParameters.json)
33 bool jsonLoaded = LoadComponentsFromJSON("Gamedata\\EntityPrefab\\ComponentsParameters.json");
34
35 // If JSON load failed, try forward slash variant
36 if (!jsonLoaded)
37 {
38 SYSTEM_LOG << "[ComponentPalettePanel] Attempting with forward slashes...\n";
39 jsonLoaded = LoadComponentsFromJSON("Gamedata/EntityPrefab/ComponentsParameters.json");
40 }
41
42 // If JSON load failed, try absolute path as fallback
43 if (!jsonLoaded)
44 {
45 char cwdBuffer[_MAX_PATH];
46 if (_getcwd(cwdBuffer, sizeof(cwdBuffer)) != nullptr)
47 {
48 std::string absolutePath = std::string(cwdBuffer) + "\\Gamedata\\EntityPrefab\\ComponentsParameters.json";
49 SYSTEM_LOG << "[ComponentPalettePanel] Attempting absolute path: " << absolutePath << "\n";
51 }
52 }
53
54 // If JSON load failed, try legacy location for backward compatibility
55 if (!jsonLoaded)
56 {
57 SYSTEM_LOG << "[ComponentPalettePanel] Attempting to load from legacy location...\n";
58 jsonLoaded = LoadComponentsFromJSON("Gamedata/PrefabEntities/ComponentsParameters.json");
59 }
60
61 // If JSON load failed, use hardcoded components as fallback
62 if (!jsonLoaded)
63 {
64 SYSTEM_LOG << "[ComponentPalettePanel] Using hardcoded component types as fallback\n";
65 RegisterComponentType("Transform", "Core", "Position, rotation, scale");
66 RegisterComponentType("Identity", "Core", "Entity identity and naming");
67 RegisterComponentType("Movement", "Physics", "Movement and velocity");
68 RegisterComponentType("Sprite", "Graphics", "Sprite rendering");
69 RegisterComponentType("Collision", "Physics", "Collision bounds");
70 RegisterComponentType("Health", "Gameplay", "Health points system");
71 RegisterComponentType("AIBlackboard", "AI", "AI data storage");
72 RegisterComponentType("BehaviorTree", "AI", "Behavior tree execution");
73 RegisterComponentType("VisualSprite", "Graphics", "Visual sprite data");
74 RegisterComponentType("AnimationController", "Graphics", "Animation state machine");
75
76 // Build categories list
77 std::map<std::string, bool> categoryMap;
78 for (size_t i = 0; i < m_componentTypes.size(); ++i)
79 {
80 categoryMap[m_componentTypes[i].category] = true;
81 }
82 for (auto it = categoryMap.begin(); it != categoryMap.end(); ++it)
83 {
84 m_categories.push_back(it->first);
85 }
86
87 // Sort categories alphabetically
88 std::sort(m_categories.begin(), m_categories.end());
89 }
90
91 // Initialize category expanded state (all categories start expanded)
92 m_categoryExpanded.resize(m_categories.size(), true);
93
94 SYSTEM_LOG << "[ComponentPalettePanel] Initialization complete with " << m_componentTypes.size() << " component types\n";
95 }
96
98 {
99 if (!document) { return; }
100
101 ImGui::BeginChild("##ComponentPalette", ImVec2(0, 0), false, ImGuiWindowFlags_NoScrollbar);
102
103 // ========== Dense UI Header (Blue background like VisualScriptEditor) ==========
104 ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0.0f, 0.4f, 0.8f, 1.0f));
105 ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0.0f, 0.5f, 0.9f, 1.0f));
106 ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImVec4(0.0f, 0.3f, 0.7f, 1.0f));
107 ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
108 ImGui::Selectable("Components", true, ImGuiSelectableFlags_None, ImVec2(0.f, 20.f));
109 ImGui::PopStyleColor(4);
110
111 // ========== Search bar (compact) ==========
112 ImGui::PushItemWidth(-1.0f);
113 ImGui::InputText("##ComponentSearch", m_searchBuffer, sizeof(m_searchBuffer), ImGuiInputTextFlags_CharsNoBlank);
115 ImGui::PopItemWidth();
116
117 // ========== Category tabs (collapsible tree) ==========
118 ImGui::Spacing();
119 ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 2.0f));
121 ImGui::PopStyleVar();
122
123 // ========== Help text at bottom ==========
124 ImGui::Spacing();
125 ImGui::TextDisabled("Tip: Drag & drop components onto the graph to add them");
126
127 ImGui::EndChild();
128 }
129
131 {
132 ImGui::TextUnformatted("Search:");
133 ImGui::InputText("##search", m_searchBuffer, sizeof(m_searchBuffer));
134 }
135
137 {
138 // "All" category (non-collapsible, shows all components)
139 ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0.2f, 0.35f, 0.6f, 1.0f));
140 ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0.25f, 0.4f, 0.7f, 1.0f));
141 ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImVec4(0.15f, 0.3f, 0.55f, 1.0f));
142
144 if (ImGui::Selectable("All", allSelected, ImGuiSelectableFlags_None, ImVec2(0.f, CATEGORY_HEADER_HEIGHT)))
145 {
147 }
148 ImGui::PopStyleColor(3);
149
150 // Ensure m_categoryExpanded is initialized
151 if (m_categoryExpanded.size() != m_categories.size())
152 {
153 m_categoryExpanded.resize(m_categories.size(), true); // All categories start expanded
154 }
155
156 // Individual categories as collapsible trees
157 for (size_t i = 0; i < m_categories.size(); ++i)
158 {
159 ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0.2f, 0.35f, 0.6f, 1.0f));
160 ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0.25f, 0.4f, 0.7f, 1.0f));
161 ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImVec4(0.15f, 0.3f, 0.55f, 1.0f));
162
163 // Category header with TreeNodeEx for toggle
164 bool categoryExpanded = ImGui::TreeNodeEx(
168 );
169
170 ImGui::PopStyleColor(3);
171
173 {
174 m_categoryExpanded[i] = true;
175
176 // Render components in this category
177 ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 1.0f));
178
179 for (size_t j = 0; j < m_componentTypes.size(); ++j)
180 {
182
183 // Filter by category
184 if (component.category != m_categories[i])
185 {
186 continue;
187 }
188
189 // Filter by search
190 if (!m_searchFilter.empty())
191 {
192 bool matches = false;
193 std::string nameLower = component.name;
194 std::transform(nameLower.begin(), nameLower.end(), nameLower.begin(), ::tolower);
195 std::string searchLower = m_searchFilter;
196 std::transform(searchLower.begin(), searchLower.end(), searchLower.begin(), ::tolower);
197 if (nameLower.find(searchLower) != std::string::npos)
198 {
199 matches = true;
200 }
201 if (!matches)
202 {
203 continue;
204 }
205 }
206
207 // Render compact component item
208 std::string label = component.name + "##" + std::to_string(i) + "_" + std::to_string(j);
209
210 // Use InvisibleButton for drag-drop support
211 ImGui::InvisibleButton(label.c_str(), ImVec2(-1.0f, COMPONENT_ITEM_HEIGHT));
212
213 // Draw background
214 ImVec2 itemMin = ImGui::GetItemRectMin();
215 ImVec2 itemMax = ImGui::GetItemRectMax();
216 ImDrawList* drawList = ImGui::GetWindowDrawList();
217
218 if (ImGui::IsItemHovered())
219 {
220 drawList->AddRectFilled(itemMin, itemMax, ImGui::GetColorU32(ImVec4(0.4f, 0.4f, 0.5f, 1.0f)));
221 }
222 else
223 {
224 drawList->AddRectFilled(itemMin, itemMax, ImGui::GetColorU32(ImVec4(0.25f, 0.25f, 0.3f, 1.0f)));
225 }
226
227 // Draw compact text
229 ImGui::GetColorU32(ImVec4(0.9f, 0.9f, 0.9f, 1.0f)),
230 component.name.c_str());
231
232 // Drag-and-drop source
233 if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_SourceAllowNullID))
234 {
236 component.name.c_str(), _TRUNCATE);
237
238 ImGui::SetDragDropPayload("COMPONENT_TYPE",
241
242 ImGui::Text("%s", component.name.c_str());
243
244 ImGui::EndDragDropSource();
245 }
246
247 // Tooltip
248 if (ImGui::IsItemHovered() && !component.description.empty())
249 {
250 ImGui::SetTooltip("%s", component.description.c_str());
251 }
252 }
253
254 ImGui::PopStyleVar();
255 ImGui::TreePop();
256 }
257 else
258 {
259 m_categoryExpanded[i] = false;
260 }
261 }
262 }
263
265 {
266 // Component list rendering is now integrated into RenderCategoryTabs()
267 // This method is kept for backward compatibility
268 }
269
271 {
272 if (!document) { return; }
273
274 // Create new node at center of visible area
275 // For now, add at origin (0, 0) - can be improved to position near mouse
276 NodeId newNodeId = document->CreateComponentNode(componentType.name, componentType.name);
277
278 // Position new node in a reasonable location
280 if (newNode)
281 {
282 // Place new node at offset from center, or at reasonable default
283 newNode->position = Vector(100.0f, 100.0f, 0.0f);
284 newNode->size = Vector(150.0f, 80.0f, 0.0f);
285 newNode->enabled = true;
286 }
287
288 SYSTEM_LOG << "[ComponentPalettePanel] Added component: " << componentType.name << " (id=" << newNodeId << ")\n";
289 }
290
291 const std::vector<ComponentType>& ComponentPalettePanel::GetComponentTypes() const
292 {
293 return m_componentTypes;
294 }
295
296 void ComponentPalettePanel::RegisterComponentType(const std::string& name, const std::string& category, const std::string& description)
297 {
298 m_componentTypes.push_back(ComponentType(name, category, description));
299 }
300
301 bool ComponentPalettePanel::LoadComponentsFromJSON(const std::string& filepath)
302 {
303 using nlohmann::json;
304
305 SYSTEM_LOG << "[ComponentPalettePanel] Loading components from: " << filepath << "\n";
306
307 try
308 {
309 // Check if file exists using Windows _access() (0 = exists)
310 if (_access(filepath.c_str(), 0) == -1)
311 {
312 SYSTEM_LOG << "[ComponentPalettePanel] WARNING: File does not exist: " << filepath << "\n";
313 return false;
314 }
315
316 std::ifstream file(filepath);
317 if (!file.is_open())
318 {
319 SYSTEM_LOG << "[ComponentPalettePanel] WARNING: Could not open file: " << filepath << " (permissions or other I/O issue)\n";
320 return false;
321 }
322
324 file >> jsonData;
325 file.close();
326
327 // Clear existing components (loaded from JSON replaces hardcoded)
328 m_componentTypes.clear();
329 m_categories.clear();
330
331 // Try new format first (schemas array with componentType)
332 if (jsonData.contains("schemas") && jsonData["schemas"].is_array())
333 {
334 SYSTEM_LOG << "[ComponentPalettePanel] Detected new JSON format (schemas array)\n";
335
336 const json& schemasArray = jsonData["schemas"];
337 SYSTEM_LOG << "[ComponentPalettePanel] Found " << schemasArray.size() << " component types\n";
338
339 // Parse each component schema
340 for (const auto& schemaJson : schemasArray)
341 {
342 if (!schemaJson.contains("componentType"))
343 {
344 SYSTEM_LOG << "[ComponentPalettePanel] WARNING: Skipping schema missing componentType\n";
345 continue;
346 }
347
348 std::string componentType = schemaJson["componentType"].get<std::string>();
349 std::string category = ExtractCategoryFromComponentType(componentType);
350 std::string description = schemaJson.contains("description")
351 ? schemaJson["description"].get<std::string>()
352 : componentType; // Use componentType as description if none provided
353
354 RegisterComponentType(componentType, category, description);
355 SYSTEM_LOG << "[ComponentPalettePanel] Loaded: " << componentType << " (" << category << ")\n";
356 }
357 }
358 // Fallback to old format (components array with name)
359 else if (jsonData.contains("components") && jsonData["components"].is_array())
360 {
361 SYSTEM_LOG << "[ComponentPalettePanel] Detected old JSON format (components array)\n";
362
363 const json& componentsArray = jsonData["components"];
364 SYSTEM_LOG << "[ComponentPalettePanel] Found " << componentsArray.size() << " component types\n";
365
366 // Parse each component
367 for (const auto& compJson : componentsArray)
368 {
369 if (!compJson.contains("name") || !compJson.contains("category"))
370 {
371 SYSTEM_LOG << "[ComponentPalettePanel] WARNING: Skipping component missing name or category\n";
372 continue;
373 }
374
375 std::string name = compJson["name"].get<std::string>();
376 std::string category = compJson["category"].get<std::string>();
377 std::string description = compJson.contains("description")
378 ? compJson["description"].get<std::string>()
379 : "";
380
381 RegisterComponentType(name, category, description);
382 SYSTEM_LOG << "[ComponentPalettePanel] Loaded: " << name << " (" << category << ")\n";
383 }
384 }
385 else
386 {
387 SYSTEM_LOG << "[ComponentPalettePanel] ERROR: JSON missing 'schemas' or 'components' array\n";
388 return false;
389 }
390
391 // Rebuild categories list
392 std::map<std::string, bool> categoryMap;
393 for (size_t i = 0; i < m_componentTypes.size(); ++i)
394 {
395 categoryMap[m_componentTypes[i].category] = true;
396 }
397 for (auto it = categoryMap.begin(); it != categoryMap.end(); ++it)
398 {
399 m_categories.push_back(it->first);
400 }
401
402 // Sort categories alphabetically
403 std::sort(m_categories.begin(), m_categories.end());
404
405 SYSTEM_LOG << "[ComponentPalettePanel] Successfully loaded " << m_componentTypes.size()
406 << " components from JSON (" << m_categories.size() << " categories)\n";
407 return true;
408 }
409 catch (const std::exception& e)
410 {
411 SYSTEM_LOG << "[ComponentPalettePanel] ERROR parsing JSON: " << e.what() << "\n";
412 return false;
413 }
414 }
415
416 std::string ComponentPalettePanel::ExtractCategoryFromComponentType(const std::string& componentType)
417 {
418 // Extract category from component type name
419 // Examples: "Identity_data" → "Core", "Camera_data" → "Camera", "PhysicsBody_data" → "Physics"
420
421 if (componentType.find("Identity") != std::string::npos) return "Core";
422 if (componentType.find("Position") != std::string::npos) return "Core";
423 if (componentType.find("GridSettings") != std::string::npos) return "Core";
424 if (componentType.find("EditorContext") != std::string::npos) return "Core";
425
426 if (componentType.find("Physics") != std::string::npos) return "Physics";
427 if (componentType.find("Movement") != std::string::npos) return "Physics";
428 if (componentType.find("Collision") != std::string::npos) return "Physics";
429 if (componentType.find("BoundingBox") != std::string::npos) return "Physics";
430 if (componentType.find("TriggerZone") != std::string::npos) return "Physics";
431 if (componentType.find("NavigationAgent") != std::string::npos) return "Physics";
432
433 if (componentType.find("Visual") != std::string::npos) return "Graphics";
434 if (componentType.find("Animation") != std::string::npos) return "Graphics";
435 if (componentType.find("Sprite") != std::string::npos) return "Graphics";
436 if (componentType.find("FX") != std::string::npos) return "Graphics";
437
438 if (componentType.find("Camera") != std::string::npos) return "Camera";
439
440 if (componentType.find("AI") != std::string::npos) return "AI";
441 if (componentType.find("Behavior") != std::string::npos) return "AI";
442 if (componentType.find("Controller") != std::string::npos) return "AI";
443 if (componentType.find("NPC") != std::string::npos) return "AI";
444 if (componentType.find("InputMapping") != std::string::npos) return "AI";
445
446 if (componentType.find("Audio") != std::string::npos) return "Audio";
447 if (componentType.find("Sound") != std::string::npos) return "Audio";
448
449 if (componentType.find("Health") != std::string::npos) return "Gameplay";
450 if (componentType.find("Inventory") != std::string::npos) return "Gameplay";
451
452 if (componentType.find("Player") != std::string::npos) return "Player";
453
454 // Default category
455 return "Other";
456 }
457
458} // namespace Olympe
ComponentTypeID GetComponentTypeID_Static()
Definition ECS_Entity.h:56
std::vector< ComponentType > m_componentTypes
void Render(EntityPrefabGraphDocument *document)
void RegisterComponentType(const std::string &name, const std::string &category, const std::string &description="")
static constexpr float COMPONENT_ITEM_HEIGHT
void AddComponentToGraph(EntityPrefabGraphDocument *document, const ComponentType &componentType)
bool LoadComponentsFromJSON(const std::string &filepath)
void RenderComponentList(EntityPrefabGraphDocument *document)
static constexpr float CATEGORY_HEADER_HEIGHT
std::vector< std::string > m_categories
static constexpr float COMPONENT_ITEM_PADDING_X
const std::vector< ComponentType > & GetComponentTypes() const
static constexpr float COMPONENT_ITEM_PADDING_Y
std::string ExtractCategoryFromComponentType(const std::string &componentType)
< Provides AssetID and INVALID_ASSET_ID
nlohmann::json json
@ Vector
3-component vector (Vector from vector.h)
uint32_t NodeId
nlohmann::json json
#define SYSTEM_LOG