blob: a93ca2b164599705ba5d8077aebe1b2b659624de [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
7#include <algorithm>
8#include <cstdio>
9#include <cstring>
10
11#include <GLFW/glfw3.h>
James Kuszmaulcf324122023-01-14 14:07:17 -080012#include <IconsFontAwesome6.h>
Austin Schuh1e69f942020-11-14 15:06:14 -080013#include <imgui.h>
James Kuszmaulcf324122023-01-14 14:07:17 -080014#include <imgui_FontAwesomeSolid.h>
Austin Schuh1e69f942020-11-14 15:06:14 -080015#include <imgui_ProggyDotted.h>
16#include <imgui_impl_glfw.h>
17#include <imgui_internal.h>
18#include <implot.h>
19#include <stb_image.h>
20
21#include "wpigui_internal.h"
22
23using namespace wpi::gui;
24
25namespace wpi {
26
27Context* gui::gContext;
28
29static void ErrorCallback(int error, const char* description) {
30 std::fprintf(stderr, "GLFW Error %d: %s\n", error, description);
31}
32
33static void WindowSizeCallback(GLFWwindow* window, int width, int height) {
34 if (!gContext->maximized) {
35 gContext->width = width;
36 gContext->height = height;
37 }
Austin Schuh75263e32022-02-22 18:05:32 -080038 if (!gContext->isPlatformRendering) {
39 PlatformRenderFrame();
40 }
Austin Schuh1e69f942020-11-14 15:06:14 -080041}
42
43static void FramebufferSizeCallback(GLFWwindow* window, int width, int height) {
44 PlatformFramebufferSizeChanged(width, height);
45}
46
47static void WindowMaximizeCallback(GLFWwindow* window, int maximized) {
48 gContext->maximized = maximized;
49}
50
51static void WindowPosCallback(GLFWwindow* window, int xpos, int ypos) {
52 if (!gContext->maximized) {
53 gContext->xPos = xpos;
54 gContext->yPos = ypos;
55 }
56}
57
58static void* IniReadOpen(ImGuiContext* ctx, ImGuiSettingsHandler* handler,
59 const char* name) {
Austin Schuh812d0d12021-11-04 20:16:48 -070060 if (std::strcmp(name, "GLOBAL") != 0) {
61 return nullptr;
62 }
Austin Schuh1e69f942020-11-14 15:06:14 -080063 return static_cast<SavedSettings*>(gContext);
64}
65
66static void IniReadLine(ImGuiContext* ctx, ImGuiSettingsHandler* handler,
67 void* entry, const char* lineStr) {
68 auto impl = static_cast<SavedSettings*>(entry);
69 const char* value = std::strchr(lineStr, '=');
Austin Schuh812d0d12021-11-04 20:16:48 -070070 if (!value) {
71 return;
72 }
Austin Schuh1e69f942020-11-14 15:06:14 -080073 ++value;
74 int num = std::atoi(value);
75 if (std::strncmp(lineStr, "width=", 6) == 0) {
76 impl->width = num;
77 impl->loadedWidthHeight = true;
78 } else if (std::strncmp(lineStr, "height=", 7) == 0) {
79 impl->height = num;
80 impl->loadedWidthHeight = true;
81 } else if (std::strncmp(lineStr, "maximized=", 10) == 0) {
82 impl->maximized = num;
83 } else if (std::strncmp(lineStr, "xpos=", 5) == 0) {
84 impl->xPos = num;
85 } else if (std::strncmp(lineStr, "ypos=", 5) == 0) {
86 impl->yPos = num;
87 } else if (std::strncmp(lineStr, "userScale=", 10) == 0) {
88 impl->userScale = num;
89 } else if (std::strncmp(lineStr, "style=", 6) == 0) {
90 impl->style = num;
91 }
92}
93
94static void IniWriteAll(ImGuiContext* ctx, ImGuiSettingsHandler* handler,
95 ImGuiTextBuffer* out_buf) {
Austin Schuh812d0d12021-11-04 20:16:48 -070096 if (!gContext) {
97 return;
98 }
Austin Schuh1e69f942020-11-14 15:06:14 -080099 out_buf->appendf(
100 "[MainWindow][GLOBAL]\nwidth=%d\nheight=%d\nmaximized=%d\n"
101 "xpos=%d\nypos=%d\nuserScale=%d\nstyle=%d\n\n",
Austin Schuh75263e32022-02-22 18:05:32 -0800102 gContext->width, gContext->height, gContext->maximized ? 1 : 0,
103 gContext->xPos, gContext->yPos, gContext->userScale, gContext->style);
Austin Schuh1e69f942020-11-14 15:06:14 -0800104}
105
106void gui::CreateContext() {
107 gContext = new Context;
108 AddFont("ProggyDotted", [](ImGuiIO& io, float size, const ImFontConfig* cfg) {
James Kuszmaulcf324122023-01-14 14:07:17 -0800109 auto font = ImGui::AddFontProggyDotted(io, size, cfg);
110 static const ImWchar icons_ranges[] = {ICON_MIN_FA, ICON_MAX_16_FA, 0};
111 ImFontConfig icons_cfg;
112 icons_cfg.MergeMode = true;
113 icons_cfg.PixelSnapH = true;
114 ImGui::AddFontFontAwesomeSolid(io, size, &icons_cfg, icons_ranges);
115 return font;
Austin Schuh1e69f942020-11-14 15:06:14 -0800116 });
117 PlatformCreateContext();
118}
119
120void gui::DestroyContext() {
121 PlatformDestroyContext();
122 delete gContext;
123 gContext = nullptr;
124}
125
James Kuszmaulcf324122023-01-14 14:07:17 -0800126static void UpdateFontScale() {
127 // Scale based on OS window content scaling
128 float windowScale = 1.0;
129#ifndef __APPLE__
130 glfwGetWindowContentScale(gContext->window, &windowScale, nullptr);
131#endif
132 // map to closest font size: 0 = 0.5x, 1 = 0.75x, 2 = 1.0x, 3 = 1.25x,
133 // 4 = 1.5x, 5 = 1.75x, 6 = 2x
134 int fontScale =
135 gContext->userScale + static_cast<int>((windowScale - 1.0) * 4);
136 if (fontScale < 0) {
137 fontScale = 0;
138 }
139 if (gContext->fontScale != fontScale) {
140 gContext->reloadFonts = true;
141 gContext->fontScale = fontScale;
142 }
143}
144
145// the range is based on 13px being the "nominal" 100% size and going from
146// ~0.5x (7px) to ~2.0x (25px)
147static void ReloadFonts() {
148 auto& io = ImGui::GetIO();
149 io.Fonts->Clear();
150 gContext->fonts.clear();
151 float size = 7.0f + gContext->fontScale * 3.0f;
152 bool first = true;
153 for (auto&& makeFont : gContext->makeFonts) {
154 if (makeFont.second) {
155 ImFontConfig cfg;
156 std::snprintf(cfg.Name, sizeof(cfg.Name), "%s", makeFont.first);
157 ImFont* font = makeFont.second(io, size, &cfg);
158 if (first) {
159 ImGui::GetIO().FontDefault = font;
160 first = false;
161 }
162 gContext->fonts.emplace_back(font);
163 }
164 }
165}
166
167bool gui::Initialize(const char* title, int width, int height,
168 ImGuiConfigFlags configFlags) {
Austin Schuh1e69f942020-11-14 15:06:14 -0800169 gContext->title = title;
170 gContext->width = width;
171 gContext->height = height;
172 gContext->defaultWidth = width;
173 gContext->defaultHeight = height;
174
175 // Setup window
176 glfwSetErrorCallback(ErrorCallback);
177 glfwInitHint(GLFW_JOYSTICK_HAT_BUTTONS, GLFW_FALSE);
James Kuszmaulcf324122023-01-14 14:07:17 -0800178 glfwInitHint(GLFW_COCOA_CHDIR_RESOURCES, GLFW_FALSE);
Austin Schuh1e69f942020-11-14 15:06:14 -0800179 PlatformGlfwInitHints();
Austin Schuh812d0d12021-11-04 20:16:48 -0700180 if (!glfwInit()) {
181 return false;
182 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800183
184 PlatformGlfwWindowHints();
185
186 // Setup Dear ImGui context
187 IMGUI_CHECKVERSION();
188 ImGui::CreateContext();
189 ImPlot::CreateContext();
190 ImGuiIO& io = ImGui::GetIO();
James Kuszmaulcf324122023-01-14 14:07:17 -0800191 io.ConfigFlags |= configFlags;
Austin Schuh1e69f942020-11-14 15:06:14 -0800192
193 // Hook ini handler to save settings
194 ImGuiSettingsHandler iniHandler;
195 iniHandler.TypeName = "MainWindow";
196 iniHandler.TypeHash = ImHashStr(iniHandler.TypeName);
197 iniHandler.ReadOpenFn = IniReadOpen;
198 iniHandler.ReadLineFn = IniReadLine;
199 iniHandler.WriteAllFn = IniWriteAll;
200 ImGui::GetCurrentContext()->SettingsHandlers.push_back(iniHandler);
201
Austin Schuh75263e32022-02-22 18:05:32 -0800202 if (gContext->loadSettings) {
203 gContext->loadSettings();
204 io.IniFilename = nullptr;
205 } else {
206 io.IniFilename = gContext->iniPath.c_str();
207 }
Austin Schuh812d0d12021-11-04 20:16:48 -0700208
Austin Schuh1e69f942020-11-14 15:06:14 -0800209 for (auto&& initialize : gContext->initializers) {
Austin Schuh812d0d12021-11-04 20:16:48 -0700210 if (initialize) {
211 initialize();
212 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800213 }
214
215 // Load INI file
Austin Schuh75263e32022-02-22 18:05:32 -0800216 if (gContext->loadIniSettings) {
217 gContext->loadIniSettings();
218 } else if (io.IniFilename) {
219 ImGui::LoadIniSettingsFromDisk(io.IniFilename);
220 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800221
222 // Set initial window settings
223 glfwWindowHint(GLFW_MAXIMIZED, gContext->maximized ? GLFW_TRUE : GLFW_FALSE);
224
225 if (gContext->width == 0 || gContext->height == 0) {
226 gContext->width = gContext->defaultWidth;
227 gContext->height = gContext->defaultHeight;
228 gContext->loadedWidthHeight = false;
229 }
230
231 float windowScale = 1.0;
232 if (!gContext->loadedWidthHeight) {
233 glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE);
234 // get the primary monitor work area to see if we have a reasonable initial
235 // window size; if not, maximize, and default scaling to smaller
236 if (GLFWmonitor* primary = glfwGetPrimaryMonitor()) {
237 int monWidth, monHeight;
238 glfwGetMonitorWorkarea(primary, nullptr, nullptr, &monWidth, &monHeight);
239 if (monWidth < gContext->width || monHeight < gContext->height) {
240 glfwWindowHint(GLFW_MAXIMIZED, GLFW_TRUE);
241 windowScale = (std::min)(monWidth * 1.0 / gContext->width,
242 monHeight * 1.0 / gContext->height);
243 }
244 }
245 }
Austin Schuh812d0d12021-11-04 20:16:48 -0700246 if (gContext->xPos != -1 && gContext->yPos != -1) {
Austin Schuh1e69f942020-11-14 15:06:14 -0800247 glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
Austin Schuh812d0d12021-11-04 20:16:48 -0700248 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800249
250 // Create window with graphics context
251 gContext->window =
252 glfwCreateWindow(gContext->width, gContext->height,
253 gContext->title.c_str(), nullptr, nullptr);
Austin Schuh812d0d12021-11-04 20:16:48 -0700254 if (!gContext->window) {
255 return false;
256 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800257
258 if (!gContext->loadedWidthHeight) {
Austin Schuh812d0d12021-11-04 20:16:48 -0700259#ifndef __APPLE__
260 if (windowScale == 1.0) {
Austin Schuh1e69f942020-11-14 15:06:14 -0800261 glfwGetWindowContentScale(gContext->window, &windowScale, nullptr);
Austin Schuh812d0d12021-11-04 20:16:48 -0700262 }
263#endif
Austin Schuh1e69f942020-11-14 15:06:14 -0800264 // force user scale if window scale is smaller
Austin Schuh812d0d12021-11-04 20:16:48 -0700265 if (windowScale <= 0.5) {
Austin Schuh1e69f942020-11-14 15:06:14 -0800266 gContext->userScale = 0;
Austin Schuh812d0d12021-11-04 20:16:48 -0700267 } else if (windowScale <= 0.75) {
Austin Schuh1e69f942020-11-14 15:06:14 -0800268 gContext->userScale = 1;
Austin Schuh812d0d12021-11-04 20:16:48 -0700269 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800270 if (windowScale != 1.0) {
Austin Schuh812d0d12021-11-04 20:16:48 -0700271 for (auto&& func : gContext->windowScalers) {
272 func(windowScale);
273 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800274 }
275 }
276
277 // Update window settings
278 if (gContext->xPos != -1 && gContext->yPos != -1) {
Austin Schuh812d0d12021-11-04 20:16:48 -0700279 // check to make sure the position isn't off-screen
280 bool found = false;
281 int monCount;
282 GLFWmonitor** monitors = glfwGetMonitors(&monCount);
283 for (int i = 0; i < monCount; ++i) {
284 int monXPos, monYPos, monWidth, monHeight;
285 glfwGetMonitorWorkarea(monitors[i], &monXPos, &monYPos, &monWidth,
286 &monHeight);
287 if (gContext->xPos >= monXPos && gContext->xPos < (monXPos + monWidth) &&
288 gContext->yPos >= monYPos && gContext->yPos < (monYPos + monHeight)) {
289 found = true;
290 break;
291 }
292 }
293 if (found) {
294 glfwSetWindowPos(gContext->window, gContext->xPos, gContext->yPos);
295 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800296 glfwShowWindow(gContext->window);
297 }
298
299 // Set window callbacks
300 glfwGetWindowSize(gContext->window, &gContext->width, &gContext->height);
301 glfwSetWindowSizeCallback(gContext->window, WindowSizeCallback);
302 glfwSetFramebufferSizeCallback(gContext->window, FramebufferSizeCallback);
303 glfwSetWindowMaximizeCallback(gContext->window, WindowMaximizeCallback);
304 glfwSetWindowPosCallback(gContext->window, WindowPosCallback);
305
Austin Schuh812d0d12021-11-04 20:16:48 -0700306 // Set icons
307 if (!gContext->icons.empty()) {
308 glfwSetWindowIcon(gContext->window, gContext->icons.size(),
309 gContext->icons.data());
310 for (auto&& icon : gContext->icons) {
311 stbi_image_free(icon.pixels);
312 }
313 gContext->icons.clear();
314 }
315
Austin Schuh1e69f942020-11-14 15:06:14 -0800316 // Setup Dear ImGui style
317 SetStyle(static_cast<Style>(gContext->style));
318
319 // Load Fonts
James Kuszmaulcf324122023-01-14 14:07:17 -0800320 UpdateFontScale();
321 ReloadFonts();
322 gContext->reloadFonts = false; // init renderer will do this
Austin Schuh1e69f942020-11-14 15:06:14 -0800323
Austin Schuh812d0d12021-11-04 20:16:48 -0700324 if (!PlatformInitRenderer()) {
325 return false;
326 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800327
328 return true;
329}
330
331void gui::Main() {
332 // Main loop
333 while (!glfwWindowShouldClose(gContext->window) && !gContext->exit) {
334 // Poll and handle events (inputs, window resize, etc.)
335 glfwPollEvents();
Austin Schuh75263e32022-02-22 18:05:32 -0800336 gContext->isPlatformRendering = true;
James Kuszmaulcf324122023-01-14 14:07:17 -0800337 UpdateFontScale();
338 if (gContext->reloadFonts) {
339 ReloadFonts();
340 // PlatformRenderFrame() will clear reloadFonts flag
341 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800342 PlatformRenderFrame();
Austin Schuh75263e32022-02-22 18:05:32 -0800343 gContext->isPlatformRendering = false;
344
James Kuszmaulcf324122023-01-14 14:07:17 -0800345 auto& io = ImGui::GetIO();
346
Austin Schuh75263e32022-02-22 18:05:32 -0800347 // custom saving
348 if (gContext->saveSettings) {
Austin Schuh75263e32022-02-22 18:05:32 -0800349 if (io.WantSaveIniSettings) {
350 gContext->saveSettings(false);
351 io.WantSaveIniSettings = false; // reset flag
352 }
353 }
354 }
355
356 // Save (if custom save)
357 if (gContext->saveSettings) {
358 gContext->saveSettings(true);
Austin Schuh1e69f942020-11-14 15:06:14 -0800359 }
360
361 // Cleanup
362 PlatformShutdown();
363 ImGui_ImplGlfw_Shutdown();
364 ImPlot::DestroyContext();
365 ImGui::DestroyContext();
366
Austin Schuh812d0d12021-11-04 20:16:48 -0700367 // Delete the save file if requested.
Austin Schuh75263e32022-02-22 18:05:32 -0800368 if (!gContext->saveSettings && gContext->resetOnExit) {
369 std::remove(gContext->iniPath.c_str());
Austin Schuh812d0d12021-11-04 20:16:48 -0700370 }
371
Austin Schuh1e69f942020-11-14 15:06:14 -0800372 glfwDestroyWindow(gContext->window);
373 glfwTerminate();
374}
375
376void gui::CommonRenderFrame() {
377 ImGui_ImplGlfw_NewFrame();
378
379 // Start the Dear ImGui frame
380 ImGui::NewFrame();
381
Austin Schuh1e69f942020-11-14 15:06:14 -0800382 for (size_t i = 0; i < gContext->earlyExecutors.size(); ++i) {
383 auto& execute = gContext->earlyExecutors[i];
Austin Schuh812d0d12021-11-04 20:16:48 -0700384 if (execute) {
385 execute();
386 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800387 }
388
389 for (size_t i = 0; i < gContext->lateExecutors.size(); ++i) {
390 auto& execute = gContext->lateExecutors[i];
Austin Schuh812d0d12021-11-04 20:16:48 -0700391 if (execute) {
392 execute();
393 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800394 }
395
396 // Rendering
397 ImGui::Render();
398}
399
400void gui::Exit() {
Austin Schuh812d0d12021-11-04 20:16:48 -0700401 if (!gContext) {
402 return;
403 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800404 gContext->exit = true;
405}
406
407void gui::AddInit(std::function<void()> initialize) {
Austin Schuh812d0d12021-11-04 20:16:48 -0700408 if (initialize) {
409 gContext->initializers.emplace_back(std::move(initialize));
410 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800411}
412
413void gui::AddWindowScaler(std::function<void(float scale)> windowScaler) {
Austin Schuh812d0d12021-11-04 20:16:48 -0700414 if (windowScaler) {
Austin Schuh1e69f942020-11-14 15:06:14 -0800415 gContext->windowScalers.emplace_back(std::move(windowScaler));
Austin Schuh812d0d12021-11-04 20:16:48 -0700416 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800417}
418
419void gui::AddEarlyExecute(std::function<void()> execute) {
Austin Schuh812d0d12021-11-04 20:16:48 -0700420 if (execute) {
421 gContext->earlyExecutors.emplace_back(std::move(execute));
422 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800423}
424
425void gui::AddLateExecute(std::function<void()> execute) {
Austin Schuh812d0d12021-11-04 20:16:48 -0700426 if (execute) {
427 gContext->lateExecutors.emplace_back(std::move(execute));
428 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800429}
430
Austin Schuh75263e32022-02-22 18:05:32 -0800431void gui::ConfigureCustomSaveSettings(std::function<void()> load,
432 std::function<void()> loadIni,
433 std::function<void(bool)> save) {
434 gContext->loadSettings = load;
435 gContext->loadIniSettings = loadIni;
436 gContext->saveSettings = save;
437}
438
Austin Schuh812d0d12021-11-04 20:16:48 -0700439GLFWwindow* gui::GetSystemWindow() {
440 return gContext->window;
441}
442
443bool gui::AddIcon(const unsigned char* data, int len) {
444 // Load from memory
445 GLFWimage image;
446 image.pixels =
447 stbi_load_from_memory(data, len, &image.width, &image.height, nullptr, 4);
448 if (!data) {
449 return false;
450 }
451 gContext->icons.emplace_back(std::move(image));
452 return true;
453}
Austin Schuh1e69f942020-11-14 15:06:14 -0800454
455int gui::AddFont(
456 const char* name,
457 std::function<ImFont*(ImGuiIO& io, float size, const ImFontConfig* cfg)>
458 makeFont) {
Austin Schuh812d0d12021-11-04 20:16:48 -0700459 if (makeFont) {
460 gContext->makeFonts.emplace_back(name, std::move(makeFont));
461 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800462 return gContext->makeFonts.size() - 1;
463}
464
Austin Schuh1e69f942020-11-14 15:06:14 -0800465void gui::SetStyle(Style style) {
466 gContext->style = static_cast<int>(style);
467 switch (style) {
468 case kStyleClassic:
469 ImGui::StyleColorsClassic();
470 break;
471 case kStyleDark:
472 ImGui::StyleColorsDark();
473 break;
474 case kStyleLight:
475 ImGui::StyleColorsLight();
476 break;
477 }
478}
479
Austin Schuh812d0d12021-11-04 20:16:48 -0700480void gui::SetClearColor(ImVec4 color) {
481 gContext->clearColor = color;
482}
483
Austin Schuh75263e32022-02-22 18:05:32 -0800484std::string gui::GetPlatformSaveFileDir() {
Austin Schuh812d0d12021-11-04 20:16:48 -0700485#if defined(_MSC_VER)
486 const char* env = std::getenv("APPDATA");
487 if (env) {
Austin Schuh75263e32022-02-22 18:05:32 -0800488 return env + std::string("/");
Austin Schuh812d0d12021-11-04 20:16:48 -0700489 }
490#elif defined(__APPLE__)
491 const char* env = std::getenv("HOME");
492 if (env) {
Austin Schuh75263e32022-02-22 18:05:32 -0800493 return env + std::string("/Library/Preferences/");
Austin Schuh812d0d12021-11-04 20:16:48 -0700494 }
495#else
496 const char* xdg = std::getenv("XDG_CONFIG_HOME");
497 const char* env = std::getenv("HOME");
498 if (xdg) {
Austin Schuh75263e32022-02-22 18:05:32 -0800499 return xdg + std::string("/");
Austin Schuh812d0d12021-11-04 20:16:48 -0700500 } else if (env) {
Austin Schuh75263e32022-02-22 18:05:32 -0800501 return env + std::string("/.config/");
Austin Schuh812d0d12021-11-04 20:16:48 -0700502 }
503#endif
Austin Schuh75263e32022-02-22 18:05:32 -0800504 return "";
505}
506
507void gui::ConfigurePlatformSaveFile(const std::string& name) {
508 gContext->iniPath = GetPlatformSaveFileDir() + name;
Austin Schuh812d0d12021-11-04 20:16:48 -0700509}
Austin Schuh1e69f942020-11-14 15:06:14 -0800510
511void gui::EmitViewMenu() {
512 if (ImGui::BeginMenu("View")) {
513 if (ImGui::BeginMenu("Style")) {
514 bool selected;
515 selected = gContext->style == kStyleClassic;
Austin Schuh812d0d12021-11-04 20:16:48 -0700516 if (ImGui::MenuItem("Classic", nullptr, &selected, true)) {
Austin Schuh1e69f942020-11-14 15:06:14 -0800517 SetStyle(kStyleClassic);
Austin Schuh812d0d12021-11-04 20:16:48 -0700518 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800519 selected = gContext->style == kStyleDark;
Austin Schuh812d0d12021-11-04 20:16:48 -0700520 if (ImGui::MenuItem("Dark", nullptr, &selected, true)) {
Austin Schuh1e69f942020-11-14 15:06:14 -0800521 SetStyle(kStyleDark);
Austin Schuh812d0d12021-11-04 20:16:48 -0700522 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800523 selected = gContext->style == kStyleLight;
Austin Schuh812d0d12021-11-04 20:16:48 -0700524 if (ImGui::MenuItem("Light", nullptr, &selected, true)) {
Austin Schuh1e69f942020-11-14 15:06:14 -0800525 SetStyle(kStyleLight);
Austin Schuh812d0d12021-11-04 20:16:48 -0700526 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800527 ImGui::EndMenu();
528 }
529
530 if (ImGui::BeginMenu("Zoom")) {
James Kuszmaulcf324122023-01-14 14:07:17 -0800531 for (int i = 0; i < kFontScaledLevels && (25 * (i + 2)) <= 200; ++i) {
Austin Schuh1e69f942020-11-14 15:06:14 -0800532 char label[20];
533 std::snprintf(label, sizeof(label), "%d%%", 25 * (i + 2));
534 bool selected = gContext->userScale == i;
James Kuszmaulcf324122023-01-14 14:07:17 -0800535 if (ImGui::MenuItem(label, nullptr, &selected)) {
Austin Schuh1e69f942020-11-14 15:06:14 -0800536 gContext->userScale = i;
Austin Schuh812d0d12021-11-04 20:16:48 -0700537 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800538 }
539 ImGui::EndMenu();
540 }
Austin Schuh812d0d12021-11-04 20:16:48 -0700541
Austin Schuh75263e32022-02-22 18:05:32 -0800542 if (!gContext->saveSettings) {
543 ImGui::MenuItem("Reset UI on Exit?", nullptr, &gContext->resetOnExit);
544 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800545 ImGui::EndMenu();
546 }
547}
548
549bool gui::UpdateTextureFromImage(ImTextureID* texture, int width, int height,
550 const unsigned char* data, int len) {
551 // Load from memory
552 int width2 = 0;
553 int height2 = 0;
554 unsigned char* imgData =
555 stbi_load_from_memory(data, len, &width2, &height2, nullptr, 4);
Austin Schuh812d0d12021-11-04 20:16:48 -0700556 if (!data) {
557 return false;
558 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800559
Austin Schuh812d0d12021-11-04 20:16:48 -0700560 if (width2 == width && height2 == height) {
Austin Schuh1e69f942020-11-14 15:06:14 -0800561 UpdateTexture(texture, kPixelRGBA, width2, height2, imgData);
Austin Schuh812d0d12021-11-04 20:16:48 -0700562 } else {
Austin Schuh1e69f942020-11-14 15:06:14 -0800563 *texture = CreateTexture(kPixelRGBA, width2, height2, imgData);
Austin Schuh812d0d12021-11-04 20:16:48 -0700564 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800565
566 stbi_image_free(imgData);
567
568 return true;
569}
570
571bool gui::CreateTextureFromFile(const char* filename, ImTextureID* out_texture,
572 int* out_width, int* out_height) {
573 // Load from file
574 int width = 0;
575 int height = 0;
576 unsigned char* data = stbi_load(filename, &width, &height, nullptr, 4);
Austin Schuh812d0d12021-11-04 20:16:48 -0700577 if (!data) {
578 return false;
579 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800580
581 *out_texture = CreateTexture(kPixelRGBA, width, height, data);
Austin Schuh812d0d12021-11-04 20:16:48 -0700582 if (out_width) {
583 *out_width = width;
584 }
585 if (out_height) {
586 *out_height = height;
587 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800588
589 stbi_image_free(data);
590
591 return true;
592}
593
594bool gui::CreateTextureFromImage(const unsigned char* data, int len,
595 ImTextureID* out_texture, int* out_width,
596 int* out_height) {
597 // Load from memory
598 int width = 0;
599 int height = 0;
600 unsigned char* imgData =
601 stbi_load_from_memory(data, len, &width, &height, nullptr, 4);
Austin Schuh812d0d12021-11-04 20:16:48 -0700602 if (!imgData) {
603 return false;
604 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800605
606 *out_texture = CreateTexture(kPixelRGBA, width, height, imgData);
Austin Schuh812d0d12021-11-04 20:16:48 -0700607 if (out_width) {
608 *out_width = width;
609 }
610 if (out_height) {
611 *out_height = height;
612 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800613
614 stbi_image_free(imgData);
615
616 return true;
617}
618
619void gui::MaxFit(ImVec2* min, ImVec2* max, float width, float height) {
620 float destWidth = max->x - min->x;
621 float destHeight = max->y - min->y;
Austin Schuh812d0d12021-11-04 20:16:48 -0700622 if (width == 0 || height == 0) {
623 return;
624 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800625 if (destWidth * height > destHeight * width) {
626 float outputWidth = width * destHeight / height;
627 min->x += (destWidth - outputWidth) / 2;
628 max->x -= (destWidth - outputWidth) / 2;
629 } else {
630 float outputHeight = height * destWidth / width;
631 min->y += (destHeight - outputHeight) / 2;
632 max->y -= (destHeight - outputHeight) / 2;
633 }
634}
635
636} // namespace wpi