GCC Code Coverage Report


./
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
0 of 325, 0 excluded
0.0%
Functions:
0 of 32, 0 excluded
0.0%
Branches:
0 of 348, 0 excluded
0.0%

libs/imgui/src/eu/imgui/ui.cc
Line Branch Exec Source
1 #include "eu/imgui/ui.h"
2
3 #include "eu/core/ui.h"
4 #include "eu/render/texture.h"
5 #include "eu/render/dependency_glad.h"
6
7 #include <iostream>
8 #include <numbers>
9
10 #include "dear_imgui/imgui.h"
11 #include "dear_imgui/imgui_internal.h"
12
13 namespace eu::imgui
14 {
15 // opengl code copied from the imgui opengl3 backend with minor modifications
16 // todo(Gustav): should we use the imgui backend code for "backend" rendering or use our own shader?
17
18 namespace
19 {
20 ✗ bool check_imgui_shader(GLuint handle, const char* desc)
21 {
22 ✗ GLint status = 0;
23 ✗ GLint log_length = 0;
24
25 ✗ glGetShaderiv(handle, GL_COMPILE_STATUS, &status);
26 ✗ glGetShaderiv(handle, GL_INFO_LOG_LENGTH, &log_length);
27 ✗ if (status == GL_FALSE)
28 {
29 ✗ std::cerr << "ERROR: Shader code: failed to compile " << desc << "!\n";
30 }
31
32 ✗ if (log_length > 1)
33 {
34 ✗ std::vector<GLchar> buf;
35 ✗ buf.resize(sizet_from_int(log_length + 1));
36 ✗ glGetShaderInfoLog(handle, log_length, nullptr, buf.data());
37 ✗ std::cerr << buf.data();
38 ✗ }
39 ✗ return status == GL_TRUE;
40 }
41
42 ✗ bool imgui_check_program(GLuint handle, const char* desc)
43 {
44 ✗ GLint status = 0;
45 ✗ GLint log_length = 0;
46 ✗ glGetProgramiv(handle, GL_LINK_STATUS, &status);
47 ✗ glGetProgramiv(handle, GL_INFO_LOG_LENGTH, &log_length);
48 ✗ if (status == GL_FALSE)
49 {
50 ✗ std::cerr << "ERROR: Shader program: failed to link " << desc << "!\n";
51 }
52
53 ✗ if (log_length > 1)
54 {
55 ✗ std::vector<GLchar> buf;
56 ✗ buf.resize(sizet_from_int(log_length + 1));
57 ✗ glGetProgramInfoLog(handle, log_length, nullptr, buf.data());
58 ✗ std::cerr << buf.data();
59 ✗ }
60 ✗ return status == GL_TRUE;
61 }
62
63 ✗ void imgui_destroy_shader(ImguiShaderProgram* bd)
64 {
65 ✗ if (bd->program_handle == 0)
66 {
67 ✗ return;
68 }
69
70 ✗ glDeleteProgram(bd->program_handle);
71 ✗ bd->program_handle = 0;
72 }
73
74 ✗ ImguiShaderProgram imgui_load_shader(const char* glsl_version_string, const char* vertex_shader, const char* fragment_shader)
75 {
76 // Create shaders
77 ✗ const GLchar* const vertex_shader_with_version[2] = { glsl_version_string, vertex_shader };
78 ✗ const auto vert_handle = glCreateShader(GL_VERTEX_SHADER);
79 ✗ glShaderSource(vert_handle, 2, vertex_shader_with_version, nullptr);
80 ✗ glCompileShader(vert_handle);
81 ✗ if (!check_imgui_shader(vert_handle, "vertex shader"))
82 {
83 ✗ return {};
84 }
85
86 ✗ const GLchar* const fragment_shader_with_version[2] = { glsl_version_string, fragment_shader };
87 ✗ const auto frag_handle = glCreateShader(GL_FRAGMENT_SHADER);
88 ✗ glShaderSource(frag_handle, 2, fragment_shader_with_version, nullptr);
89 ✗ glCompileShader(frag_handle);
90
91 ✗ if (!check_imgui_shader(frag_handle, "fragment shader"))
92 {
93 ✗ return {};
94 }
95
96 // Link
97 ✗ ImguiShaderProgram prog;
98 ✗ prog.program_handle = glCreateProgram();
99 ✗ glAttachShader(prog.program_handle, vert_handle);
100 ✗ glAttachShader(prog.program_handle, frag_handle);
101 ✗ glLinkProgram(prog.program_handle);
102 ✗ if (!imgui_check_program(prog.program_handle, "shader program"))
103 {
104 ✗ imgui_destroy_shader(&prog);
105 ✗ return {};
106 }
107
108 ✗ glDetachShader(prog.program_handle, vert_handle);
109 ✗ glDetachShader(prog.program_handle, frag_handle);
110 ✗ glDeleteShader(vert_handle);
111 ✗ glDeleteShader(frag_handle);
112
113 ✗ prog.texture_attrib = glGetUniformLocation(prog.program_handle, "Texture");
114 ✗ prog.projection_attrib = glGetUniformLocation(prog.program_handle, "ProjMtx");
115 ✗ return prog;
116 }
117
118 ✗ void imgui_set_shared_shader_params(ImguiShaderProgram* prog)
119 {
120 ✗ const ImDrawData* draw_data = ImGui::GetDrawData();
121
122 ✗ const auto le = draw_data->DisplayPos.x;
123 ✗ const auto ri = draw_data->DisplayPos.x + draw_data->DisplaySize.x;
124 ✗ const auto to = draw_data->DisplayPos.y;
125 ✗ const auto bo = draw_data->DisplayPos.y + draw_data->DisplaySize.y;
126
127 ✗ const float ortho_projection[4][4] = {
128 ✗ {2.0f / (ri - le), 0.0f, 0.0f, 0.0f},
129 ✗ {0.0f, 2.0f / (to - bo), 0.0f, 0.0f},
130 {0.0f, 0.0f, -1.0f, 0.0f},
131 ✗ {(ri + le) / (le - ri), (to + bo) / (bo - to), 0.0f, 1.0f},
132 ✗ };
133 ✗ glUseProgram(prog->program_handle);
134 ✗ glUniform1i(prog->texture_attrib, 0);
135 ✗ glUniformMatrix4fv(prog->projection_attrib, 1, GL_FALSE, &ortho_projection[0][0]);
136 ✗ }
137
138 ✗ void im_draw_callback_linear_to_gamma(const ImDrawList*, const ImDrawCmd* cmd)
139 {
140 ✗ auto* prog = static_cast<ImguiShaderProgram*>(cmd->UserCallbackData);
141
142 ✗ imgui_set_shared_shader_params(prog);
143 ✗ }
144
145 ✗ void im_draw_callback_depth_ortho(const ImDrawList*, const ImDrawCmd* cmd)
146 {
147 ✗ auto* prog = static_cast<ImguiShaderProgram*>(cmd->UserCallbackData);
148
149 ✗ imgui_set_shared_shader_params(prog);
150 ✗ }
151
152 ✗ void draw_imgui_image(const render::FrameBuffer& img, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& border_col, ImguiShaderCache* cache, ImageShader shader)
153 {
154 ✗ ImDrawList* draw_list = ImGui::GetWindowDrawList();
155
156 // set shader
157 ✗ switch (shader)
158 {
159 ✗ case ImageShader::tonemap_and_gamma:
160 ✗ draw_list->AddCallback(im_draw_callback_linear_to_gamma, &cache->linear_to_gamma_shader);
161 ✗ break;
162 ✗ case ImageShader::depth_ortho:
163 ✗ draw_list->AddCallback(im_draw_callback_depth_ortho, &cache->depth_ortho_shader);
164 ✗ break;
165 ✗ case ImageShader::none:
166 // no shader
167 ✗ break;
168 }
169
170 // draw image
171 ✗ ImGui::ImageWithBg(img.id, image_size, uv0, uv1, border_col);
172
173 // reset shader
174 ✗ if (shader != ImageShader::none)
175 {
176 ✗ draw_list->AddCallback(ImDrawCallback_ResetRenderState, nullptr);
177 }
178 ✗ }
179
180 ✗ v2 v2_from_vec(const ImVec2& v)
181 {
182 ✗ return { v.x, v.y };
183 }
184
185
186 ✗ void image_tooltip(const render::FrameBuffer& texture_id, const ImVec2 texture_size, const float& region_size, const float& hover_size,
187 const ImVec2 mouse_pos, const ImVec2 widget_size, const ImVec2 pos, const ImVec4 border_col, ImguiShaderCache* cache, ImageShader shader)
188 {
189 ✗ const auto region = core::calculate_region(v2_from_vec(mouse_pos), v2_from_vec(pos), v2_from_vec(texture_size), v2_from_vec(widget_size), region_size);
190 ✗ const auto flipped_region_y = texture_size.y - region.y;
191 ✗ const auto uv0 = ImVec2{ region.x / texture_size.x, flipped_region_y / texture_size.y };
192 ✗ const auto uv1 = ImVec2{ (region.x + region_size) / texture_size.x, (flipped_region_y - region_size) / texture_size.y };
193
194 // todo(Gustav): can we display pixel value instead of where we are looking? is the region important information?
195 ✗ imgui_text(fmt::format("UL: {} {}", region.x, region.y));
196 ✗ imgui_text(fmt::format("LR: {} {}", region.x + region_size, region.y + region_size));
197 ✗ draw_imgui_image(texture_id, ImVec2(hover_size, hover_size), uv0, uv1, border_col, cache, shader);
198 ✗ }
199
200 ✗ float length2(const ImVec2& v)
201 {
202 ✗ return v.x * v.x + v.y * v.y;
203 }
204
205 ✗ float length(const ImVec2& v)
206 {
207 ✗ return std::sqrt(v.x * v.x + v.y * v.y);
208 }
209
210 ✗ ImVec2 normalize(const ImVec2& v, float len)
211 {
212 ✗ return v / len;
213 }
214
215 ✗ ImVec2 normalize(const ImVec2& v)
216 {
217 ✗ return normalize(v, length(v));
218 }
219
220 ✗ float dot(const ImVec2& lhs, const ImVec2& rhs)
221 {
222 ✗ return lhs.x * rhs.x + lhs.y * rhs.y;
223 }
224
225 ✗ bool is_positive(float f)
226 {
227 ✗ return f >= 0.0f;
228 }
229
230 struct GearState
231 {
232 ImGuiID id;
233 ImVec2 center;
234 ImVec2 pos;
235 int turns;
236 float orig;
237 std::optional<float> initial_angle;
238 };
239
240 // dl functions are copied directly from ImDrawList with an additional starting angle and "special cases" removed.
241
242 ✗ void dl_path_arc_to_n(ImDrawList* dl, const ImVec2& center, float radius, float a_min, float a_max, int num_segments, float ang)
243 {
244 ✗ if (radius < 0.5f)
245 {
246 ✗ dl->_Path.push_back(center);
247 ✗ return;
248 }
249
250 // Note that we are adding a point at both a_min and a_max.
251 // If you are trying to draw a full closed circle you don't want the overlapping points!
252 ✗ dl->_Path.reserve(dl->_Path.Size + (num_segments + 1));
253 ✗ for (int segment_index = 0; segment_index <= num_segments; segment_index++)
254 {
255 ✗ const float a = a_min + (static_cast<float>(segment_index) / static_cast<float>(num_segments)) * (a_max - a_min);
256 ✗ dl->_Path.push_back(ImVec2(center.x + ImCos(a + ang) * radius, center.y + ImSin(a + ang) * radius));
257 }
258 }
259
260 ✗ void dl_path_arc_to(ImDrawList* dl, const ImVec2& center, float radius, float a_min, float a_max, int num_segments, float ang)
261 {
262 ✗ if (radius < 0.5f)
263 {
264 ✗ dl->_Path.push_back(center);
265 ✗ return;
266 }
267
268 ✗ dl_path_arc_to_n(dl, center, radius, a_min, a_max, num_segments, ang);
269 }
270
271 ✗ void dl_add_circle(ImDrawList* dl, const ImVec2& center, float radius, ImU32 col, int num_segments, float thickness, float ang)
272 {
273 ✗ if ((col & IM_COL32_A_MASK) == 0 || radius < 0.5f)
274 {
275 ✗ return;
276 }
277
278 // Explicit segment count (still clamp to avoid drawing insanely tessellated shapes)
279 ✗ num_segments = ImClamp(num_segments, 3, IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_MAX);
280
281 // Because we are filling a closed shape we remove 1 from the count of segments/points
282 ✗ const float a_max = (IM_PI * 2.0f) * (static_cast<float>(num_segments) - 1.0f) / static_cast<float>(num_segments);
283 ✗ dl_path_arc_to(dl, center, radius - 0.5f, 0.0f, a_max, num_segments - 1, ang);
284
285 ✗ dl->PathStroke(col, ImDrawFlags_Closed, thickness);
286 }
287
288
289 ✗ ImVec2 calc_button_size(const char* label, const ImVec2& size_arg = ImVec2(0, 0))
290 {
291 ✗ const auto style = ImGui::GetStyle();
292 ✗ const ImVec2 label_size = ImGui::CalcTextSize(label, nullptr, true);
293 ✗ ImVec2 size = ImGui::CalcItemSize(size_arg, label_size.x + style.FramePadding.x * 2.0f, label_size.y + style.FramePadding.y * 2.0f);
294 ✗ return size;
295 }
296
297 // https://anttweakbar.sourceforge.io/doc/tools_anttweakbar_rotoslider.html
298 ✗ bool gear_icon(const char* label, float* drag, const ImVec2& size)
299 {
300 static std::optional<GearState> state = std::nullopt;
301
302 // config
303 ✗ const auto circle_col = ImColor(100, 100, 255);
304 ✗ const float min_radius = 20.0f;
305 ✗ const float radius = min_radius;
306 ✗ const float one_turn = 10.0f;
307 ✗ constexpr float circle_thickness = 1.0f;
308 ✗ constexpr int gear_segments = 6;
309
310 ✗ const auto mp = ImGui::GetMousePos();
311
312 ✗ ImGui::Button(label, size);
313 ✗ const auto id = ImGui::GetItemID();
314 ✗ const auto active = ImGui::IsItemActive();
315
316 ✗ if (active && state && state->id != id)
317 {
318 // another id is active, but so are we => treat it as the previous lost the activity
319 ✗ state = std::nullopt;
320 }
321
322 ✗ auto orig = state ? state->orig : *drag;
323 ✗ const auto center = state.has_value() ? state->center : mp;
324
325 // interaction
326 ✗ bool changed = false;
327 ✗ if (state.has_value() == false || state->id == id)
328 {
329 // only change state if we are the interactive item or there is no interactive item
330 ✗ if (active)
331 {
332 ✗ int turns = state ? state->turns : 0;
333 ✗ const auto input = mp - center;
334 ✗ std::optional<float> initial_angle = state ? state->initial_angle : std::nullopt;
335 ✗ if (length2(input) > (min_radius * min_radius))
336 {
337 ✗ const auto dir_cur = normalize(input);
338 ✗ const auto ang_right = std::acos(dot(dir_cur, { 1, 0 })) * (180.0f / std::numbers::pi_v<float>);
339 ✗ const auto ang = dir_cur.y < 0 ? ang_right : 360 - ang_right;
340
341 ✗ if (initial_angle.has_value() == false)
342 {
343 ✗ initial_angle = ang;
344 }
345
346 ✗ if (state.has_value())
347 {
348 ✗ const auto changed_y = is_positive(input.y) != is_positive(state->pos.y);
349 ✗ const auto on_right_side = input.x > 0 && state->pos.x > 0;
350 ✗ const auto changed_dir = changed_y && on_right_side;
351 ✗ if (changed_dir)
352 {
353 ✗ turns += is_positive(input.y) ? -1 : 1;
354 }
355 }
356 ✗ const auto new_ang = ang - *initial_angle + static_cast<float>(turns) * 360.0f;
357 ✗ const auto val = orig + (new_ang / 360.0f) * one_turn;
358
359 ✗ if (drag != nullptr)
360 {
361 ✗ *drag = val;
362 ✗ changed = true;
363 }
364 }
365 else
366 {
367 ✗ initial_angle = std::nullopt;
368 ✗ turns = 0;
369 ✗ orig = *drag;
370 }
371
372 ✗ state = GearState
373 {
374 .id = id,
375 .center = center,
376 .pos = input,
377 .turns = turns,
378 .orig = orig,
379 .initial_angle = initial_angle
380 ✗ };
381 }
382 else
383 {
384 ✗ state = std::nullopt;
385 }
386 }
387
388 // drawing
389 {
390 // draw gear
391 ✗ if (active)
392 {
393 ✗ auto* fg = ImGui::GetForegroundDrawList();
394 // fg->AddCircle(center, radius, circle_col, gear_segments, thickness);
395 ✗ constexpr auto segment_factor = 1.0f / static_cast<float>(gear_segments - 1);
396 ✗ const auto turn_factor = std::fmodf(*drag, one_turn) / one_turn;
397 ✗ const auto ang = (turn_factor + segment_factor) * -2.0f * IM_PI;
398 ✗ dl_add_circle(fg, center, radius, circle_col, gear_segments, circle_thickness, ang);
399
400 // draw wrench instead?
401 ✗ fg->AddLine(center, mp, circle_col);
402 }
403 }
404
405 ✗ return changed;
406 }
407 }
408 }
409
410
411
412 namespace eu::imgui
413 {
414 constexpr auto dear_imgui_shader_version = "#version 330 core\n";
415
416 constexpr auto linear_to_gamma_glsl_vert = R"glsl(
417 layout (location = 0) in vec2 Position;
418 layout (location = 1) in vec2 UV;
419 layout (location = 2) in vec4 Color;
420 uniform mat4 ProjMtx;
421 out vec2 Frag_UV;
422 out vec4 Frag_Color;
423 void main()
424 {
425 Frag_UV = UV;
426 Frag_Color = Color;
427 gl_Position = ProjMtx * vec4(Position.xy,0,1);
428 }
429 )glsl";
430
431 constexpr auto linear_to_gamma_glsl_frag = R"glsl(
432 in vec2 Frag_UV;
433 in vec4 Frag_Color;
434 uniform sampler2D Texture;
435 layout (location = 0) out vec4 Out_Color;
436 void main()
437 {
438 vec4 sample = texture(Texture, Frag_UV.st);
439 vec3 color = sample.rgb / (sample.rgb + vec3(1.0f)); // reinhard tone mapping
440 float gamma = 2.2f;
441 vec3 gamma_corrected = pow(color.rgb, vec3(1.0f/gamma));
442 Out_Color = Frag_Color * vec4(gamma_corrected, sample.a);
443 }
444 )glsl";
445
446
447 constexpr auto depth_ortho_glsl_vert = R"glsl(
448 layout (location = 0) in vec2 Position;
449 layout (location = 1) in vec2 UV;
450 layout (location = 2) in vec4 Color;
451 uniform mat4 ProjMtx;
452 out vec2 Frag_UV;
453 out vec4 Frag_Color;
454 void main()
455 {
456 Frag_UV = UV;
457 Frag_Color = Color;
458 gl_Position = ProjMtx * vec4(Position.xy,0,1);
459 }
460 )glsl";
461
462 constexpr auto depth_ortho_glsl_frag = R"glsl(
463 in vec2 Frag_UV;
464 in vec4 Frag_Color;
465 uniform sampler2D Texture;
466 layout (location = 0) out vec4 Out_Color;
467
468 void main()
469 {
470 vec4 sample = texture(Texture, Frag_UV.st);
471 Out_Color = Frag_Color * vec4(vec3(sample.r), 1);
472 }
473 )glsl";
474
475
476 ✗ ImguiShaderCache::ImguiShaderCache()
477 ✗ : linear_to_gamma_shader(imgui_load_shader(dear_imgui_shader_version,
478 linear_to_gamma_glsl_vert, linear_to_gamma_glsl_frag))
479 ✗ , depth_ortho_shader(imgui_load_shader(dear_imgui_shader_version,
480 depth_ortho_glsl_vert, depth_ortho_glsl_frag)
481 )
482 {
483 ✗ }
484
485 ✗ ImguiShaderCache::~ImguiShaderCache()
486 {
487 ✗ imgui_destroy_shader(&linear_to_gamma_shader);
488 ✗ imgui_destroy_shader(&depth_ortho_shader);
489 ✗ }
490
491
492 ✗ void label(const char* label)
493 {
494 ✗ ImGuiWindow* window = ImGui::GetCurrentWindow();
495 ✗ const float full_width = ImGui::GetContentRegionAvail().x;
496 ✗ const float item_width = full_width * 0.6f;
497 ✗ const ImVec2 text_size = ImGui::CalcTextSize(label);
498 ✗ ImRect text_rect;
499 ✗ text_rect.Min = ImGui::GetCursorScreenPos();
500 ✗ text_rect.Max = text_rect.Min;
501 ✗ text_rect.Max.x += full_width - item_width;
502 ✗ text_rect.Max.y += text_size.y;
503
504 ✗ ImGui::AlignTextToFramePadding();
505 ✗ text_rect.Min.y += window->DC.CurrLineTextBaseOffset;
506 ✗ text_rect.Max.y += window->DC.CurrLineTextBaseOffset;
507
508 ✗ ImGui::ItemSize(text_rect);
509 ✗ if (ImGui::ItemAdd(text_rect, window->GetID(label)))
510 {
511 ✗ constexpr float ellipsis_max = 3.0f;
512 ✗ ImGui::RenderTextEllipsis(ImGui::GetWindowDrawList(), text_rect.Min, text_rect.Max, ellipsis_max, label, nullptr, &text_size);
513
514 ✗ if (text_rect.GetWidth() < text_size.x && ImGui::IsItemHovered())
515 ✗ ImGui::SetTooltip("%s", label);
516 }
517 ✗ ImGui::SameLine();
518 ✗ ImGui::SetNextItemWidth(-1);
519 ✗ }
520
521
522 ✗ bool centered_button(const char* label)
523 {
524 ✗ const auto window_width = ImGui::GetContentRegionAvail().x;
525 ✗ const auto text_width = ImGui::CalcTextSize(label).x;
526 ✗ const auto padding_hor = ImGui::GetStyle().FramePadding.x * 2; // padding on both sides
527 ✗ const auto whitespace = window_width - text_width - padding_hor;
528 ✗ const float x = whitespace * 0.5f;
529 ✗ ImGui::SetCursorPosX(x);
530 ✗ return ImGui::Button(label);
531 }
532
533
534 ✗ void imgui_text(const std::string& str)
535 {
536 ✗ ImGui::Text("%s", str.c_str());
537 ✗ }
538
539
540 ✗ void imgui_image(const char* name, const render::FrameBuffer& img, ImguiShaderCache* cache, ImageShader shader)
541 {
542 ✗ const auto texture_size = ImVec2{float_from_int(img.size.width), float_from_int(img.size.height)};
543
544 // todo(Gustav): make the arguments widget_size and zoom level AND make them configurable (with scrolling)
545 static float widget_height = 100.0f;
546 static float region_size = 32.0f;
547 static float hover_size = 128.0f;
548 ✗ const auto& io = ImGui::GetIO();
549
550 ✗ imgui_text(fmt::format("{}: {}x{}", name, texture_size.x, texture_size.y));
551
552 ✗ const auto widget_width = (texture_size.x / texture_size.y) * widget_height;
553 ✗ const auto widget_size = ImVec2{widget_width, widget_height};
554
555 ✗ const auto pos = ImGui::GetCursorScreenPos();
556 ✗ constexpr auto uv_min = ImVec2{0.0f, 1.0f}; // Top-left
557 ✗ constexpr auto uv_max = ImVec2{1.0f, 0.0f}; // Lower-right
558 ✗ const auto border_col = ImGui::GetStyleColorVec4(ImGuiCol_Border);
559
560 static ImVec2 latest_tooltip;
561 static ImGuiID current_id = 0;
562
563 ✗ const constexpr char* const popup_id = "image config popup";
564
565 ✗ draw_imgui_image(img, widget_size, uv_min, uv_max, border_col, cache, shader);
566 ✗ const auto id = ImGui::GetID(name);
567
568 ✗ if (id == current_id && ImGui::BeginPopupContextItem(popup_id))
569 {
570 ✗ ImGui::DragFloat("Base", &widget_height, 1.0f);
571 ✗ ImGui::DragFloat("Size", &region_size, 0.01f);
572 ✗ ImGui::DragFloat("Scale", &hover_size, 1.0f);
573 ✗ image_tooltip(img, texture_size, region_size, hover_size, latest_tooltip, widget_size, pos, border_col, cache, shader);
574 ✗ if (ImGui::Button("Close"))
575 {
576 ✗ ImGui::CloseCurrentPopup();
577 }
578 ✗ ImGui::EndPopup();
579 }
580 ✗ ImGui::OpenPopupOnItemClick(popup_id, ImGuiPopupFlags_MouseButtonRight);
581
582 // todo(Gustav): look into SetItemTooltip and BeginItemTooltip from https://github.com/ocornut/imgui/releases/tag/v1.89.7
583 ✗ if (ImGui::IsItemHovered())
584 {
585 ✗ current_id = id;
586 ✗ latest_tooltip = io.MousePos;
587 ✗ if (ImGui::BeginTooltip())
588 {
589 ✗ image_tooltip(img, texture_size, region_size, hover_size, io.MousePos, widget_size, pos, border_col, cache, shader);
590 ✗ ImGui::EndTooltip();
591 }
592 }
593 ✗ }
594
595
596
597 ✗ bool simple_gamma_slider(const char* label, float* gamma, float curve, float min_gamma, float max_gamma)
598 {
599 ✗ if (curve < 0.0f)
600 {
601 ✗ return ImGui::SliderFloat(label, gamma, min_gamma, max_gamma);
602 }
603
604 // todo(Gustav): is this the correct way? it doesn't feel exactly right but perhaps that's just dear imgui
605 ✗ const auto gamma_range = max_gamma - min_gamma;
606 ✗ const auto t = (*gamma - min_gamma) / gamma_range;
607
608 ✗ auto slider_value = std::pow(t, 1.0f / curve);
609 ✗ if (ImGui::SliderFloat(label, &slider_value, 0.0f, 1.0f) == false)
610 {
611 ✗ return false;
612 }
613
614 ✗ const auto perceptual = std::pow(slider_value, curve);
615 ✗ *gamma = min_gamma + perceptual * gamma_range;
616 ✗ return true;
617 }
618
619
620 ✗ bool drag(const char* label, v3* drag)
621 {
622 ✗ return ImGui::DragFloat3(label, drag->get_data_ptr());
623 }
624
625 ✗ bool drag(const char* label, Ypr* drag)
626 {
627 float angles[3] = {
628 ✗ drag->yaw.as_degrees(),
629 ✗ drag->pitch.as_degrees(),
630 ✗ drag->roll.as_degrees()
631 ✗ };
632
633 ✗ const auto changed = ImGui::DragFloat3(label, angles, 1.0f, -360.0f, 360.0f);
634
635 ✗ if (changed)
636 {
637 ✗ drag->yaw = An::from_degrees(angles[0]);
638 ✗ drag->pitch = An::from_degrees(angles[1]);
639 ✗ drag->roll = An::from_degrees(angles[2]);
640 }
641
642 ✗ return changed;
643 }
644
645 ✗ bool gear(const char* label, v3* drag)
646 {
647 ✗ constexpr const char* gear_string = ".";
648 ✗ const auto gear_size = calc_button_size(gear_string);
649
650 ✗ auto& style = ImGui::GetStyle();
651
652 ✗ bool value_changed = false;
653 ✗ ImGui::BeginGroup();
654 ✗ ImGui::PushID(label);
655
656 ✗ const float w_items = ImMax(0.0f, ImGui::CalcItemWidth() - style.ItemInnerSpacing.x * (6 - 1) - gear_size.x * 3);
657 ✗ for (int item_index=0; item_index<3; item_index+=1)
658 {
659 ✗ ImGui::PushItemWidth(gear_size.x);
660 ✗ ImGui::PushItemWidth(w_items / 3);
661 }
662
663 ✗ const auto widget = [&](int i, float* p_data)
664 {
665 ✗ ImGui::PushID(i);
666 ✗ if (i > 0)
667 {
668 ✗ ImGui::SameLine(0, style.ItemInnerSpacing.x);
669 }
670 ✗ value_changed |= ImGui::DragFloat("", p_data);
671 ✗ ImGui::PopItemWidth();
672 ✗ ImGui::SameLine(0, style.ItemInnerSpacing.x);
673 ✗ value_changed |= gear_icon(gear_string, p_data, gear_size);
674 ✗ ImGui::PopItemWidth();
675 ✗ ImGui::PopID();
676 ✗ };
677 ✗ widget(0, &drag->x);
678 ✗ widget(1, &drag->y);
679 ✗ widget(2, &drag->z);
680 ✗ ImGui::PopID();
681
682 ✗ const char* label_end = ImGui::FindRenderedTextEnd(label);
683 ✗ if (label != label_end)
684 {
685 ✗ ImGui::SameLine(0, style.ItemInnerSpacing.x);
686 ✗ ImGui::TextEx(label, label_end);
687 }
688
689 ✗ ImGui::EndGroup();
690
691 ✗ return value_changed;
692 }
693
694 }
695
696