28#include <GLFW/glfw3.h>
30#include <imgui_impl_glfw.h>
31#include <imgui_impl_opengl3.h>
89 ImGui::CreateContext();
90 ImGuiIO& io = ImGui::GetIO();
91 io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
94 ImPlot::CreateContext();
100 ImGui_ImplGlfw_InitForOpenGL(window,
true);
101 ImGui_ImplOpenGL3_Init(
"#version 330");
103 m_initialized =
true;
109 ImGui_ImplOpenGL3_Shutdown();
110 ImGui_ImplGlfw_Shutdown();
111 ImPlot::DestroyContext();
112 ImGui::DestroyContext();
113 m_initialized =
false;
118 ImGui_ImplOpenGL3_NewFrame();
119 ImGui_ImplGlfw_NewFrame();
125 ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
129 std::function<
void()> onShowInputDialog) {
136 if (ImGui::BeginChild(
"MainContent", ImVec2(0, contentHeight), ImGuiChildFlags_None, ImGuiWindowFlags_None)) {
138 ImGui::Text(
"Welcome to MetaImGUI!");
141 ImGui::Text(
"This is a template for creating ImGui-based applications.");
144 ImGui::Text(
"Use the menu bar above to access the About dialog.");
148 if (ImGui::Button(loc.Tr(
"button.show_about").c_str())) {
156 if (ImGui::Button(loc.Tr(
"button.show_demo").c_str())) {
164 if (ImGui::Button(loc.Tr(
"button.show_input").c_str())) {
165 if (onShowInputDialog) {
174 std::function<
void()> onCheckUpdates, std::function<
void()> onShowAbout,
175 bool showDemoWindow, std::function<
void()> onToggleISSTracker,
bool showISSTracker) {
178 if (ImGui::BeginMenuBar()) {
179 if (ImGui::BeginMenu(loc.Tr(
"menu.file").c_str())) {
180 if (ImGui::MenuItem(loc.Tr(
"menu.exit").c_str(),
"Alt+F4")) {
188 if (ImGui::BeginMenu(loc.Tr(
"menu.view").c_str())) {
189 if (ImGui::MenuItem(loc.Tr(
"menu.demo_window").c_str(),
nullptr, showDemoWindow)) {
195 if (ImGui::MenuItem(
"ISS Tracker",
nullptr, showISSTracker)) {
196 if (onToggleISSTracker) {
197 onToggleISSTracker();
203 if (ImGui::BeginMenu(loc.Tr(
"menu.theme").c_str())) {
221 if (ImGui::BeginMenu(loc.Tr(
"menu.language").c_str())) {
222 if (ImGui::MenuItem(
"English",
nullptr, loc.GetCurrentLanguage() ==
"en")) {
223 loc.SetLanguage(
"en");
225 if (ImGui::MenuItem(
"Español",
nullptr, loc.GetCurrentLanguage() ==
"es")) {
226 loc.SetLanguage(
"es");
228 if (ImGui::MenuItem(
"Français",
nullptr, loc.GetCurrentLanguage() ==
"fr")) {
229 loc.SetLanguage(
"fr");
231 if (ImGui::MenuItem(
"Deutsch",
nullptr, loc.GetCurrentLanguage() ==
"de")) {
232 loc.SetLanguage(
"de");
239 if (ImGui::BeginMenu(loc.Tr(
"menu.help").c_str())) {
240 if (ImGui::MenuItem(loc.Tr(
"menu.check_updates").c_str())) {
241 if (onCheckUpdates) {
246 if (ImGui::MenuItem(loc.Tr(
"menu.about").c_str(),
"Ctrl+A")) {
259 bool updateInProgress) {
261 const ImVec4 windowBg = ImGui::GetStyle().Colors[ImGuiCol_WindowBg];
262 const ImVec4 statusBarBg = ImVec4(windowBg.x * 0.85f,
263 windowBg.y * 0.85f, windowBg.z * 0.85f, 1.0f);
265 ImGui::PushStyleColor(ImGuiCol_ChildBg, statusBarBg);
270 ImDrawList* drawList = ImGui::GetWindowDrawList();
271 const ImVec2 statusBarPos = ImGui::GetCursorScreenPos();
272 const ImVec2 lineEnd = ImVec2(statusBarPos.x + ImGui::GetContentRegionAvail().x, statusBarPos.y);
273 const ImVec4 separatorColor = ImGui::GetStyle().Colors[ImGuiCol_Separator];
274 drawList->AddLine(statusBarPos, lineEnd, ImGui::ColorConvertFloat4ToU32(separatorColor), 1.0f);
277 ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse)) {
283 ImGui::AlignTextToFramePadding();
286 drawList = ImGui::GetWindowDrawList();
287 const ImVec2 textPos = ImGui::GetCursorScreenPos();
288 const ImVec2 circleCenter =
290 textPos.y + (ImGui::GetFrameHeight() * 0.5f));
292 ImU32 indicatorColor = IM_COL32(50, 200, 50, 255);
293 if (updateInProgress) {
294 indicatorColor = IM_COL32(255, 200, 50, 255);
296 indicatorColor = IM_COL32(50, 200, 50, 255);
302 ImGui::Text(
"%s", statusMessage.c_str());
309 ImGui::TextDisabled(
"v%s", version);
313 ImGui::TextDisabled(
"|");
317 ImGui::TextDisabled(
"%.0f FPS", fps);
321 ImGui::PopStyleVar(2);
322 ImGui::PopStyleColor();
326 if (!showAboutWindow) {
333 ImGuiCond_FirstUseEver);
334 if (ImGui::Begin(
"About MetaImGUI", &showAboutWindow, ImGuiWindowFlags_AlwaysAutoResize)) {
335 ImGui::Text(
"MetaImGUI v%s", Version::VERSION);
336 ImGui::TextDisabled(
"Build: %s", Version::VERSION_FULL);
339 ImGui::Text(
"A template for creating ImGui-based applications");
342 ImGui::Text(
"Built with:");
343 ImGui::BulletText(
"ImGui v1.92.4");
344 ImGui::BulletText(
"ImPlot v0.17");
345 ImGui::BulletText(
"GLFW");
346 ImGui::BulletText(
"OpenGL 4.6 (4.1 on macOS)");
347 ImGui::BulletText(
"C++20");
350 ImGui::Text(
"This template provides:");
351 ImGui::BulletText(
"Basic ImGui application structure");
352 ImGui::BulletText(
"Cross-platform build system");
353 ImGui::BulletText(
"Dependency management");
354 ImGui::BulletText(
"Automated CI/CD and releases");
355 ImGui::BulletText(
"Version management from git");
356 ImGui::BulletText(
"Modern C++20 codebase");
358 ImGui::TextWrapped(
"Use this as a starting point for your own ImGui applications!");
361 ImGui::TextDisabled(
"Git: %s (%s)", Version::COMMIT, Version::BRANCH);
362 ImGui::TextDisabled(
"Config: %s", Version::BUILD_CONFIG);
365 if (ImGui::Button(loc.Tr(
"button.close").c_str())) {
366 showAboutWindow =
false;
373 if (updateInfo ==
nullptr) {
374 showUpdateNotification =
false;
379 ImGuiCond_FirstUseEver);
380 ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
382 const char* windowTitle = updateInfo->
updateAvailable ?
"Update Available" :
"No Updates Available";
383 if (ImGui::Begin(windowTitle, &showUpdateNotification, ImGuiWindowFlags_NoCollapse)) {
386 ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.2f, 0.8f, 0.2f, 1.0f));
387 ImGui::Text(
"A new version is available!");
388 ImGui::PopStyleColor();
393 ImGui::Text(
"Current version: v%s", updateInfo->
currentVersion.c_str());
394 ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.4f, 0.8f, 1.0f, 1.0f));
395 ImGui::Text(
"Latest version: v%s", updateInfo->
latestVersion.c_str());
396 ImGui::PopStyleColor();
403 ImGui::Text(
"Release Notes:");
405 ImGui::TextWrapped(
"%s", updateInfo->
releaseNotes.c_str());
413 ImGui::Text(
"Visit the release page to download:");
417 if (ImGui::Button(
"Open Release Page",
420 const std::string url = updateInfo->
releaseUrl;
424 bool isValidUrl =
false;
425 if (url.starts_with(
"https://")) {
427 if (url.find(
"github.com") != std::string::npos) {
429 const std::string dangerousChars =
";|&$`\n<>(){}[]'\"\\";
430 bool hasDangerousChars =
false;
431 for (
const char c : dangerousChars) {
432 if (url.find(c) != std::string::npos) {
433 hasDangerousChars =
true;
437 isValidUrl = !hasDangerousChars;
444 const std::string cmd =
"start \"\" \"" + url +
"\"";
445 [[maybe_unused]]
const int result = std::system(cmd.c_str());
448 const std::string cmd =
"open \"" + url +
"\"";
449 [[maybe_unused]]
const int result = std::system(cmd.c_str());
452 const std::string cmd =
"xdg-open \"" + url +
"\"";
454 [[maybe_unused]]
const int result = std::system(cmd.c_str());
457 LOG_ERROR(
"Rejected potentially malicious URL: {}", url);
463 if (ImGui::Button(
"Remind Me Later",
465 showUpdateNotification =
false;
471 showUpdateNotification =
false;
475 ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.4f, 0.8f, 0.4f, 1.0f));
476 ImGui::Text(
"You're up to date!");
477 ImGui::PopStyleColor();
482 ImGui::Text(
"Current version: v%s", updateInfo->
currentVersion.c_str());
484 ImGui::Text(
"Latest version: v%s", updateInfo->
latestVersion.c_str());
491 ImGui::TextWrapped(
"You are running the latest version of MetaImGUI.");
492 ImGui::TextWrapped(
"Check back later for updates!");
499 const float buttonWidth = 100.0f;
500 const float windowWidth = ImGui::GetWindowSize().x;
501 ImGui::SetCursorPosX((windowWidth - buttonWidth) * 0.5f);
504 showUpdateNotification =
false;
512 if (showDemoWindow) {
513 ImGui::ShowDemoWindow(&showDemoWindow);
518 if (!showISSTracker || issTracker ==
nullptr) {
522 ImGui::SetNextWindowSize(ImVec2(900, 700), ImGuiCond_FirstUseEver);
523 if (ImGui::Begin(
"ISS Tracker", &showISSTracker)) {
530 ImGui::Text(
"ISS Position Tracker");
535 if (ImGui::Button(
"Stop Tracking")) {
539 if (ImGui::Button(
"Start Tracking")) {
547 if (currentPos.
valid) {
548 ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.4f, 0.8f, 0.4f, 1.0f));
549 ImGui::Text(
"Status: Active");
550 ImGui::PopStyleColor();
553 ImGui::Text(
"Latitude: %.4f°", currentPos.
latitude);
554 ImGui::Text(
"Longitude: %.4f°", currentPos.
longitude);
555 ImGui::Text(
"Altitude: %.2f km", currentPos.
altitude);
556 ImGui::Text(
"Velocity: %.2f km/h", currentPos.
velocity);
560 const auto time =
static_cast<time_t
>(currentPos.
timestamp);
561 struct tm timeinfo = {};
562 std::array<char, 80> buffer{};
566 if (gmtime_s(&timeinfo, &time) == 0) {
567 strftime(buffer.data(), buffer.size(),
"%Y-%m-%d %H:%M:%S UTC", &timeinfo);
568 ImGui::Text(
"Time: %s", buffer.data());
570 ImGui::Text(
"Time: (error converting timestamp)");
573 if (gmtime_r(&time, &timeinfo) !=
nullptr) {
574 strftime(buffer.data(), buffer.size(),
"%Y-%m-%d %H:%M:%S UTC", &timeinfo);
575 ImGui::Text(
"Time: %s", buffer.data());
577 ImGui::Text(
"Time: (error converting timestamp)");
582 ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.8f, 0.4f, 0.4f, 1.0f));
583 ImGui::Text(
"Status: No data");
584 ImGui::PopStyleColor();
585 ImGui::TextWrapped(
"Click 'Start Tracking' or 'Fetch Now' to get ISS position data.");
593 std::vector<double> latitudes, longitudes;
597 if (ImPlot::BeginPlot(
"ISS Orbit", ImVec2(-1, -1))) {
599 ImPlot::SetupAxes(
"Longitude (°)",
"Latitude (°)");
600 ImPlot::SetupAxisLimits(ImAxis_X1, -180, 180, ImGuiCond_Always);
601 ImPlot::SetupAxisLimits(ImAxis_Y1, -90, 90, ImGuiCond_Always);
604 if (!latitudes.empty() && !longitudes.empty()) {
605 ImPlot::SetNextMarkerStyle(ImPlotMarker_Circle, 2.0f);
606 ImPlot::PlotLine(
"Orbit Trail", longitudes.data(), latitudes.data(),
607 static_cast<int>(longitudes.size()));
611 if (currentPos.
valid) {
612 ImPlot::SetNextMarkerStyle(ImPlotMarker_Circle, 5.0f, ImVec4(1.0f, 0.3f, 0.3f, 1.0f));
613 ImPlot::PlotScatter(
"Current Position", ¤tPos.
longitude, ¤tPos.
latitude, 1);
617 ImPlot::SetNextLineStyle(ImVec4(0.5f, 0.5f, 0.5f, 0.3f));
618 std::array<double, 2> xRange = {-180.0, 180.0};
619 std::array<double, 2> yZero = {0.0, 0.0};
620 ImPlot::PlotLine(
"Equator", xRange.data(), yZero.data(), 2);
622 std::array<double, 2> xZero = {0.0, 0.0};
623 std::array<double, 2> yRange = {-90.0, 90.0};
624 ImPlot::PlotLine(
"Prime Meridian", xZero.data(), yRange.data(), 2);
632 if (!showISSTracker && issTracker->
IsTracking()) {
638 ImGui::TextDisabled(
"(?)");
639 if (ImGui::IsItemHovered()) {
640 ImGui::BeginTooltip();
642 ImGui::TextUnformatted(desc);
643 ImGui::PopTextWrapPos();