blob: 4b200b5af95b268d8091984e7f1c477cc82158e7 [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>
Austin Schuh812d0d12021-11-04 20:16:48 -070018#include <wpi/fs.h>
Austin Schuh1e69f942020-11-14 15:06:14 -080019
20#include "wpigui_internal.h"
21
22using namespace wpi::gui;
23
24namespace wpi {
25
26Context* gui::gContext;
27
28static void ErrorCallback(int error, const char* description) {
29 std::fprintf(stderr, "GLFW Error %d: %s\n", error, description);
30}
31
32static void WindowSizeCallback(GLFWwindow* window, int width, int height) {
33 if (!gContext->maximized) {
34 gContext->width = width;
35 gContext->height = height;
36 }
37 PlatformRenderFrame();
38}
39
40static void FramebufferSizeCallback(GLFWwindow* window, int width, int height) {
41 PlatformFramebufferSizeChanged(width, height);
42}
43
44static void WindowMaximizeCallback(GLFWwindow* window, int maximized) {
45 gContext->maximized = maximized;
46}
47
48static void WindowPosCallback(GLFWwindow* window, int xpos, int ypos) {
49 if (!gContext->maximized) {
50 gContext->xPos = xpos;
51 gContext->yPos = ypos;
52 }
53}
54
55static void* IniReadOpen(ImGuiContext* ctx, ImGuiSettingsHandler* handler,
56 const char* name) {
Austin Schuh812d0d12021-11-04 20:16:48 -070057 if (std::strcmp(name, "GLOBAL") != 0) {
58 return nullptr;
59 }
Austin Schuh1e69f942020-11-14 15:06:14 -080060 return static_cast<SavedSettings*>(gContext);
61}
62
63static void IniReadLine(ImGuiContext* ctx, ImGuiSettingsHandler* handler,
64 void* entry, const char* lineStr) {
65 auto impl = static_cast<SavedSettings*>(entry);
66 const char* value = std::strchr(lineStr, '=');
Austin Schuh812d0d12021-11-04 20:16:48 -070067 if (!value) {
68 return;
69 }
Austin Schuh1e69f942020-11-14 15:06:14 -080070 ++value;
71 int num = std::atoi(value);
72 if (std::strncmp(lineStr, "width=", 6) == 0) {
73 impl->width = num;
74 impl->loadedWidthHeight = true;
75 } else if (std::strncmp(lineStr, "height=", 7) == 0) {
76 impl->height = num;
77 impl->loadedWidthHeight = true;
78 } else if (std::strncmp(lineStr, "maximized=", 10) == 0) {
79 impl->maximized = num;
80 } else if (std::strncmp(lineStr, "xpos=", 5) == 0) {
81 impl->xPos = num;
82 } else if (std::strncmp(lineStr, "ypos=", 5) == 0) {
83 impl->yPos = num;
84 } else if (std::strncmp(lineStr, "userScale=", 10) == 0) {
85 impl->userScale = num;
86 } else if (std::strncmp(lineStr, "style=", 6) == 0) {
87 impl->style = num;
88 }
89}
90
91static void IniWriteAll(ImGuiContext* ctx, ImGuiSettingsHandler* handler,
92 ImGuiTextBuffer* out_buf) {
Austin Schuh812d0d12021-11-04 20:16:48 -070093 if (!gContext) {
94 return;
95 }
Austin Schuh1e69f942020-11-14 15:06:14 -080096 out_buf->appendf(
97 "[MainWindow][GLOBAL]\nwidth=%d\nheight=%d\nmaximized=%d\n"
98 "xpos=%d\nypos=%d\nuserScale=%d\nstyle=%d\n\n",
99 gContext->width, gContext->height, gContext->maximized, gContext->xPos,
100 gContext->yPos, gContext->userScale, gContext->style);
101}
102
103void gui::CreateContext() {
104 gContext = new Context;
105 AddFont("ProggyDotted", [](ImGuiIO& io, float size, const ImFontConfig* cfg) {
106 return ImGui::AddFontProggyDotted(io, size, cfg);
107 });
108 PlatformCreateContext();
109}
110
111void gui::DestroyContext() {
112 PlatformDestroyContext();
113 delete gContext;
114 gContext = nullptr;
115}
116
117bool gui::Initialize(const char* title, int width, int height) {
118 gContext->title = title;
119 gContext->width = width;
120 gContext->height = height;
121 gContext->defaultWidth = width;
122 gContext->defaultHeight = height;
123
124 // Setup window
125 glfwSetErrorCallback(ErrorCallback);
126 glfwInitHint(GLFW_JOYSTICK_HAT_BUTTONS, GLFW_FALSE);
127 PlatformGlfwInitHints();
Austin Schuh812d0d12021-11-04 20:16:48 -0700128 if (!glfwInit()) {
129 return false;
130 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800131
132 PlatformGlfwWindowHints();
133
134 // Setup Dear ImGui context
135 IMGUI_CHECKVERSION();
136 ImGui::CreateContext();
137 ImPlot::CreateContext();
138 ImGuiIO& io = ImGui::GetIO();
139
140 // Hook ini handler to save settings
141 ImGuiSettingsHandler iniHandler;
142 iniHandler.TypeName = "MainWindow";
143 iniHandler.TypeHash = ImHashStr(iniHandler.TypeName);
144 iniHandler.ReadOpenFn = IniReadOpen;
145 iniHandler.ReadLineFn = IniReadLine;
146 iniHandler.WriteAllFn = IniWriteAll;
147 ImGui::GetCurrentContext()->SettingsHandlers.push_back(iniHandler);
148
Austin Schuh812d0d12021-11-04 20:16:48 -0700149 io.IniFilename = gContext->iniPath.c_str();
150
Austin Schuh1e69f942020-11-14 15:06:14 -0800151 for (auto&& initialize : gContext->initializers) {
Austin Schuh812d0d12021-11-04 20:16:48 -0700152 if (initialize) {
153 initialize();
154 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800155 }
156
157 // Load INI file
158 ImGui::LoadIniSettingsFromDisk(io.IniFilename);
159
160 // Set initial window settings
161 glfwWindowHint(GLFW_MAXIMIZED, gContext->maximized ? GLFW_TRUE : GLFW_FALSE);
162
163 if (gContext->width == 0 || gContext->height == 0) {
164 gContext->width = gContext->defaultWidth;
165 gContext->height = gContext->defaultHeight;
166 gContext->loadedWidthHeight = false;
167 }
168
169 float windowScale = 1.0;
170 if (!gContext->loadedWidthHeight) {
171 glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE);
172 // get the primary monitor work area to see if we have a reasonable initial
173 // window size; if not, maximize, and default scaling to smaller
174 if (GLFWmonitor* primary = glfwGetPrimaryMonitor()) {
175 int monWidth, monHeight;
176 glfwGetMonitorWorkarea(primary, nullptr, nullptr, &monWidth, &monHeight);
177 if (monWidth < gContext->width || monHeight < gContext->height) {
178 glfwWindowHint(GLFW_MAXIMIZED, GLFW_TRUE);
179 windowScale = (std::min)(monWidth * 1.0 / gContext->width,
180 monHeight * 1.0 / gContext->height);
181 }
182 }
183 }
Austin Schuh812d0d12021-11-04 20:16:48 -0700184 if (gContext->xPos != -1 && gContext->yPos != -1) {
Austin Schuh1e69f942020-11-14 15:06:14 -0800185 glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
Austin Schuh812d0d12021-11-04 20:16:48 -0700186 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800187
188 // Create window with graphics context
189 gContext->window =
190 glfwCreateWindow(gContext->width, gContext->height,
191 gContext->title.c_str(), nullptr, nullptr);
Austin Schuh812d0d12021-11-04 20:16:48 -0700192 if (!gContext->window) {
193 return false;
194 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800195
196 if (!gContext->loadedWidthHeight) {
Austin Schuh812d0d12021-11-04 20:16:48 -0700197#ifndef __APPLE__
198 if (windowScale == 1.0) {
Austin Schuh1e69f942020-11-14 15:06:14 -0800199 glfwGetWindowContentScale(gContext->window, &windowScale, nullptr);
Austin Schuh812d0d12021-11-04 20:16:48 -0700200 }
201#endif
Austin Schuh1e69f942020-11-14 15:06:14 -0800202 // force user scale if window scale is smaller
Austin Schuh812d0d12021-11-04 20:16:48 -0700203 if (windowScale <= 0.5) {
Austin Schuh1e69f942020-11-14 15:06:14 -0800204 gContext->userScale = 0;
Austin Schuh812d0d12021-11-04 20:16:48 -0700205 } else if (windowScale <= 0.75) {
Austin Schuh1e69f942020-11-14 15:06:14 -0800206 gContext->userScale = 1;
Austin Schuh812d0d12021-11-04 20:16:48 -0700207 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800208 if (windowScale != 1.0) {
Austin Schuh812d0d12021-11-04 20:16:48 -0700209 for (auto&& func : gContext->windowScalers) {
210 func(windowScale);
211 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800212 }
213 }
214
215 // Update window settings
216 if (gContext->xPos != -1 && gContext->yPos != -1) {
Austin Schuh812d0d12021-11-04 20:16:48 -0700217 // check to make sure the position isn't off-screen
218 bool found = false;
219 int monCount;
220 GLFWmonitor** monitors = glfwGetMonitors(&monCount);
221 for (int i = 0; i < monCount; ++i) {
222 int monXPos, monYPos, monWidth, monHeight;
223 glfwGetMonitorWorkarea(monitors[i], &monXPos, &monYPos, &monWidth,
224 &monHeight);
225 if (gContext->xPos >= monXPos && gContext->xPos < (monXPos + monWidth) &&
226 gContext->yPos >= monYPos && gContext->yPos < (monYPos + monHeight)) {
227 found = true;
228 break;
229 }
230 }
231 if (found) {
232 glfwSetWindowPos(gContext->window, gContext->xPos, gContext->yPos);
233 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800234 glfwShowWindow(gContext->window);
235 }
236
237 // Set window callbacks
238 glfwGetWindowSize(gContext->window, &gContext->width, &gContext->height);
239 glfwSetWindowSizeCallback(gContext->window, WindowSizeCallback);
240 glfwSetFramebufferSizeCallback(gContext->window, FramebufferSizeCallback);
241 glfwSetWindowMaximizeCallback(gContext->window, WindowMaximizeCallback);
242 glfwSetWindowPosCallback(gContext->window, WindowPosCallback);
243
Austin Schuh812d0d12021-11-04 20:16:48 -0700244 // Set icons
245 if (!gContext->icons.empty()) {
246 glfwSetWindowIcon(gContext->window, gContext->icons.size(),
247 gContext->icons.data());
248 for (auto&& icon : gContext->icons) {
249 stbi_image_free(icon.pixels);
250 }
251 gContext->icons.clear();
252 }
253
Austin Schuh1e69f942020-11-14 15:06:14 -0800254 // Setup Dear ImGui style
255 SetStyle(static_cast<Style>(gContext->style));
256
257 // Load Fonts
258 // this range is based on 13px being the "nominal" 100% size and going from
259 // ~0.5x (7px) to ~2.0x (25px)
260 for (auto&& makeFont : gContext->makeFonts) {
261 if (makeFont.second) {
262 auto& font = gContext->fonts.emplace_back();
263 for (int i = 0; i < Font::kScaledLevels; ++i) {
264 float size = 7.0f + i * 3.0f;
265 ImFontConfig cfg;
266 std::snprintf(cfg.Name, sizeof(cfg.Name), "%s-%d", makeFont.first,
267 static_cast<int>(size));
268 font.scaled[i] = makeFont.second(io, size, &cfg);
269 }
270 }
271 }
272
Austin Schuh812d0d12021-11-04 20:16:48 -0700273 if (!PlatformInitRenderer()) {
274 return false;
275 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800276
277 return true;
278}
279
280void gui::Main() {
281 // Main loop
282 while (!glfwWindowShouldClose(gContext->window) && !gContext->exit) {
283 // Poll and handle events (inputs, window resize, etc.)
284 glfwPollEvents();
285 PlatformRenderFrame();
286 }
287
288 // Cleanup
289 PlatformShutdown();
290 ImGui_ImplGlfw_Shutdown();
291 ImPlot::DestroyContext();
292 ImGui::DestroyContext();
293
Austin Schuh812d0d12021-11-04 20:16:48 -0700294 // Delete the save file if requested.
295 if (gContext->resetOnExit) {
296 fs::remove(fs::path{gContext->iniPath});
297 }
298
Austin Schuh1e69f942020-11-14 15:06:14 -0800299 glfwDestroyWindow(gContext->window);
300 glfwTerminate();
301}
302
303void gui::CommonRenderFrame() {
304 ImGui_ImplGlfw_NewFrame();
305
306 // Start the Dear ImGui frame
307 ImGui::NewFrame();
308
309 // Scale based on OS window content scaling
310 float windowScale = 1.0;
Austin Schuh812d0d12021-11-04 20:16:48 -0700311#ifndef __APPLE__
Austin Schuh1e69f942020-11-14 15:06:14 -0800312 glfwGetWindowContentScale(gContext->window, &windowScale, nullptr);
Austin Schuh812d0d12021-11-04 20:16:48 -0700313#endif
Austin Schuh1e69f942020-11-14 15:06:14 -0800314 // map to closest font size: 0 = 0.5x, 1 = 0.75x, 2 = 1.0x, 3 = 1.25x,
315 // 4 = 1.5x, 5 = 1.75x, 6 = 2x
316 gContext->fontScale = std::clamp(
317 gContext->userScale + static_cast<int>((windowScale - 1.0) * 4), 0,
318 Font::kScaledLevels - 1);
319 ImGui::GetIO().FontDefault = gContext->fonts[0].scaled[gContext->fontScale];
320
321 for (size_t i = 0; i < gContext->earlyExecutors.size(); ++i) {
322 auto& execute = gContext->earlyExecutors[i];
Austin Schuh812d0d12021-11-04 20:16:48 -0700323 if (execute) {
324 execute();
325 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800326 }
327
328 for (size_t i = 0; i < gContext->lateExecutors.size(); ++i) {
329 auto& execute = gContext->lateExecutors[i];
Austin Schuh812d0d12021-11-04 20:16:48 -0700330 if (execute) {
331 execute();
332 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800333 }
334
335 // Rendering
336 ImGui::Render();
337}
338
339void gui::Exit() {
Austin Schuh812d0d12021-11-04 20:16:48 -0700340 if (!gContext) {
341 return;
342 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800343 gContext->exit = true;
344}
345
346void gui::AddInit(std::function<void()> initialize) {
Austin Schuh812d0d12021-11-04 20:16:48 -0700347 if (initialize) {
348 gContext->initializers.emplace_back(std::move(initialize));
349 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800350}
351
352void gui::AddWindowScaler(std::function<void(float scale)> windowScaler) {
Austin Schuh812d0d12021-11-04 20:16:48 -0700353 if (windowScaler) {
Austin Schuh1e69f942020-11-14 15:06:14 -0800354 gContext->windowScalers.emplace_back(std::move(windowScaler));
Austin Schuh812d0d12021-11-04 20:16:48 -0700355 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800356}
357
358void gui::AddEarlyExecute(std::function<void()> execute) {
Austin Schuh812d0d12021-11-04 20:16:48 -0700359 if (execute) {
360 gContext->earlyExecutors.emplace_back(std::move(execute));
361 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800362}
363
364void gui::AddLateExecute(std::function<void()> execute) {
Austin Schuh812d0d12021-11-04 20:16:48 -0700365 if (execute) {
366 gContext->lateExecutors.emplace_back(std::move(execute));
367 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800368}
369
Austin Schuh812d0d12021-11-04 20:16:48 -0700370GLFWwindow* gui::GetSystemWindow() {
371 return gContext->window;
372}
373
374bool gui::AddIcon(const unsigned char* data, int len) {
375 // Load from memory
376 GLFWimage image;
377 image.pixels =
378 stbi_load_from_memory(data, len, &image.width, &image.height, nullptr, 4);
379 if (!data) {
380 return false;
381 }
382 gContext->icons.emplace_back(std::move(image));
383 return true;
384}
Austin Schuh1e69f942020-11-14 15:06:14 -0800385
386int gui::AddFont(
387 const char* name,
388 std::function<ImFont*(ImGuiIO& io, float size, const ImFontConfig* cfg)>
389 makeFont) {
Austin Schuh812d0d12021-11-04 20:16:48 -0700390 if (makeFont) {
391 gContext->makeFonts.emplace_back(name, std::move(makeFont));
392 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800393 return gContext->makeFonts.size() - 1;
394}
395
396ImFont* gui::GetFont(int font) {
397 return gContext->fonts[font].scaled[gContext->fontScale];
398}
399
400void gui::SetStyle(Style style) {
401 gContext->style = static_cast<int>(style);
402 switch (style) {
403 case kStyleClassic:
404 ImGui::StyleColorsClassic();
405 break;
406 case kStyleDark:
407 ImGui::StyleColorsDark();
408 break;
409 case kStyleLight:
410 ImGui::StyleColorsLight();
411 break;
412 }
413}
414
Austin Schuh812d0d12021-11-04 20:16:48 -0700415void gui::SetClearColor(ImVec4 color) {
416 gContext->clearColor = color;
417}
418
419void gui::ConfigurePlatformSaveFile(const std::string& name) {
420 gContext->iniPath = name;
421#if defined(_MSC_VER)
422 const char* env = std::getenv("APPDATA");
423 if (env) {
424 gContext->iniPath = env + std::string("/" + name);
425 }
426#elif defined(__APPLE__)
427 const char* env = std::getenv("HOME");
428 if (env) {
429 gContext->iniPath = env + std::string("/Library/Preferences/" + name);
430 }
431#else
432 const char* xdg = std::getenv("XDG_CONFIG_HOME");
433 const char* env = std::getenv("HOME");
434 if (xdg) {
435 gContext->iniPath = xdg + std::string("/" + name);
436 } else if (env) {
437 gContext->iniPath = env + std::string("/.config/" + name);
438 }
439#endif
440}
Austin Schuh1e69f942020-11-14 15:06:14 -0800441
442void gui::EmitViewMenu() {
443 if (ImGui::BeginMenu("View")) {
444 if (ImGui::BeginMenu("Style")) {
445 bool selected;
446 selected = gContext->style == kStyleClassic;
Austin Schuh812d0d12021-11-04 20:16:48 -0700447 if (ImGui::MenuItem("Classic", nullptr, &selected, true)) {
Austin Schuh1e69f942020-11-14 15:06:14 -0800448 SetStyle(kStyleClassic);
Austin Schuh812d0d12021-11-04 20:16:48 -0700449 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800450 selected = gContext->style == kStyleDark;
Austin Schuh812d0d12021-11-04 20:16:48 -0700451 if (ImGui::MenuItem("Dark", nullptr, &selected, true)) {
Austin Schuh1e69f942020-11-14 15:06:14 -0800452 SetStyle(kStyleDark);
Austin Schuh812d0d12021-11-04 20:16:48 -0700453 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800454 selected = gContext->style == kStyleLight;
Austin Schuh812d0d12021-11-04 20:16:48 -0700455 if (ImGui::MenuItem("Light", nullptr, &selected, true)) {
Austin Schuh1e69f942020-11-14 15:06:14 -0800456 SetStyle(kStyleLight);
Austin Schuh812d0d12021-11-04 20:16:48 -0700457 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800458 ImGui::EndMenu();
459 }
460
461 if (ImGui::BeginMenu("Zoom")) {
462 for (int i = 0; i < Font::kScaledLevels && (25 * (i + 2)) <= 200; ++i) {
463 char label[20];
464 std::snprintf(label, sizeof(label), "%d%%", 25 * (i + 2));
465 bool selected = gContext->userScale == i;
466 bool enabled = (gContext->fontScale - gContext->userScale + i) >= 0 &&
467 (gContext->fontScale - gContext->userScale + i) <
468 Font::kScaledLevels;
Austin Schuh812d0d12021-11-04 20:16:48 -0700469 if (ImGui::MenuItem(label, nullptr, &selected, enabled)) {
Austin Schuh1e69f942020-11-14 15:06:14 -0800470 gContext->userScale = i;
Austin Schuh812d0d12021-11-04 20:16:48 -0700471 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800472 }
473 ImGui::EndMenu();
474 }
Austin Schuh812d0d12021-11-04 20:16:48 -0700475
476 ImGui::MenuItem("Reset UI on Exit?", nullptr, &gContext->resetOnExit);
Austin Schuh1e69f942020-11-14 15:06:14 -0800477 ImGui::EndMenu();
478 }
479}
480
481bool gui::UpdateTextureFromImage(ImTextureID* texture, int width, int height,
482 const unsigned char* data, int len) {
483 // Load from memory
484 int width2 = 0;
485 int height2 = 0;
486 unsigned char* imgData =
487 stbi_load_from_memory(data, len, &width2, &height2, nullptr, 4);
Austin Schuh812d0d12021-11-04 20:16:48 -0700488 if (!data) {
489 return false;
490 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800491
Austin Schuh812d0d12021-11-04 20:16:48 -0700492 if (width2 == width && height2 == height) {
Austin Schuh1e69f942020-11-14 15:06:14 -0800493 UpdateTexture(texture, kPixelRGBA, width2, height2, imgData);
Austin Schuh812d0d12021-11-04 20:16:48 -0700494 } else {
Austin Schuh1e69f942020-11-14 15:06:14 -0800495 *texture = CreateTexture(kPixelRGBA, width2, height2, imgData);
Austin Schuh812d0d12021-11-04 20:16:48 -0700496 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800497
498 stbi_image_free(imgData);
499
500 return true;
501}
502
503bool gui::CreateTextureFromFile(const char* filename, ImTextureID* out_texture,
504 int* out_width, int* out_height) {
505 // Load from file
506 int width = 0;
507 int height = 0;
508 unsigned char* data = stbi_load(filename, &width, &height, nullptr, 4);
Austin Schuh812d0d12021-11-04 20:16:48 -0700509 if (!data) {
510 return false;
511 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800512
513 *out_texture = CreateTexture(kPixelRGBA, width, height, data);
Austin Schuh812d0d12021-11-04 20:16:48 -0700514 if (out_width) {
515 *out_width = width;
516 }
517 if (out_height) {
518 *out_height = height;
519 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800520
521 stbi_image_free(data);
522
523 return true;
524}
525
526bool gui::CreateTextureFromImage(const unsigned char* data, int len,
527 ImTextureID* out_texture, int* out_width,
528 int* out_height) {
529 // Load from memory
530 int width = 0;
531 int height = 0;
532 unsigned char* imgData =
533 stbi_load_from_memory(data, len, &width, &height, nullptr, 4);
Austin Schuh812d0d12021-11-04 20:16:48 -0700534 if (!imgData) {
535 return false;
536 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800537
538 *out_texture = CreateTexture(kPixelRGBA, width, height, imgData);
Austin Schuh812d0d12021-11-04 20:16:48 -0700539 if (out_width) {
540 *out_width = width;
541 }
542 if (out_height) {
543 *out_height = height;
544 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800545
546 stbi_image_free(imgData);
547
548 return true;
549}
550
551void gui::MaxFit(ImVec2* min, ImVec2* max, float width, float height) {
552 float destWidth = max->x - min->x;
553 float destHeight = max->y - min->y;
Austin Schuh812d0d12021-11-04 20:16:48 -0700554 if (width == 0 || height == 0) {
555 return;
556 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800557 if (destWidth * height > destHeight * width) {
558 float outputWidth = width * destHeight / height;
559 min->x += (destWidth - outputWidth) / 2;
560 max->x -= (destWidth - outputWidth) / 2;
561 } else {
562 float outputHeight = height * destWidth / width;
563 min->y += (destHeight - outputHeight) / 2;
564 max->y -= (destHeight - outputHeight) / 2;
565 }
566}
567
568} // namespace wpi