blob: e2e05ce0674e54da8baa6be3a0dcafff8727a169 [file] [log] [blame]
Austin Schuh812d0d12021-11-04 20:16:48 -07001// Copyright (c) FIRST and other WPILib contributors.
2// Open Source Software; you can modify and/or share it under the terms of
3// the WPILib BSD license file in the root directory of this project.
Austin Schuh1e69f942020-11-14 15:06:14 -08004
5#include "wpigui.h"
6
James Kuszmaulb13e13f2023-11-22 20:44:04 -08007#include <stdint.h>
8
Austin Schuh1e69f942020-11-14 15:06:14 -08009#include <algorithm>
James Kuszmaulb13e13f2023-11-22 20:44:04 -080010#include <chrono>
Austin Schuh1e69f942020-11-14 15:06:14 -080011#include <cstdio>
12#include <cstring>
James Kuszmaulb13e13f2023-11-22 20:44:04 -080013#include <thread>
Austin Schuh1e69f942020-11-14 15:06:14 -080014
15#include <GLFW/glfw3.h>
James Kuszmaulcf324122023-01-14 14:07:17 -080016#include <IconsFontAwesome6.h>
Austin Schuh1e69f942020-11-14 15:06:14 -080017#include <imgui.h>
James Kuszmaulcf324122023-01-14 14:07:17 -080018#include <imgui_FontAwesomeSolid.h>
Austin Schuh1e69f942020-11-14 15:06:14 -080019#include <imgui_ProggyDotted.h>
20#include <imgui_impl_glfw.h>
21#include <imgui_internal.h>
22#include <implot.h>
23#include <stb_image.h>
24
25#include "wpigui_internal.h"
26
27using namespace wpi::gui;
28
29namespace wpi {
30
31Context* gui::gContext;
32
33static void ErrorCallback(int error, const char* description) {
34 std::fprintf(stderr, "GLFW Error %d: %s\n", error, description);
35}
36
37static void WindowSizeCallback(GLFWwindow* window, int width, int height) {
38 if (!gContext->maximized) {
39 gContext->width = width;
40 gContext->height = height;
41 }
Austin Schuh75263e32022-02-22 18:05:32 -080042 if (!gContext->isPlatformRendering) {
43 PlatformRenderFrame();
44 }
Austin Schuh1e69f942020-11-14 15:06:14 -080045}
46
47static void FramebufferSizeCallback(GLFWwindow* window, int width, int height) {
48 PlatformFramebufferSizeChanged(width, height);
49}
50
51static void WindowMaximizeCallback(GLFWwindow* window, int maximized) {
52 gContext->maximized = maximized;
53}
54
55static void WindowPosCallback(GLFWwindow* window, int xpos, int ypos) {
56 if (!gContext->maximized) {
57 gContext->xPos = xpos;
58 gContext->yPos = ypos;
59 }
60}
61
62static void* IniReadOpen(ImGuiContext* ctx, ImGuiSettingsHandler* handler,
63 const char* name) {
Austin Schuh812d0d12021-11-04 20:16:48 -070064 if (std::strcmp(name, "GLOBAL") != 0) {
65 return nullptr;
66 }
Austin Schuh1e69f942020-11-14 15:06:14 -080067 return static_cast<SavedSettings*>(gContext);
68}
69
70static void IniReadLine(ImGuiContext* ctx, ImGuiSettingsHandler* handler,
71 void* entry, const char* lineStr) {
72 auto impl = static_cast<SavedSettings*>(entry);
73 const char* value = std::strchr(lineStr, '=');
Austin Schuh812d0d12021-11-04 20:16:48 -070074 if (!value) {
75 return;
76 }
Austin Schuh1e69f942020-11-14 15:06:14 -080077 ++value;
78 int num = std::atoi(value);
79 if (std::strncmp(lineStr, "width=", 6) == 0) {
80 impl->width = num;
81 impl->loadedWidthHeight = true;
82 } else if (std::strncmp(lineStr, "height=", 7) == 0) {
83 impl->height = num;
84 impl->loadedWidthHeight = true;
85 } else if (std::strncmp(lineStr, "maximized=", 10) == 0) {
86 impl->maximized = num;
87 } else if (std::strncmp(lineStr, "xpos=", 5) == 0) {
88 impl->xPos = num;
89 } else if (std::strncmp(lineStr, "ypos=", 5) == 0) {
90 impl->yPos = num;
91 } else if (std::strncmp(lineStr, "userScale=", 10) == 0) {
92 impl->userScale = num;
93 } else if (std::strncmp(lineStr, "style=", 6) == 0) {
94 impl->style = num;
James Kuszmaulb13e13f2023-11-22 20:44:04 -080095 } else if (std::strncmp(lineStr, "fps=", 4) == 0) {
96 impl->fps = num;
Austin Schuh1e69f942020-11-14 15:06:14 -080097 }
98}
99
100static void IniWriteAll(ImGuiContext* ctx, ImGuiSettingsHandler* handler,
101 ImGuiTextBuffer* out_buf) {
Austin Schuh812d0d12021-11-04 20:16:48 -0700102 if (!gContext) {
103 return;
104 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800105 out_buf->appendf(
106 "[MainWindow][GLOBAL]\nwidth=%d\nheight=%d\nmaximized=%d\n"
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800107 "xpos=%d\nypos=%d\nuserScale=%d\nstyle=%d\nfps=%d\n\n",
Austin Schuh75263e32022-02-22 18:05:32 -0800108 gContext->width, gContext->height, gContext->maximized ? 1 : 0,
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800109 gContext->xPos, gContext->yPos, gContext->userScale, gContext->style,
110 gContext->fps);
Austin Schuh1e69f942020-11-14 15:06:14 -0800111}
112
113void gui::CreateContext() {
114 gContext = new Context;
115 AddFont("ProggyDotted", [](ImGuiIO& io, float size, const ImFontConfig* cfg) {
James Kuszmaulcf324122023-01-14 14:07:17 -0800116 auto font = ImGui::AddFontProggyDotted(io, size, cfg);
117 static const ImWchar icons_ranges[] = {ICON_MIN_FA, ICON_MAX_16_FA, 0};
118 ImFontConfig icons_cfg;
119 icons_cfg.MergeMode = true;
120 icons_cfg.PixelSnapH = true;
121 ImGui::AddFontFontAwesomeSolid(io, size, &icons_cfg, icons_ranges);
122 return font;
Austin Schuh1e69f942020-11-14 15:06:14 -0800123 });
124 PlatformCreateContext();
125}
126
127void gui::DestroyContext() {
128 PlatformDestroyContext();
129 delete gContext;
130 gContext = nullptr;
131}
132
James Kuszmaulcf324122023-01-14 14:07:17 -0800133static void UpdateFontScale() {
134 // Scale based on OS window content scaling
135 float windowScale = 1.0;
136#ifndef __APPLE__
137 glfwGetWindowContentScale(gContext->window, &windowScale, nullptr);
138#endif
139 // map to closest font size: 0 = 0.5x, 1 = 0.75x, 2 = 1.0x, 3 = 1.25x,
140 // 4 = 1.5x, 5 = 1.75x, 6 = 2x
141 int fontScale =
142 gContext->userScale + static_cast<int>((windowScale - 1.0) * 4);
143 if (fontScale < 0) {
144 fontScale = 0;
145 }
146 if (gContext->fontScale != fontScale) {
147 gContext->reloadFonts = true;
148 gContext->fontScale = fontScale;
149 }
150}
151
152// the range is based on 13px being the "nominal" 100% size and going from
153// ~0.5x (7px) to ~2.0x (25px)
154static void ReloadFonts() {
155 auto& io = ImGui::GetIO();
156 io.Fonts->Clear();
157 gContext->fonts.clear();
158 float size = 7.0f + gContext->fontScale * 3.0f;
159 bool first = true;
160 for (auto&& makeFont : gContext->makeFonts) {
161 if (makeFont.second) {
162 ImFontConfig cfg;
163 std::snprintf(cfg.Name, sizeof(cfg.Name), "%s", makeFont.first);
164 ImFont* font = makeFont.second(io, size, &cfg);
165 if (first) {
166 ImGui::GetIO().FontDefault = font;
167 first = false;
168 }
169 gContext->fonts.emplace_back(font);
170 }
171 }
172}
173
174bool gui::Initialize(const char* title, int width, int height,
175 ImGuiConfigFlags configFlags) {
Austin Schuh1e69f942020-11-14 15:06:14 -0800176 gContext->title = title;
177 gContext->width = width;
178 gContext->height = height;
179 gContext->defaultWidth = width;
180 gContext->defaultHeight = height;
181
182 // Setup window
183 glfwSetErrorCallback(ErrorCallback);
184 glfwInitHint(GLFW_JOYSTICK_HAT_BUTTONS, GLFW_FALSE);
James Kuszmaulcf324122023-01-14 14:07:17 -0800185 glfwInitHint(GLFW_COCOA_CHDIR_RESOURCES, GLFW_FALSE);
Austin Schuh1e69f942020-11-14 15:06:14 -0800186 PlatformGlfwInitHints();
Austin Schuh812d0d12021-11-04 20:16:48 -0700187 if (!glfwInit()) {
188 return false;
189 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800190
191 PlatformGlfwWindowHints();
192
193 // Setup Dear ImGui context
194 IMGUI_CHECKVERSION();
195 ImGui::CreateContext();
196 ImPlot::CreateContext();
197 ImGuiIO& io = ImGui::GetIO();
James Kuszmaulcf324122023-01-14 14:07:17 -0800198 io.ConfigFlags |= configFlags;
Austin Schuh1e69f942020-11-14 15:06:14 -0800199
200 // Hook ini handler to save settings
201 ImGuiSettingsHandler iniHandler;
202 iniHandler.TypeName = "MainWindow";
203 iniHandler.TypeHash = ImHashStr(iniHandler.TypeName);
204 iniHandler.ReadOpenFn = IniReadOpen;
205 iniHandler.ReadLineFn = IniReadLine;
206 iniHandler.WriteAllFn = IniWriteAll;
207 ImGui::GetCurrentContext()->SettingsHandlers.push_back(iniHandler);
208
Austin Schuh75263e32022-02-22 18:05:32 -0800209 if (gContext->loadSettings) {
210 gContext->loadSettings();
211 io.IniFilename = nullptr;
212 } else {
213 io.IniFilename = gContext->iniPath.c_str();
214 }
Austin Schuh812d0d12021-11-04 20:16:48 -0700215
Austin Schuh1e69f942020-11-14 15:06:14 -0800216 for (auto&& initialize : gContext->initializers) {
Austin Schuh812d0d12021-11-04 20:16:48 -0700217 if (initialize) {
218 initialize();
219 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800220 }
221
222 // Load INI file
Austin Schuh75263e32022-02-22 18:05:32 -0800223 if (gContext->loadIniSettings) {
224 gContext->loadIniSettings();
225 } else if (io.IniFilename) {
226 ImGui::LoadIniSettingsFromDisk(io.IniFilename);
227 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800228
229 // Set initial window settings
Austin Schuh1e69f942020-11-14 15:06:14 -0800230 if (gContext->width == 0 || gContext->height == 0) {
231 gContext->width = gContext->defaultWidth;
232 gContext->height = gContext->defaultHeight;
233 gContext->loadedWidthHeight = false;
234 }
235
236 float windowScale = 1.0;
237 if (!gContext->loadedWidthHeight) {
238 glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE);
239 // get the primary monitor work area to see if we have a reasonable initial
240 // window size; if not, maximize, and default scaling to smaller
241 if (GLFWmonitor* primary = glfwGetPrimaryMonitor()) {
242 int monWidth, monHeight;
243 glfwGetMonitorWorkarea(primary, nullptr, nullptr, &monWidth, &monHeight);
244 if (monWidth < gContext->width || monHeight < gContext->height) {
245 glfwWindowHint(GLFW_MAXIMIZED, GLFW_TRUE);
246 windowScale = (std::min)(monWidth * 1.0 / gContext->width,
247 monHeight * 1.0 / gContext->height);
248 }
249 }
250 }
Austin Schuh812d0d12021-11-04 20:16:48 -0700251 if (gContext->xPos != -1 && gContext->yPos != -1) {
Austin Schuh1e69f942020-11-14 15:06:14 -0800252 glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
Austin Schuh812d0d12021-11-04 20:16:48 -0700253 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800254
255 // Create window with graphics context
256 gContext->window =
257 glfwCreateWindow(gContext->width, gContext->height,
258 gContext->title.c_str(), nullptr, nullptr);
Austin Schuh812d0d12021-11-04 20:16:48 -0700259 if (!gContext->window) {
260 return false;
261 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800262
263 if (!gContext->loadedWidthHeight) {
Austin Schuh812d0d12021-11-04 20:16:48 -0700264#ifndef __APPLE__
265 if (windowScale == 1.0) {
Austin Schuh1e69f942020-11-14 15:06:14 -0800266 glfwGetWindowContentScale(gContext->window, &windowScale, nullptr);
Austin Schuh812d0d12021-11-04 20:16:48 -0700267 }
268#endif
Austin Schuh1e69f942020-11-14 15:06:14 -0800269 // force user scale if window scale is smaller
Austin Schuh812d0d12021-11-04 20:16:48 -0700270 if (windowScale <= 0.5) {
Austin Schuh1e69f942020-11-14 15:06:14 -0800271 gContext->userScale = 0;
Austin Schuh812d0d12021-11-04 20:16:48 -0700272 } else if (windowScale <= 0.75) {
Austin Schuh1e69f942020-11-14 15:06:14 -0800273 gContext->userScale = 1;
Austin Schuh812d0d12021-11-04 20:16:48 -0700274 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800275 if (windowScale != 1.0) {
Austin Schuh812d0d12021-11-04 20:16:48 -0700276 for (auto&& func : gContext->windowScalers) {
277 func(windowScale);
278 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800279 }
280 }
281
282 // Update window settings
283 if (gContext->xPos != -1 && gContext->yPos != -1) {
Austin Schuh812d0d12021-11-04 20:16:48 -0700284 // check to make sure the position isn't off-screen
285 bool found = false;
286 int monCount;
287 GLFWmonitor** monitors = glfwGetMonitors(&monCount);
288 for (int i = 0; i < monCount; ++i) {
289 int monXPos, monYPos, monWidth, monHeight;
290 glfwGetMonitorWorkarea(monitors[i], &monXPos, &monYPos, &monWidth,
291 &monHeight);
292 if (gContext->xPos >= monXPos && gContext->xPos < (monXPos + monWidth) &&
293 gContext->yPos >= monYPos && gContext->yPos < (monYPos + monHeight)) {
294 found = true;
295 break;
296 }
297 }
298 if (found) {
299 glfwSetWindowPos(gContext->window, gContext->xPos, gContext->yPos);
300 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800301 glfwShowWindow(gContext->window);
302 }
303
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800304 if (gContext->maximized) {
305 glfwMaximizeWindow(gContext->window);
306 }
307
Austin Schuh1e69f942020-11-14 15:06:14 -0800308 // Set window callbacks
309 glfwGetWindowSize(gContext->window, &gContext->width, &gContext->height);
310 glfwSetWindowSizeCallback(gContext->window, WindowSizeCallback);
311 glfwSetFramebufferSizeCallback(gContext->window, FramebufferSizeCallback);
312 glfwSetWindowMaximizeCallback(gContext->window, WindowMaximizeCallback);
313 glfwSetWindowPosCallback(gContext->window, WindowPosCallback);
314
Austin Schuh812d0d12021-11-04 20:16:48 -0700315 // Set icons
316 if (!gContext->icons.empty()) {
317 glfwSetWindowIcon(gContext->window, gContext->icons.size(),
318 gContext->icons.data());
319 for (auto&& icon : gContext->icons) {
320 stbi_image_free(icon.pixels);
321 }
322 gContext->icons.clear();
323 }
324
Austin Schuh1e69f942020-11-14 15:06:14 -0800325 // Setup Dear ImGui style
326 SetStyle(static_cast<Style>(gContext->style));
327
328 // Load Fonts
James Kuszmaulcf324122023-01-14 14:07:17 -0800329 UpdateFontScale();
330 ReloadFonts();
331 gContext->reloadFonts = false; // init renderer will do this
Austin Schuh1e69f942020-11-14 15:06:14 -0800332
Austin Schuh812d0d12021-11-04 20:16:48 -0700333 if (!PlatformInitRenderer()) {
334 return false;
335 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800336
337 return true;
338}
339
340void gui::Main() {
341 // Main loop
342 while (!glfwWindowShouldClose(gContext->window) && !gContext->exit) {
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800343 double startTime = glfwGetTime();
344
Austin Schuh1e69f942020-11-14 15:06:14 -0800345 // Poll and handle events (inputs, window resize, etc.)
346 glfwPollEvents();
Austin Schuh75263e32022-02-22 18:05:32 -0800347 gContext->isPlatformRendering = true;
James Kuszmaulcf324122023-01-14 14:07:17 -0800348 UpdateFontScale();
349 if (gContext->reloadFonts) {
350 ReloadFonts();
351 // PlatformRenderFrame() will clear reloadFonts flag
352 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800353 PlatformRenderFrame();
Austin Schuh75263e32022-02-22 18:05:32 -0800354 gContext->isPlatformRendering = false;
355
James Kuszmaulcf324122023-01-14 14:07:17 -0800356 auto& io = ImGui::GetIO();
357
Austin Schuh75263e32022-02-22 18:05:32 -0800358 // custom saving
359 if (gContext->saveSettings) {
Austin Schuh75263e32022-02-22 18:05:32 -0800360 if (io.WantSaveIniSettings) {
361 gContext->saveSettings(false);
362 io.WantSaveIniSettings = false; // reset flag
363 }
364 }
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800365
366 // if FPS limiting, sleep until next frame time
367 if (gContext->fps != 0) {
368 double sleepTime = (1.0 / gContext->fps) - (glfwGetTime() - startTime);
369 if (sleepTime > 1e-6) {
370 std::this_thread::sleep_for(
371 std::chrono::microseconds(static_cast<int64_t>(sleepTime * 1e6)));
372 }
373 }
Austin Schuh75263e32022-02-22 18:05:32 -0800374 }
375
376 // Save (if custom save)
377 if (gContext->saveSettings) {
378 gContext->saveSettings(true);
Austin Schuh1e69f942020-11-14 15:06:14 -0800379 }
380
381 // Cleanup
382 PlatformShutdown();
383 ImGui_ImplGlfw_Shutdown();
384 ImPlot::DestroyContext();
385 ImGui::DestroyContext();
386
Austin Schuh812d0d12021-11-04 20:16:48 -0700387 // Delete the save file if requested.
Austin Schuh75263e32022-02-22 18:05:32 -0800388 if (!gContext->saveSettings && gContext->resetOnExit) {
389 std::remove(gContext->iniPath.c_str());
Austin Schuh812d0d12021-11-04 20:16:48 -0700390 }
391
Austin Schuh1e69f942020-11-14 15:06:14 -0800392 glfwDestroyWindow(gContext->window);
393 glfwTerminate();
394}
395
396void gui::CommonRenderFrame() {
397 ImGui_ImplGlfw_NewFrame();
398
399 // Start the Dear ImGui frame
400 ImGui::NewFrame();
401
Austin Schuh1e69f942020-11-14 15:06:14 -0800402 for (size_t i = 0; i < gContext->earlyExecutors.size(); ++i) {
403 auto& execute = gContext->earlyExecutors[i];
Austin Schuh812d0d12021-11-04 20:16:48 -0700404 if (execute) {
405 execute();
406 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800407 }
408
409 for (size_t i = 0; i < gContext->lateExecutors.size(); ++i) {
410 auto& execute = gContext->lateExecutors[i];
Austin Schuh812d0d12021-11-04 20:16:48 -0700411 if (execute) {
412 execute();
413 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800414 }
415
416 // Rendering
417 ImGui::Render();
418}
419
420void gui::Exit() {
Austin Schuh812d0d12021-11-04 20:16:48 -0700421 if (!gContext) {
422 return;
423 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800424 gContext->exit = true;
425}
426
427void gui::AddInit(std::function<void()> initialize) {
Austin Schuh812d0d12021-11-04 20:16:48 -0700428 if (initialize) {
429 gContext->initializers.emplace_back(std::move(initialize));
430 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800431}
432
433void gui::AddWindowScaler(std::function<void(float scale)> windowScaler) {
Austin Schuh812d0d12021-11-04 20:16:48 -0700434 if (windowScaler) {
Austin Schuh1e69f942020-11-14 15:06:14 -0800435 gContext->windowScalers.emplace_back(std::move(windowScaler));
Austin Schuh812d0d12021-11-04 20:16:48 -0700436 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800437}
438
439void gui::AddEarlyExecute(std::function<void()> execute) {
Austin Schuh812d0d12021-11-04 20:16:48 -0700440 if (execute) {
441 gContext->earlyExecutors.emplace_back(std::move(execute));
442 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800443}
444
445void gui::AddLateExecute(std::function<void()> execute) {
Austin Schuh812d0d12021-11-04 20:16:48 -0700446 if (execute) {
447 gContext->lateExecutors.emplace_back(std::move(execute));
448 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800449}
450
Austin Schuh75263e32022-02-22 18:05:32 -0800451void gui::ConfigureCustomSaveSettings(std::function<void()> load,
452 std::function<void()> loadIni,
453 std::function<void(bool)> save) {
454 gContext->loadSettings = load;
455 gContext->loadIniSettings = loadIni;
456 gContext->saveSettings = save;
457}
458
Austin Schuh812d0d12021-11-04 20:16:48 -0700459GLFWwindow* gui::GetSystemWindow() {
460 return gContext->window;
461}
462
463bool gui::AddIcon(const unsigned char* data, int len) {
464 // Load from memory
465 GLFWimage image;
466 image.pixels =
467 stbi_load_from_memory(data, len, &image.width, &image.height, nullptr, 4);
468 if (!data) {
469 return false;
470 }
471 gContext->icons.emplace_back(std::move(image));
472 return true;
473}
Austin Schuh1e69f942020-11-14 15:06:14 -0800474
475int gui::AddFont(
476 const char* name,
477 std::function<ImFont*(ImGuiIO& io, float size, const ImFontConfig* cfg)>
478 makeFont) {
Austin Schuh812d0d12021-11-04 20:16:48 -0700479 if (makeFont) {
480 gContext->makeFonts.emplace_back(name, std::move(makeFont));
481 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800482 return gContext->makeFonts.size() - 1;
483}
484
Austin Schuh1e69f942020-11-14 15:06:14 -0800485void gui::SetStyle(Style style) {
486 gContext->style = static_cast<int>(style);
487 switch (style) {
488 case kStyleClassic:
489 ImGui::StyleColorsClassic();
490 break;
491 case kStyleDark:
492 ImGui::StyleColorsDark();
493 break;
494 case kStyleLight:
495 ImGui::StyleColorsLight();
496 break;
497 }
498}
499
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800500void gui::SetFPS(int fps) {
501 gContext->fps = fps;
502}
503
Austin Schuh812d0d12021-11-04 20:16:48 -0700504void gui::SetClearColor(ImVec4 color) {
505 gContext->clearColor = color;
506}
507
Austin Schuh75263e32022-02-22 18:05:32 -0800508std::string gui::GetPlatformSaveFileDir() {
Austin Schuh812d0d12021-11-04 20:16:48 -0700509#if defined(_MSC_VER)
510 const char* env = std::getenv("APPDATA");
511 if (env) {
Austin Schuh75263e32022-02-22 18:05:32 -0800512 return env + std::string("/");
Austin Schuh812d0d12021-11-04 20:16:48 -0700513 }
514#elif defined(__APPLE__)
515 const char* env = std::getenv("HOME");
516 if (env) {
Austin Schuh75263e32022-02-22 18:05:32 -0800517 return env + std::string("/Library/Preferences/");
Austin Schuh812d0d12021-11-04 20:16:48 -0700518 }
519#else
520 const char* xdg = std::getenv("XDG_CONFIG_HOME");
521 const char* env = std::getenv("HOME");
522 if (xdg) {
Austin Schuh75263e32022-02-22 18:05:32 -0800523 return xdg + std::string("/");
Austin Schuh812d0d12021-11-04 20:16:48 -0700524 } else if (env) {
Austin Schuh75263e32022-02-22 18:05:32 -0800525 return env + std::string("/.config/");
Austin Schuh812d0d12021-11-04 20:16:48 -0700526 }
527#endif
Austin Schuh75263e32022-02-22 18:05:32 -0800528 return "";
529}
530
531void gui::ConfigurePlatformSaveFile(const std::string& name) {
532 gContext->iniPath = GetPlatformSaveFileDir() + name;
Austin Schuh812d0d12021-11-04 20:16:48 -0700533}
Austin Schuh1e69f942020-11-14 15:06:14 -0800534
535void gui::EmitViewMenu() {
536 if (ImGui::BeginMenu("View")) {
537 if (ImGui::BeginMenu("Style")) {
538 bool selected;
539 selected = gContext->style == kStyleClassic;
Austin Schuh812d0d12021-11-04 20:16:48 -0700540 if (ImGui::MenuItem("Classic", nullptr, &selected, true)) {
Austin Schuh1e69f942020-11-14 15:06:14 -0800541 SetStyle(kStyleClassic);
Austin Schuh812d0d12021-11-04 20:16:48 -0700542 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800543 selected = gContext->style == kStyleDark;
Austin Schuh812d0d12021-11-04 20:16:48 -0700544 if (ImGui::MenuItem("Dark", nullptr, &selected, true)) {
Austin Schuh1e69f942020-11-14 15:06:14 -0800545 SetStyle(kStyleDark);
Austin Schuh812d0d12021-11-04 20:16:48 -0700546 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800547 selected = gContext->style == kStyleLight;
Austin Schuh812d0d12021-11-04 20:16:48 -0700548 if (ImGui::MenuItem("Light", nullptr, &selected, true)) {
Austin Schuh1e69f942020-11-14 15:06:14 -0800549 SetStyle(kStyleLight);
Austin Schuh812d0d12021-11-04 20:16:48 -0700550 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800551 ImGui::EndMenu();
552 }
553
554 if (ImGui::BeginMenu("Zoom")) {
James Kuszmaulcf324122023-01-14 14:07:17 -0800555 for (int i = 0; i < kFontScaledLevels && (25 * (i + 2)) <= 200; ++i) {
Austin Schuh1e69f942020-11-14 15:06:14 -0800556 char label[20];
557 std::snprintf(label, sizeof(label), "%d%%", 25 * (i + 2));
558 bool selected = gContext->userScale == i;
James Kuszmaulcf324122023-01-14 14:07:17 -0800559 if (ImGui::MenuItem(label, nullptr, &selected)) {
Austin Schuh1e69f942020-11-14 15:06:14 -0800560 gContext->userScale = i;
Austin Schuh812d0d12021-11-04 20:16:48 -0700561 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800562 }
563 ImGui::EndMenu();
564 }
Austin Schuh812d0d12021-11-04 20:16:48 -0700565
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800566 if (ImGui::BeginMenu("Frame Rate")) {
567 bool selected;
568 selected = gContext->fps == 0;
569 if (ImGui::MenuItem("vsync", nullptr, &selected)) {
570 gContext->fps = 0;
571 }
572 selected = gContext->fps == 30;
573 if (ImGui::MenuItem("30 fps", nullptr, &selected)) {
574 gContext->fps = 30;
575 }
576 selected = gContext->fps == 60;
577 if (ImGui::MenuItem("60 fps", nullptr, &selected)) {
578 gContext->fps = 60;
579 }
580 selected = gContext->fps == 120;
581 if (ImGui::MenuItem("120 fps", nullptr, &selected)) {
582 gContext->fps = 120;
583 }
584 ImGui::EndMenu();
585 }
586
Austin Schuh75263e32022-02-22 18:05:32 -0800587 if (!gContext->saveSettings) {
588 ImGui::MenuItem("Reset UI on Exit?", nullptr, &gContext->resetOnExit);
589 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800590 ImGui::EndMenu();
591 }
592}
593
594bool gui::UpdateTextureFromImage(ImTextureID* texture, int width, int height,
595 const unsigned char* data, int len) {
596 // Load from memory
597 int width2 = 0;
598 int height2 = 0;
599 unsigned char* imgData =
600 stbi_load_from_memory(data, len, &width2, &height2, nullptr, 4);
Austin Schuh812d0d12021-11-04 20:16:48 -0700601 if (!data) {
602 return false;
603 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800604
Austin Schuh812d0d12021-11-04 20:16:48 -0700605 if (width2 == width && height2 == height) {
Austin Schuh1e69f942020-11-14 15:06:14 -0800606 UpdateTexture(texture, kPixelRGBA, width2, height2, imgData);
Austin Schuh812d0d12021-11-04 20:16:48 -0700607 } else {
Austin Schuh1e69f942020-11-14 15:06:14 -0800608 *texture = CreateTexture(kPixelRGBA, width2, height2, imgData);
Austin Schuh812d0d12021-11-04 20:16:48 -0700609 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800610
611 stbi_image_free(imgData);
612
613 return true;
614}
615
616bool gui::CreateTextureFromFile(const char* filename, ImTextureID* out_texture,
617 int* out_width, int* out_height) {
618 // Load from file
619 int width = 0;
620 int height = 0;
621 unsigned char* data = stbi_load(filename, &width, &height, nullptr, 4);
Austin Schuh812d0d12021-11-04 20:16:48 -0700622 if (!data) {
623 return false;
624 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800625
626 *out_texture = CreateTexture(kPixelRGBA, width, height, data);
Austin Schuh812d0d12021-11-04 20:16:48 -0700627 if (out_width) {
628 *out_width = width;
629 }
630 if (out_height) {
631 *out_height = height;
632 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800633
634 stbi_image_free(data);
635
636 return true;
637}
638
639bool gui::CreateTextureFromImage(const unsigned char* data, int len,
640 ImTextureID* out_texture, int* out_width,
641 int* out_height) {
642 // Load from memory
643 int width = 0;
644 int height = 0;
645 unsigned char* imgData =
646 stbi_load_from_memory(data, len, &width, &height, nullptr, 4);
Austin Schuh812d0d12021-11-04 20:16:48 -0700647 if (!imgData) {
648 return false;
649 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800650
651 *out_texture = CreateTexture(kPixelRGBA, width, height, imgData);
Austin Schuh812d0d12021-11-04 20:16:48 -0700652 if (out_width) {
653 *out_width = width;
654 }
655 if (out_height) {
656 *out_height = height;
657 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800658
659 stbi_image_free(imgData);
660
661 return true;
662}
663
664void gui::MaxFit(ImVec2* min, ImVec2* max, float width, float height) {
665 float destWidth = max->x - min->x;
666 float destHeight = max->y - min->y;
Austin Schuh812d0d12021-11-04 20:16:48 -0700667 if (width == 0 || height == 0) {
668 return;
669 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800670 if (destWidth * height > destHeight * width) {
671 float outputWidth = width * destHeight / height;
672 min->x += (destWidth - outputWidth) / 2;
673 max->x -= (destWidth - outputWidth) / 2;
674 } else {
675 float outputHeight = height * destWidth / width;
676 min->y += (destHeight - outputHeight) / 2;
677 max->y -= (destHeight - outputHeight) / 2;
678 }
679}
680
681} // namespace wpi