blob: d4602b59517724e7c56b2297f4bd22616fde0aa1 [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#pragma once
6
7#include <functional>
Austin Schuh812d0d12021-11-04 20:16:48 -07008#include <string>
9#include <string_view>
Austin Schuh1e69f942020-11-14 15:06:14 -080010
11#include <imgui.h>
12
13extern "C" struct GLFWwindow;
14
15namespace wpi::gui {
16
17/**
18 * Creates GUI context. Must be called prior to calling any other functions.
19 */
20void CreateContext();
21
22/**
23 * Destroys GUI context.
24 */
25void DestroyContext();
26
27/**
28 * Initializes the GUI.
29 *
30 * @param title main application window title
31 * @param width main application window width
32 * @param height main application window height
33 */
34bool Initialize(const char* title, int width, int height);
35
36/**
37 * Runs main GUI loop. On some OS'es this must be called from the main thread.
38 * Does not return until Exit() is called.
39 */
40void Main();
41
42/**
43 * Exits main GUI loop when current loop iteration finishes.
44 * Safe to call from any thread, including from within main GUI loop.
45 */
46void Exit();
47
48/**
49 * Adds initializer to GUI. The passed function is called once, immediately
50 * after the GUI (both GLFW and Dear ImGui) are initialized in Initialize().
51 * To have any effect, must be called prior to Initialize().
52 *
53 * @param initialize initialization function
54 */
55void AddInit(std::function<void()> initialize);
56
57/**
58 * Adds window scaler function. The passed function is called once during
59 * Initialize() if the window scale is not 1.0. To have any effect, must
60 * be called prior to Initialize().
61 *
62 * @param windowScaler window scaler function
63 */
64void AddWindowScaler(std::function<void(float scale)> windowScaler);
65
66/**
67 * Adds per-frame executor to GUI. The passed function is called on each
68 * Dear ImGui frame prior to any of the late execute functions.
69 *
70 * @param execute frame execution function
71 */
72void AddEarlyExecute(std::function<void()> execute);
73
74/**
75 * Adds per-frame executor to GUI. The passed function is called on each
76 * Dear ImGui frame after all of the early execute functions.
77 *
78 * @param execute frame execution function
79 */
80void AddLateExecute(std::function<void()> execute);
81
82/**
Austin Schuh75263e32022-02-22 18:05:32 -080083 * Customizes save/load behavior.
84 *
85 * By default, the integrated ImGui functions are used for this;
86 * ImGui::LoadIniSettingsFromDisk(io.IniFilename) is called at startup, and
87 * ImGui default automatic save file handling is used via io.IniFilename.
88 *
89 * Calling this function results in the load function being called at startup,
90 * io.IniFilename set to null (which disables ImGui's integrated file saving),
91 * and the save function being called when io.WantSaveIniSettings is true.
92 * The loadIni function should call ImGui::LoadIniSettingsFromMemory() to load
93 * ImGui save data, and the save function should call
94 * ImGui::SaveIniSettingsToMemory() to get ImGui save data.
95 *
96 * The load function is called PRIOR to AddInit() functions, and the loadIni
97 * function is called AFTER to AddInit() functions. This allows initialize
98 * functions that use custom storage to handle the loaded values, and initialize
99 * functions that use INI storage to add hooks prior to the load INI occurring.
100 *
101 * This must be called prior to Initialize().
102 *
103 * @param load load function
104 * @param loadIni load INI function
105 * @param save save function; false is passed periodically, true is passed once
106 * when the main loop is exiting
107 */
108void ConfigureCustomSaveSettings(std::function<void()> load,
109 std::function<void()> loadIni,
110 std::function<void(bool exiting)> save);
111
112/**
Austin Schuh1e69f942020-11-14 15:06:14 -0800113 * Gets GLFW window handle.
114 */
115GLFWwindow* GetSystemWindow();
116
117/**
Austin Schuh812d0d12021-11-04 20:16:48 -0700118 * Adds an application icon. Multiple icons (of different sizes) may be
119 * set. This must be called prior to initialization to have an effect.
120 *
121 * @param data image data
122 * @param len image data length
123 * @return False if image data could not be read
124 */
125bool AddIcon(const unsigned char* data, int len);
126
127inline bool AddIcon(std::string_view data) {
128 return AddIcon(reinterpret_cast<const unsigned char*>(data.data()),
129 data.size());
130}
131
132/**
Austin Schuh1e69f942020-11-14 15:06:14 -0800133 * Adds a font to the GUI. The passed function is called during
134 * initialization as many times as necessary to create a range of sizes.
135 *
136 * @param name font name
137 * @param makeFont font creation / loader function
138 * @return Font index for later use with GetFont()
139 */
140int AddFont(
141 const char* name,
142 std::function<ImFont*(ImGuiIO& io, float size, const ImFontConfig* cfg)>
143 makeFont);
144
145/**
146 * Gets a font added with AddFont() with the appropriate font size for
147 * the current scaling of the GUI.
148 *
149 * @param font font index returned by AddFont()
150 * @return Font pointer
151 */
152ImFont* GetFont(int font);
153
154enum Style { kStyleClassic = 0, kStyleDark, kStyleLight };
155
156/**
157 * Sets the ImGui style. Using this function makes this setting persistent.
158 *
159 * @param style Style
160 */
161void SetStyle(Style style);
162
163/**
164 * Sets the clear (background) color.
165 *
166 * @param color Color
167 */
168void SetClearColor(ImVec4 color);
169
170/**
Austin Schuh75263e32022-02-22 18:05:32 -0800171 * Gets the (platform-specific) absolute directory for save files.
172 *
173 * @return Absolute path, including trailing "/". Empty string if directory
174 * could not be determined.
175 */
176std::string GetPlatformSaveFileDir();
177
178/**
Austin Schuh812d0d12021-11-04 20:16:48 -0700179 * Configures a save file (.ini) in a platform specific location. On Windows,
180 * the .ini is saved in %APPDATA%; on macOS the .ini is saved in
181 * ~/Library/Preferences; on Linux the .ini is stored in $XDG_CONFIG_HOME or
182 * ~/.config if the former is not defined. This must be called before
183 * gui::Initialize().
184 */
185void ConfigurePlatformSaveFile(const std::string& name);
186
187/**
Austin Schuh1e69f942020-11-14 15:06:14 -0800188 * Emits a View menu (e.g. for a main menu bar) that allows setting of
189 * style and zoom. Internally starts with ImGui::BeginMenu("View").
190 */
191void EmitViewMenu();
192
193/**
194 * Pixel formats for texture pixel data.
195 */
196enum PixelFormat { kPixelRGBA, kPixelBGRA };
197
198/**
199 * Creates a texture from pixel data.
200 *
201 * @param format pixel format
202 * @param width image width
203 * @param height image height
204 * @param data pixel data
205 * @return Texture
206 */
207ImTextureID CreateTexture(PixelFormat format, int width, int height,
208 const unsigned char* data);
209
210/**
211 * Updates a texture from pixel data.
212 * The passed-in width and height must match the width and height of the
213 * texture.
214 *
215 * @param texture texture
216 * @param format pixel format
217 * @param width texture width
218 * @param height texture height
219 * @param data pixel data
220 */
221void UpdateTexture(ImTextureID texture, PixelFormat format, int width,
222 int height, const unsigned char* data);
223
224/**
225 * Updates a texture from image data.
226 * The pixel format of the texture must be RGBA. The passed-in width and
227 * height must match the width and height of the texture. If the width and
228 * height of the image differ from the passed-in width and height, a new
229 * texture is created (note this may be inefficient).
230 *
231 * @param texture texture (pointer, may be updated)
232 * @param width texture width
233 * @param height texture height
234 * @param data image data
235 * @param len image data length
236 *
237 * @return True on success, false on failure.
238 */
239bool UpdateTextureFromImage(ImTextureID* texture, int width, int height,
240 const unsigned char* data, int len);
241
242/**
243 * Creates a texture from an image file.
244 *
245 * @param filename filename
246 * @param out_texture texture (output)
247 * @param out_width image width (output)
248 * @param out_height image height (output)
249 * @return True on success, false on failure.
250 */
251bool CreateTextureFromFile(const char* filename, ImTextureID* out_texture,
252 int* out_width, int* out_height);
253
254/**
255 * Creates a texture from image data.
256 *
257 * @param data image data
258 * @param len image data length
259 * @param out_texture texture (output)
260 * @param out_width image width (output)
261 * @param out_height image height (output)
262 * @return True on success, false on failure.
263 */
264bool CreateTextureFromImage(const unsigned char* data, int len,
265 ImTextureID* out_texture, int* out_width,
266 int* out_height);
267
268/**
269 * Deletes a texture.
270 *
271 * @param texture texture
272 */
273void DeleteTexture(ImTextureID texture);
274
275/**
276 * RAII wrapper around ImTextureID. Also keeps track of width, height, and
277 * pixel format.
278 */
279class Texture {
280 public:
281 Texture() = default;
282
283 /**
284 * Constructs a texture from pixel data.
285 *
286 * @param format pixel format
287 * @param width image width
288 * @param height image height
289 * @param data pixel data
290 */
291 Texture(PixelFormat format, int width, int height, const unsigned char* data)
292 : m_format{format}, m_width{width}, m_height{height} {
293 m_texture = CreateTexture(format, width, height, data);
294 }
295
296 Texture(const Texture&) = delete;
297 Texture(Texture&& oth)
298 : m_texture{oth.m_texture},
299 m_format{oth.m_format},
300 m_width{oth.m_width},
301 m_height{oth.m_height} {
Austin Schuh812d0d12021-11-04 20:16:48 -0700302 oth.m_texture = 0; // NOLINT
Austin Schuh1e69f942020-11-14 15:06:14 -0800303 }
304
305 Texture& operator=(const Texture&) = delete;
306 Texture& operator=(Texture&& oth) {
Austin Schuh812d0d12021-11-04 20:16:48 -0700307 if (m_texture) {
308 DeleteTexture(m_texture);
309 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800310 m_texture = oth.m_texture;
Austin Schuh812d0d12021-11-04 20:16:48 -0700311 oth.m_texture = 0; // NOLINT
Austin Schuh1e69f942020-11-14 15:06:14 -0800312 m_format = oth.m_format;
313 m_width = oth.m_width;
314 m_height = oth.m_height;
315 return *this;
316 }
317
318 ~Texture() {
Austin Schuh812d0d12021-11-04 20:16:48 -0700319 if (m_texture) {
320 DeleteTexture(m_texture);
321 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800322 }
323
324 /**
325 * Evaluates to true if the texture is valid.
326 */
327 explicit operator bool() const { return m_texture; }
328
329 /**
330 * Implicit conversion to ImTextureID.
331 */
Austin Schuh812d0d12021-11-04 20:16:48 -0700332 operator ImTextureID() const { return m_texture; } // NOLINT
Austin Schuh1e69f942020-11-14 15:06:14 -0800333
334 /**
335 * Gets the texture pixel format.
336 *
337 * @return pixel format
338 */
339 PixelFormat GetFormat() const { return m_format; }
340
341 /**
342 * Gets the texture width.
343 *
344 * @return width
345 */
346 int GetWidth() const { return m_width; }
347
348 /**
349 * Gets the texture height.
350 *
351 * @return height
352 */
353 int GetHeight() const { return m_height; }
354
355 /**
356 * Updates the texture from pixel data.
357 * The image data size and format is assumed to match that of the texture.
358 *
359 * @param format pixel format
360 * @param data pixel data
361 */
362 void Update(const unsigned char* data) {
363 UpdateTexture(m_texture, m_format, m_width, m_height, data);
364 }
365
366 /**
367 * Updates the texture from image data.
368 * The pixel format of the texture must be RGBA. If the width and height of
369 * the image differ from the texture width and height, a new texture is
370 * created (note this may be inefficient).
371 *
372 * @param data image data
373 * @param len image data length
374 *
375 * @return True on success, false on failure.
376 */
377 bool UpdateFromImage(const unsigned char* data, int len) {
378 return UpdateTextureFromImage(&m_texture, m_width, m_height, data, len);
379 }
380
381 /**
382 * Creates a texture by loading an image file.
383 *
384 * @param filename filename
385 *
386 * @return Texture, or invalid (empty) texture on failure.
387 */
388 static Texture CreateFromFile(const char* filename) {
389 Texture texture;
390 if (!CreateTextureFromFile(filename, &texture.m_texture, &texture.m_width,
Austin Schuh812d0d12021-11-04 20:16:48 -0700391 &texture.m_height)) {
Austin Schuh1e69f942020-11-14 15:06:14 -0800392 return {};
Austin Schuh812d0d12021-11-04 20:16:48 -0700393 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800394 return texture;
395 }
396
397 /**
398 * Creates a texture from image data.
399 *
400 * @param data image data
401 * @param len image data length
402 *
403 * @return Texture, or invalid (empty) texture on failure.
404 */
405 static Texture CreateFromImage(const unsigned char* data, int len) {
406 Texture texture;
407 if (!CreateTextureFromImage(data, len, &texture.m_texture, &texture.m_width,
Austin Schuh812d0d12021-11-04 20:16:48 -0700408 &texture.m_height)) {
Austin Schuh1e69f942020-11-14 15:06:14 -0800409 return {};
Austin Schuh812d0d12021-11-04 20:16:48 -0700410 }
Austin Schuh1e69f942020-11-14 15:06:14 -0800411 return texture;
412 }
413
414 private:
415 ImTextureID m_texture = nullptr;
416 PixelFormat m_format = kPixelRGBA;
417 int m_width = 0;
418 int m_height = 0;
419};
420
421/**
422 * Get square of distance between two ImVec2's.
423 *
424 * @param a first ImVec
425 * @param b second ImVec
426 *
427 * @return Distance^2 between a and b.
428 */
429inline float GetDistSquared(const ImVec2& a, const ImVec2& b) {
430 float deltaX = b.x - a.x;
431 float deltaY = b.y - a.y;
432 return deltaX * deltaX + deltaY * deltaY;
433}
434
435/**
436 * Maximize fit in "window" while preserving aspect ratio. Min and max
437 * passed-in values are modified such that the object maximally fits.
438 *
439 * @param min upper left corner of window (modified to fit)
440 * @param max lower right corner of window (modified to fit)
441 * @param width width of object to fit
442 * @param height height of object to fit
443 */
444void MaxFit(ImVec2* min, ImVec2* max, float width, float height);
445
446} // namespace wpi::gui