RunicVTT Open Source Virtual Tabletop for TTRPG using P2P
Loading...
Searching...
No Matches
BoardManager.h
Go to the documentation of this file.
1#pragma once
2#include <vector>
3#include "Renderer.h"
4#include "Texture.h"
5#include "Shader.h"
6#include "VertexArray.h"
7#include "VertexBuffer.h"
8#include "IndexBuffer.h"
9#include "glm/glm.hpp"
10#include "glm/gtc/matrix_transform.hpp"
11
12#include "imgui.h"
13#include "imgui_impl_glfw.h"
14#include "imgui_impl_opengl3.h"
15#include "flecs.h"
16#include "IdentityManager.h"
17#include "Components.h"
18#include "DirectoryWindow.h"
19
20class NetworkManager;
21
23{
24 GLuint textureID = 0;
25 glm::vec2 size{};
26 std::string path; // can be empty for memory loads
27 BoardImageData() = default;
28 BoardImageData(GLuint id, glm::vec2 s, std::string p) :
29 textureID(id), size(s), path(std::move(p)) {}
30};
31
32class Camera
33{
34public:
36 position(0.0f, 0.0f), zoom_level(1.0f), fbo_dimensions(0, 0) {} // Set initial zoom to 1.0f for no scaling by default
37
38 void pan(glm::vec2 delta)
39 {
40 position += delta;
41 }
42
43 void zoom(float factor, glm::vec2 mouse_world_pos_before_zoom)
44 {
45 float old_zoom_level = zoom_level;
46 zoom_level *= factor;
47 zoom_level = glm::clamp(zoom_level, 0.05f, 20.0f); // Example limits
48 if (zoom_level == old_zoom_level)
49 {
50 return;
51 }
52 float zoom_ratio = zoom_level / old_zoom_level;
53 position = mouse_world_pos_before_zoom - (mouse_world_pos_before_zoom - position) / zoom_ratio;
54 }
55
56 void setPosition(glm::vec2 newPosition)
57 {
58 position = newPosition;
59 }
60
61 glm::vec2 getPosition() const
62 {
63 return position;
64 }
65
66 void setZoom(float newZoomLevel)
67 {
68 zoom_level = newZoomLevel;
69 zoom_level = glm::clamp(zoom_level, 0.1f, 10.0f);
70 }
71
72 float getZoom() const
73 {
74 return zoom_level;
75 }
76
77 glm::mat4 getViewMatrix() const
78 {
79 return glm::translate(glm::mat4(1.0f), glm::vec3(-position.x, -position.y, 0.0f));
80 }
81
82 glm::mat4 getProjectionMatrix() const
83 {
84 float half_width = (fbo_dimensions.x / 2.0f) / zoom_level;
85 float half_height = (fbo_dimensions.y / 2.0f) / zoom_level;
86 return glm::ortho(-half_width, half_width, half_height, -half_height, -1.0f, 1.0f);
87 }
88
89 glm::vec2 worldToScreenPosition(glm::vec2 world_position) const
90 {
91 glm::vec4 world_homogeneous = glm::vec4(world_position.x, world_position.y, 0.0f, 1.0f);
92 /*glm::vec4 camera_coords = getViewMatrix() * world_homogeneous;
93 glm::vec4 clip_coords = getProjectionMatrix() * camera_coords;*/
94
95 glm::mat4 pv_matrix = getProjectionMatrix() * getViewMatrix(); // Combine PV matrix
96 glm::vec4 clip_coords = pv_matrix * world_homogeneous;
97
98 glm::vec2 ndc;
99 if (clip_coords.w != 0.0f)
100 { // Avoid division by zero
101 ndc.x = clip_coords.x / clip_coords.w;
102 ndc.y = clip_coords.y / clip_coords.w;
103 }
104 else
105 {
106 return glm::vec2(NAN, NAN); // Indicate invalid position
107 }
108 glm::vec2 fbo_pixel_top_left_origin;
109 fbo_pixel_top_left_origin.x = (ndc.x + 1.0f) * 0.5f * fbo_dimensions.x;
110 fbo_pixel_top_left_origin.y = (1.0f - ndc.y) * 0.5f * fbo_dimensions.y;
111
112 return fbo_pixel_top_left_origin;
113 }
114
115 glm::vec2 fboToNdcPos(glm::vec2 fbo_pixel_top_left_origin) const
116 {
117 glm::vec2 ndc;
118 ndc.x = (2.0f * fbo_pixel_top_left_origin.x) / fbo_dimensions.x - 1.0f;
119 ndc.y = 1.0f - (2.0f * fbo_pixel_top_left_origin.y) / fbo_dimensions.y;
120 return ndc;
121 }
122
123 glm::vec2 screenToWorldPosition(glm::vec2 fbo_pixel_top_left_origin) const
124 {
125
126 glm::vec2 ndc = fboToNdcPos(fbo_pixel_top_left_origin);
127 glm::vec4 ndc_homogeneous = glm::vec4(ndc.x, ndc.y, 0.0f, 1.0f);
128 glm::mat4 inverse_pv_matrix = glm::inverse(getProjectionMatrix() * getViewMatrix());
129 glm::vec4 world_homogeneous = inverse_pv_matrix * ndc_homogeneous;
130 glm::vec2 world_position;
131 if (world_homogeneous.w != 0.0f)
132 {
133 world_position.x = world_homogeneous.x / world_homogeneous.w;
134 world_position.y = world_homogeneous.y / world_homogeneous.w;
135 }
136 else
137 {
138 return glm::vec2(NAN, NAN); // Indicate invalid result
139 }
140
141 return world_position;
142 }
143
144 void setFboDimensions(glm::vec2 dims)
145 {
146 fbo_dimensions = dims;
147 }
148
149private:
150 glm::vec2 position; // 2D position of the camera (X, Y)
151 float zoom_level; // Zoom level, where 1.0f means no zoom, > 1.0f means zoom in, < 1.0f means zoom out
152 glm::vec2 fbo_dimensions; // current_fbo_dimension
153};
154
155enum class Tool
156{
157 MOVE,
158 FOG,
159 MARKER,
160 SELECT
161};
162
163//MOVE - Move Camera and Markers;
164//FOG - Create FOG and TOGGLE VISIBILITY
165//MARKER - Toggle Marker Visibility? MIGHT NOT BE NECESSARY
166//SELECT - Select Marker/Fog, opening an edit window(change size, visibility, and delete)
167
168class BoardManager : std::enable_shared_from_this<BoardManager>
169{
170public:
171 BoardManager(flecs::world ecs, std::weak_ptr<NetworkManager> network_manager, std::shared_ptr<IdentityManager> identity_manager, std::shared_ptr<DirectoryWindow> map_directory, std::shared_ptr<DirectoryWindow> marker_directory);
173
174 void renderBoard(VertexArray& va, IndexBuffer& ib, Shader& shader, Shader& grid_shader, Renderer& renderer); // Render board elements (map, markers, fog)
175 void renderToolbar(const ImVec2& window_position);
176 void resetCamera();
177
178 // Marker interactions
179 flecs::entity createMarker(const std::string& imageFilePath, GLuint textureId, glm::vec2 position, glm::vec2 size);
180 void deleteMarker(flecs::entity markerEntity);
181 void handleMarkerDragging(glm::vec2 mousePos);
182 bool isMouseOverMarker(glm::vec2 mousePos);
183 bool canMoveMarker(const MarkerComponent* mc, flecs::entity markerEnt) const;
184
185 glm::vec2 computeMarkerDrawSize_ARFit(const TextureComponent& tex, float basePx, float scale);
186 // Fog of War interactions
187 flecs::entity createFogOfWar(glm::vec2 startPos, glm::vec2 size);
188 void deleteFogOfWar(flecs::entity fogEntity);
189 void handleFogCreation(glm::vec2 end_world_position);
190
191 // Camera manipulation
192 void panBoard(glm::vec2 currentMousePos);
193 //void zoomBoard(float zoomFactor);
194
195 // Toolbar tool management
196 Tool getCurrentTool() const;
197 void setCurrentTool(Tool newTool);
198
199 bool isBoardActive();
200 flecs::entity createBoard(std::string board_name, std::string map_image_path, GLuint texture_id, glm::vec2 size);
201
202 void closeBoard();
203 void setActiveBoard(flecs::entity board_entity);
204 bool isEditWindowOpen() const;
205 void renderEditWindow();
206 void renderCameraWindow();
207 void startMouseDrag(glm::vec2 mousePos, bool draggingMarker);
208 void endMouseDrag();
209 glm::vec2 getMouseStartPosition() const;
210 bool isPanning();
211 bool isDraggingMarker(bool local_drag_only = true);
212 flecs::entity getEntityAtMousePosition(glm::vec2 mouse_position);
213
214 flecs::entity findBoardById(uint64_t boardId);
215 bool shouldSendMarkerMove(uint64_t markerId) const;
216 // Generates a unique 64-bit ID
217 uint64_t generateUniqueId();
218 // Finds an entity by its Identifier component with the specified ID
219 flecs::entity findEntityById(uint64_t target_id);
220
221 //Grid
222 glm::vec2 snapToGrid(glm::vec2 raw_world_pos);
223 void renderGridWindow();
224 //Network
226 void sendEntityUpdate(flecs::entity entity /*, MessageType message_type*/);
227
228 //flecs::entity deserializeBoard(const std::vector<unsigned char>& buffer, size_t& offset);
229 //void serializeBoard(flecs::entity board, std::vector<unsigned char>& buffer);
230 flecs::entity getActiveBoard() const;
231 void loadActiveBoard(const std::string& filePath);
232 void saveActiveBoard(const std::string& filePath);
233 void saveActiveBoard(std::filesystem::path& filePath);
234
235 std::string board_name;
236 std::shared_ptr<DirectoryWindow> map_directory;
237 std::shared_ptr<DirectoryWindow> marker_directory;
239
240 bool isCreatingFog() const
241 {
242 return is_creating_fog;
243 };
244 bool getShowEditWindow() const
245 {
246 return showEditWindow;
247 };
248 void setShowEditWindow(bool show, flecs::entity edit_entity = flecs::entity())
249 {
250 if (showEditWindow && edit_window_entity.is_valid())
251 return;
252 showEditWindow = show;
253 edit_window_entity = edit_entity;
254 };
255
256 BoardImageData LoadTextureFromMemory(const unsigned char* bytes, size_t sizeBytes);
257
258 void killIfMouseUp(bool isMouseDown);
259 void resnapAllMarkersToNearest(const Grid& grid);
260
261 //void replaceOwnerUsernameEverywhere(const std::string& oldUsername,
262 // const std::string& newUsername);
263 void onUsernameChanged(const std::string& uniqueId, const std::string& newUsername);
264
265private:
266 bool showEditWindow = false;
267 bool showGridSettings = false;
268 bool showCameraSettings = false;
269 float markerBasePx = 50.0f;
270 flecs::entity edit_window_entity = flecs::entity();
271 std::weak_ptr<NetworkManager> network_manager;
272 std::shared_ptr<IdentityManager> identity_manager;
273 //glm::vec2 mouseStartPos;
274
278
279 flecs::world ecs;
280 flecs::entity active_board = flecs::entity();
281 flecs::entity grid_entity = flecs::entity();
282
283 bool is_creating_fog = false;
284 Tool currentTool; // Active tool for interaction
286};
287/*ENTITY MANAGER
288
289// BoardManager.h (add these)
290bool showEntityManager = false; // similar to showCameraSettings
291flecs::entity_t entityMgrSelectedId = 0; // current selection
292
293//V2
294void BoardManager::renderEntityManagerWindow()
295{
296 if (!showEntityManager)
297 {
298 setIsNonMapWindowHovered(false);
299 return;
300 }
301
302 // place near mouse on first open (same as your camera window)
303 ImVec2 mousePos = ImGui::GetMousePos();
304 ImGui::SetNextWindowPos(ImVec2(mousePos.x, mousePos.y + ImGui::GetFrameHeightWithSpacing()), ImGuiCond_Appearing);
305
306 ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.10f, 0.12f, 0.16f, 1.0f));
307 ImGui::Begin("Entity Manager", &showEntityManager,
308 ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse);
309
310 // important: disable board panning while on this window
311 setIsNonMapWindowHovered(ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup));
312
313 if (!isBoardActive())
314 {
315 ImGui::TextUnformatted("No active board.");
316 ImGui::End();
317 ImGui::PopStyleColor();
318 return;
319 }
320
321 // filter tabs
322 static int filterTab = 0; // 0=All, 1=Markers, 2=Fog
323 ImGui::TextUnformatted("Show:");
324 ImGui::SameLine();
325 ImGui::RadioButton("All", &filterTab, 0); ImGui::SameLine();
326 ImGui::RadioButton("Markers", &filterTab, 1); ImGui::SameLine();
327 ImGui::RadioButton("Fog", &filterTab, 2);
328
329 ImGui::Separator();
330
331 // left: table of entities with inline controls
332 ImGui::BeginChild("entity_list", ImVec2(600, 300), true, ImGuiWindowFlags_HorizontalScrollbar);
333
334 if (ImGui::BeginTable("EntityTable", 5,
335 ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable | ImGuiTableFlags_SizingStretchProp))
336 {
337 ImGui::TableSetupColumn("Entity");
338 ImGui::TableSetupColumn("Visible", ImGuiTableColumnFlags_WidthFixed, 80.0f);
339 ImGui::TableSetupColumn("- Size", ImGuiTableColumnFlags_WidthFixed, 70.0f);
340 ImGui::TableSetupColumn("+ Size", ImGuiTableColumnFlags_WidthFixed, 70.0f);
341 ImGui::TableSetupColumn("Delete", ImGuiTableColumnFlags_WidthFixed, 80.0f);
342 ImGui::TableHeadersRow();
343
344 // collect candidates
345 struct Row { flecs::entity e; bool isMarker; uint64_t id; };
346 std::vector<Row> rows; rows.reserve(64);
347
348 ecs.defer_begin();
349 active_board.children([&](flecs::entity child)
350 {
351 const bool isMarker = child.has<MarkerComponent>();
352 const bool isFog = child.has<FogOfWar>();
353 if (!isMarker && !isFog) return;
354
355 if (filterTab == 1 && !isMarker) return;
356 if (filterTab == 2 && !isFog) return;
357
358 uint64_t id = 0;
359 if (child.has<Identifier>()) id = child.get<Identifier>()->id;
360 rows.push_back(Row{child, isMarker, id});
361 });
362 ecs.defer_end();
363
364 // simple stable sort: markers first, then by id
365 std::sort(rows.begin(), rows.end(), [](const Row& a, const Row& b){
366 if (a.isMarker != b.isMarker) return a.isMarker > b.isMarker;
367 return a.id < b.id;
368 });
369
370 // render rows
371 for (auto& r : rows)
372 {
373 ImGui::TableNextRow();
374 ImGui::PushID((int)r.e.id());
375
376 // col 0: label + selection (clicking the label selects)
377 ImGui::TableSetColumnIndex(0);
378 const bool selected = (entityMgrSelectedId == r.e.id());
379 const std::string label = (r.isMarker ? "Marker_" : "Fog_") + std::to_string(r.id);
380 if (ImGui::Selectable(label.c_str(), selected, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowDoubleClick))
381 {
382 entityMgrSelectedId = r.e.id();
383 }
384
385 // col 1: visibility checkbox
386 ImGui::TableSetColumnIndex(1);
387 if (r.e.has<Visibility>())
388 {
389 auto v = r.e.get<Visibility>();
390 bool vis = v->isVisible;
391 if (ImGui::Checkbox("##vis", &vis))
392 {
393 if (auto vm = r.e.get_mut<Visibility>())
394 vm->isVisible = vis;
395 }
396 }
397 else
398 {
399 ImGui::BeginDisabled();
400 bool dummy = true; ImGui::Checkbox("##vis_d", &dummy);
401 ImGui::EndDisabled();
402 }
403
404 // col 2: - Size
405 ImGui::TableSetColumnIndex(2);
406 if (r.e.has<Size>())
407 {
408 if (ImGui::SmallButton("-"))
409 {
410 if (auto s = r.e.get_mut<Size>())
411 {
412 s->width *= 0.90f;
413 s->height *= 0.90f;
414 }
415 }
416 }
417 else
418 {
419 ImGui::BeginDisabled(); ImGui::SmallButton("-"); ImGui::EndDisabled();
420 }
421
422 // col 3: + Size
423 ImGui::TableSetColumnIndex(3);
424 if (r.e.has<Size>())
425 {
426 if (ImGui::SmallButton("+"))
427 {
428 if (auto s = r.e.get_mut<Size>())
429 {
430 s->width *= 1.10f;
431 s->height *= 1.10f;
432 }
433 }
434 }
435 else
436 {
437 ImGui::BeginDisabled(); ImGui::SmallButton("+"); ImGui::EndDisabled();
438 }
439
440 // col 4: Delete (with per-row confirmation)
441 ImGui::TableSetColumnIndex(4);
442 if (ImGui::SmallButton("Delete"))
443 {
444 ImGui::OpenPopup("Confirm##del");
445 }
446 bool opened = ImGui::BeginPopupModal("Confirm##del", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
447 if (opened)
448 {
449 ImGui::Text("Delete %s ?", label.c_str());
450 ImGui::Separator();
451 if (ImGui::Button("Yes", ImVec2(80, 0)))
452 {
453 if (r.e.is_alive()) r.e.destruct();
454 if (entityMgrSelectedId == r.e.id()) entityMgrSelectedId = 0;
455 ImGui::CloseCurrentPopup();
456 }
457 ImGui::SameLine();
458 if (ImGui::Button("No", ImVec2(80, 0)))
459 {
460 ImGui::CloseCurrentPopup();
461 }
462 ImGui::EndPopup();
463 }
464
465 ImGui::PopID();
466 }
467
468 ImGui::EndTable();
469 }
470
471 ImGui::EndChild();
472
473 ImGui::Separator();
474
475 // right below: details for the selected entity (read-only info; edits are on the row)
476 if (entityMgrSelectedId != 0)
477 {
478 flecs::entity sel = ecs.entity(entityMgrSelectedId);
479 if (sel.is_valid() && sel.is_alive() && sel.has(flecs::ChildOf, active_board))
480 {
481 ImGui::TextUnformatted("Selected:");
482 bool isMarker = sel.has<MarkerComponent>();
483 uint64_t id = sel.has<Identifier>() ? sel.get<Identifier>()->id : 0;
484 ImGui::SameLine();
485 ImGui::Text("%s_%llu", isMarker ? "Marker" : (sel.has<FogOfWar>() ? "Fog" : "Entity"),
486 (unsigned long long)id);
487
488 if (sel.has<TextureComponent>() && isMarker)
489 {
490 // show filename (full path is stored; you said that’s what you have)
491 const auto* t = sel.get<TextureComponent>();
492 if (t)
493 {
494 ImGui::Text("Texture: %s", t->imagePath.c_str());
495 }
496 }
497
498 if (sel.has<Position>())
499 {
500 const auto* p = sel.get<Position>();
501 ImGui::Text("Position: (%d, %d)", p->x, p->y);
502 }
503 if (sel.has<Size>())
504 {
505 const auto* s = sel.get<Size>();
506 ImGui::Text("Size: (%.1f, %.1f)", s->width, s->height);
507 }
508 }
509 else
510 {
511 ImGui::TextUnformatted("Selected entity is no longer valid.");
512 }
513 }
514 else
515 {
516 ImGui::TextUnformatted("No selection.");
517 }
518
519 ImGui::End();
520 ImGui::PopStyleColor();
521}
522
523//V1
524void BoardManager::renderEntityManagerWindow()
525{
526 if (!showEntityManager)
527 {
528 setIsNonMapWindowHovered(false);
529 return;
530 }
531
532 // Dock near mouse on first open, same vibe as your camera window
533 ImVec2 mousePos = ImGui::GetMousePos();
534 ImGui::SetNextWindowPos(ImVec2(mousePos.x, mousePos.y + ImGui::GetFrameHeightWithSpacing()), ImGuiCond_Appearing);
535
536 ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.10f, 0.12f, 0.16f, 1.0f));
537 ImGui::Begin("Entity Manager", &showEntityManager,
538 ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse);
539
540 // Important: disable board panning while over this window
541 setIsNonMapWindowHovered(ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup));
542
543 // Guard: must have an active board
544 if (!isBoardActive())
545 {
546 ImGui::TextUnformatted("No active board.");
547 ImGui::End();
548 ImGui::PopStyleColor();
549 return;
550 }
551
552 // --- Filter (Markers / Fog / Both) ---
553 static int filterTab = 0; // 0=All, 1=Markers, 2=Fog
554 ImGui::TextUnformatted("Show:");
555 ImGui::SameLine();
556 ImGui::RadioButton("All", &filterTab, 0); ImGui::SameLine();
557 ImGui::RadioButton("Markers", &filterTab, 1); ImGui::SameLine();
558 ImGui::RadioButton("Fog", &filterTab, 2);
559
560 ImGui::Separator();
561
562 // --- Left: list of entities ---
563 ImGui::BeginChild("entity_list", ImVec2(260, 280), true, ImGuiWindowFlags_HorizontalScrollbar);
564
565 struct Row {
566 flecs::entity e;
567 bool isMarker = false;
568 float area = 0.f;
569 std::string label;
570 };
571 std::vector<Row> rows; rows.reserve(64);
572
573 // Build list
574 ecs.defer_begin(); // okay to keep pattern; not strictly required for reads
575 active_board.children([&](flecs::entity child)
576 {
577 const bool isMarker = child.has<MarkerComponent>();
578 const bool isFog = child.has<FogOfWar>();
579 if (!isMarker && !isFog) return;
580
581 if (filterTab == 1 && !isMarker) return; // Markers only
582 if (filterTab == 2 && !isFog) return; // Fog only
583
584 // Visible name
585 std::string name;
586 if (child.has<Identifier>())
587 {
588 auto id = child.get<Identifier>()->id;
589 name = std::to_string(id);
590 }
591
592 std::string display = isMarker ? "[Marker] " : "[Fog] ";
593 if (isMarker && child.has<MarkerComponent>())
594 {
595 const auto* mc = child.get<MarkerComponent>();
596 if (mc && !mc->name.empty())
597 display += mc->name + " (id:" + (name.empty()? "?" : name) + ")";
598 else
599 display += (name.empty()? "(unnamed)" : "id:" + name);
600 }
601 else
602 {
603 display += (name.empty()? "fog" : "id:" + name);
604 }
605
606 float area = 0.f;
607 if (child.has<Size>())
608 {
609 const auto* s = child.get<Size>();
610 area = std::max(0.f, s->width) * std::max(0.f, s->height);
611 }
612
613 rows.push_back(Row{ child, isMarker, area, std::move(display) });
614 });
615 ecs.defer_end();
616
617 // Sort for stable UI: Markers first, then by smaller area
618 std::sort(rows.begin(), rows.end(), [](const Row& a, const Row& b){
619 if (a.isMarker != b.isMarker) return a.isMarker > b.isMarker; // markers first
620 return a.area < b.area; // smaller first
621 });
622
623 // Render list
624 bool selectionStillValid = false;
625 for (size_t i = 0; i < rows.size(); ++i)
626 {
627 const bool selected = (entityMgrSelectedId != 0 && rows[i].e.id() == entityMgrSelectedId);
628 if (ImGui::Selectable(rows[i].label.c_str(), selected))
629 {
630 entityMgrSelectedId = rows[i].e.id();
631 }
632 if (selected) selectionStillValid = true;
633 }
634 if (!selectionStillValid) entityMgrSelectedId = 0;
635
636 ImGui::EndChild();
637
638 ImGui::SameLine();
639
640 // --- Right: details and controls for selected entity ---
641 ImGui::BeginChild("entity_details", ImVec2(320, 280), true);
642
643 if (entityMgrSelectedId == 0)
644 {
645 ImGui::TextUnformatted("Select an entity to edit.");
646 }
647 else
648 {
649 flecs::entity sel = ecs.entity(entityMgrSelectedId);
650 if (!sel.is_valid() || !sel.is_alive() || !sel.has(flecs::ChildOf, active_board))
651 {
652 ImGui::TextUnformatted("Entity is no longer valid.");
653 }
654 else
655 {
656 // Basic header
657 bool isMarker = sel.has<MarkerComponent>();
658 ImGui::Text("%s", isMarker ? "Marker" : (sel.has<FogOfWar>() ? "Fog of War" : "Entity"));
659
660 if (sel.has<Identifier>())
661 {
662 auto id = sel.get<Identifier>()->id;
663 ImGui::SameLine();
664 ImGui::TextDisabled(" (id: %llu)", (unsigned long long)id);
665 }
666
667 ImGui::Separator();
668
669 // Visibility
670 if (sel.has<Visibility>())
671 {
672 auto v = sel.get_mut<Visibility>();
673 bool vis = v->isVisible;
674 if (ImGui::Checkbox("Visible", &vis))
675 {
676 v->isVisible = vis;
677 }
678 }
679
680 // Size
681 if (sel.has<Size>())
682 {
683 auto s = sel.get_mut<Size>();
684 float w = s->width, h = s->height;
685
686 if (ImGui::InputFloat("Width", &w, 1.0f, 10.0f, "%.1f")) { s->width = std::max(0.0f, w); }
687 if (ImGui::InputFloat("Height", &h, 1.0f, 10.0f, "%.1f")) { s->height = std::max(0.0f, h); }
688
689 ImGui::SameLine();
690 if (ImGui::Button("+10%")) { s->width *= 1.1f; s->height *= 1.1f; }
691 ImGui::SameLine();
692 if (ImGui::Button("-10%")) { s->width *= 0.9f; s->height *= 0.9f; }
693 }
694
695 // Position
696 if (sel.has<Position>())
697 {
698 auto p = sel.get_mut<Position>();
699 int x = p->x, y = p->y;
700 if (ImGui::InputInt("X", &x)) p->x = x;
701 if (ImGui::InputInt("Y", &y)) p->y = y;
702 }
703
704 // Marker specifics
705 if (isMarker && sel.has<MarkerComponent>())
706 {
707 auto mc = sel.get_mut<MarkerComponent>();
708 char nameBuf[128] = {};
709 if (!mc->name.empty())
710 {
711 std::snprintf(nameBuf, sizeof(nameBuf), "%s", mc->name.c_str());
712 }
713 if (ImGui::InputText("Name", nameBuf, IM_ARRAYSIZE(nameBuf)))
714 {
715 mc->name = nameBuf;
716 }
717 }
718
719 ImGui::Separator();
720
721 // Delete (with confirmation)
722 static bool openConfirm = false;
723 if (ImGui::Button("Delete"))
724 {
725 ImGui::OpenPopup("Confirm Delete##EntityMgr");
726 openConfirm = true;
727 }
728 if (ImGui::BeginPopupModal("Confirm Delete##EntityMgr", nullptr, ImGuiWindowFlags_AlwaysAutoResize))
729 {
730 ImGui::TextUnformatted("Delete this entity?");
731 ImGui::Separator();
732 if (ImGui::Button("Yes", ImVec2(100, 0)))
733 {
734 if (sel.is_alive())
735 {
736 sel.destruct();
737 entityMgrSelectedId = 0;
738 }
739 ImGui::CloseCurrentPopup();
740 openConfirm = false;
741 }
742 ImGui::SameLine();
743 if (ImGui::Button("No", ImVec2(100, 0)))
744 {
745 ImGui::CloseCurrentPopup();
746 openConfirm = false;
747 }
748 ImGui::EndPopup();
749 }
750
751 // While the modal is open, ensure board panning remains disabled
752 if (openConfirm) setIsNonMapWindowHovered(true);
753 }
754 }
755
756 ImGui::EndChild();
757
758 ImGui::End();
759 ImGui::PopStyleColor();
760}
761
762//TOOLBAR
763ImGui::PushStyleColor(ImGuiCol_Button, button_popup_color);
764ImGui::PushStyleColor(ImGuiCol_ButtonHovered, button_popup_hover);
765ImGui::PushStyleColor(ImGuiCol_ButtonActive, button_popup_active);
766if (ImGui::Button("Entity Manager", ImVec2(110, 40)))
767{
768 showEntityManager = !showEntityManager;
769}
770ImGui::PopStyleColor(3);
771
772// later in your frame:
773renderEntityManagerWindow();
774
775
776*/
Tool
@ MARKER
@ SELECT
flecs::entity grid_entity
void deleteMarker(flecs::entity markerEntity)
flecs::entity getEntityAtMousePosition(glm::vec2 mouse_position)
void renderBoard(VertexArray &va, IndexBuffer &ib, Shader &shader, Shader &grid_shader, Renderer &renderer)
void renderToolbar(const ImVec2 &window_position)
uint64_t generateUniqueId()
bool isEditWindowOpen() const
void sendGameState()
flecs::entity findBoardById(uint64_t boardId)
bool isMouseOverMarker(glm::vec2 mousePos)
flecs::entity edit_window_entity
void renderEditWindow()
void setShowEditWindow(bool show, flecs::entity edit_entity=flecs::entity())
bool getShowEditWindow() const
void saveActiveBoard(const std::string &filePath)
bool showCameraSettings
void sendEntityUpdate(flecs::entity entity)
bool canMoveMarker(const MarkerComponent *mc, flecs::entity markerEnt) const
void startMouseDrag(glm::vec2 mousePos, bool draggingMarker)
flecs::entity findEntityById(uint64_t target_id)
glm::vec2 mouse_current_world_pos
BoardImageData LoadTextureFromMemory(const unsigned char *bytes, size_t sizeBytes)
glm::vec2 getMouseStartPosition() const
std::string board_name
glm::vec2 mouse_start_world_pos
bool showGridSettings
void deleteFogOfWar(flecs::entity fogEntity)
BoardManager(flecs::world ecs, std::weak_ptr< NetworkManager > network_manager, std::shared_ptr< IdentityManager > identity_manager, std::shared_ptr< DirectoryWindow > map_directory, std::shared_ptr< DirectoryWindow > marker_directory)
void renderGridWindow()
Tool getCurrentTool() const
bool isBoardActive()
void renderCameraWindow()
void onUsernameChanged(const std::string &uniqueId, const std::string &newUsername)
std::shared_ptr< DirectoryWindow > marker_directory
void setActiveBoard(flecs::entity board_entity)
std::weak_ptr< NetworkManager > network_manager
flecs::world ecs
glm::vec2 computeMarkerDrawSize_ARFit(const TextureComponent &tex, float basePx, float scale)
void setCurrentTool(Tool newTool)
void resnapAllMarkersToNearest(const Grid &grid)
flecs::entity createFogOfWar(glm::vec2 startPos, glm::vec2 size)
bool isCreatingFog() const
flecs::entity getActiveBoard() const
void killIfMouseUp(bool isMouseDown)
glm::vec2 snapToGrid(glm::vec2 raw_world_pos)
bool isDraggingMarker(bool local_drag_only=true)
flecs::entity createMarker(const std::string &imageFilePath, GLuint textureId, glm::vec2 position, glm::vec2 size)
flecs::entity active_board
std::shared_ptr< IdentityManager > identity_manager
bool shouldSendMarkerMove(uint64_t markerId) const
void loadActiveBoard(const std::string &filePath)
void handleFogCreation(glm::vec2 end_world_position)
void handleMarkerDragging(glm::vec2 mousePos)
std::shared_ptr< DirectoryWindow > map_directory
glm::vec2 mouse_start_screen_pos
void panBoard(glm::vec2 currentMousePos)
flecs::entity createBoard(std::string board_name, std::string map_image_path, GLuint texture_id, glm::vec2 size)
void zoom(float factor, glm::vec2 mouse_world_pos_before_zoom)
void setZoom(float newZoomLevel)
glm::mat4 getViewMatrix() const
glm::mat4 getProjectionMatrix() const
glm::vec2 fboToNdcPos(glm::vec2 fbo_pixel_top_left_origin) const
void setPosition(glm::vec2 newPosition)
float getZoom() const
glm::vec2 screenToWorldPosition(glm::vec2 fbo_pixel_top_left_origin) const
glm::vec2 getPosition() const
float zoom_level
void setFboDimensions(glm::vec2 dims)
glm::vec2 worldToScreenPosition(glm::vec2 world_position) const
glm::vec2 position
void pan(glm::vec2 delta)
glm::vec2 fbo_dimensions
glm::vec2 size
BoardImageData()=default
BoardImageData(GLuint id, glm::vec2 s, std::string p)
std::string path