MetaImGUI 1.0.0
ImGui Application Template for C++20
Loading...
Searching...
No Matches
UIRenderer.cpp
Go to the documentation of this file.
1/*
2 MetaImGUI
3 Copyright (C) 2026 A P Nicholson
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <https://www.gnu.org/licenses/>.
17*/
18
19#include "UIRenderer.h"
20
21#include "ISSTracker.h"
22#include "Localization.h"
23#include "Logger.h"
24#include "ThemeManager.h"
25#include "UIEvents.h"
26#include "UpdateChecker.h"
27#include "version.h"
28
29#include <GLFW/glfw3.h>
30#include <imgui.h>
31#include <imgui_impl_glfw.h>
32#include <imgui_impl_opengl3.h>
33#include <implot.h>
34
35#include <array>
36#include <cstdlib>
37#include <ctime>
38
39namespace MetaImGUI {
40
41// UI Layout Constants
42namespace UILayout {
43// Margins and spacing
44constexpr float LEFT_MARGIN = 50.0f;
45constexpr float TOP_MARGIN = 100.0f;
46constexpr float STATUS_BAR_HEIGHT = 28.0f;
47
48// Window sizes
49constexpr float ABOUT_WINDOW_WIDTH = 450.0f;
50constexpr float ABOUT_WINDOW_HEIGHT = 350.0f;
51constexpr float UPDATE_WINDOW_WIDTH = 450.0f;
52constexpr float UPDATE_WINDOW_HEIGHT = 300.0f;
53constexpr float RELEASE_NOTES_HEIGHT = 120.0f;
54
55// Button sizes
56constexpr float BUTTON_OPEN_RELEASE_WIDTH = 200.0f;
57constexpr float BUTTON_REMIND_LATER_WIDTH = 150.0f;
58constexpr float BUTTON_CLOSE_WIDTH = 75.0f;
59constexpr float BUTTON_HEIGHT = 30.0f;
60
61// Status bar
62constexpr float STATUS_CIRCLE_RADIUS = 5.0f;
63constexpr float STATUS_CIRCLE_PADDING = 6.0f;
64constexpr float STATUS_RIGHT_SIDE_WIDTH = 200.0f;
65
66// Padding and style
67constexpr float WINDOW_PADDING_X = 8.0f;
68constexpr float WINDOW_PADDING_Y = 4.0f;
69constexpr float ITEM_SPACING_X = 12.0f;
70constexpr float ITEM_SPACING_Y = 0.0f;
71constexpr float VERTICAL_SPACING_SMALL = 10.0f;
72constexpr float TEXT_WRAP_POS_MULTIPLIER = 35.0f;
73} // namespace UILayout
74
75UIRenderer::UIRenderer() = default;
76
80
81bool UIRenderer::Initialize(GLFWwindow* window) {
82 if (m_initialized) {
83 return true;
84 }
85
86 // Setup ImGui context
87 IMGUI_CHECKVERSION();
88 ImGui::CreateContext();
89 ImGuiIO& io = ImGui::GetIO();
90 io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
91
92 // Setup ImPlot context
93 ImPlot::CreateContext();
94
95 // Setup ImGui style
97
98 // Setup Platform/Renderer backends
99 ImGui_ImplGlfw_InitForOpenGL(window, true);
100 ImGui_ImplOpenGL3_Init("#version 330");
101
102 m_initialized = true;
103 return true;
104}
105
107 if (m_initialized) {
108 ImGui_ImplOpenGL3_Shutdown();
109 ImGui_ImplGlfw_Shutdown();
110 ImPlot::DestroyContext();
111 ImGui::DestroyContext();
112 m_initialized = false;
113 }
114}
115
117 ImGui_ImplOpenGL3_NewFrame();
118 ImGui_ImplGlfw_NewFrame();
119 ImGui::NewFrame();
120}
121
123 ImGui::Render();
124 ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
125}
126
128 auto& loc = Localization::Instance();
129
130 const float contentHeight = ImGui::GetContentRegionAvail().y - UILayout::STATUS_BAR_HEIGHT;
131
132 // Use natural ImGui flow layout instead of pixel-positioned cursor moves.
133 // WindowPadding gives the left/top margin; ItemSpacing handles vertical
134 // gaps between widgets. This adapts cleanly to font scaling, DPI changes,
135 // and translated button labels of varying widths.
136 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(UILayout::LEFT_MARGIN, UILayout::TOP_MARGIN));
137 ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing,
138 ImVec2(ImGui::GetStyle().ItemSpacing.x, UILayout::VERTICAL_SPACING_SMALL));
139
140 if (ImGui::BeginChild("MainContent", ImVec2(0, contentHeight), ImGuiChildFlags_None, ImGuiWindowFlags_None)) {
141 ImGui::TextUnformatted("Welcome to MetaImGUI!");
142 ImGui::TextUnformatted("This is a template for creating ImGui-based applications.");
143 ImGui::TextUnformatted("Use the menu bar above to access the About dialog.");
144
145 // Visual gap between text and button column.
146 ImGui::Dummy(ImVec2(0, UILayout::VERTICAL_SPACING_SMALL));
147
148 if (ImGui::Button(loc.Tr("button.show_about").c_str())) {
149 events.showAboutRequested.Emit();
150 }
151 if (ImGui::Button(loc.Tr("button.show_demo").c_str())) {
152 events.showDemoWindow.Emit();
153 }
154 if (ImGui::Button(loc.Tr("button.show_input").c_str())) {
156 }
157 }
158 ImGui::EndChild();
159
160 ImGui::PopStyleVar(2);
161}
162
163void UIRenderer::RenderMenuBar(UIEvents& events, bool showDemoWindow, bool showISSTracker) {
164 auto& loc = Localization::Instance();
165
166 if (ImGui::BeginMenuBar()) {
167 if (ImGui::BeginMenu(loc.Tr("menu.file").c_str())) {
168 if (ImGui::MenuItem(loc.Tr("menu.exit").c_str(), "Alt+F4")) {
169 events.exitRequested.Emit();
170 }
171 ImGui::EndMenu();
172 }
173
174 if (ImGui::BeginMenu(loc.Tr("menu.view").c_str())) {
175 if (ImGui::MenuItem(loc.Tr("menu.demo_window").c_str(), nullptr, showDemoWindow)) {
176 events.toggleDemoWindow.Emit();
177 }
178
179 if (ImGui::MenuItem("ISS Tracker", nullptr, showISSTracker)) {
180 events.toggleISSTracker.Emit();
181 }
182
183 ImGui::Separator();
184
185 if (ImGui::BeginMenu(loc.Tr("menu.theme").c_str())) {
186 if (ImGui::MenuItem("Dark", nullptr, ThemeManager::GetCurrent() == ThemeManager::Theme::Dark)) {
188 }
189 if (ImGui::MenuItem("Light", nullptr, ThemeManager::GetCurrent() == ThemeManager::Theme::Light)) {
191 }
192 if (ImGui::MenuItem("Classic", nullptr, ThemeManager::GetCurrent() == ThemeManager::Theme::Classic)) {
194 }
195 if (ImGui::MenuItem("Modern", nullptr, ThemeManager::GetCurrent() == ThemeManager::Theme::Modern)) {
197 }
198 ImGui::EndMenu();
199 }
200
201 ImGui::Separator();
202
203 if (ImGui::BeginMenu(loc.Tr("menu.language").c_str())) {
204 if (ImGui::MenuItem("English", nullptr, loc.GetCurrentLanguage() == "en")) {
205 loc.SetLanguage("en");
206 }
207 if (ImGui::MenuItem("Español", nullptr, loc.GetCurrentLanguage() == "es")) {
208 loc.SetLanguage("es");
209 }
210 if (ImGui::MenuItem("Français", nullptr, loc.GetCurrentLanguage() == "fr")) {
211 loc.SetLanguage("fr");
212 }
213 if (ImGui::MenuItem("Deutsch", nullptr, loc.GetCurrentLanguage() == "de")) {
214 loc.SetLanguage("de");
215 }
216 ImGui::EndMenu();
217 }
218 ImGui::EndMenu();
219 }
220
221 if (ImGui::BeginMenu(loc.Tr("menu.help").c_str())) {
222 if (ImGui::MenuItem(loc.Tr("menu.check_updates").c_str())) {
224 }
225 ImGui::Separator();
226 if (ImGui::MenuItem(loc.Tr("menu.about").c_str(), "Ctrl+A")) {
227 events.showAboutRequested.Emit();
228 }
229 ImGui::EndMenu();
230 }
231
232 ImGui::EndMenuBar();
233 }
234}
235
236void UIRenderer::RenderStatusBar(const std::string& statusMessage, float fps, const char* version,
237 bool updateInProgress) {
238 // Status bar styling - theme-aware background
239 const ImVec4 windowBg = ImGui::GetStyle().Colors[ImGuiCol_WindowBg];
240 const ImVec4 statusBarBg = ImVec4(windowBg.x * 0.85f, // Slightly darker/lighter than window
241 windowBg.y * 0.85f, windowBg.z * 0.85f, 1.0f);
242
243 ImGui::PushStyleColor(ImGuiCol_ChildBg, statusBarBg);
244 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(UILayout::WINDOW_PADDING_X, UILayout::WINDOW_PADDING_Y));
245 ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(UILayout::ITEM_SPACING_X, UILayout::ITEM_SPACING_Y));
246
247 // Draw subtle top separator line - theme-aware
248 ImDrawList* drawList = ImGui::GetWindowDrawList();
249 const ImVec2 statusBarPos = ImGui::GetCursorScreenPos();
250 const ImVec2 lineEnd = ImVec2(statusBarPos.x + ImGui::GetContentRegionAvail().x, statusBarPos.y);
251 const ImVec4 separatorColor = ImGui::GetStyle().Colors[ImGuiCol_Separator];
252 drawList->AddLine(statusBarPos, lineEnd, ImGui::ColorConvertFloat4ToU32(separatorColor), 1.0f);
253
254 if (ImGui::BeginChild("StatusBar", ImVec2(0, UILayout::STATUS_BAR_HEIGHT), ImGuiChildFlags_None,
255 ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse)) {
256 // Left side - Status message with indicator
257
258 // Reserve space for the circle and get aligned position
259 ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (UILayout::STATUS_CIRCLE_RADIUS * 2) +
261 ImGui::AlignTextToFramePadding();
262
263 // Draw status indicator circle aligned with text
264 drawList = ImGui::GetWindowDrawList();
265 const ImVec2 textPos = ImGui::GetCursorScreenPos();
266 const ImVec2 circleCenter =
268 textPos.y + (ImGui::GetFrameHeight() * 0.5f));
269
270 ImU32 indicatorColor = IM_COL32(50, 200, 50, 255); // Green by default
271 if (updateInProgress) {
272 indicatorColor = IM_COL32(255, 200, 50, 255); // Yellow for in-progress
273 } else {
274 indicatorColor = IM_COL32(50, 200, 50, 255); // Green for ready
275 }
276
277 drawList->AddCircleFilled(circleCenter, UILayout::STATUS_CIRCLE_RADIUS, indicatorColor);
278
279 // Draw text
280 ImGui::Text("%s", statusMessage.c_str());
281
282 // Right side - Version and FPS
283 ImGui::SameLine();
284 ImGui::SetCursorPosX(ImGui::GetWindowWidth() - UILayout::STATUS_RIGHT_SIDE_WIDTH);
285
286 // Version
287 ImGui::TextDisabled("v%s", version);
288
289 // Separator
290 ImGui::SameLine();
291 ImGui::TextDisabled("|");
292
293 // FPS counter
294 ImGui::SameLine();
295 ImGui::TextDisabled("%.0f FPS", fps);
296 }
297 ImGui::EndChild();
298
299 ImGui::PopStyleVar(2);
300 ImGui::PopStyleColor();
301}
302
303void UIRenderer::RenderAboutWindow(bool& showAboutWindow) {
304 if (!showAboutWindow) {
305 return;
306 }
307
308 auto& loc = Localization::Instance();
309
310 ImGui::SetNextWindowSize(ImVec2(UILayout::ABOUT_WINDOW_WIDTH, UILayout::ABOUT_WINDOW_HEIGHT),
311 ImGuiCond_FirstUseEver);
312 if (ImGui::Begin("About MetaImGUI", &showAboutWindow, ImGuiWindowFlags_AlwaysAutoResize)) {
313 ImGui::Text("MetaImGUI v%s", Version::VERSION);
314 ImGui::TextDisabled("Build: %s", Version::VERSION_FULL);
315 ImGui::Separator();
316
317 ImGui::Text("A template for creating ImGui-based applications");
318 ImGui::Spacing();
319
320 ImGui::Text("Built with:");
321 ImGui::BulletText("ImGui v1.92.4");
322 ImGui::BulletText("ImPlot v0.17");
323 ImGui::BulletText("GLFW");
324 ImGui::BulletText("OpenGL 4.6 (4.1 on macOS)");
325 ImGui::BulletText("C++20");
326 ImGui::Separator();
327
328 ImGui::Text("This template provides:");
329 ImGui::BulletText("Basic ImGui application structure");
330 ImGui::BulletText("Cross-platform build system");
331 ImGui::BulletText("Dependency management");
332 ImGui::BulletText("Automated CI/CD and releases");
333 ImGui::BulletText("Version management from git");
334 ImGui::BulletText("Modern C++20 codebase");
335 ImGui::Spacing();
336 ImGui::TextWrapped("Use this as a starting point for your own ImGui applications!");
337
338 ImGui::Separator();
339 ImGui::TextDisabled("Git: %s (%s)", Version::COMMIT, Version::BRANCH);
340 ImGui::TextDisabled("Config: %s", Version::BUILD_CONFIG);
341
342 ImGui::SetCursorPosY(ImGui::GetCursorPosY() + UILayout::VERTICAL_SPACING_SMALL);
343 if (ImGui::Button(loc.Tr("button.close").c_str())) {
344 showAboutWindow = false;
345 }
346 }
347 ImGui::End();
348}
349
350void UIRenderer::RenderUpdateNotification(bool& showUpdateNotification, UpdateInfo* updateInfo) {
351 if (updateInfo == nullptr) {
352 showUpdateNotification = false;
353 return;
354 }
355
356 ImGui::SetNextWindowSize(ImVec2(UILayout::UPDATE_WINDOW_WIDTH, UILayout::UPDATE_WINDOW_HEIGHT),
357 ImGuiCond_FirstUseEver);
358 ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
359
360 const char* windowTitle = updateInfo->updateAvailable ? "Update Available" : "No Updates Available";
361 if (ImGui::Begin(windowTitle, &showUpdateNotification, ImGuiWindowFlags_NoCollapse)) {
362 if (updateInfo->updateAvailable) {
363 // Update available UI
364 ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.2f, 0.8f, 0.2f, 1.0f));
365 ImGui::Text("A new version is available!");
366 ImGui::PopStyleColor();
367
368 ImGui::Separator();
369 ImGui::Spacing();
370
371 ImGui::Text("Current version: v%s", updateInfo->currentVersion.c_str());
372 ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.4f, 0.8f, 1.0f, 1.0f));
373 ImGui::Text("Latest version: v%s", updateInfo->latestVersion.c_str());
374 ImGui::PopStyleColor();
375
376 ImGui::Spacing();
377 ImGui::Separator();
378 ImGui::Spacing();
379
380 if (!updateInfo->releaseNotes.empty()) {
381 ImGui::Text("Release Notes:");
382 ImGui::BeginChild("ReleaseNotes", ImVec2(0, UILayout::RELEASE_NOTES_HEIGHT), ImGuiChildFlags_Border);
383 ImGui::TextWrapped("%s", updateInfo->releaseNotes.c_str());
384 ImGui::EndChild();
385 }
386
387 ImGui::Spacing();
388 ImGui::Separator();
389 ImGui::Spacing();
390
391 ImGui::Text("Visit the release page to download:");
392 ImGui::Spacing();
393
394 // Buttons for update available
395 if (ImGui::Button("Open Release Page",
397 // Open URL in browser
398 const std::string url = updateInfo->releaseUrl;
399
400 // Security: Validate URL before opening to prevent command injection
401 // Only allow https:// URLs from github.com
402 bool isValidUrl = false;
403 if (url.starts_with("https://")) {
404 // Check if it contains github.com (the expected domain)
405 if (url.find("github.com") != std::string::npos) {
406 // Check for shell metacharacters that could be used for injection
407 const std::string dangerousChars = ";|&$`\n<>(){}[]'\"\\";
408 bool hasDangerousChars = false;
409 for (const char c : dangerousChars) {
410 if (url.find(c) != std::string::npos) {
411 hasDangerousChars = true;
412 break;
413 }
414 }
415 isValidUrl = !hasDangerousChars;
416 }
417 }
418
419 if (isValidUrl) {
420#ifdef _WIN32
421 // Windows: Use system() with start command
422 const std::string cmd = "start \"\" \"" + url + "\"";
423 [[maybe_unused]] const int result = std::system(cmd.c_str());
424#elif __APPLE__
425 // macOS: Use system() but with validated/escaped URL
426 const std::string cmd = "open \"" + url + "\"";
427 [[maybe_unused]] const int result = std::system(cmd.c_str());
428#else
429 // Linux: Use system() but with validated/escaped URL
430 const std::string cmd = "xdg-open \"" + url + "\"";
431 // NOLINTNEXTLINE(concurrency-mt-unsafe) - Safe: URL validated, single-threaded UI context
432 [[maybe_unused]] const int result = std::system(cmd.c_str());
433#endif
434 } else {
435 LOG_ERROR("Rejected potentially malicious URL: {}", url);
436 }
437 }
438
439 ImGui::SameLine();
440
441 if (ImGui::Button("Remind Me Later",
443 showUpdateNotification = false;
444 }
445
446 ImGui::SameLine();
447
448 if (ImGui::Button("Close", ImVec2(UILayout::BUTTON_CLOSE_WIDTH, UILayout::BUTTON_HEIGHT))) {
449 showUpdateNotification = false;
450 }
451 } else {
452 // No update available UI
453 ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.4f, 0.8f, 0.4f, 1.0f));
454 ImGui::Text("You're up to date!");
455 ImGui::PopStyleColor();
456
457 ImGui::Separator();
458 ImGui::Spacing();
459
460 ImGui::Text("Current version: v%s", updateInfo->currentVersion.c_str());
461 if (!updateInfo->latestVersion.empty()) {
462 ImGui::Text("Latest version: v%s", updateInfo->latestVersion.c_str());
463 }
464
465 ImGui::Spacing();
466 ImGui::Separator();
467 ImGui::Spacing();
468
469 ImGui::TextWrapped("You are running the latest version of MetaImGUI.");
470 ImGui::TextWrapped("Check back later for updates!");
471
472 ImGui::Spacing();
473 ImGui::Separator();
474 ImGui::Spacing();
475
476 // Center the OK button
477 const float buttonWidth = 100.0f;
478 const float windowWidth = ImGui::GetWindowSize().x;
479 ImGui::SetCursorPosX((windowWidth - buttonWidth) * 0.5f);
480
481 if (ImGui::Button("OK", ImVec2(buttonWidth, UILayout::BUTTON_HEIGHT))) {
482 showUpdateNotification = false;
483 }
484 }
485 }
486 ImGui::End();
487}
488
489void UIRenderer::ShowDemoWindow(bool& showDemoWindow) {
490 if (showDemoWindow) {
491 ImGui::ShowDemoWindow(&showDemoWindow);
492 }
493}
494
495void UIRenderer::RenderISSTrackerWindow(bool& showISSTracker, ISSTracker* issTracker) {
496 if (!showISSTracker || issTracker == nullptr) {
497 return;
498 }
499
500 ImGui::SetNextWindowSize(ImVec2(900, 700), ImGuiCond_FirstUseEver);
501 if (ImGui::Begin("ISS Tracker", &showISSTracker)) {
502 // Get current position
503 const ISSPosition currentPos = issTracker->GetCurrentPosition();
504
505 // Control panel
506 ImGui::BeginGroup();
507 {
508 ImGui::Text("ISS Position Tracker");
509 ImGui::Separator();
510
511 // Control buttons
512 if (issTracker->IsTracking()) {
513 if (ImGui::Button("Stop Tracking")) {
514 issTracker->StopTracking();
515 }
516 } else {
517 if (ImGui::Button("Start Tracking")) {
518 issTracker->StartTracking();
519 }
520 }
521
522 ImGui::Separator();
523
524 // Display current position info
525 if (currentPos.valid) {
526 ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.4f, 0.8f, 0.4f, 1.0f));
527 ImGui::Text("Status: Active");
528 ImGui::PopStyleColor();
529
530 ImGui::Spacing();
531 ImGui::Text("Latitude: %.4f°", currentPos.latitude);
532 ImGui::Text("Longitude: %.4f°", currentPos.longitude);
533 ImGui::Text("Altitude: %.2f km", currentPos.altitude);
534 ImGui::Text("Velocity: %.2f km/h", currentPos.velocity);
535
536 // Convert Unix timestamp to readable time
537 if (currentPos.timestamp > 0) {
538 const auto time = static_cast<time_t>(currentPos.timestamp);
539 struct tm timeinfo = {};
540 std::array<char, 80> buffer{};
541
542 // Use thread-safe versions of gmtime
543#ifdef _WIN32
544 if (gmtime_s(&timeinfo, &time) == 0) {
545 strftime(buffer.data(), buffer.size(), "%Y-%m-%d %H:%M:%S UTC", &timeinfo);
546 ImGui::Text("Time: %s", buffer.data());
547 } else {
548 ImGui::Text("Time: (error converting timestamp)");
549 }
550#else
551 if (gmtime_r(&time, &timeinfo) != nullptr) {
552 strftime(buffer.data(), buffer.size(), "%Y-%m-%d %H:%M:%S UTC", &timeinfo);
553 ImGui::Text("Time: %s", buffer.data());
554 } else {
555 ImGui::Text("Time: (error converting timestamp)");
556 }
557#endif
558 }
559 } else {
560 ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.8f, 0.4f, 0.4f, 1.0f));
561 ImGui::Text("Status: No data");
562 ImGui::PopStyleColor();
563 ImGui::TextWrapped("Click 'Start Tracking' or 'Fetch Now' to get ISS position data.");
564 }
565 }
566 ImGui::EndGroup();
567
568 ImGui::Separator();
569
570 // Get position history
571 std::vector<double> latitudes, longitudes;
572 issTracker->GetPositionHistory(latitudes, longitudes);
573
574 // Plot area
575 if (ImPlot::BeginPlot("ISS Orbit", ImVec2(-1, -1))) {
576 // Set axis limits for Earth coordinates
577 ImPlot::SetupAxes("Longitude (°)", "Latitude (°)");
578 ImPlot::SetupAxisLimits(ImAxis_X1, -180, 180, ImGuiCond_Always);
579 ImPlot::SetupAxisLimits(ImAxis_Y1, -90, 90, ImGuiCond_Always);
580
581 // Plot orbit trail if we have data
582 if (!latitudes.empty() && !longitudes.empty()) {
583 ImPlot::SetNextMarkerStyle(ImPlotMarker_Circle, 2.0f);
584 ImPlot::PlotLine("Orbit Trail", longitudes.data(), latitudes.data(),
585 static_cast<int>(longitudes.size()));
586 }
587
588 // Plot current position as a larger marker
589 if (currentPos.valid) {
590 ImPlot::SetNextMarkerStyle(ImPlotMarker_Circle, 5.0f, ImVec4(1.0f, 0.3f, 0.3f, 1.0f));
591 ImPlot::PlotScatter("Current Position", &currentPos.longitude, &currentPos.latitude, 1);
592 }
593
594 // Add grid reference lines
595 ImPlot::SetNextLineStyle(ImVec4(0.5f, 0.5f, 0.5f, 0.3f));
596 std::array<double, 2> xRange = {-180.0, 180.0};
597 std::array<double, 2> yZero = {0.0, 0.0};
598 ImPlot::PlotLine("Equator", xRange.data(), yZero.data(), 2);
599
600 std::array<double, 2> xZero = {0.0, 0.0};
601 std::array<double, 2> yRange = {-90.0, 90.0};
602 ImPlot::PlotLine("Prime Meridian", xZero.data(), yRange.data(), 2);
603
604 ImPlot::EndPlot();
605 }
606 }
607 ImGui::End();
608
609 // If window was closed, stop tracking
610 if (!showISSTracker && issTracker->IsTracking()) {
611 issTracker->StopTracking();
612 }
613}
614
615void UIRenderer::HelpMarker(const char* desc) {
616 ImGui::TextDisabled("(?)");
617 if (ImGui::IsItemHovered()) {
618 ImGui::BeginTooltip();
619 ImGui::PushTextWrapPos(ImGui::GetFontSize() * UILayout::TEXT_WRAP_POS_MULTIPLIER);
620 ImGui::TextUnformatted(desc);
621 ImGui::PopTextWrapPos();
622 ImGui::EndTooltip();
623 }
624}
625
626} // namespace MetaImGUI
#define LOG_ERROR(...)
Definition Logger.h:168
ISS Tracker that fetches ISS position data asynchronously.
Definition ISSTracker.h:51
void GetPositionHistory(std::vector< double > &latitudes, std::vector< double > &longitudes) const
Get position history for orbit trail (thread-safe)
ISSPosition GetCurrentPosition() const
Get the current ISS position (thread-safe)
void StopTracking()
Stop tracking.
void StartTracking(std::function< void(const ISSPosition &)> callback=nullptr)
Start tracking ISS position asynchronously.
bool IsTracking() const
Check if tracking is active.
static Localization & Instance()
Get singleton instance.
void Emit(Args... args) const
Fire the signal.
Definition Signal.h:139
static void Apply(Theme theme)
Apply a theme to the current ImGui context.
static Theme GetCurrent()
Get the currently active theme.
@ Light
ImGui's default light theme.
@ Dark
ImGui's default dark theme.
@ Modern
Custom light theme with rounded corners and borders.
@ Classic
ImGui's classic theme.
void ShowDemoWindow(bool &showDemoWindow)
Show ImGui demo window.
void RenderUpdateNotification(bool &showUpdateNotification, UpdateInfo *updateInfo)
Render the update notification dialog.
void RenderAboutWindow(bool &showAboutWindow)
Render the about dialog.
void RenderStatusBar(const std::string &statusMessage, float fps, const char *version, bool updateInProgress)
Render the status bar.
void Shutdown()
Shutdown ImGui context.
void BeginFrame()
Begin a new ImGui frame.
void RenderMenuBar(UIEvents &events, bool showDemoWindow, bool showISSTracker)
Render the menu bar.
static void HelpMarker(const char *desc)
Helper to show tooltip with question mark.
void RenderISSTrackerWindow(bool &showISSTracker, ISSTracker *issTracker)
Render the ISS tracker window.
void RenderMainWindow(UIEvents &events)
Render the main application window.
bool Initialize(GLFWwindow *window)
Initialize ImGui context and backends.
void EndFrame()
End the current ImGui frame and render.
constexpr float STATUS_RIGHT_SIDE_WIDTH
constexpr float BUTTON_REMIND_LATER_WIDTH
constexpr float ABOUT_WINDOW_HEIGHT
constexpr float RELEASE_NOTES_HEIGHT
constexpr float BUTTON_HEIGHT
constexpr float ABOUT_WINDOW_WIDTH
constexpr float BUTTON_OPEN_RELEASE_WIDTH
constexpr float STATUS_CIRCLE_RADIUS
constexpr float VERTICAL_SPACING_SMALL
constexpr float BUTTON_CLOSE_WIDTH
constexpr float WINDOW_PADDING_Y
constexpr float TOP_MARGIN
constexpr float STATUS_BAR_HEIGHT
constexpr float LEFT_MARGIN
constexpr float WINDOW_PADDING_X
constexpr float STATUS_CIRCLE_PADDING
constexpr float ITEM_SPACING_Y
constexpr float UPDATE_WINDOW_HEIGHT
constexpr float UPDATE_WINDOW_WIDTH
constexpr float TEXT_WRAP_POS_MULTIPLIER
constexpr float ITEM_SPACING_X
Decoupled UI-event bus shared between UIRenderer and Application.
Definition UIEvents.h:33
Signal toggleISSTracker
Definition UIEvents.h:40
Signal toggleDemoWindow
Definition UIEvents.h:35
Signal checkUpdatesRequested
Definition UIEvents.h:37
Signal showDemoWindow
Definition UIEvents.h:36
Signal showAboutRequested
Definition UIEvents.h:38
Signal showInputDialogRequested
Definition UIEvents.h:39
std::string latestVersion
std::string currentVersion