29#include <GLFW/glfw3.h>
31#include <imgui_impl_glfw.h>
32#include <imgui_impl_opengl3.h>
88 ImGui::CreateContext();
89 ImGuiIO& io = ImGui::GetIO();
90 io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
93 ImPlot::CreateContext();
99 ImGui_ImplGlfw_InitForOpenGL(window,
true);
100 ImGui_ImplOpenGL3_Init(
"#version 330");
102 m_initialized =
true;
108 ImGui_ImplOpenGL3_Shutdown();
109 ImGui_ImplGlfw_Shutdown();
110 ImPlot::DestroyContext();
111 ImGui::DestroyContext();
112 m_initialized =
false;
117 ImGui_ImplOpenGL3_NewFrame();
118 ImGui_ImplGlfw_NewFrame();
124 ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
137 ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing,
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.");
148 if (ImGui::Button(loc.Tr(
"button.show_about").c_str())) {
151 if (ImGui::Button(loc.Tr(
"button.show_demo").c_str())) {
154 if (ImGui::Button(loc.Tr(
"button.show_input").c_str())) {
160 ImGui::PopStyleVar(2);
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")) {
174 if (ImGui::BeginMenu(loc.Tr(
"menu.view").c_str())) {
175 if (ImGui::MenuItem(loc.Tr(
"menu.demo_window").c_str(),
nullptr, showDemoWindow)) {
179 if (ImGui::MenuItem(
"ISS Tracker",
nullptr, showISSTracker)) {
185 if (ImGui::BeginMenu(loc.Tr(
"menu.theme").c_str())) {
203 if (ImGui::BeginMenu(loc.Tr(
"menu.language").c_str())) {
204 if (ImGui::MenuItem(
"English",
nullptr, loc.GetCurrentLanguage() ==
"en")) {
205 loc.SetLanguage(
"en");
207 if (ImGui::MenuItem(
"Español",
nullptr, loc.GetCurrentLanguage() ==
"es")) {
208 loc.SetLanguage(
"es");
210 if (ImGui::MenuItem(
"Français",
nullptr, loc.GetCurrentLanguage() ==
"fr")) {
211 loc.SetLanguage(
"fr");
213 if (ImGui::MenuItem(
"Deutsch",
nullptr, loc.GetCurrentLanguage() ==
"de")) {
214 loc.SetLanguage(
"de");
221 if (ImGui::BeginMenu(loc.Tr(
"menu.help").c_str())) {
222 if (ImGui::MenuItem(loc.Tr(
"menu.check_updates").c_str())) {
226 if (ImGui::MenuItem(loc.Tr(
"menu.about").c_str(),
"Ctrl+A")) {
237 bool updateInProgress) {
239 const ImVec4 windowBg = ImGui::GetStyle().Colors[ImGuiCol_WindowBg];
240 const ImVec4 statusBarBg = ImVec4(windowBg.x * 0.85f,
241 windowBg.y * 0.85f, windowBg.z * 0.85f, 1.0f);
243 ImGui::PushStyleColor(ImGuiCol_ChildBg, statusBarBg);
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);
255 ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse)) {
261 ImGui::AlignTextToFramePadding();
264 drawList = ImGui::GetWindowDrawList();
265 const ImVec2 textPos = ImGui::GetCursorScreenPos();
266 const ImVec2 circleCenter =
268 textPos.y + (ImGui::GetFrameHeight() * 0.5f));
270 ImU32 indicatorColor = IM_COL32(50, 200, 50, 255);
271 if (updateInProgress) {
272 indicatorColor = IM_COL32(255, 200, 50, 255);
274 indicatorColor = IM_COL32(50, 200, 50, 255);
280 ImGui::Text(
"%s", statusMessage.c_str());
287 ImGui::TextDisabled(
"v%s", version);
291 ImGui::TextDisabled(
"|");
295 ImGui::TextDisabled(
"%.0f FPS", fps);
299 ImGui::PopStyleVar(2);
300 ImGui::PopStyleColor();
304 if (!showAboutWindow) {
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);
317 ImGui::Text(
"A template for creating ImGui-based applications");
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");
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");
336 ImGui::TextWrapped(
"Use this as a starting point for your own ImGui applications!");
339 ImGui::TextDisabled(
"Git: %s (%s)", Version::COMMIT, Version::BRANCH);
340 ImGui::TextDisabled(
"Config: %s", Version::BUILD_CONFIG);
343 if (ImGui::Button(loc.Tr(
"button.close").c_str())) {
344 showAboutWindow =
false;
351 if (updateInfo ==
nullptr) {
352 showUpdateNotification =
false;
357 ImGuiCond_FirstUseEver);
358 ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
360 const char* windowTitle = updateInfo->
updateAvailable ?
"Update Available" :
"No Updates Available";
361 if (ImGui::Begin(windowTitle, &showUpdateNotification, ImGuiWindowFlags_NoCollapse)) {
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();
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();
381 ImGui::Text(
"Release Notes:");
383 ImGui::TextWrapped(
"%s", updateInfo->
releaseNotes.c_str());
391 ImGui::Text(
"Visit the release page to download:");
395 if (ImGui::Button(
"Open Release Page",
398 const std::string url = updateInfo->
releaseUrl;
402 bool isValidUrl =
false;
403 if (url.starts_with(
"https://")) {
405 if (url.find(
"github.com") != std::string::npos) {
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;
415 isValidUrl = !hasDangerousChars;
422 const std::string cmd =
"start \"\" \"" + url +
"\"";
423 [[maybe_unused]]
const int result = std::system(cmd.c_str());
426 const std::string cmd =
"open \"" + url +
"\"";
427 [[maybe_unused]]
const int result = std::system(cmd.c_str());
430 const std::string cmd =
"xdg-open \"" + url +
"\"";
432 [[maybe_unused]]
const int result = std::system(cmd.c_str());
435 LOG_ERROR(
"Rejected potentially malicious URL: {}", url);
441 if (ImGui::Button(
"Remind Me Later",
443 showUpdateNotification =
false;
449 showUpdateNotification =
false;
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();
460 ImGui::Text(
"Current version: v%s", updateInfo->
currentVersion.c_str());
462 ImGui::Text(
"Latest version: v%s", updateInfo->
latestVersion.c_str());
469 ImGui::TextWrapped(
"You are running the latest version of MetaImGUI.");
470 ImGui::TextWrapped(
"Check back later for updates!");
477 const float buttonWidth = 100.0f;
478 const float windowWidth = ImGui::GetWindowSize().x;
479 ImGui::SetCursorPosX((windowWidth - buttonWidth) * 0.5f);
482 showUpdateNotification =
false;
490 if (showDemoWindow) {
491 ImGui::ShowDemoWindow(&showDemoWindow);
496 if (!showISSTracker || issTracker ==
nullptr) {
500 ImGui::SetNextWindowSize(ImVec2(900, 700), ImGuiCond_FirstUseEver);
501 if (ImGui::Begin(
"ISS Tracker", &showISSTracker)) {
508 ImGui::Text(
"ISS Position Tracker");
513 if (ImGui::Button(
"Stop Tracking")) {
517 if (ImGui::Button(
"Start Tracking")) {
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();
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);
538 const auto time =
static_cast<time_t
>(currentPos.
timestamp);
539 struct tm timeinfo = {};
540 std::array<char, 80> buffer{};
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());
548 ImGui::Text(
"Time: (error converting timestamp)");
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());
555 ImGui::Text(
"Time: (error converting timestamp)");
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.");
571 std::vector<double> latitudes, longitudes;
575 if (ImPlot::BeginPlot(
"ISS Orbit", ImVec2(-1, -1))) {
577 ImPlot::SetupAxes(
"Longitude (°)",
"Latitude (°)");
578 ImPlot::SetupAxisLimits(ImAxis_X1, -180, 180, ImGuiCond_Always);
579 ImPlot::SetupAxisLimits(ImAxis_Y1, -90, 90, ImGuiCond_Always);
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()));
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", ¤tPos.
longitude, ¤tPos.
latitude, 1);
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);
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);
610 if (!showISSTracker && issTracker->
IsTracking()) {
616 ImGui::TextDisabled(
"(?)");
617 if (ImGui::IsItemHovered()) {
618 ImGui::BeginTooltip();
620 ImGui::TextUnformatted(desc);
621 ImGui::PopTextWrapPos();