blob: b1e11437e6b86491b3459734294d3b9015798b75 [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>
12#include <imgui.h>
13#include <imgui_ProggyDotted.h>
14#include <imgui_impl_glfw.h>
15#include <imgui_internal.h>
16#include <implot.h>
17#include <stb_image.h>
18
19#include "wpigui_internal.h"
20
21using namespace wpi::gui;
22
23namespace wpi {
24
25Context* gui::gContext;
26
27static void ErrorCallback(int error, const char* description) {
28 std::fprintf(stderr, "GLFW Error %d: %s\n", error, description);
29}
30
31static void WindowSizeCallback(GLFWwindow* window, int width, int height) {
32 if (!gContext->maximized) {
33 gContext->width = width;
34 gContext->height = height;
35 }
Austin Schuh75263e32022-02-22 18:05:32 -080036 if (!gContext->isPlatformRendering) {
37 PlatformRenderFrame();
38 }
Austin Schuh1e69f942020-11-14 15:06:14 -080039}
40
41static void FramebufferSizeCallback(GLFWwindow* window, int width, int height) {
42 PlatformFramebufferSizeChanged(width, height);
43}
44
45static void WindowMaximizeCallback(GLFWwindow* window, int maximized) {
46 gContext->maximized = maximized;
47}
48
49static void WindowPosCallback(GLFWwindow* window, int xpos, int ypos) {
50 if (!gContext->maximized) {
51 gContext->xPos = xpos;
52 gContext->yPos = ypos;
53 }
54}
55
56static void* IniReadOpen(ImGuiContext* ctx, ImGuiSettingsHandler* handler,
57 const char* name) {
Austin Schuh812d0d12021-11-04 20:16:48 -070058 if (std::strcmp(name, "GLOBAL") != 0) {
59 return nullptr;
60 }
Austin Schuh1e69f942020-11-14 15:06:14 -080061 return static_cast<SavedSettings*>(gContext);
62}
63
64static void IniReadLine(ImGuiContext* ctx, ImGuiSettingsHandler* handler,
65 void* entry, const char* lineStr) {
66 auto impl = static_cast<SavedSettings*>(entry);
67 const char* value = std::strchr(lineStr, '=');
Austin Schuh812d0d12021-11-04 20:16:48 -070068 if (!value) {
69 return;
70 }
Austin Schuh1e69f942020-11-14 15:06:14 -080071 ++value;
72 int num = std::atoi(value);
73 if (std::strncmp(lineStr, "width=", 6) == 0) {
74 impl->width = num;
75 impl->loadedWidthHeight = true;
76 } else if (std::strncmp(lineStr, "height=", 7) == 0) {
77 impl->height = num;
78 impl->loadedWidthHeight = true;
79 } else if (std::strncmp(lineStr, "maximized=", 10) == 0) {
80 impl->maximized = num;
81 } else if (std::strncmp(lineStr, "xpos=", 5) == 0) {
82 impl->xPos = num;
83 } else if (std::strncmp(lineStr, "ypos=", 5) == 0) {
84 impl->yPos = num;
85 } else if (std::strncmp(lineStr, "userScale=", 10) == 0) {
86 impl->userScale = num;
87 } else if (std::strncmp(lineStr, "style=", 6) == 0) {
88 impl->style = num;
89 }
90}
91
92static void IniWriteAll(ImGuiContext* ctx, ImGuiSettingsHandler* handler,
93 ImGuiTextBuffer* out_buf) {
Austin Schuh812d0d12021-11-04 20:16:48 -070094 if (!gContext) {
95 return;
96 }
Austin Schuh1e69f942020-11-14 15:06:14 -080097 out_buf->appendf(
98 "[MainWindow][GLOBAL]\nwidth=%d\nheight=%d\nmaximized=%d\n"
99 "xpos=%d\nypos=%d\nuserScale=%d\nstyle=%d\n\n",
Austin Schuh75263e32022-02-22 18:05:32 -0800100 gContext->width, gContext->height, gContext->maximized ? 1 : 0,
101 gContext->xPos, gContext->yPos, gContext->userScale, gContext->style);
Austin Schuh1e69f942020-11-14 15:06:14 -0800102}
103
104void gui::CreateContext() {
105 gContext = new Context;
106 AddFont("ProggyDotted", [](ImGuiIO& io, float size, const ImFontConfig* cfg) {
107 return ImGui::AddFontProggyDotted(io, size, cfg);
108 });
109 PlatformCreateContext();
110}
111
112void gui::DestroyContext() {
113 PlatformDestroyContext();
114 delete gContext;
115 gContext = nullptr;
116}
117
118bool gui::Initialize(const char* title, int width, int height) {
119 gContext->title = title;
120 gContext->width = width;
121 gContext->height = height;
122 gContext->defaultWidth = width;
123 gContext->defaultHeight = height;
124
125 // Setup window
126 glfwSetErrorCallback(ErrorCallback);
127 glfwInitHint(GLFW_JOYSTICK_HAT_BUTTONS, GLFW_FALSE);
128 PlatformGlfwInitHints();
Austin Schuh812d0d12021-11-04 20:16:48 -0700129 if (!glfwInit()) {
130 return false;
131 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800132
133 PlatformGlfwWindowHints();
134
135 // Setup Dear ImGui context
136 IMGUI_CHECKVERSION();
137 ImGui::CreateContext();
138 ImPlot::CreateContext();
139 ImGuiIO& io = ImGui::GetIO();
140
141 // Hook ini handler to save settings
142 ImGuiSettingsHandler iniHandler;
143 iniHandler.TypeName = "MainWindow";
144 iniHandler.TypeHash = ImHashStr(iniHandler.TypeName);
145 iniHandler.ReadOpenFn = IniReadOpen;
146 iniHandler.ReadLineFn = IniReadLine;
147 iniHandler.WriteAllFn = IniWriteAll;
148 ImGui::GetCurrentContext()->SettingsHandlers.push_back(iniHandler);
149
Austin Schuh75263e32022-02-22 18:05:32 -0800150 if (gContext->loadSettings) {
151 gContext->loadSettings();
152 io.IniFilename = nullptr;
153 } else {
154 io.IniFilename = gContext->iniPath.c_str();
155 }
Austin Schuh812d0d12021-11-04 20:16:48 -0700156
Austin Schuh1e69f942020-11-14 15:06:14 -0800157 for (auto&& initialize : gContext->initializers) {
Austin Schuh812d0d12021-11-04 20:16:48 -0700158 if (initialize) {
159 initialize();
160 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800161 }
162
163 // Load INI file
Austin Schuh75263e32022-02-22 18:05:32 -0800164 if (gContext->loadIniSettings) {
165 gContext->loadIniSettings();
166 } else if (io.IniFilename) {
167 ImGui::LoadIniSettingsFromDisk(io.IniFilename);
168 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800169
170 // Set initial window settings
171 glfwWindowHint(GLFW_MAXIMIZED, gContext->maximized ? GLFW_TRUE : GLFW_FALSE);
172
173 if (gContext->width == 0 || gContext->height == 0) {
174 gContext->width = gContext->defaultWidth;
175 gContext->height = gContext->defaultHeight;
176 gContext->loadedWidthHeight = false;
177 }
178
179 float windowScale = 1.0;
180 if (!gContext->loadedWidthHeight) {
181 glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE);
182 // get the primary monitor work area to see if we have a reasonable initial
183 // window size; if not, maximize, and default scaling to smaller
184 if (GLFWmonitor* primary = glfwGetPrimaryMonitor()) {
185 int monWidth, monHeight;
186 glfwGetMonitorWorkarea(primary, nullptr, nullptr, &monWidth, &monHeight);
187 if (monWidth < gContext->width || monHeight < gContext->height) {
188 glfwWindowHint(GLFW_MAXIMIZED, GLFW_TRUE);
189 windowScale = (std::min)(monWidth * 1.0 / gContext->width,
190 monHeight * 1.0 / gContext->height);
191 }
192 }
193 }
Austin Schuh812d0d12021-11-04 20:16:48 -0700194 if (gContext->xPos != -1 && gContext->yPos != -1) {
Austin Schuh1e69f942020-11-14 15:06:14 -0800195 glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
Austin Schuh812d0d12021-11-04 20:16:48 -0700196 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800197
198 // Create window with graphics context
199 gContext->window =
200 glfwCreateWindow(gContext->width, gContext->height,
201 gContext->title.c_str(), nullptr, nullptr);
Austin Schuh812d0d12021-11-04 20:16:48 -0700202 if (!gContext->window) {
203 return false;
204 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800205
206 if (!gContext->loadedWidthHeight) {
Austin Schuh812d0d12021-11-04 20:16:48 -0700207#ifndef __APPLE__
208 if (windowScale == 1.0) {
Austin Schuh1e69f942020-11-14 15:06:14 -0800209 glfwGetWindowContentScale(gContext->window, &windowScale, nullptr);
Austin Schuh812d0d12021-11-04 20:16:48 -0700210 }
211#endif
Austin Schuh1e69f942020-11-14 15:06:14 -0800212 // force user scale if window scale is smaller
Austin Schuh812d0d12021-11-04 20:16:48 -0700213 if (windowScale <= 0.5) {
Austin Schuh1e69f942020-11-14 15:06:14 -0800214 gContext->userScale = 0;
Austin Schuh812d0d12021-11-04 20:16:48 -0700215 } else if (windowScale <= 0.75) {
Austin Schuh1e69f942020-11-14 15:06:14 -0800216 gContext->userScale = 1;
Austin Schuh812d0d12021-11-04 20:16:48 -0700217 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800218 if (windowScale != 1.0) {
Austin Schuh812d0d12021-11-04 20:16:48 -0700219 for (auto&& func : gContext->windowScalers) {
220 func(windowScale);
221 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800222 }
223 }
224
225 // Update window settings
226 if (gContext->xPos != -1 && gContext->yPos != -1) {
Austin Schuh812d0d12021-11-04 20:16:48 -0700227 // check to make sure the position isn't off-screen
228 bool found = false;
229 int monCount;
230 GLFWmonitor** monitors = glfwGetMonitors(&monCount);
231 for (int i = 0; i < monCount; ++i) {
232 int monXPos, monYPos, monWidth, monHeight;
233 glfwGetMonitorWorkarea(monitors[i], &monXPos, &monYPos, &monWidth,
234 &monHeight);
235 if (gContext->xPos >= monXPos && gContext->xPos < (monXPos + monWidth) &&
236 gContext->yPos >= monYPos && gContext->yPos < (monYPos + monHeight)) {
237 found = true;
238 break;
239 }
240 }
241 if (found) {
242 glfwSetWindowPos(gContext->window, gContext->xPos, gContext->yPos);
243 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800244 glfwShowWindow(gContext->window);
245 }
246
247 // Set window callbacks
248 glfwGetWindowSize(gContext->window, &gContext->width, &gContext->height);
249 glfwSetWindowSizeCallback(gContext->window, WindowSizeCallback);
250 glfwSetFramebufferSizeCallback(gContext->window, FramebufferSizeCallback);
251 glfwSetWindowMaximizeCallback(gContext->window, WindowMaximizeCallback);
252 glfwSetWindowPosCallback(gContext->window, WindowPosCallback);
253
Austin Schuh812d0d12021-11-04 20:16:48 -0700254 // Set icons
255 if (!gContext->icons.empty()) {
256 glfwSetWindowIcon(gContext->window, gContext->icons.size(),
257 gContext->icons.data());
258 for (auto&& icon : gContext->icons) {
259 stbi_image_free(icon.pixels);
260 }
261 gContext->icons.clear();
262 }
263
Austin Schuh1e69f942020-11-14 15:06:14 -0800264 // Setup Dear ImGui style
265 SetStyle(static_cast<Style>(gContext->style));
266
267 // Load Fonts
268 // this range is based on 13px being the "nominal" 100% size and going from
269 // ~0.5x (7px) to ~2.0x (25px)
270 for (auto&& makeFont : gContext->makeFonts) {
271 if (makeFont.second) {
272 auto& font = gContext->fonts.emplace_back();
273 for (int i = 0; i < Font::kScaledLevels; ++i) {
274 float size = 7.0f + i * 3.0f;
275 ImFontConfig cfg;
276 std::snprintf(cfg.Name, sizeof(cfg.Name), "%s-%d", makeFont.first,
277 static_cast<int>(size));
278 font.scaled[i] = makeFont.second(io, size, &cfg);
279 }
280 }
281 }
282
Austin Schuh812d0d12021-11-04 20:16:48 -0700283 if (!PlatformInitRenderer()) {
284 return false;
285 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800286
287 return true;
288}
289
290void gui::Main() {
291 // Main loop
292 while (!glfwWindowShouldClose(gContext->window) && !gContext->exit) {
293 // Poll and handle events (inputs, window resize, etc.)
294 glfwPollEvents();
Austin Schuh75263e32022-02-22 18:05:32 -0800295 gContext->isPlatformRendering = true;
Austin Schuh1e69f942020-11-14 15:06:14 -0800296 PlatformRenderFrame();
Austin Schuh75263e32022-02-22 18:05:32 -0800297 gContext->isPlatformRendering = false;
298
299 // custom saving
300 if (gContext->saveSettings) {
301 auto& io = ImGui::GetIO();
302 if (io.WantSaveIniSettings) {
303 gContext->saveSettings(false);
304 io.WantSaveIniSettings = false; // reset flag
305 }
306 }
307 }
308
309 // Save (if custom save)
310 if (gContext->saveSettings) {
311 gContext->saveSettings(true);
Austin Schuh1e69f942020-11-14 15:06:14 -0800312 }
313
314 // Cleanup
315 PlatformShutdown();
316 ImGui_ImplGlfw_Shutdown();
317 ImPlot::DestroyContext();
318 ImGui::DestroyContext();
319
Austin Schuh812d0d12021-11-04 20:16:48 -0700320 // Delete the save file if requested.
Austin Schuh75263e32022-02-22 18:05:32 -0800321 if (!gContext->saveSettings && gContext->resetOnExit) {
322 std::remove(gContext->iniPath.c_str());
Austin Schuh812d0d12021-11-04 20:16:48 -0700323 }
324
Austin Schuh1e69f942020-11-14 15:06:14 -0800325 glfwDestroyWindow(gContext->window);
326 glfwTerminate();
327}
328
329void gui::CommonRenderFrame() {
330 ImGui_ImplGlfw_NewFrame();
331
332 // Start the Dear ImGui frame
333 ImGui::NewFrame();
334
335 // Scale based on OS window content scaling
336 float windowScale = 1.0;
Austin Schuh812d0d12021-11-04 20:16:48 -0700337#ifndef __APPLE__
Austin Schuh1e69f942020-11-14 15:06:14 -0800338 glfwGetWindowContentScale(gContext->window, &windowScale, nullptr);
Austin Schuh812d0d12021-11-04 20:16:48 -0700339#endif
Austin Schuh1e69f942020-11-14 15:06:14 -0800340 // map to closest font size: 0 = 0.5x, 1 = 0.75x, 2 = 1.0x, 3 = 1.25x,
341 // 4 = 1.5x, 5 = 1.75x, 6 = 2x
342 gContext->fontScale = std::clamp(
343 gContext->userScale + static_cast<int>((windowScale - 1.0) * 4), 0,
344 Font::kScaledLevels - 1);
345 ImGui::GetIO().FontDefault = gContext->fonts[0].scaled[gContext->fontScale];
346
347 for (size_t i = 0; i < gContext->earlyExecutors.size(); ++i) {
348 auto& execute = gContext->earlyExecutors[i];
Austin Schuh812d0d12021-11-04 20:16:48 -0700349 if (execute) {
350 execute();
351 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800352 }
353
354 for (size_t i = 0; i < gContext->lateExecutors.size(); ++i) {
355 auto& execute = gContext->lateExecutors[i];
Austin Schuh812d0d12021-11-04 20:16:48 -0700356 if (execute) {
357 execute();
358 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800359 }
360
361 // Rendering
362 ImGui::Render();
363}
364
365void gui::Exit() {
Austin Schuh812d0d12021-11-04 20:16:48 -0700366 if (!gContext) {
367 return;
368 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800369 gContext->exit = true;
370}
371
372void gui::AddInit(std::function<void()> initialize) {
Austin Schuh812d0d12021-11-04 20:16:48 -0700373 if (initialize) {
374 gContext->initializers.emplace_back(std::move(initialize));
375 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800376}
377
378void gui::AddWindowScaler(std::function<void(float scale)> windowScaler) {
Austin Schuh812d0d12021-11-04 20:16:48 -0700379 if (windowScaler) {
Austin Schuh1e69f942020-11-14 15:06:14 -0800380 gContext->windowScalers.emplace_back(std::move(windowScaler));
Austin Schuh812d0d12021-11-04 20:16:48 -0700381 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800382}
383
384void gui::AddEarlyExecute(std::function<void()> execute) {
Austin Schuh812d0d12021-11-04 20:16:48 -0700385 if (execute) {
386 gContext->earlyExecutors.emplace_back(std::move(execute));
387 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800388}
389
390void gui::AddLateExecute(std::function<void()> execute) {
Austin Schuh812d0d12021-11-04 20:16:48 -0700391 if (execute) {
392 gContext->lateExecutors.emplace_back(std::move(execute));
393 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800394}
395
Austin Schuh75263e32022-02-22 18:05:32 -0800396void gui::ConfigureCustomSaveSettings(std::function<void()> load,
397 std::function<void()> loadIni,
398 std::function<void(bool)> save) {
399 gContext->loadSettings = load;
400 gContext->loadIniSettings = loadIni;
401 gContext->saveSettings = save;
402}
403
Austin Schuh812d0d12021-11-04 20:16:48 -0700404GLFWwindow* gui::GetSystemWindow() {
405 return gContext->window;
406}
407
408bool gui::AddIcon(const unsigned char* data, int len) {
409 // Load from memory
410 GLFWimage image;
411 image.pixels =
412 stbi_load_from_memory(data, len, &image.width, &image.height, nullptr, 4);
413 if (!data) {
414 return false;
415 }
416 gContext->icons.emplace_back(std::move(image));
417 return true;
418}
Austin Schuh1e69f942020-11-14 15:06:14 -0800419
420int gui::AddFont(
421 const char* name,
422 std::function<ImFont*(ImGuiIO& io, float size, const ImFontConfig* cfg)>
423 makeFont) {
Austin Schuh812d0d12021-11-04 20:16:48 -0700424 if (makeFont) {
425 gContext->makeFonts.emplace_back(name, std::move(makeFont));
426 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800427 return gContext->makeFonts.size() - 1;
428}
429
430ImFont* gui::GetFont(int font) {
431 return gContext->fonts[font].scaled[gContext->fontScale];
432}
433
434void gui::SetStyle(Style style) {
435 gContext->style = static_cast<int>(style);
436 switch (style) {
437 case kStyleClassic:
438 ImGui::StyleColorsClassic();
439 break;
440 case kStyleDark:
441 ImGui::StyleColorsDark();
442 break;
443 case kStyleLight:
444 ImGui::StyleColorsLight();
445 break;
446 }
447}
448
Austin Schuh812d0d12021-11-04 20:16:48 -0700449void gui::SetClearColor(ImVec4 color) {
450 gContext->clearColor = color;
451}
452
Austin Schuh75263e32022-02-22 18:05:32 -0800453std::string gui::GetPlatformSaveFileDir() {
Austin Schuh812d0d12021-11-04 20:16:48 -0700454#if defined(_MSC_VER)
455 const char* env = std::getenv("APPDATA");
456 if (env) {
Austin Schuh75263e32022-02-22 18:05:32 -0800457 return env + std::string("/");
Austin Schuh812d0d12021-11-04 20:16:48 -0700458 }
459#elif defined(__APPLE__)
460 const char* env = std::getenv("HOME");
461 if (env) {
Austin Schuh75263e32022-02-22 18:05:32 -0800462 return env + std::string("/Library/Preferences/");
Austin Schuh812d0d12021-11-04 20:16:48 -0700463 }
464#else
465 const char* xdg = std::getenv("XDG_CONFIG_HOME");
466 const char* env = std::getenv("HOME");
467 if (xdg) {
Austin Schuh75263e32022-02-22 18:05:32 -0800468 return xdg + std::string("/");
Austin Schuh812d0d12021-11-04 20:16:48 -0700469 } else if (env) {
Austin Schuh75263e32022-02-22 18:05:32 -0800470 return env + std::string("/.config/");
Austin Schuh812d0d12021-11-04 20:16:48 -0700471 }
472#endif
Austin Schuh75263e32022-02-22 18:05:32 -0800473 return "";
474}
475
476void gui::ConfigurePlatformSaveFile(const std::string& name) {
477 gContext->iniPath = GetPlatformSaveFileDir() + name;
Austin Schuh812d0d12021-11-04 20:16:48 -0700478}
Austin Schuh1e69f942020-11-14 15:06:14 -0800479
480void gui::EmitViewMenu() {
481 if (ImGui::BeginMenu("View")) {
482 if (ImGui::BeginMenu("Style")) {
483 bool selected;
484 selected = gContext->style == kStyleClassic;
Austin Schuh812d0d12021-11-04 20:16:48 -0700485 if (ImGui::MenuItem("Classic", nullptr, &selected, true)) {
Austin Schuh1e69f942020-11-14 15:06:14 -0800486 SetStyle(kStyleClassic);
Austin Schuh812d0d12021-11-04 20:16:48 -0700487 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800488 selected = gContext->style == kStyleDark;
Austin Schuh812d0d12021-11-04 20:16:48 -0700489 if (ImGui::MenuItem("Dark", nullptr, &selected, true)) {
Austin Schuh1e69f942020-11-14 15:06:14 -0800490 SetStyle(kStyleDark);
Austin Schuh812d0d12021-11-04 20:16:48 -0700491 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800492 selected = gContext->style == kStyleLight;
Austin Schuh812d0d12021-11-04 20:16:48 -0700493 if (ImGui::MenuItem("Light", nullptr, &selected, true)) {
Austin Schuh1e69f942020-11-14 15:06:14 -0800494 SetStyle(kStyleLight);
Austin Schuh812d0d12021-11-04 20:16:48 -0700495 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800496 ImGui::EndMenu();
497 }
498
499 if (ImGui::BeginMenu("Zoom")) {
500 for (int i = 0; i < Font::kScaledLevels && (25 * (i + 2)) <= 200; ++i) {
501 char label[20];
502 std::snprintf(label, sizeof(label), "%d%%", 25 * (i + 2));
503 bool selected = gContext->userScale == i;
504 bool enabled = (gContext->fontScale - gContext->userScale + i) >= 0 &&
505 (gContext->fontScale - gContext->userScale + i) <
506 Font::kScaledLevels;
Austin Schuh812d0d12021-11-04 20:16:48 -0700507 if (ImGui::MenuItem(label, nullptr, &selected, enabled)) {
Austin Schuh1e69f942020-11-14 15:06:14 -0800508 gContext->userScale = i;
Austin Schuh812d0d12021-11-04 20:16:48 -0700509 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800510 }
511 ImGui::EndMenu();
512 }
Austin Schuh812d0d12021-11-04 20:16:48 -0700513
Austin Schuh75263e32022-02-22 18:05:32 -0800514 if (!gContext->saveSettings) {
515 ImGui::MenuItem("Reset UI on Exit?", nullptr, &gContext->resetOnExit);
516 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800517 ImGui::EndMenu();
518 }
519}
520
521bool gui::UpdateTextureFromImage(ImTextureID* texture, int width, int height,
522 const unsigned char* data, int len) {
523 // Load from memory
524 int width2 = 0;
525 int height2 = 0;
526 unsigned char* imgData =
527 stbi_load_from_memory(data, len, &width2, &height2, nullptr, 4);
Austin Schuh812d0d12021-11-04 20:16:48 -0700528 if (!data) {
529 return false;
530 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800531
Austin Schuh812d0d12021-11-04 20:16:48 -0700532 if (width2 == width && height2 == height) {
Austin Schuh1e69f942020-11-14 15:06:14 -0800533 UpdateTexture(texture, kPixelRGBA, width2, height2, imgData);
Austin Schuh812d0d12021-11-04 20:16:48 -0700534 } else {
Austin Schuh1e69f942020-11-14 15:06:14 -0800535 *texture = CreateTexture(kPixelRGBA, width2, height2, imgData);
Austin Schuh812d0d12021-11-04 20:16:48 -0700536 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800537
538 stbi_image_free(imgData);
539
540 return true;
541}
542
543bool gui::CreateTextureFromFile(const char* filename, ImTextureID* out_texture,
544 int* out_width, int* out_height) {
545 // Load from file
546 int width = 0;
547 int height = 0;
548 unsigned char* data = stbi_load(filename, &width, &height, nullptr, 4);
Austin Schuh812d0d12021-11-04 20:16:48 -0700549 if (!data) {
550 return false;
551 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800552
553 *out_texture = CreateTexture(kPixelRGBA, width, height, data);
Austin Schuh812d0d12021-11-04 20:16:48 -0700554 if (out_width) {
555 *out_width = width;
556 }
557 if (out_height) {
558 *out_height = height;
559 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800560
561 stbi_image_free(data);
562
563 return true;
564}
565
566bool gui::CreateTextureFromImage(const unsigned char* data, int len,
567 ImTextureID* out_texture, int* out_width,
568 int* out_height) {
569 // Load from memory
570 int width = 0;
571 int height = 0;
572 unsigned char* imgData =
573 stbi_load_from_memory(data, len, &width, &height, nullptr, 4);
Austin Schuh812d0d12021-11-04 20:16:48 -0700574 if (!imgData) {
575 return false;
576 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800577
578 *out_texture = CreateTexture(kPixelRGBA, width, height, imgData);
Austin Schuh812d0d12021-11-04 20:16:48 -0700579 if (out_width) {
580 *out_width = width;
581 }
582 if (out_height) {
583 *out_height = height;
584 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800585
586 stbi_image_free(imgData);
587
588 return true;
589}
590
591void gui::MaxFit(ImVec2* min, ImVec2* max, float width, float height) {
592 float destWidth = max->x - min->x;
593 float destHeight = max->y - min->y;
Austin Schuh812d0d12021-11-04 20:16:48 -0700594 if (width == 0 || height == 0) {
595 return;
596 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800597 if (destWidth * height > destHeight * width) {
598 float outputWidth = width * destHeight / height;
599 min->x += (destWidth - outputWidth) / 2;
600 max->x -= (destWidth - outputWidth) / 2;
601 } else {
602 float outputHeight = height * destWidth / width;
603 min->y += (destHeight - outputHeight) / 2;
604 max->y -= (destHeight - outputHeight) / 2;
605 }
606}
607
608} // namespace wpi