Olympe Engine 2.0
2D Game Engine with ECS Architecture
Loading...
Searching...
No Matches
TilemapEditorApp.cpp
Go to the documentation of this file.
1/*
2 * Olympe Tilemap Editor - Main Application Implementation
3 */
4
5#include "../include/TilemapEditorApp.h"
6#include "../../third_party/imgui/imgui.h"
7#include <iostream>
8#include <cstring>
9
10namespace Olympe {
11namespace Editor {
12
14 : m_showNewLevelDialog(false)
15 , m_showOpenLevelDialog(false)
16 , m_showSaveLevelDialog(false)
17 , m_showAboutDialog(false)
18 , m_viewportZoom(1.0f)
19 , m_viewportOffset(0, 0, 0)
20 , m_selectedTileId(0)
21 , m_initialized(false)
22 {
23 std::memset(m_newLevelNameBuffer, 0, sizeof(m_newLevelNameBuffer));
24 std::memset(m_filePathBuffer, 0, sizeof(m_filePathBuffer));
25 }
26
31
33 {
34 if (m_initialized)
35 {
36 std::cerr << "[TilemapEditorApp] Already initialized" << std::endl;
37 return true;
38 }
39
40 std::cout << "[TilemapEditorApp] Initializing..." << std::endl;
41
42 // Create core systems
43 m_levelManager = std::make_unique<LevelManager>();
44 m_editorState = std::make_unique<EditorState>();
45
46 // Create a default new level
47 m_levelManager->NewLevel("UntitledLevel");
48
49 m_initialized = true;
50 std::cout << "[TilemapEditorApp] Initialization complete" << std::endl;
51
52 return true;
53 }
54
56 {
57 if (!m_initialized)
58 {
59 return;
60 }
61
62 std::cout << "[TilemapEditorApp] Shutting down..." << std::endl;
63
64 m_editorState.reset();
65 m_levelManager.reset();
66
67 m_initialized = false;
68 }
69
71 {
72 if (!m_initialized)
73 {
74 return;
75 }
76
77 // Main menu bar
79
80 // Render UI panels directly without dockspace
87
88 // Render dialogs
97 }
98
99 // ========================================================================
100 // File Operations
101 // ========================================================================
102
104 {
106 }
107
112
114 {
115 const std::string& currentPath = m_levelManager->GetCurrentLevelPath();
116 if (currentPath.empty())
117 {
118 SaveLevelAs();
119 }
120 else
121 {
122 m_levelManager->SaveLevel(currentPath);
123 }
124 }
125
130
132 {
133 return m_levelManager && m_levelManager->HasUnsavedChanges();
134 }
135
136 const std::string& TilemapEditorApp::GetCurrentLevelName() const
137 {
138 static std::string defaultName = "Untitled";
139 if (m_levelManager)
140 {
141 return m_levelManager->GetLevelDefinition().levelName;
142 }
143 return defaultName;
144 }
145
146 // ========================================================================
147 // UI Rendering
148 // ========================================================================
149
151 {
152 if (ImGui::BeginMainMenuBar())
153 {
154 if (ImGui::BeginMenu("File"))
155 {
156 if (ImGui::MenuItem("New Level", "Ctrl+N"))
157 {
158 NewLevel();
159 }
160 if (ImGui::MenuItem("Open Level...", "Ctrl+O"))
161 {
162 OpenLevel();
163 }
164 ImGui::Separator();
165 if (ImGui::MenuItem("Save", "Ctrl+S"))
166 {
167 SaveLevel();
168 }
169 if (ImGui::MenuItem("Save As...", "Ctrl+Shift+S"))
170 {
171 SaveLevelAs();
172 }
173 ImGui::Separator();
174 if (ImGui::MenuItem("Exit", "Alt+F4"))
175 {
176 // Exit will be handled by main loop
177 }
178 ImGui::EndMenu();
179 }
180
181 if (ImGui::BeginMenu("Edit"))
182 {
183 bool canUndo = m_editorState->CanUndo();
184 bool canRedo = m_editorState->CanRedo();
185
186 if (ImGui::MenuItem("Undo", "Ctrl+Z", false, canUndo))
187 {
189 }
190 if (ImGui::MenuItem("Redo", "Ctrl+Y", false, canRedo))
191 {
193 }
194 ImGui::EndMenu();
195 }
196
197 if (ImGui::BeginMenu("View"))
198 {
199 if (ImGui::MenuItem("Reset Zoom"))
200 {
201 m_viewportZoom = 1.0f;
202 }
203 if (ImGui::MenuItem("Reset Pan"))
204 {
205 m_viewportOffset = Vector(0, 0, 0);
206 }
207 ImGui::EndMenu();
208 }
209
210 if (ImGui::BeginMenu("Help"))
211 {
212 if (ImGui::MenuItem("About"))
213 {
214 m_showAboutDialog = true;
215 }
216 ImGui::EndMenu();
217 }
218
219 ImGui::EndMainMenuBar();
220 }
221 }
222
224 {
225 ImGui::Begin("Toolbar");
226
227 ImGui::Text("Tools");
228 ImGui::Separator();
229
230 if (ImGui::Button("Select", ImVec2(80, 30)))
231 {
232 // Activate select tool
233 }
234 ImGui::SameLine();
235
236 if (ImGui::Button("Paint Tile", ImVec2(80, 30)))
237 {
238 // Activate tile painting tool
239 }
240 ImGui::SameLine();
241
242 if (ImGui::Button("Entity", ImVec2(80, 30)))
243 {
244 // Activate entity placement tool
245 }
246
247 ImGui::Separator();
248 ImGui::Text("Selected Tile ID: %d", m_selectedTileId);
249 ImGui::SliderInt("Tile ID", &m_selectedTileId, 0, 255);
250
251 ImGui::End();
252 }
253
255 {
256 ImGui::Begin("Level Viewport");
257
258 ImGui::Text("Level: %s", GetCurrentLevelName().c_str());
259 ImGui::SameLine();
260 if (HasUnsavedChanges())
261 {
262 ImGui::TextColored(ImVec4(1.0f, 0.5f, 0.0f, 1.0f), "*");
263 }
264
265 ImGui::Separator();
266
267 // Viewport controls
268 ImGui::Text("Zoom: %.2f", m_viewportZoom);
269 ImGui::SameLine();
270 if (ImGui::Button("-"))
271 {
272 m_viewportZoom = std::max(0.1f, m_viewportZoom - 0.1f);
273 }
274 ImGui::SameLine();
275 if (ImGui::Button("+"))
276 {
277 m_viewportZoom = std::min(5.0f, m_viewportZoom + 0.1f);
278 }
279
280 ImGui::Separator();
281
282 // Placeholder for actual viewport rendering
283 ImVec2 viewport_size = ImGui::GetContentRegionAvail();
284 ImGui::BeginChild("ViewportCanvas", viewport_size, true);
285
286 ImGui::Text("Canvas Area");
287 ImGui::Text("Size: %.0f x %.0f", viewport_size.x, viewport_size.y);
288 ImGui::Text("Entities: %zu", m_levelManager->GetAllEntities().size());
289
290 // TODO: Render actual level content here
291 // - Grid
292 // - Tiles
293 // - Entities
294 // - Selection highlights
295
296 ImGui::EndChild();
297
298 ImGui::End();
299 }
300
302 {
303 ImGui::Begin("Entity List");
304
305 ImGui::Text("Entities");
306 ImGui::Separator();
307
308 if (ImGui::Button("Add Entity"))
309 {
310 auto cmd = std::make_unique<PlaceEntityCommand>(
311 "Blueprints/DefaultEntity.json",
312 Vector(0, 0, 0)
313 );
314 m_editorState->ExecuteCommand(std::move(cmd), *m_levelManager);
315 }
316
317 ImGui::Separator();
318
319 // List all entities
320 auto entities = m_levelManager->GetAllEntities();
321 for (auto* entity : entities)
322 {
324 if (m_selectedEntityId == entity->id)
325 {
327 }
328
329 ImGui::TreeNodeEx(entity->id.c_str(), node_flags, "%s", entity->name.c_str());
330 if (ImGui::IsItemClicked())
331 {
332 m_selectedEntityId = entity->id;
333 }
334
335 // Context menu
336 if (ImGui::BeginPopupContextItem())
337 {
338 if (ImGui::MenuItem("Delete"))
339 {
340 auto cmd = std::make_unique<DeleteEntityCommand>(entity->id);
341 m_editorState->ExecuteCommand(std::move(cmd), *m_levelManager);
343 }
344 ImGui::EndPopup();
345 }
346 }
347
348 ImGui::End();
349 }
350
352 {
353 ImGui::Begin("Properties");
354
355 if (!m_selectedEntityId.empty())
356 {
358 if (entity)
359 {
360 ImGui::Text("Entity Properties");
361 ImGui::Separator();
362
363 // Name
364 char nameBuffer[256];
365#ifdef _MSC_VER
366 strncpy_s(nameBuffer, sizeof(nameBuffer), entity->name.c_str(), _TRUNCATE);
367#else
368 std::strncpy(nameBuffer, entity->name.c_str(), sizeof(nameBuffer) - 1);
369 nameBuffer[sizeof(nameBuffer) - 1] = '\0';
370#endif
371 if (ImGui::InputText("Name", nameBuffer, sizeof(nameBuffer)))
372 {
373 entity->name = nameBuffer;
374 m_levelManager->MarkDirty();
375 }
376
377 // Prefab path (read-only for now)
378 ImGui::Text("Prefab: %s", entity->prefabPath.c_str());
379
380 // Position
381 float posX = static_cast<float>(entity->position.x);
382 float posY = static_cast<float>(entity->position.y);
383 if (ImGui::DragFloat("Position X", &posX, 1.0f))
384 {
385 entity->position.x = posX;
386 m_levelManager->MarkDirty();
387 }
388 if (ImGui::DragFloat("Position Y", &posY, 1.0f))
389 {
390 entity->position.y = posY;
391 m_levelManager->MarkDirty();
392 }
393 }
394 else
395 {
396 ImGui::Text("Selected entity not found");
398 }
399 }
400 else
401 {
402 ImGui::Text("No entity selected");
403 }
404
405 ImGui::End();
406 }
407
409 {
410 ImGui::Begin("History");
411
412 ImGui::Text("Command History");
413 ImGui::Separator();
414
415 if (ImGui::Button("Undo") && m_editorState->CanUndo())
416 {
418 }
419 ImGui::SameLine();
420 if (ImGui::Button("Redo") && m_editorState->CanRedo())
421 {
423 }
424
425 ImGui::Separator();
426
427 ImGui::Text("History Size: %zu", m_editorState->GetHistorySize());
428 ImGui::Text("Current Index: %zu", m_editorState->GetHistoryIndex());
429
430 if (m_editorState->CanUndo())
431 {
432 ImGui::Text("Next Undo: %s", m_editorState->GetUndoDescription().c_str());
433 }
434 if (m_editorState->CanRedo())
435 {
436 ImGui::Text("Next Redo: %s", m_editorState->GetRedoDescription().c_str());
437 }
438
439 ImGui::End();
440 }
441
443 {
444 ImGuiViewport* viewport = ImGui::GetMainViewport();
445 ImGui::SetNextWindowPos(ImVec2(viewport->WorkPos.x, viewport->WorkPos.y + viewport->WorkSize.y - 25));
446 ImGui::SetNextWindowSize(ImVec2(viewport->WorkSize.x, 25));
447
449 ImGui::Begin("StatusBar", nullptr, window_flags);
450
451 ImGui::Text("Olympe Tilemap Editor - Phase 1");
452 ImGui::SameLine(viewport->WorkSize.x - 200);
453 ImGui::Text("Entities: %zu", m_levelManager->GetAllEntities().size());
454
455 ImGui::End();
456 }
457
458 // ========================================================================
459 // Dialogs
460 // ========================================================================
461
463 {
464 ImGui::OpenPopup("New Level");
465
466 ImVec2 center = ImGui::GetMainViewport()->GetCenter();
467 ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
468
469 if (ImGui::BeginPopupModal("New Level", &m_showNewLevelDialog, ImGuiWindowFlags_AlwaysAutoResize))
470 {
471 ImGui::Text("Create a new level");
472 ImGui::Separator();
473
474 ImGui::InputText("Level Name", m_newLevelNameBuffer, sizeof(m_newLevelNameBuffer));
475
476 ImGui::Separator();
477
478 if (ImGui::Button("Create", ImVec2(120, 0)))
479 {
480 std::string levelName = m_newLevelNameBuffer;
481 if (levelName.empty())
482 {
483 levelName = "UntitledLevel";
484 }
485 m_levelManager->NewLevel(levelName);
486 m_editorState->ClearHistory();
487 m_showNewLevelDialog = false;
488 std::memset(m_newLevelNameBuffer, 0, sizeof(m_newLevelNameBuffer));
489 }
490 ImGui::SameLine();
491 if (ImGui::Button("Cancel", ImVec2(120, 0)))
492 {
493 m_showNewLevelDialog = false;
494 }
495
496 ImGui::EndPopup();
497 }
498 }
499
501 {
502 ImGui::OpenPopup("Open Level");
503
504 ImVec2 center = ImGui::GetMainViewport()->GetCenter();
505 ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
506
507 if (ImGui::BeginPopupModal("Open Level", &m_showOpenLevelDialog, ImGuiWindowFlags_AlwaysAutoResize))
508 {
509 ImGui::Text("Open an existing level");
510 ImGui::Separator();
511
512 ImGui::InputText("File Path", m_filePathBuffer, sizeof(m_filePathBuffer));
513 ImGui::Text("Note: Use full or relative path to JSON file");
514
515 ImGui::Separator();
516
517 if (ImGui::Button("Open", ImVec2(120, 0)))
518 {
519 std::string filePath = m_filePathBuffer;
520 if (!filePath.empty())
521 {
522 if (m_levelManager->LoadLevel(filePath))
523 {
524 m_editorState->ClearHistory();
525 m_showOpenLevelDialog = false;
526 std::memset(m_filePathBuffer, 0, sizeof(m_filePathBuffer));
527 }
528 else
529 {
530 // Show error in console
531 std::cerr << "[TilemapEditorApp] Failed to open level: " << filePath << std::endl;
532 }
533 }
534 }
535 ImGui::SameLine();
536 if (ImGui::Button("Cancel", ImVec2(120, 0)))
537 {
538 m_showOpenLevelDialog = false;
539 }
540
541 ImGui::EndPopup();
542 }
543 }
544
546 {
547 ImGui::OpenPopup("Save Level As");
548
549 ImVec2 center = ImGui::GetMainViewport()->GetCenter();
550 ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
551
552 if (ImGui::BeginPopupModal("Save Level As", &m_showSaveLevelDialog, ImGuiWindowFlags_AlwaysAutoResize))
553 {
554 ImGui::Text("Save level to file");
555 ImGui::Separator();
556
557 ImGui::InputText("File Path", m_filePathBuffer, sizeof(m_filePathBuffer));
558 ImGui::Text("Note: Use full or relative path (e.g., Levels/my_level.json)");
559
560 ImGui::Separator();
561
562 if (ImGui::Button("Save", ImVec2(120, 0)))
563 {
564 std::string filePath = m_filePathBuffer;
565 if (!filePath.empty())
566 {
567 if (m_levelManager->SaveLevel(filePath))
568 {
569 m_showSaveLevelDialog = false;
570 std::memset(m_filePathBuffer, 0, sizeof(m_filePathBuffer));
571 }
572 else
573 {
574 std::cerr << "[TilemapEditorApp] Failed to save level: " << filePath << std::endl;
575 }
576 }
577 }
578 ImGui::SameLine();
579 if (ImGui::Button("Cancel", ImVec2(120, 0)))
580 {
581 m_showSaveLevelDialog = false;
582 }
583
584 ImGui::EndPopup();
585 }
586 }
587
589 {
590 ImGui::OpenPopup("About");
591
592 ImVec2 center = ImGui::GetMainViewport()->GetCenter();
593 ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
594
595 if (ImGui::BeginPopupModal("About", &m_showAboutDialog, ImGuiWindowFlags_AlwaysAutoResize))
596 {
597 ImGui::Text("Olympe Tilemap Editor");
598 ImGui::Separator();
599 ImGui::Text("Version: 1.0.0 (Phase 1)");
600 ImGui::Text("Author: Atlasbruce");
601 ImGui::Separator();
602 ImGui::Text("A tilemap/level editor for Olympe Engine");
603 ImGui::Text("Built with SDL3 + ImGui");
604 ImGui::Separator();
605
606 if (ImGui::Button("Close", ImVec2(120, 0)))
607 {
608 m_showAboutDialog = false;
609 }
610
611 ImGui::EndPopup();
612 }
613 }
614
615} // namespace Editor
616} // namespace Olympe
ComponentTypeID GetComponentTypeID_Static()
Definition ECS_Entity.h:56
std::unique_ptr< EditorState > m_editorState
const std::string & GetCurrentLevelName() const
std::unique_ptr< LevelManager > m_levelManager
float y
Definition vector.h:27
float x
Definition vector.h:27