./
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
0 / 1011
0.0%
Branches:
0 / 830
0.0%
Functions:
0 / 126
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/misc/cpp/imgui_stdlib.h"
12 #include "dear_imgui/imgui_internal.h"
13
14 namespace eu::imgui
15 {
16 // opengl code copied from the imgui opengl3 backend with minor modifications
17 // todo(Gustav): should we use the imgui backend code for "backend" rendering or use our own shader?
18
19 namespace
20 {
21 ✗ bool check_imgui_shader(GLuint handle, const char* desc)
22 {
23 ✗ GLint status = 0;
24 ✗ GLint log_length = 0;
25
26
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 22 not taken.
✗ glGetShaderiv(handle, GL_COMPILE_STATUS, &status);
27
0/2
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 22 not taken.
✗ glGetShaderiv(handle, GL_INFO_LOG_LENGTH, &log_length);
28
0/2
✗ Branch 4 → 5 not taken.
✗ Branch 4 → 8 not taken.
✗ if (status == GL_FALSE)
29 {
30
0/6
✗ Branch 5 → 6 not taken.
✗ Branch 5 → 22 not taken.
✗ Branch 6 → 7 not taken.
✗ Branch 6 → 22 not taken.
✗ Branch 7 → 8 not taken.
✗ Branch 7 → 22 not taken.
✗ std::cerr << "ERROR: Shader code: failed to compile " << desc << "!\n";
31 }
32
33
0/2
✗ Branch 8 → 9 not taken.
✗ Branch 8 → 17 not taken.
✗ if (log_length > 1)
34 {
35 ✗ std::vector<GLchar> buf;
36
0/2
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 19 not taken.
✗ buf.resize(sizet_from_int(log_length + 1));
37
0/2
✗ Branch 12 → 13 not taken.
✗ Branch 12 → 19 not taken.
✗ glGetShaderInfoLog(handle, log_length, nullptr, buf.data());
38
0/2
✗ Branch 14 → 15 not taken.
✗ Branch 14 → 19 not taken.
✗ std::cerr << buf.data();
39 ✗ }
40 ✗ return status == GL_TRUE;
41 }
42
43 ✗ bool imgui_check_program(GLuint handle, const char* desc)
44 {
45 ✗ GLint status = 0;
46 ✗ GLint log_length = 0;
47
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 22 not taken.
✗ glGetProgramiv(handle, GL_LINK_STATUS, &status);
48
0/2
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 22 not taken.
✗ glGetProgramiv(handle, GL_INFO_LOG_LENGTH, &log_length);
49
0/2
✗ Branch 4 → 5 not taken.
✗ Branch 4 → 8 not taken.
✗ if (status == GL_FALSE)
50 {
51
0/6
✗ Branch 5 → 6 not taken.
✗ Branch 5 → 22 not taken.
✗ Branch 6 → 7 not taken.
✗ Branch 6 → 22 not taken.
✗ Branch 7 → 8 not taken.
✗ Branch 7 → 22 not taken.
✗ std::cerr << "ERROR: Shader program: failed to link " << desc << "!\n";
52 }
53
54
0/2
✗ Branch 8 → 9 not taken.
✗ Branch 8 → 17 not taken.
✗ if (log_length > 1)
55 {
56 ✗ std::vector<GLchar> buf;
57
0/2
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 19 not taken.
✗ buf.resize(sizet_from_int(log_length + 1));
58
0/2
✗ Branch 12 → 13 not taken.
✗ Branch 12 → 19 not taken.
✗ glGetProgramInfoLog(handle, log_length, nullptr, buf.data());
59
0/2
✗ Branch 14 → 15 not taken.
✗ Branch 14 → 19 not taken.
✗ std::cerr << buf.data();
60 ✗ }
61 ✗ return status == GL_TRUE;
62 }
63
64 ✗ void imgui_destroy_shader(ImguiShaderProgram* bd)
65 {
66
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 4 not taken.
✗ if (bd->program_handle == 0)
67 {
68 ✗ return;
69 }
70
71 ✗ glDeleteProgram(bd->program_handle);
72 ✗ bd->program_handle = 0;
73 }
74
75 ✗ ImguiShaderProgram imgui_load_shader(const char* glsl_version_string, const char* vertex_shader, const char* fragment_shader)
76 {
77 // Create shaders
78 ✗ const GLchar* const vertex_shader_with_version[2] = { glsl_version_string, vertex_shader };
79
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 31 not taken.
✗ const auto vert_handle = glCreateShader(GL_VERTEX_SHADER);
80
0/2
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 31 not taken.
✗ glShaderSource(vert_handle, 2, vertex_shader_with_version, nullptr);
81
0/2
✗ Branch 4 → 5 not taken.
✗ Branch 4 → 31 not taken.
✗ glCompileShader(vert_handle);
82
0/4
✗ Branch 5 → 6 not taken.
✗ Branch 5 → 31 not taken.
✗ Branch 6 → 7 not taken.
✗ Branch 6 → 8 not taken.
✗ if (!check_imgui_shader(vert_handle, "vertex shader"))
83 {
84 ✗ return {};
85 }
86
87 ✗ const GLchar* const fragment_shader_with_version[2] = { glsl_version_string, fragment_shader };
88
0/2
✗ Branch 8 → 9 not taken.
✗ Branch 8 → 31 not taken.
✗ const auto frag_handle = glCreateShader(GL_FRAGMENT_SHADER);
89
0/2
✗ Branch 9 → 10 not taken.
✗ Branch 9 → 31 not taken.
✗ glShaderSource(frag_handle, 2, fragment_shader_with_version, nullptr);
90
0/2
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 31 not taken.
✗ glCompileShader(frag_handle);
91
92
0/4
✗ Branch 11 → 12 not taken.
✗ Branch 11 → 31 not taken.
✗ Branch 12 → 13 not taken.
✗ Branch 12 → 14 not taken.
✗ if (!check_imgui_shader(frag_handle, "fragment shader"))
93 {
94 ✗ return {};
95 }
96
97 // Link
98 ✗ ImguiShaderProgram prog;
99
0/2
✗ Branch 14 → 15 not taken.
✗ Branch 14 → 31 not taken.
✗ prog.program_handle = glCreateProgram();
100
0/2
✗ Branch 15 → 16 not taken.
✗ Branch 15 → 31 not taken.
✗ glAttachShader(prog.program_handle, vert_handle);
101
0/2
✗ Branch 16 → 17 not taken.
✗ Branch 16 → 31 not taken.
✗ glAttachShader(prog.program_handle, frag_handle);
102
0/2
✗ Branch 17 → 18 not taken.
✗ Branch 17 → 31 not taken.
✗ glLinkProgram(prog.program_handle);
103
0/4
✗ Branch 18 → 19 not taken.
✗ Branch 18 → 31 not taken.
✗ Branch 19 → 20 not taken.
✗ Branch 19 → 22 not taken.
✗ if (!imgui_check_program(prog.program_handle, "shader program"))
104 {
105
0/2
✗ Branch 20 → 21 not taken.
✗ Branch 20 → 31 not taken.
✗ imgui_destroy_shader(&prog);
106 ✗ return {};
107 }
108
109
0/2
✗ Branch 22 → 23 not taken.
✗ Branch 22 → 31 not taken.
✗ glDetachShader(prog.program_handle, vert_handle);
110
0/2
✗ Branch 23 → 24 not taken.
✗ Branch 23 → 31 not taken.
✗ glDetachShader(prog.program_handle, frag_handle);
111
0/2
✗ Branch 24 → 25 not taken.
✗ Branch 24 → 31 not taken.
✗ glDeleteShader(vert_handle);
112
0/2
✗ Branch 25 → 26 not taken.
✗ Branch 25 → 31 not taken.
✗ glDeleteShader(frag_handle);
113
114
0/2
✗ Branch 26 → 27 not taken.
✗ Branch 26 → 31 not taken.
✗ prog.texture_attrib = glGetUniformLocation(prog.program_handle, "Texture");
115
0/2
✗ Branch 27 → 28 not taken.
✗ Branch 27 → 31 not taken.
✗ prog.projection_attrib = glGetUniformLocation(prog.program_handle, "ProjMtx");
116 ✗ return prog;
117 }
118
119 ✗ void imgui_set_shared_shader_params(ImguiShaderProgram* prog)
120 {
121
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 7 not taken.
✗ const ImDrawData* draw_data = ImGui::GetDrawData();
122
123 ✗ const auto le = draw_data->DisplayPos.x;
124 ✗ const auto ri = draw_data->DisplayPos.x + draw_data->DisplaySize.x;
125 ✗ const auto to = draw_data->DisplayPos.y;
126 ✗ const auto bo = draw_data->DisplayPos.y + draw_data->DisplaySize.y;
127
128 ✗ const float ortho_projection[4][4] = {
129 ✗ {2.0f / (ri - le), 0.0f, 0.0f, 0.0f},
130 ✗ {0.0f, 2.0f / (to - bo), 0.0f, 0.0f},
131 {0.0f, 0.0f, -1.0f, 0.0f},
132 ✗ {(ri + le) / (le - ri), (to + bo) / (bo - to), 0.0f, 1.0f},
133 ✗ };
134
0/2
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 7 not taken.
✗ glUseProgram(prog->program_handle);
135
0/2
✗ Branch 4 → 5 not taken.
✗ Branch 4 → 7 not taken.
✗ glUniform1i(prog->texture_attrib, 0);
136
0/2
✗ Branch 5 → 6 not taken.
✗ Branch 5 → 7 not taken.
✗ glUniformMatrix4fv(prog->projection_attrib, 1, GL_FALSE, &ortho_projection[0][0]);
137 ✗ }
138
139 ✗ void im_draw_callback_linear_to_gamma(const ImDrawList*, const ImDrawCmd* cmd)
140 {
141 ✗ auto* prog = static_cast<ImguiShaderProgram*>(cmd->UserCallbackData);
142
143 ✗ imgui_set_shared_shader_params(prog);
144 ✗ }
145
146 ✗ void im_draw_callback_depth_ortho(const ImDrawList*, const ImDrawCmd* cmd)
147 {
148 ✗ auto* prog = static_cast<ImguiShaderProgram*>(cmd->UserCallbackData);
149
150 ✗ imgui_set_shared_shader_params(prog);
151 ✗ }
152
153 ✗ 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)
154 {
155 ✗ ImDrawList* draw_list = ImGui::GetWindowDrawList();
156
157 // set shader
158
0/4
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 6 not taken.
✗ Branch 3 → 8 not taken.
✗ Branch 3 → 9 not taken.
✗ switch (shader)
159 {
160 ✗ case ImageShader::tonemap_and_gamma:
161 ✗ draw_list->AddCallback(im_draw_callback_linear_to_gamma, &cache->linear_to_gamma_shader);
162 ✗ break;
163 ✗ case ImageShader::depth_ortho:
164 ✗ draw_list->AddCallback(im_draw_callback_depth_ortho, &cache->depth_ortho_shader);
165 ✗ break;
166 ✗ case ImageShader::none:
167 // no shader
168 ✗ break;
169 }
170
171 // draw image
172
0/2
✗ Branch 11 → 12 not taken.
✗ Branch 11 → 15 not taken.
✗ ImGui::ImageWithBg(img.id, image_size, uv0, uv1, border_col);
173
174 // reset shader
175
0/2
✗ Branch 12 → 13 not taken.
✗ Branch 12 → 14 not taken.
✗ if (shader != ImageShader::none)
176 {
177 ✗ draw_list->AddCallback(ImDrawCallback_ResetRenderState, nullptr);
178 }
179 ✗ }
180
181 ✗ v2 v2_from_vec(const ImVec2& v)
182 {
183 ✗ return { v.x, v.y };
184 }
185
186
187 ✗ void image_tooltip(const render::FrameBuffer& texture_id, const ImVec2 texture_size, const float& region_size, const float& hover_size,
188 const ImVec2 mouse_pos, const ImVec2 widget_size, const ImVec2 pos, const ImVec4 border_col, ImguiShaderCache* cache, ImageShader shader)
189 {
190
0/2
✗ Branch 6 → 7 not taken.
✗ Branch 6 → 18 not taken.
✗ 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);
191 ✗ const auto flipped_region_y = texture_size.y - region.y;
192 ✗ const auto uv0 = ImVec2{ region.x / texture_size.x, flipped_region_y / texture_size.y };
193 ✗ const auto uv1 = ImVec2{ (region.x + region_size) / texture_size.x, (flipped_region_y - region_size) / texture_size.y };
194
195 // todo(Gustav): can we display pixel value instead of where we are looking? is the region important information?
196
0/4
✗ Branch 9 → 10 not taken.
✗ Branch 9 → 24 not taken.
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 22 not taken.
✗ imgui_text(fmt::format("UL: {} {}", region.x, region.y));
197
0/4
✗ Branch 12 → 13 not taken.
✗ Branch 12 → 28 not taken.
✗ Branch 13 → 14 not taken.
✗ Branch 13 → 26 not taken.
✗ imgui_text(fmt::format("LR: {} {}", region.x + region_size, region.y + region_size));
198
0/2
✗ Branch 16 → 17 not taken.
✗ Branch 16 → 32 not taken.
✗ draw_imgui_image(texture_id, ImVec2(hover_size, hover_size), uv0, uv1, border_col, cache, shader);
199 ✗ }
200
201 ✗ float length2(const ImVec2& v)
202 {
203 ✗ return v.x * v.x + v.y * v.y;
204 }
205
206 ✗ float length(const ImVec2& v)
207 {
208 ✗ return std::sqrt(v.x * v.x + v.y * v.y);
209 }
210
211 ✗ ImVec2 normalize(const ImVec2& v, float len)
212 {
213 ✗ return v / len;
214 }
215
216 ✗ ImVec2 normalize(const ImVec2& v)
217 {
218 ✗ return normalize(v, length(v));
219 }
220
221 ✗ float dot(const ImVec2& lhs, const ImVec2& rhs)
222 {
223 ✗ return lhs.x * rhs.x + lhs.y * rhs.y;
224 }
225
226 ✗ bool is_positive(float f)
227 {
228 ✗ return f >= 0.0f;
229 }
230
231 struct GearState
232 {
233 ImGuiID id;
234 ImVec2 center;
235 ImVec2 pos;
236 int turns;
237 float orig;
238 std::optional<float> initial_angle;
239 };
240
241 // dl functions are copied directly from ImDrawList with an additional starting angle and "special cases" removed.
242
243 ✗ void dl_path_arc_to_n(ImDrawList* dl, const ImVec2& center, float radius, float a_min, float a_max, int num_segments, float ang)
244 {
245
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 5 not taken.
✗ if (radius < 0.5f)
246 {
247 ✗ dl->_Path.push_back(center);
248 ✗ return;
249 }
250
251 // Note that we are adding a point at both a_min and a_max.
252 // If you are trying to draw a full closed circle you don't want the overlapping points!
253 ✗ dl->_Path.reserve(dl->_Path.Size + (num_segments + 1));
254
0/2
✗ Branch 10 → 7 not taken.
✗ Branch 10 → 11 not taken.
✗ for (int segment_index = 0; segment_index <= num_segments; segment_index++)
255 {
256 ✗ const float a = a_min + (static_cast<float>(segment_index) / static_cast<float>(num_segments)) * (a_max - a_min);
257
0/2
✗ Branch 8 → 9 not taken.
✗ Branch 8 → 12 not taken.
✗ dl->_Path.push_back(ImVec2(center.x + ImCos(a + ang) * radius, center.y + ImSin(a + ang) * radius));
258 }
259 }
260
261 ✗ void dl_path_arc_to(ImDrawList* dl, const ImVec2& center, float radius, float a_min, float a_max, int num_segments, float ang)
262 {
263
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 5 not taken.
✗ if (radius < 0.5f)
264 {
265 ✗ dl->_Path.push_back(center);
266 ✗ return;
267 }
268
269 ✗ dl_path_arc_to_n(dl, center, radius, a_min, a_max, num_segments, ang);
270 }
271
272 ✗ void dl_add_circle(ImDrawList* dl, const ImVec2& center, float radius, ImU32 col, int num_segments, float thickness, float ang)
273 {
274
0/4
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 4 not taken.
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 5 not taken.
✗ if ((col & IM_COL32_A_MASK) == 0 || radius < 0.5f)
275 {
276 ✗ return;
277 }
278
279 // Explicit segment count (still clamp to avoid drawing insanely tessellated shapes)
280 ✗ num_segments = ImClamp(num_segments, 3, IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_MAX);
281
282 // Because we are filling a closed shape we remove 1 from the count of segments/points
283 ✗ const float a_max = (IM_PI * 2.0f) * (static_cast<float>(num_segments) - 1.0f) / static_cast<float>(num_segments);
284 ✗ dl_path_arc_to(dl, center, radius - 0.5f, 0.0f, a_max, num_segments - 1, ang);
285
286 ✗ dl->PathStroke(col, ImDrawFlags_Closed, thickness);
287 }
288
289
290 ✗ ImVec2 calc_button_size(const ImVec2& label_size, const ImVec2& size_arg = ImVec2(0, 0))
291 {
292
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 6 not taken.
✗ const auto style = ImGui::GetStyle();
293
0/2
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 6 not taken.
✗ const auto 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 ✗ ImVec2 calc_button_size(const char* label, const ImVec2& size_arg = ImVec2(0, 0))
298 {
299
0/4
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 7 not taken.
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 7 not taken.
✗ return calc_button_size(ImGui::CalcTextSize(label, nullptr, true), size_arg);
300 }
301
302 // https://anttweakbar.sourceforge.io/doc/tools_anttweakbar_rotoslider.html
303 ✗ bool gear_functionality(float* drag)
304 {
305 static std::optional<GearState> state = std::nullopt;
306
307 // config
308 ✗ const auto circle_col = ImColor(100, 100, 255);
309 ✗ const float min_radius = 20.0f;
310 ✗ const float radius = min_radius;
311 ✗ const float one_turn = 10.0f;
312 ✗ constexpr float circle_thickness = 1.0f;
313 ✗ constexpr int gear_segments = 6;
314
315
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 93 not taken.
✗ const auto mp = ImGui::GetMousePos();
316
317
0/2
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 93 not taken.
✗ const auto id = ImGui::GetItemID();
318
0/2
✗ Branch 4 → 5 not taken.
✗ Branch 4 → 93 not taken.
✗ const auto active = ImGui::IsItemActive();
319
320
0/8
✗ Branch 5 → 6 not taken.
✗ Branch 5 → 11 not taken.
✗ Branch 7 → 8 not taken.
✗ Branch 7 → 11 not taken.
✗ Branch 9 → 10 not taken.
✗ Branch 9 → 11 not taken.
✗ Branch 12 → 13 not taken.
✗ Branch 12 → 14 not taken.
✗ if (active && state && state->id != id)
321 {
322 // another id is active, but so are we => treat it as the previous lost the activity
323 ✗ state = std::nullopt;
324 }
325
326
0/2
✗ Branch 15 → 16 not taken.
✗ Branch 15 → 18 not taken.
✗ auto orig = state ? state->orig : *drag;
327
0/2
✗ Branch 20 → 21 not taken.
✗ Branch 20 → 23 not taken.
✗ const auto center = state.has_value() ? state->center : mp;
328
329 // interaction
330 ✗ bool changed = false;
331
0/6
✗ Branch 25 → 26 not taken.
✗ Branch 25 → 28 not taken.
✗ Branch 27 → 28 not taken.
✗ Branch 27 → 29 not taken.
✗ Branch 30 → 31 not taken.
✗ Branch 30 → 85 not taken.
✗ if (state.has_value() == false || state->id == id)
332 {
333 // only change state if we are the interactive item or there is no interactive item
334
0/2
✗ Branch 31 → 32 not taken.
✗ Branch 31 → 84 not taken.
✗ if (active)
335 {
336
0/2
✗ Branch 33 → 34 not taken.
✗ Branch 33 → 36 not taken.
✗ int turns = state ? state->turns : 0;
337 ✗ const auto input = mp - center;
338
0/2
✗ Branch 39 → 40 not taken.
✗ Branch 39 → 42 not taken.
✗ std::optional<float> initial_angle = state ? state->initial_angle : std::nullopt;
339
0/2
✗ Branch 44 → 45 not taken.
✗ Branch 44 → 80 not taken.
✗ if (length2(input) > (min_radius * min_radius))
340 {
341 ✗ const auto dir_cur = normalize(input);
342 ✗ const auto ang_right = std::acos(dot(dir_cur, { 1, 0 })) * (180.0f / std::numbers::pi_v<float>);
343
0/2
✗ Branch 49 → 50 not taken.
✗ Branch 49 → 51 not taken.
✗ const auto ang = dir_cur.y < 0 ? ang_right : 360 - ang_right;
344
345
0/2
✗ Branch 53 → 54 not taken.
✗ Branch 53 → 56 not taken.
✗ if (initial_angle.has_value() == false)
346 {
347 ✗ initial_angle = ang;
348 }
349
350
0/2
✗ Branch 57 → 58 not taken.
✗ Branch 57 → 76 not taken.
✗ if (state.has_value())
351 {
352 ✗ const auto changed_y = is_positive(input.y) != is_positive(state->pos.y);
353
0/4
✗ Branch 61 → 62 not taken.
✗ Branch 61 → 65 not taken.
✗ Branch 63 → 64 not taken.
✗ Branch 63 → 65 not taken.
✗ const auto on_right_side = input.x > 0 && state->pos.x > 0;
354
0/4
✗ Branch 66 → 67 not taken.
✗ Branch 66 → 69 not taken.
✗ Branch 67 → 68 not taken.
✗ Branch 67 → 69 not taken.
✗ const auto changed_dir = changed_y && on_right_side;
355
0/2
✗ Branch 70 → 71 not taken.
✗ Branch 70 → 76 not taken.
✗ if (changed_dir)
356 {
357
0/2
✗ Branch 72 → 73 not taken.
✗ Branch 72 → 74 not taken.
✗ turns += is_positive(input.y) ? -1 : 1;
358 }
359 }
360 ✗ const auto new_ang = ang - *initial_angle + static_cast<float>(turns) * 360.0f;
361 ✗ const auto val = orig + (new_ang / 360.0f) * one_turn;
362
363
0/2
✗ Branch 77 → 78 not taken.
✗ Branch 77 → 79 not taken.
✗ if (drag != nullptr)
364 {
365 ✗ *drag = val;
366 ✗ changed = true;
367 }
368 }
369 else
370 {
371 ✗ initial_angle = std::nullopt;
372 ✗ turns = 0;
373 ✗ orig = *drag;
374 }
375
376 ✗ state = GearState
377 {
378 .id = id,
379 .center = center,
380 .pos = input,
381 .turns = turns,
382 .orig = orig,
383 .initial_angle = initial_angle
384 ✗ };
385 }
386 else
387 {
388 ✗ state = std::nullopt;
389 }
390 }
391
392 // drawing
393 {
394 // draw gear
395
0/2
✗ Branch 85 → 86 not taken.
✗ Branch 85 → 91 not taken.
✗ if (active)
396 {
397
0/2
✗ Branch 86 → 87 not taken.
✗ Branch 86 → 93 not taken.
✗ auto* fg = ImGui::GetForegroundDrawList();
398 // fg->AddCircle(center, radius, circle_col, gear_segments, thickness);
399 ✗ constexpr auto segment_factor = 1.0f / static_cast<float>(gear_segments - 1);
400 ✗ const auto turn_factor = std::fmodf(*drag, one_turn) / one_turn;
401 ✗ const auto ang = (turn_factor + segment_factor) * -2.0f * IM_PI;
402
0/4
✗ Branch 87 → 88 not taken.
✗ Branch 87 → 93 not taken.
✗ Branch 88 → 89 not taken.
✗ Branch 88 → 93 not taken.
✗ dl_add_circle(fg, center, radius, circle_col, gear_segments, circle_thickness, ang);
403
404 // draw wrench instead?
405
0/4
✗ Branch 89 → 90 not taken.
✗ Branch 89 → 93 not taken.
✗ Branch 90 → 91 not taken.
✗ Branch 90 → 93 not taken.
✗ fg->AddLine(center, mp, circle_col);
406 }
407 }
408
409 ✗ return changed;
410 }
411
412 ✗ bool gear_icon(const char* label, float* drag, const ImVec2& size)
413 {
414 ✗ ImGui::Button(label, size);
415 ✗ return gear_functionality(drag);
416 }
417
418 struct CompoundWidget
419 {
420 const char* label;
421 int item_count;
422
423 ✗ explicit CompoundWidget(const char* l, int ic = 3)
424 ✗ : label(l)
425 ✗ , item_count(ic)
426 {
427 ✗ ImGui::BeginGroup();
428 ✗ ImGui::PushID(label);
429 ✗ }
430
431 ✗ void setup(float extra_x = -1.0f)
432 {
433 ✗ auto& style = ImGui::GetStyle();
434 ✗ const float w_items = ImMax(0.0f, ImGui::CalcItemWidth() - style.ItemInnerSpacing.x * (item_count*2.0f - 1.0f) - extra_x * item_count);
435
0/2
✗ Branch 10 → 6 not taken.
✗ Branch 10 → 11 not taken.
✗ for (int item_index = 0; item_index < item_count; item_index += 1)
436 {
437
0/2
✗ Branch 6 → 7 not taken.
✗ Branch 6 → 8 not taken.
✗ if (extra_x > -0.5f)
438 {
439 ✗ ImGui::PushItemWidth(extra_x);
440 }
441 ✗ ImGui::PushItemWidth(w_items / static_cast<float>(item_count));
442 }
443 ✗ }
444
445 ✗ void begin_widget(int i)
446 {
447 ✗ ImGui::PushID(i);
448
449
0/2
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 6 not taken.
✗ if (i > 0)
450 {
451 ✗ auto& style = ImGui::GetStyle();
452 ✗ ImGui::SameLine(0, style.ItemInnerSpacing.x);
453 }
454 ✗ }
455
456 ✗ void next_sub_widget()
457 {
458 ✗ auto& style = ImGui::GetStyle();
459 ✗ ImGui::PopItemWidth();
460 ✗ ImGui::SameLine(0, style.ItemInnerSpacing.x);
461 ✗ }
462
463 ✗ void end_widget()
464 {
465 ✗ ImGui::PopItemWidth();
466 ✗ ImGui::PopID();
467 ✗ }
468
469 ✗ ~CompoundWidget()
470 {
471 ✗ auto& style = ImGui::GetStyle();
472
473 ✗ ImGui::PopID();
474
475 ✗ const char* label_end = ImGui::FindRenderedTextEnd(label);
476
0/2
✗ Branch 5 → 6 not taken.
✗ Branch 5 → 8 not taken.
✗ if (label != label_end)
477 {
478 ✗ ImGui::SameLine(0, style.ItemInnerSpacing.x);
479 ✗ ImGui::TextEx(label, label_end);
480 }
481
482 ✗ ImGui::EndGroup();
483 ✗ }
484
485 CompoundWidget(const CompoundWidget&) = delete;
486 CompoundWidget(CompoundWidget&&) = delete;
487 void operator=(const CompoundWidget&) = delete;
488 void operator=(CompoundWidget&&) = delete;
489 };
490
491 constexpr const char* gear_string = ".";
492 struct GearWidget : CompoundWidget
493 {
494 ImVec2 gear_size;
495 bool value_changed = false;
496
497 ✗ explicit GearWidget(const char* l)
498 ✗ : CompoundWidget(l, 3)
499
0/2
✗ Branch 4 → 5 not taken.
✗ Branch 4 → 7 not taken.
✗ , gear_size(calc_button_size(gear_string))
500 {
501
0/2
✗ Branch 5 → 6 not taken.
✗ Branch 5 → 8 not taken.
✗ setup(gear_size.x);
502 ✗ }
503
504
505 ✗ void widget(int i, float* p_data)
506 {
507 ✗ begin_widget(i);
508
509 ✗ value_changed |= ImGui::DragFloat("", p_data);
510 ✗ next_sub_widget();
511 ✗ value_changed |= gear_icon(gear_string, p_data, gear_size);
512 ✗ end_widget();
513 ✗ }
514 ✗ void widget(int i, An* p_data)
515 {
516
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 17 not taken.
✗ begin_widget(i);
517
518
0/2
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 17 not taken.
✗ float deg = p_data->as_degrees();
519
520
0/2
✗ Branch 4 → 5 not taken.
✗ Branch 4 → 17 not taken.
✗ const auto drag_changed = ImGui::DragFloat("", &deg);
521
0/2
✗ Branch 5 → 6 not taken.
✗ Branch 5 → 17 not taken.
✗ next_sub_widget();
522
0/2
✗ Branch 6 → 7 not taken.
✗ Branch 6 → 17 not taken.
✗ const auto gear_changed = gear_icon(gear_string, &deg, gear_size);
523
0/2
✗ Branch 7 → 8 not taken.
✗ Branch 7 → 17 not taken.
✗ end_widget();
524
525
0/4
✗ Branch 8 → 9 not taken.
✗ Branch 8 → 10 not taken.
✗ Branch 9 → 10 not taken.
✗ Branch 9 → 11 not taken.
✗ if (drag_changed || gear_changed)
526 {
527
0/2
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 17 not taken.
✗ *p_data = An::from_degrees(deg);
528 }
529
530
0/6
✗ Branch 11 → 12 not taken.
✗ Branch 11 → 14 not taken.
✗ Branch 12 → 13 not taken.
✗ Branch 12 → 14 not taken.
✗ Branch 13 → 14 not taken.
✗ Branch 13 → 15 not taken.
✗ value_changed = value_changed || drag_changed || gear_changed;
531 ✗ }
532 };
533
534 constexpr float c(int r)
535 {
536 return static_cast<float>(r) * (1.0f / 255.0f);
537 }
538
539 constexpr ImVec4 rgb(int r, int g, int b, float a = 1.0f)
540 {
541 return { c(r), c(g), c(b), a};
542 }
543
544 namespace colors
545 {
546 constexpr auto base = rgb(25, 28, 33);
547 constexpr auto tab = rgb(29, 32, 37);
548 constexpr auto tab_other = rgb(25, 28, 33);
549 constexpr auto edit_bg = rgb(11, 14, 19);
550 constexpr auto button_bg = rgb(33, 37, 43);
551 constexpr auto header_bg = rgb(29, 32, 37);
552 constexpr auto button_hover = rgb(42, 48, 56);
553 constexpr auto button_active = rgb(31, 45, 24);
554 constexpr auto border = rgb(66, 73, 54, 0.3f);
555 constexpr auto lime = rgb(158, 233, 57);
556 constexpr auto scroll_bg = rgb(16, 20, 24);
557 constexpr auto scroll_grab = rgb(63, 69, 81);
558 constexpr auto scroll_hover = rgb(39, 43, 51);
559 constexpr auto scroll_active = rgb(132, 204, 22);
560 constexpr auto text_selected_bg = rgb(31, 45, 24);
561 constexpr auto text = rgb(225, 226, 233);
562 constexpr auto text_disabled = rgb(113, 113, 122);
563 constexpr auto transparent = rgb(0, 0, 0, 0.0f);
564
565 constexpr ImVec4 text_label = rgb(139, 148, 125);
566 constexpr ImVec4 button_active_border = rgb(158, 233, 5);
567 constexpr ImVec4 button_default_border = rgb(48, 53, 64);
568 }
569 }
570 }
571
572
573
574 namespace eu::imgui
575 {
576 constexpr auto dear_imgui_shader_version = "#version 330 core\n";
577
578 constexpr auto linear_to_gamma_glsl_vert = R"glsl(
579 layout (location = 0) in vec2 Position;
580 layout (location = 1) in vec2 UV;
581 layout (location = 2) in vec4 Color;
582 uniform mat4 ProjMtx;
583 out vec2 Frag_UV;
584 out vec4 Frag_Color;
585 void main()
586 {
587 Frag_UV = UV;
588 Frag_Color = Color;
589 gl_Position = ProjMtx * vec4(Position.xy,0,1);
590 }
591 )glsl";
592
593 constexpr auto linear_to_gamma_glsl_frag = R"glsl(
594 in vec2 Frag_UV;
595 in vec4 Frag_Color;
596 uniform sampler2D Texture;
597 layout (location = 0) out vec4 Out_Color;
598 void main()
599 {
600 vec4 sample = texture(Texture, Frag_UV.st);
601 vec3 color = sample.rgb / (sample.rgb + vec3(1.0f)); // reinhard tone mapping
602 float gamma = 2.2f;
603 vec3 gamma_corrected = pow(color.rgb, vec3(1.0f/gamma));
604 Out_Color = Frag_Color * vec4(gamma_corrected, sample.a);
605 }
606 )glsl";
607
608
609 constexpr auto depth_ortho_glsl_vert = R"glsl(
610 layout (location = 0) in vec2 Position;
611 layout (location = 1) in vec2 UV;
612 layout (location = 2) in vec4 Color;
613 uniform mat4 ProjMtx;
614 out vec2 Frag_UV;
615 out vec4 Frag_Color;
616 void main()
617 {
618 Frag_UV = UV;
619 Frag_Color = Color;
620 gl_Position = ProjMtx * vec4(Position.xy,0,1);
621 }
622 )glsl";
623
624 constexpr auto depth_ortho_glsl_frag = R"glsl(
625 in vec2 Frag_UV;
626 in vec4 Frag_Color;
627 uniform sampler2D Texture;
628 layout (location = 0) out vec4 Out_Color;
629
630 void main()
631 {
632 vec4 sample = texture(Texture, Frag_UV.st);
633 Out_Color = Frag_Color * vec4(vec3(sample.r), 1);
634 }
635 )glsl";
636
637
638 ✗ ImguiShaderCache::ImguiShaderCache()
639 ✗ : linear_to_gamma_shader(imgui_load_shader(dear_imgui_shader_version,
640 linear_to_gamma_glsl_vert, linear_to_gamma_glsl_frag))
641 ✗ , depth_ortho_shader(imgui_load_shader(dear_imgui_shader_version,
642 depth_ortho_glsl_vert, depth_ortho_glsl_frag)
643 )
644 {
645 ✗ }
646
647 ✗ ImguiShaderCache::~ImguiShaderCache()
648 {
649 ✗ imgui_destroy_shader(&linear_to_gamma_shader);
650 ✗ imgui_destroy_shader(&depth_ortho_shader);
651 ✗ }
652
653 ✗ void setup_colors()
654 {
655
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 12 not taken.
✗ ImGuiStyle& style = ImGui::GetStyle();
656 ✗ ImVec4* colors = style.Colors;
657
658 // initially inspired and based on by Catppuccin Mocha Palette
659 // https://github.com/ocornut/imgui/issues/707#issuecomment-3592676777
660
661 ✗ constexpr auto base = colors::base;
662 ✗ constexpr auto tab = colors::tab;
663 ✗ constexpr auto tab_other = colors::tab_other;
664 ✗ constexpr auto edit_bg = colors::edit_bg;
665 ✗ constexpr auto button_bg = colors::button_bg;
666 ✗ constexpr auto header_bg = colors::header_bg;
667 ✗ constexpr auto button_hover = colors::button_hover;
668 ✗ constexpr auto button_active = colors::button_active;
669 ✗ constexpr auto border = colors::border;
670 ✗ constexpr auto lime = colors::lime;
671 ✗ constexpr auto scroll_bg = colors::scroll_bg;
672 ✗ constexpr auto scroll_grab = colors::scroll_grab;
673 ✗ constexpr auto scroll_hover = colors::scroll_hover;
674 ✗ constexpr auto scroll_active = colors::scroll_active;
675 ✗ constexpr auto text_selected_bg = colors::text_selected_bg;
676 ✗ constexpr auto text = colors::text;
677 ✗ constexpr auto text_disabled = colors::text_disabled;
678 ✗ constexpr auto transparent = colors::transparent;
679 ✗ constexpr auto table_border_strong = colors::text_label;
680
681 // Main window and backgrounds
682 ✗ colors[ImGuiCol_Text] = text;
683 ✗ colors[ImGuiCol_TextDisabled] = text_disabled;
684 ✗ colors[ImGuiCol_WindowBg] = base; // Background of normal windows
685 ✗ colors[ImGuiCol_ChildBg] = base; // Background of child windows
686 ✗ colors[ImGuiCol_PopupBg] = base; // Background of popups, menus, tooltips windows
687 ✗ colors[ImGuiCol_Border] = border;
688 ✗ colors[ImGuiCol_BorderShadow] = transparent;
689 ✗ colors[ImGuiCol_FrameBg] = edit_bg; // Background of checkbox, radio button, plot, slider, text input
690 ✗ colors[ImGuiCol_FrameBgHovered] = edit_bg;
691 ✗ colors[ImGuiCol_FrameBgActive] = edit_bg;
692 ✗ colors[ImGuiCol_TitleBg] = base; // Title bar
693 ✗ colors[ImGuiCol_TitleBgActive] = base; // Title bar when focused
694 ✗ colors[ImGuiCol_TitleBgCollapsed] = base; // Title bar when collapsed
695 ✗ colors[ImGuiCol_MenuBarBg] = base;
696 ✗ colors[ImGuiCol_ScrollbarBg] = scroll_bg;
697 ✗ colors[ImGuiCol_ScrollbarGrab] = scroll_grab;
698 ✗ colors[ImGuiCol_ScrollbarGrabHovered] = scroll_hover;
699 ✗ colors[ImGuiCol_ScrollbarGrabActive] = scroll_active;
700 ✗ colors[ImGuiCol_CheckMark] = lime; // Checkbox tick and RadioButton circle
701 ✗ colors[ImGuiCol_SliderGrab] = scroll_grab;
702 ✗ colors[ImGuiCol_SliderGrabActive] = scroll_active;
703 ✗ colors[ImGuiCol_Button] = button_bg;
704 ✗ colors[ImGuiCol_ButtonHovered] = button_hover;
705 ✗ colors[ImGuiCol_ButtonActive] = button_active;
706 ✗ colors[ImGuiCol_Header] = header_bg; // Header* colors are used for CollapsingHeader, TreeNode, Selectable, MenuItem
707 ✗ colors[ImGuiCol_HeaderHovered] = header_bg;
708 ✗ colors[ImGuiCol_HeaderActive] = header_bg;
709 ✗ colors[ImGuiCol_Separator] = border;
710 ✗ colors[ImGuiCol_SeparatorHovered] = border;
711 ✗ colors[ImGuiCol_SeparatorActive] = border;
712 ✗ colors[ImGuiCol_ResizeGrip] = scroll_grab; // Resize grip in lower-right and lower-left corners of windows.
713 ✗ colors[ImGuiCol_ResizeGripHovered] = scroll_hover;
714 ✗ colors[ImGuiCol_ResizeGripActive] = scroll_active;
715 ✗ colors[ImGuiCol_InputTextCursor] = text; // InputText cursor/caret
716 ✗ colors[ImGuiCol_TabHovered] = tab; // Tab background, when hovered
717 ✗ colors[ImGuiCol_Tab] = tab_other; // Tab background, when tab-bar is focused & tab is unselected
718 ✗ colors[ImGuiCol_TabSelected] = tab; // Tab background, when tab-bar is focused & tab is selected
719 ✗ colors[ImGuiCol_TabSelectedOverline] = lime; // Tab horizontal overline, when tab-bar is focused & tab is selected
720 ✗ colors[ImGuiCol_TabDimmed] = tab; // Tab background, when tab-bar is unfocused & tab is unselected
721 ✗ colors[ImGuiCol_TabDimmedSelected] = tab; // Tab background, when tab-bar is unfocused & tab is selected
722 ✗ colors[ImGuiCol_TabDimmedSelectedOverline] = lime; // horizontal overline, when tab-bar is unfocused & tab is selected
723 // colors[ImGuiCol_DockingPreview] = sapphire;
724 // colors[ImGuiCol_DockingEmptyBg] = base;
725 ✗ colors[ImGuiCol_PlotLines] = lime;
726 ✗ colors[ImGuiCol_PlotLinesHovered] = lime;
727 ✗ colors[ImGuiCol_PlotHistogram] = lime;
728 ✗ colors[ImGuiCol_PlotHistogramHovered] = lime;
729 ✗ colors[ImGuiCol_TableHeaderBg] = base;
730 ✗ colors[ImGuiCol_TableBorderStrong] = table_border_strong;
731 ✗ colors[ImGuiCol_TableBorderLight] = base;
732 ✗ colors[ImGuiCol_TableRowBg] = transparent;
733 ✗ colors[ImGuiCol_TableRowBgAlt] = ImVec4(1.0f, 1.0f, 1.0f, 0.06f);
734 ✗ colors[ImGuiCol_TextSelectedBg] = text_selected_bg;
735 ✗ colors[ImGuiCol_DragDropTarget] = lime;
736 ✗ colors[ImGuiCol_UnsavedMarker] = lime; // Unsaved Document marker (in window title and tabs)
737 ✗ colors[ImGuiCol_NavCursor] = lime;
738 ✗ colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.0f, 1.0f, 1.0f, 0.7f);
739 ✗ colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.8f, 0.8f, 0.8f, 0.2f);
740 ✗ colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.0f, 0.0f, 0.0f, 0.35f);
741
742 // Rounded corners
743 ✗ style.WindowRounding = 6.0f;
744 ✗ style.ChildRounding = 6.0f;
745 ✗ style.FrameRounding = 4.0f;
746 ✗ style.PopupRounding = 4.0f;
747 ✗ style.ScrollbarRounding = 9.0f;
748 ✗ style.GrabRounding = 4.0f;
749 ✗ style.TabRounding = 4.0f;
750
751 // Padding and spacing
752 ✗ style.WindowPadding = ImVec2(8.0f, 8.0f);
753 ✗ style.FramePadding = ImVec2(10.0f, 6.0f);
754 ✗ style.ItemSpacing = ImVec2(8.0f, 4.0f);
755 ✗ style.ItemInnerSpacing = ImVec2(4.0f, 4.0f);
756 ✗ style.IndentSpacing = 21.0f;
757 ✗ style.ScrollbarSize = 18.0f;
758 ✗ style.GrabMinSize = 10.0f;
759
760 // Borders
761 ✗ style.WindowBorderSize = 1.0f;
762 ✗ style.ChildBorderSize = 1.0f;
763 ✗ style.PopupBorderSize = 1.0f;
764 ✗ style.FrameBorderSize = 1.0f;
765 ✗ style.TabBorderSize = 1.0f;
766 ✗ }
767
768
769 ✗ void label(const char* label)
770 {
771
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 11 not taken.
✗ ImGui::PushStyleColor(ImGuiCol_Text, colors::text_label);
772
0/2
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 11 not taken.
✗ ImGui::TextUnformatted(label);
773
0/2
✗ Branch 4 → 5 not taken.
✗ Branch 4 → 11 not taken.
✗ const auto used_size = ImGui::GetItemRectSize();
774
0/2
✗ Branch 5 → 6 not taken.
✗ Branch 5 → 11 not taken.
✗ const ImVec2 text_size = ImGui::CalcTextSize(label);
775
776
0/2
✗ Branch 6 → 7 not taken.
✗ Branch 6 → 8 not taken.
✗ if (used_size.x < text_size.x ||
777
0/2
✗ Branch 7 → 8 not taken.
✗ Branch 7 → 9 not taken.
✗ used_size.y < text_size.y )
778 {
779
0/2
✗ Branch 8 → 9 not taken.
✗ Branch 8 → 11 not taken.
✗ ImGui::SetTooltip("%s", label);
780 }
781
782
0/2
✗ Branch 9 → 10 not taken.
✗ Branch 9 → 11 not taken.
✗ ImGui::PopStyleColor();
783 ✗ }
784
785 ✗ void set_hover_cursor()
786 {
787
0/2
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 5 not taken.
✗ if (ImGui::IsItemHovered())
788 {
789 ✗ ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
790 }
791 ✗ }
792
793 ✗ bool button(const char* label)
794 {
795 ✗ ImGuiStyle& style = ImGui::GetStyle();
796
797
0/2
✗ Branch 4 → 5 not taken.
✗ Branch 4 → 21 not taken.
✗ ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10.0f, 7.0f)); // todo(Gustav): make configurable
798
799
0/2
✗ Branch 6 → 7 not taken.
✗ Branch 6 → 22 not taken.
✗ const auto clicked = ImGui::Button(label);
800
801 // todo(Gustav): when active, switch text color to lime
802
803 {
804
0/2
✗ Branch 7 → 8 not taken.
✗ Branch 7 → 24 not taken.
✗ const auto min = ImGui::GetItemRectMin();
805
0/2
✗ Branch 8 → 9 not taken.
✗ Branch 8 → 24 not taken.
✗ const auto max = ImGui::GetItemRectMax();
806
807
0/2
✗ Branch 9 → 10 not taken.
✗ Branch 9 → 24 not taken.
✗ auto* draw = ImGui::GetWindowDrawList();
808
0/4
✗ Branch 15 → 16 not taken.
✗ Branch 15 → 23 not taken.
✗ Branch 16 → 17 not taken.
✗ Branch 16 → 23 not taken.
✗ draw->AddRect(min, max, ImColor{
809
0/4
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 23 not taken.
✗ Branch 11 → 12 not taken.
✗ Branch 11 → 13 not taken.
✗ ImGui::IsItemHovered() ? colors::button_active_border : colors::button_default_border }, style.ChildRounding);
810 }
811
812 ✗ ImGui::PopStyleVar();
813
814 ✗ set_hover_cursor();
815
816 ✗ return clicked;
817 }
818
819 ✗ bool centered_button(const char* label)
820 {
821 ✗ const auto window_width = ImGui::GetContentRegionAvail().x;
822 ✗ const auto text_width = ImGui::CalcTextSize(label).x;
823 ✗ const auto padding_hor = ImGui::GetStyle().FramePadding.x * 2; // padding on both sides
824 ✗ const auto whitespace = window_width - text_width - padding_hor;
825 ✗ const float x = whitespace * 0.5f;
826 ✗ ImGui::SetCursorPosX(x);
827 ✗ return button(label);
828 }
829
830
831 ✗ bool header(const char* label, bool* closeable)
832 {
833 ✗ const ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_None;
834 const auto ret = closeable != nullptr
835
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 5 not taken.
✗ ? ImGui::CollapsingHeader(label, closeable, flags)
836 ✗ : ImGui::CollapsingHeader(label, flags);
837 ✗ return ret;
838 }
839
840
841 ✗ void imgui_text(const std::string& str)
842 {
843 ✗ ImGui::Text("%s", str.c_str());
844 ✗ }
845
846 ✗ void imgui_readonly(const char* label, const std::string& text)
847 {
848
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 11 not taken.
✗ auto temp = text;
849 // ImGuiCol_FrameBg
850
0/2
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 9 not taken.
✗ ImGui::PushStyleColor(ImGuiCol_FrameBg, colors::base);
851
0/2
✗ Branch 4 → 5 not taken.
✗ Branch 4 → 9 not taken.
✗ ImGui::PushStyleColor(ImGuiCol_Text, colors::text_label);
852
0/2
✗ Branch 5 → 6 not taken.
✗ Branch 5 → 9 not taken.
✗ ImGui::InputText(label, &temp, ImGuiInputTextFlags_ReadOnly);
853
0/2
✗ Branch 6 → 7 not taken.
✗ Branch 6 → 9 not taken.
✗ ImGui::PopStyleColor(2);
854 ✗ }
855
856 ✗ void imgui_readonly(const char* label, const v3& v)
857 {
858 ✗ auto temp = v;
859
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 8 not taken.
✗ ImGui::PushStyleColor(ImGuiCol_FrameBg, colors::base);
860
0/2
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 8 not taken.
✗ ImGui::PushStyleColor(ImGuiCol_Text, colors::text_label);
861
0/4
✗ Branch 4 → 5 not taken.
✗ Branch 4 → 8 not taken.
✗ Branch 5 → 6 not taken.
✗ Branch 5 → 8 not taken.
✗ ImGui::InputFloat3(label, temp.get_data_ptr(), "%.3f", ImGuiInputTextFlags_ReadOnly);
862
0/2
✗ Branch 6 → 7 not taken.
✗ Branch 6 → 8 not taken.
✗ ImGui::PopStyleColor(2);
863 ✗ }
864
865 ✗ void imgui_readonly(const char* label, const Ypr& y)
866 {
867
0/6
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 8 not taken.
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 8 not taken.
✗ Branch 4 → 5 not taken.
✗ Branch 4 → 8 not taken.
✗ const auto temp = v3{ y.yaw.as_degrees() ,y.pitch.as_degrees(), y.roll.as_degrees() };
868
0/2
✗ Branch 6 → 7 not taken.
✗ Branch 6 → 8 not taken.
✗ imgui_readonly(label, temp);
869 ✗ }
870
871 ✗ bool imgui_edit(const char* label, std::string* str)
872 {
873 ✗ return ImGui::InputText(label, str);
874 }
875
876 ✗ void imgui_image(const char* name, const render::FrameBuffer& img, ImguiShaderCache* cache, ImageShader shader)
877 {
878 ✗ const auto texture_size = ImVec2{float_from_int(img.size.width), float_from_int(img.size.height)};
879
880 // todo(Gustav): make the arguments widget_size and zoom level AND make them configurable (with scrolling)
881 static float widget_height = 100.0f;
882 static float region_size = 32.0f;
883 static float hover_size = 128.0f;
884
0/2
✗ Branch 5 → 6 not taken.
✗ Branch 5 → 42 not taken.
✗ const auto& io = ImGui::GetIO();
885
886
0/4
✗ Branch 6 → 7 not taken.
✗ Branch 6 → 39 not taken.
✗ Branch 7 → 8 not taken.
✗ Branch 7 → 37 not taken.
✗ imgui_text(fmt::format("{}: {}x{}", name, texture_size.x, texture_size.y));
887
888 ✗ const auto widget_width = (texture_size.x / texture_size.y) * widget_height;
889 ✗ const auto widget_size = ImVec2{widget_width, widget_height};
890
891
0/2
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 42 not taken.
✗ const auto pos = ImGui::GetCursorScreenPos();
892 ✗ constexpr auto uv_min = ImVec2{0.0f, 1.0f}; // Top-left
893 ✗ constexpr auto uv_max = ImVec2{1.0f, 0.0f}; // Lower-right
894
0/2
✗ Branch 11 → 12 not taken.
✗ Branch 11 → 42 not taken.
✗ const auto border_col = ImGui::GetStyleColorVec4(ImGuiCol_Border);
895
896 static ImVec2 latest_tooltip;
897 static ImGuiID current_id = 0;
898
899 ✗ const constexpr char* const popup_id = "image config popup";
900
901
0/2
✗ Branch 12 → 13 not taken.
✗ Branch 12 → 42 not taken.
✗ draw_imgui_image(img, widget_size, uv_min, uv_max, border_col, cache, shader);
902
0/2
✗ Branch 13 → 14 not taken.
✗ Branch 13 → 42 not taken.
✗ const auto id = ImGui::GetID(name);
903
904
0/8
✗ Branch 14 → 15 not taken.
✗ Branch 14 → 18 not taken.
✗ Branch 15 → 16 not taken.
✗ Branch 15 → 42 not taken.
✗ Branch 16 → 17 not taken.
✗ Branch 16 → 18 not taken.
✗ Branch 19 → 20 not taken.
✗ Branch 19 → 29 not taken.
✗ if (id == current_id && ImGui::BeginPopupContextItem(popup_id))
905 {
906
0/2
✗ Branch 20 → 21 not taken.
✗ Branch 20 → 42 not taken.
✗ ImGui::DragFloat("Base", &widget_height, 1.0f);
907
0/2
✗ Branch 21 → 22 not taken.
✗ Branch 21 → 42 not taken.
✗ ImGui::DragFloat("Size", &region_size, 0.01f);
908
0/2
✗ Branch 22 → 23 not taken.
✗ Branch 22 → 42 not taken.
✗ ImGui::DragFloat("Scale", &hover_size, 1.0f);
909
0/2
✗ Branch 23 → 24 not taken.
✗ Branch 23 → 42 not taken.
✗ image_tooltip(img, texture_size, region_size, hover_size, latest_tooltip, widget_size, pos, border_col, cache, shader);
910
0/4
✗ Branch 25 → 26 not taken.
✗ Branch 25 → 41 not taken.
✗ Branch 26 → 27 not taken.
✗ Branch 26 → 28 not taken.
✗ if (ImGui::Button("Close"))
911 {
912
0/2
✗ Branch 27 → 28 not taken.
✗ Branch 27 → 42 not taken.
✗ ImGui::CloseCurrentPopup();
913 }
914
0/2
✗ Branch 28 → 29 not taken.
✗ Branch 28 → 42 not taken.
✗ ImGui::EndPopup();
915 }
916
0/2
✗ Branch 29 → 30 not taken.
✗ Branch 29 → 42 not taken.
✗ ImGui::OpenPopupOnItemClick(popup_id, ImGuiPopupFlags_MouseButtonRight);
917
918 // todo(Gustav): look into SetItemTooltip and BeginItemTooltip from https://github.com/ocornut/imgui/releases/tag/v1.89.7
919
0/4
✗ Branch 30 → 31 not taken.
✗ Branch 30 → 42 not taken.
✗ Branch 31 → 32 not taken.
✗ Branch 31 → 36 not taken.
✗ if (ImGui::IsItemHovered())
920 {
921 ✗ current_id = id;
922 ✗ latest_tooltip = io.MousePos;
923
0/4
✗ Branch 32 → 33 not taken.
✗ Branch 32 → 42 not taken.
✗ Branch 33 → 34 not taken.
✗ Branch 33 → 36 not taken.
✗ if (ImGui::BeginTooltip())
924 {
925
0/2
✗ Branch 34 → 35 not taken.
✗ Branch 34 → 42 not taken.
✗ image_tooltip(img, texture_size, region_size, hover_size, io.MousePos, widget_size, pos, border_col, cache, shader);
926
0/2
✗ Branch 35 → 36 not taken.
✗ Branch 35 → 42 not taken.
✗ ImGui::EndTooltip();
927 }
928 }
929 ✗ }
930
931 ✗ bool gear(const char* label, Rui* drag)
932 {
933
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 11 not taken.
✗ GearWidget widget{ label };
934
935
0/2
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 9 not taken.
✗ widget.widget(0, &drag->right);
936
0/2
✗ Branch 4 → 5 not taken.
✗ Branch 4 → 9 not taken.
✗ widget.widget(1, &drag->up);
937
0/2
✗ Branch 5 → 6 not taken.
✗ Branch 5 → 9 not taken.
✗ widget.widget(2, &drag->in);
938
939 ✗ return widget.value_changed;
940 ✗ }
941
942 ✗ bool gear(const char* label, v3* drag)
943 {
944
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 11 not taken.
✗ GearWidget widget{ label };
945
946
0/2
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 9 not taken.
✗ widget.widget(0, &drag->x);
947
0/2
✗ Branch 4 → 5 not taken.
✗ Branch 4 → 9 not taken.
✗ widget.widget(1, &drag->y);
948
0/2
✗ Branch 5 → 6 not taken.
✗ Branch 5 → 9 not taken.
✗ widget.widget(2, &drag->z);
949
950 ✗ return widget.value_changed;
951 ✗ }
952
953 ✗ bool gear(const char* label, Ypr* drag)
954 {
955
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 11 not taken.
✗ GearWidget widget{ label };
956
957
0/2
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 9 not taken.
✗ widget.widget(0, &drag->yaw);
958
0/2
✗ Branch 4 → 5 not taken.
✗ Branch 4 → 9 not taken.
✗ widget.widget(1, &drag->pitch);
959
0/2
✗ Branch 5 → 6 not taken.
✗ Branch 5 → 9 not taken.
✗ widget.widget(2, &drag->roll);
960
961 ✗ return widget.value_changed;
962 ✗ }
963
964 ✗ void transform(const m4& m)
965 {
966
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 9 not taken.
✗ const auto t = transform_from_matrix(m);
967
968
0/2
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 9 not taken.
✗ imgui_readonly("Position", t.position);
969
0/4
✗ Branch 4 → 5 not taken.
✗ Branch 4 → 8 not taken.
✗ Branch 5 → 6 not taken.
✗ Branch 5 → 8 not taken.
✗ imgui_readonly("Rotation", Ypr::from(t.rotation));
970
0/2
✗ Branch 6 → 7 not taken.
✗ Branch 6 → 9 not taken.
✗ imgui_readonly("Scale", t.scale);
971 ✗ }
972
973 ✗ bool transform(m4* m)
974 {
975
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 18 not taken.
✗ auto t = transform_from_matrix(*m);
976
977
0/2
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 18 not taken.
✗ ImGui::PushID(m);
978
979 ✗ bool changed = false;
980
981
0/4
✗ Branch 4 → 5 not taken.
✗ Branch 4 → 18 not taken.
✗ Branch 5 → 6 not taken.
✗ Branch 5 → 7 not taken.
✗ if (gear("Position", &t.position))
982 {
983 ✗ changed = true;
984
0/2
✗ Branch 6 → 7 not taken.
✗ Branch 6 → 18 not taken.
✗ *m = matrix_from_transform(t);
985 }
986
0/2
✗ Branch 7 → 8 not taken.
✗ Branch 7 → 18 not taken.
✗ auto y = Ypr::from(t.rotation);
987
0/4
✗ Branch 8 → 9 not taken.
✗ Branch 8 → 18 not taken.
✗ Branch 9 → 10 not taken.
✗ Branch 9 → 12 not taken.
✗ if (gear("Rotation", &y))
988 {
989 ✗ changed = true;
990
0/2
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 18 not taken.
✗ t.rotation = Q::from(y);
991
0/2
✗ Branch 11 → 12 not taken.
✗ Branch 11 → 18 not taken.
✗ *m = matrix_from_transform(t);
992 }
993
0/4
✗ Branch 12 → 13 not taken.
✗ Branch 12 → 18 not taken.
✗ Branch 13 → 14 not taken.
✗ Branch 13 → 15 not taken.
✗ if (gear("Scale", &t.scale))
994 {
995 ✗ changed = true;
996
0/2
✗ Branch 14 → 15 not taken.
✗ Branch 14 → 18 not taken.
✗ *m = matrix_from_transform(t);
997 }
998
999
0/2
✗ Branch 15 → 16 not taken.
✗ Branch 15 → 18 not taken.
✗ ImGui::PopID();
1000
1001 ✗ return changed;
1002 }
1003
1004 struct Drags
1005 {
1006 float v_speed = 1.0f;
1007 float v_min = 0.0f;
1008 float v_max = 0.0f;
1009 };
1010
1011 struct ButtonStateStyle
1012 {
1013 ImVec4 background;
1014 ImVec4 text;
1015 ImVec4 border;
1016 };
1017 struct ButtonStyle
1018 {
1019 ButtonStateStyle idle;
1020 ButtonStateStyle hover;
1021 ButtonStateStyle active;
1022 };
1023
1024 ✗ bool styled_button(const ButtonStyle& style, const char* label, const std::optional<ImVec2>& button_size = std::nullopt)
1025 {
1026
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 42 not taken.
✗ const ImVec2 label_size = ImGui::CalcTextSize(label, nullptr, true);
1027
0/2
✗ Branch 4 → 5 not taken.
✗ Branch 4 → 35 not taken.
✗ const ImVec2 size = button_size.value_or(calc_button_size(label_size));
1028
0/2
✗ Branch 6 → 7 not taken.
✗ Branch 6 → 42 not taken.
✗ const auto pos = ImGui::GetCursorScreenPos();
1029
0/2
✗ Branch 7 → 8 not taken.
✗ Branch 7 → 42 not taken.
✗ const auto ret = ImGui::InvisibleButton(label, size);
1030
0/2
✗ Branch 8 → 9 not taken.
✗ Branch 8 → 42 not taken.
✗ const auto min = ImGui::GetItemRectMin();
1031
0/2
✗ Branch 9 → 10 not taken.
✗ Branch 9 → 42 not taken.
✗ const auto max = ImGui::GetItemRectMax();
1032
1033
0/2
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 42 not taken.
✗ const auto& imstyle = ImGui::GetStyle();
1034
0/2
✗ Branch 11 → 12 not taken.
✗ Branch 11 → 42 not taken.
✗ const auto is_active = ImGui::IsItemActive();
1035
0/2
✗ Branch 12 → 13 not taken.
✗ Branch 12 → 42 not taken.
✗ const auto is_hover = ImGui::IsItemHovered();
1036
0/2
✗ Branch 13 → 14 not taken.
✗ Branch 13 → 42 not taken.
✗ auto* draw = ImGui::GetWindowDrawList();
1037
1038
0/4
✗ Branch 14 → 15 not taken.
✗ Branch 14 → 16 not taken.
✗ Branch 16 → 17 not taken.
✗ Branch 16 → 18 not taken.
✗ const auto& color = is_active ? style.active :
1039 (is_hover ? style.hover : style.idle);
1040
1041 // background
1042
0/4
✗ Branch 20 → 21 not taken.
✗ Branch 20 → 37 not taken.
✗ Branch 21 → 22 not taken.
✗ Branch 21 → 37 not taken.
✗ draw->AddRectFilled(min, max, ImColor{ color.background }, imstyle.ChildRounding);
1043
1044 // label
1045 {
1046 ✗ const auto bb = ImRect{ pos, pos + size };
1047
0/2
✗ Branch 24 → 25 not taken.
✗ Branch 24 → 40 not taken.
✗ ImGui::PushStyleColor(ImGuiCol_Text, color.text);
1048
0/2
✗ Branch 27 → 28 not taken.
✗ Branch 27 → 38 not taken.
✗ ImGui::RenderTextClipped(bb.Min + imstyle.FramePadding, bb.Max - imstyle.FramePadding, label, nullptr, &label_size, imstyle.ButtonTextAlign, &bb);
1049
0/2
✗ Branch 28 → 29 not taken.
✗ Branch 28 → 40 not taken.
✗ ImGui::PopStyleColor();
1050 }
1051
1052 // border
1053
0/4
✗ Branch 30 → 31 not taken.
✗ Branch 30 → 41 not taken.
✗ Branch 31 → 32 not taken.
✗ Branch 31 → 41 not taken.
✗ draw->AddRect(min, max, ImColor{color.border}, imstyle.ChildRounding);
1054
1055
0/2
✗ Branch 32 → 33 not taken.
✗ Branch 32 → 42 not taken.
✗ set_hover_cursor();
1056
1057 ✗ return ret;
1058 }
1059
1060 ✗ static ImVec2 calculate_square_button_size()
1061 {
1062 ✗ const float line_height = GImGui->FontSize + GImGui->Style.FramePadding.y * 2.0f;
1063 ✗ const ImVec2 button_size = { line_height + 3.0f, line_height };
1064 ✗ return button_size;
1065 }
1066
1067 ✗ static bool imgui_drag_with_reset(
1068 float* xval, float* yval, float* zval,
1069 float reset_value, std::optional<float> extra_spacing,
1070 const char* xlabel, const char* ylabel, const char* zlabel,
1071 const Drags& drags = Drags{}
1072 )
1073 {
1074 ✗ bool changed = false;
1075
1076 ✗ const auto button_size = calculate_square_button_size();
1077
1078
0/2
✗ Branch 4 → 5 not taken.
✗ Branch 4 → 23 not taken.
✗ ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2{ 0, 0 });
1079
0/2
✗ Branch 5 → 6 not taken.
✗ Branch 5 → 25 not taken.
✗ const auto full_width = ImGui::CalcItemWidth();
1080
0/2
✗ Branch 7 → 8 not taken.
✗ Branch 7 → 10 not taken.
✗ if (extra_spacing.has_value())
1081 {
1082
0/2
✗ Branch 9 → 10 not taken.
✗ Branch 9 → 25 not taken.
✗ ImGui::PushItemWidth(*extra_spacing);
1083 }
1084
0/2
✗ Branch 11 → 12 not taken.
✗ Branch 11 → 24 not taken.
✗ ImGui::PushMultiItemsWidths(3, full_width - 3 * button_size.x - extra_spacing.value_or(0.0f));
1085
1086 ✗ constexpr auto red_style = ButtonStyle
1087 {
1088 .idle = {
1089 .background = rgb(76, 5, 25),
1090 .text = rgb(251, 113, 133),
1091 .border = rgb(159, 18, 57)
1092 },
1093 .hover = {
1094 .background = rgb(244, 63, 94),
1095 .text = rgb(11, 14, 19),
1096 .border = rgb(251, 113, 133)
1097 },
1098 .active = {
1099 .background = rgb(69, 36, 46),
1100 .text = rgb(251, 113, 133),
1101 .border = rgb(244, 63, 94)
1102 }
1103 };
1104 ✗ constexpr auto green_style = ButtonStyle
1105 {
1106 .idle = {
1107 .background = rgb(2, 44, 34),
1108 .text = rgb(158, 233, 57),
1109 .border = rgb(6, 95, 70)
1110 },
1111 .hover = {
1112 .background = rgb(158, 233, 57),
1113 .text = rgb(11, 14, 19),
1114 .border = rgb(190, 242, 100)
1115 },
1116 .active = {
1117 .background = rgb(52, 70, 38),
1118 .text = rgb(158, 233, 57),
1119 .border = rgb(158, 233, 57)
1120 }
1121 };
1122 ✗ constexpr auto blue_style = ButtonStyle
1123 {
1124 .idle = {
1125 .background = rgb(8, 47, 73),
1126 .text = rgb(56, 189, 248),
1127 .border = rgb(7, 89, 133)
1128 },
1129 .hover = {
1130 .background = rgb(14, 165, 233),
1131 .text = rgb(11, 14, 19),
1132 .border = rgb(56, 189, 248)
1133 },
1134 .active = {
1135 .background = rgb(23, 56, 74),
1136 .text = rgb(56, 189, 248),
1137 .border = rgb(14, 165, 233)
1138 }
1139 };
1140
1141 ✗ const auto draw_component = [&button_size, reset_value, &changed, &drags](const ButtonStyle& style, const char* label, float* val, const char* id)
1142 {
1143 // ImGui::Button(label, button_size);
1144
0/2
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 29 not taken.
✗ const auto button_clicked = styled_button(style, label, button_size);
1145
1146
0/4
✗ Branch 4 → 5 not taken.
✗ Branch 4 → 31 not taken.
✗ Branch 5 → 6 not taken.
✗ Branch 5 → 7 not taken.
✗ if (gear_functionality(val))
1147 {
1148 ✗ changed = true;
1149 }
1150
1151
0/2
✗ Branch 7 → 8 not taken.
✗ Branch 7 → 9 not taken.
✗ if (button_clicked)
1152 {
1153 ✗ *val = reset_value;
1154 ✗ changed = true;
1155 }
1156
0/2
✗ Branch 9 → 10 not taken.
✗ Branch 9 → 31 not taken.
✗ const auto min = ImGui::GetItemRectMin();
1157
0/2
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 31 not taken.
✗ ImGui::SameLine();
1158
0/2
✗ Branch 11 → 12 not taken.
✗ Branch 11 → 31 not taken.
✗ const auto dragging = ImGui::DragFloat(id, val, drags.v_speed, drags.v_min, drags.v_max, "%.2f");
1159
0/2
✗ Branch 12 → 13 not taken.
✗ Branch 12 → 14 not taken.
✗ if (dragging)
1160 {
1161 ✗ changed = true;
1162 }
1163
0/2
✗ Branch 14 → 15 not taken.
✗ Branch 14 → 31 not taken.
✗ const auto max = ImGui::GetItemRectMax();
1164
1165
0/8
✗ Branch 15 → 16 not taken.
✗ Branch 15 → 18 not taken.
✗ Branch 16 → 17 not taken.
✗ Branch 16 → 31 not taken.
✗ Branch 17 → 18 not taken.
✗ Branch 17 → 19 not taken.
✗ Branch 20 → 21 not taken.
✗ Branch 20 → 27 not taken.
✗ if (dragging || ImGui::IsItemActive())
1166 {
1167
0/2
✗ Branch 21 → 22 not taken.
✗ Branch 21 → 31 not taken.
✗ auto* draw = ImGui::GetWindowDrawList();
1168
0/6
✗ Branch 22 → 23 not taken.
✗ Branch 22 → 31 not taken.
✗ Branch 24 → 25 not taken.
✗ Branch 24 → 30 not taken.
✗ Branch 25 → 26 not taken.
✗ Branch 25 → 30 not taken.
✗ draw->AddRect(min, max, ImColor{ style.active.border }, ImGui::GetStyle().ChildRounding);
1169 }
1170
1171
0/2
✗ Branch 27 → 28 not taken.
✗ Branch 27 → 31 not taken.
✗ ImGui::PopItemWidth();
1172 ✗ };
1173
1174
0/2
✗ Branch 12 → 13 not taken.
✗ Branch 12 → 25 not taken.
✗ draw_component(red_style, xlabel, xval, "##X");
1175
1176
0/2
✗ Branch 13 → 14 not taken.
✗ Branch 13 → 25 not taken.
✗ ImGui::SameLine();
1177
1178
0/2
✗ Branch 14 → 15 not taken.
✗ Branch 14 → 25 not taken.
✗ draw_component(green_style, ylabel, yval, "##Y");
1179
1180
0/2
✗ Branch 15 → 16 not taken.
✗ Branch 15 → 25 not taken.
✗ ImGui::SameLine();
1181
1182
0/2
✗ Branch 16 → 17 not taken.
✗ Branch 16 → 25 not taken.
✗ draw_component(blue_style, zlabel, zval, "##Z");
1183
1184
0/2
✗ Branch 17 → 18 not taken.
✗ Branch 17 → 25 not taken.
✗ ImGui::PopStyleVar();
1185
1186
0/2
✗ Branch 19 → 20 not taken.
✗ Branch 19 → 21 not taken.
✗ if (extra_spacing.has_value())
1187 {
1188
0/2
✗ Branch 20 → 21 not taken.
✗ Branch 20 → 25 not taken.
✗ ImGui::SameLine();
1189 }
1190
1191 ✗ return changed;
1192 }
1193
1194 constexpr const char* default_format = "%.2f";
1195
1196 ✗ std::string format_float(float f)
1197 {
1198 char text[32];
1199
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 12 not taken.
✗ ImFormatString(text, IM_ARRAYSIZE(text), default_format, static_cast<double>(f));
1200
0/2
✗ Branch 5 → 6 not taken.
✗ Branch 5 → 9 not taken.
✗ return text;
1201 }
1202
1203 ✗ float float_identity(float f)
1204 {
1205 ✗ return f;
1206 }
1207
1208 template<typename F>
1209 ✗ bool flat_slider(const char* label, float* value, float min_value, float max_value, F display)
1210 {
1211 ✗ ImGuiWindow* window = ImGui::GetCurrentWindow();
1212
0/4
bool eu::imgui::flat_slider<float (*)(float)>(char const*, float*, float, float, float (*)(float)):
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 5 not taken.
bool eu::imgui::flat_slider<eu::imgui::simple_gamma_slider(char const*, float*, float, float, float)::{lambda(float)#1}>(char const*, float*, float, float, eu::imgui::simple_gamma_slider(char const*, float*, float, float, float)::{lambda(float)#1}):
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 5 not taken.
✗ if (window->SkipItems)
1213 {
1214 ✗ return false;
1215 }
1216
1217
0/4
bool eu::imgui::flat_slider<float (*)(float)>(char const*, float*, float, float, float (*)(float)):
✗ Branch 5 → 6 not taken.
✗ Branch 5 → 101 not taken.
bool eu::imgui::flat_slider<eu::imgui::simple_gamma_slider(char const*, float*, float, float, float)::{lambda(float)#1}>(char const*, float*, float, float, eu::imgui::simple_gamma_slider(char const*, float*, float, float, float)::{lambda(float)#1}):
✗ Branch 5 → 6 not taken.
✗ Branch 5 → 101 not taken.
✗ const auto& style = ImGui::GetStyle();
1218
0/4
bool eu::imgui::flat_slider<float (*)(float)>(char const*, float*, float, float, float (*)(float)):
✗ Branch 6 → 7 not taken.
✗ Branch 6 → 101 not taken.
bool eu::imgui::flat_slider<eu::imgui::simple_gamma_slider(char const*, float*, float, float, float)::{lambda(float)#1}>(char const*, float*, float, float, eu::imgui::simple_gamma_slider(char const*, float*, float, float, float)::{lambda(float)#1}):
✗ Branch 6 → 7 not taken.
✗ Branch 6 → 101 not taken.
✗ const auto id = window->GetID(label);
1219
1220
0/10
bool eu::imgui::flat_slider<float (*)(float)>(char const*, float*, float, float, float (*)(float)):
✗ Branch 7 → 8 not taken.
✗ Branch 7 → 84 not taken.
✗ Branch 8 → 9 not taken.
✗ Branch 8 → 84 not taken.
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 82 not taken.
bool eu::imgui::flat_slider<eu::imgui::simple_gamma_slider(char const*, float*, float, float, float)::{lambda(float)#1}>(char const*, float*, float, float, eu::imgui::simple_gamma_slider(char const*, float*, float, float, float)::{lambda(float)#1}):
✗ Branch 8 → 9 not taken.
✗ Branch 8 → 84 not taken.
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 82 not taken.
✗ const auto max_label_width = ImGui::CalcTextSize(format_float(display(max_value)).c_str()).x;
1221
0/6
bool eu::imgui::flat_slider<float (*)(float)>(char const*, float*, float, float, float (*)(float)):
✗ Branch 12 → 13 not taken.
✗ Branch 12 → 101 not taken.
✗ Branch 13 → 14 not taken.
✗ Branch 13 → 101 not taken.
bool eu::imgui::flat_slider<eu::imgui::simple_gamma_slider(char const*, float*, float, float, float)::{lambda(float)#1}>(char const*, float*, float, float, eu::imgui::simple_gamma_slider(char const*, float*, float, float, float)::{lambda(float)#1}):
✗ Branch 13 → 14 not taken.
✗ Branch 13 → 101 not taken.
✗ const auto text = format_float(display(*value));
1222
1223
0/4
bool eu::imgui::flat_slider<float (*)(float)>(char const*, float*, float, float, float (*)(float)):
✗ Branch 14 → 15 not taken.
✗ Branch 14 → 99 not taken.
bool eu::imgui::flat_slider<eu::imgui::simple_gamma_slider(char const*, float*, float, float, float)::{lambda(float)#1}>(char const*, float*, float, float, eu::imgui::simple_gamma_slider(char const*, float*, float, float, float)::{lambda(float)#1}):
✗ Branch 14 → 15 not taken.
✗ Branch 14 → 99 not taken.
✗ const auto height = ImGui::GetFrameHeight();
1224 ✗ const auto radius = height * 0.3f;
1225 ✗ const auto ring_radius = height * 0.4f;
1226
1227 ✗ const ImVec2 pos = window->DC.CursorPos;
1228
0/4
bool eu::imgui::flat_slider<float (*)(float)>(char const*, float*, float, float, float (*)(float)):
✗ Branch 15 → 16 not taken.
✗ Branch 15 → 85 not taken.
bool eu::imgui::flat_slider<eu::imgui::simple_gamma_slider(char const*, float*, float, float, float)::{lambda(float)#1}>(char const*, float*, float, float, eu::imgui::simple_gamma_slider(char const*, float*, float, float, float)::{lambda(float)#1}):
✗ Branch 15 → 16 not taken.
✗ Branch 15 → 85 not taken.
✗ const ImRect total(pos, pos + ImVec2(ImGui::CalcItemWidth(), height));
1229 ✗ const ImRect track_bb(pos, ImVec2(total.Max.x - max_label_width - style.ItemInnerSpacing.x * 2, total.Max.y));
1230
1231
0/4
bool eu::imgui::flat_slider<float (*)(float)>(char const*, float*, float, float, float (*)(float)):
✗ Branch 21 → 22 not taken.
✗ Branch 21 → 99 not taken.
bool eu::imgui::flat_slider<eu::imgui::simple_gamma_slider(char const*, float*, float, float, float)::{lambda(float)#1}>(char const*, float*, float, float, eu::imgui::simple_gamma_slider(char const*, float*, float, float, float)::{lambda(float)#1}):
✗ Branch 21 → 22 not taken.
✗ Branch 21 → 99 not taken.
✗ ImGui::ItemSize(total, style.FramePadding.y);
1232
0/8
bool eu::imgui::flat_slider<float (*)(float)>(char const*, float*, float, float, float (*)(float)):
✗ Branch 22 → 23 not taken.
✗ Branch 22 → 99 not taken.
✗ Branch 23 → 24 not taken.
✗ Branch 23 → 25 not taken.
bool eu::imgui::flat_slider<eu::imgui::simple_gamma_slider(char const*, float*, float, float, float)::{lambda(float)#1}>(char const*, float*, float, float, eu::imgui::simple_gamma_slider(char const*, float*, float, float, float)::{lambda(float)#1}):
✗ Branch 22 → 23 not taken.
✗ Branch 22 → 99 not taken.
✗ Branch 23 → 24 not taken.
✗ Branch 23 → 25 not taken.
✗ if (!ImGui::ItemAdd(total, id))
1233 {
1234 ✗ return false;
1235 }
1236
1237
0/4
bool eu::imgui::flat_slider<float (*)(float)>(char const*, float*, float, float, float (*)(float)):
✗ Branch 25 → 26 not taken.
✗ Branch 25 → 99 not taken.
bool eu::imgui::flat_slider<eu::imgui::simple_gamma_slider(char const*, float*, float, float, float)::{lambda(float)#1}>(char const*, float*, float, float, eu::imgui::simple_gamma_slider(char const*, float*, float, float, float)::{lambda(float)#1}):
✗ Branch 25 → 26 not taken.
✗ Branch 25 → 99 not taken.
✗ const bool is_hovering = ImGui::ItemHoverable(track_bb, id, ImGuiItemFlags_None);
1238
0/20
bool eu::imgui::flat_slider<float (*)(float)>(char const*, float*, float, float, float (*)(float)):
✗ Branch 26 → 27 not taken.
✗ Branch 26 → 29 not taken.
✗ Branch 27 → 28 not taken.
✗ Branch 27 → 99 not taken.
✗ Branch 28 → 29 not taken.
✗ Branch 28 → 30 not taken.
✗ Branch 29 → 30 not taken.
✗ Branch 29 → 31 not taken.
✗ Branch 32 → 33 not taken.
✗ Branch 32 → 36 not taken.
bool eu::imgui::flat_slider<eu::imgui::simple_gamma_slider(char const*, float*, float, float, float)::{lambda(float)#1}>(char const*, float*, float, float, eu::imgui::simple_gamma_slider(char const*, float*, float, float, float)::{lambda(float)#1}):
✗ Branch 26 → 27 not taken.
✗ Branch 26 → 29 not taken.
✗ Branch 27 → 28 not taken.
✗ Branch 27 → 99 not taken.
✗ Branch 28 → 29 not taken.
✗ Branch 28 → 30 not taken.
✗ Branch 29 → 30 not taken.
✗ Branch 29 → 31 not taken.
✗ Branch 32 → 33 not taken.
✗ Branch 32 → 36 not taken.
✗ if ((is_hovering && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) || GImGui->NavActivateId == id)
1239 {
1240
0/4
bool eu::imgui::flat_slider<float (*)(float)>(char const*, float*, float, float, float (*)(float)):
✗ Branch 33 → 34 not taken.
✗ Branch 33 → 99 not taken.
bool eu::imgui::flat_slider<eu::imgui::simple_gamma_slider(char const*, float*, float, float, float)::{lambda(float)#1}>(char const*, float*, float, float, eu::imgui::simple_gamma_slider(char const*, float*, float, float, float)::{lambda(float)#1}):
✗ Branch 33 → 34 not taken.
✗ Branch 33 → 99 not taken.
✗ ImGui::SetActiveID(id, window);
1241
0/4
bool eu::imgui::flat_slider<float (*)(float)>(char const*, float*, float, float, float (*)(float)):
✗ Branch 34 → 35 not taken.
✗ Branch 34 → 99 not taken.
bool eu::imgui::flat_slider<eu::imgui::simple_gamma_slider(char const*, float*, float, float, float)::{lambda(float)#1}>(char const*, float*, float, float, eu::imgui::simple_gamma_slider(char const*, float*, float, float, float)::{lambda(float)#1}):
✗ Branch 34 → 35 not taken.
✗ Branch 34 → 99 not taken.
✗ ImGui::SetFocusID(id, window);
1242
0/4
bool eu::imgui::flat_slider<float (*)(float)>(char const*, float*, float, float, float (*)(float)):
✗ Branch 35 → 36 not taken.
✗ Branch 35 → 99 not taken.
bool eu::imgui::flat_slider<eu::imgui::simple_gamma_slider(char const*, float*, float, float, float)::{lambda(float)#1}>(char const*, float*, float, float, eu::imgui::simple_gamma_slider(char const*, float*, float, float, float)::{lambda(float)#1}):
✗ Branch 35 → 36 not taken.
✗ Branch 35 → 99 not taken.
✗ ImGui::FocusWindow(window);
1243 }
1244
1245
0/4
bool eu::imgui::flat_slider<float (*)(float)>(char const*, float*, float, float, float (*)(float)):
✗ Branch 36 → 37 not taken.
✗ Branch 36 → 99 not taken.
bool eu::imgui::flat_slider<eu::imgui::simple_gamma_slider(char const*, float*, float, float, float)::{lambda(float)#1}>(char const*, float*, float, float, eu::imgui::simple_gamma_slider(char const*, float*, float, float, float)::{lambda(float)#1}):
✗ Branch 36 → 37 not taken.
✗ Branch 36 → 99 not taken.
✗ ImGui::PushStyleVar(ImGuiStyleVar_GrabMinSize, radius * 2.0f);
1246 ✗ ImRect grab;
1247
0/4
bool eu::imgui::flat_slider<float (*)(float)>(char const*, float*, float, float, float (*)(float)):
✗ Branch 37 → 38 not taken.
✗ Branch 37 → 99 not taken.
bool eu::imgui::flat_slider<eu::imgui::simple_gamma_slider(char const*, float*, float, float, float)::{lambda(float)#1}>(char const*, float*, float, float, eu::imgui::simple_gamma_slider(char const*, float*, float, float, float)::{lambda(float)#1}):
✗ Branch 37 → 38 not taken.
✗ Branch 37 → 99 not taken.
✗ const bool value_was_changed = ImGui::SliderBehavior(track_bb, id, ImGuiDataType_Float, value, &min_value, &max_value, default_format, 0, &grab);
1248
0/4
bool eu::imgui::flat_slider<float (*)(float)>(char const*, float*, float, float, float (*)(float)):
✗ Branch 38 → 39 not taken.
✗ Branch 38 → 99 not taken.
bool eu::imgui::flat_slider<eu::imgui::simple_gamma_slider(char const*, float*, float, float, float)::{lambda(float)#1}>(char const*, float*, float, float, eu::imgui::simple_gamma_slider(char const*, float*, float, float, float)::{lambda(float)#1}):
✗ Branch 38 → 39 not taken.
✗ Branch 38 → 99 not taken.
✗ ImGui::PopStyleVar();
1249
0/4
bool eu::imgui::flat_slider<float (*)(float)>(char const*, float*, float, float, float (*)(float)):
✗ Branch 39 → 40 not taken.
✗ Branch 39 → 41 not taken.
bool eu::imgui::flat_slider<eu::imgui::simple_gamma_slider(char const*, float*, float, float, float)::{lambda(float)#1}>(char const*, float*, float, float, eu::imgui::simple_gamma_slider(char const*, float*, float, float, float)::{lambda(float)#1}):
✗ Branch 39 → 40 not taken.
✗ Branch 39 → 41 not taken.
✗ if (value_was_changed)
1250 {
1251
0/4
bool eu::imgui::flat_slider<float (*)(float)>(char const*, float*, float, float, float (*)(float)):
✗ Branch 40 → 41 not taken.
✗ Branch 40 → 99 not taken.
bool eu::imgui::flat_slider<eu::imgui::simple_gamma_slider(char const*, float*, float, float, float)::{lambda(float)#1}>(char const*, float*, float, float, eu::imgui::simple_gamma_slider(char const*, float*, float, float, float)::{lambda(float)#1}):
✗ Branch 40 → 41 not taken.
✗ Branch 40 → 99 not taken.
✗ ImGui::MarkItemEdited(id);
1252 }
1253
1254 ✗ ImDrawList* draw = window->DrawList;
1255 ✗ const auto center_y = track_bb.GetCenter().y;
1256 ✗ const auto grab_center = grab.GetCenter().x;
1257 ✗ const auto is_active = GImGui->ActiveId == id;
1258
1259 {
1260 ✗ const auto track_h = ImMax(2.0f, height * 0.2f);
1261
1262 ✗ const auto left = track_bb.Min.x + radius;
1263 ✗ const auto right = track_bb.Max.x - radius;
1264 ✗ const auto top = center_y - track_h * 0.5f;
1265 ✗ const auto bottom = center_y + track_h * 0.5f;
1266 ✗ const auto half_circle_rounding = track_h * 0.5f;
1267
1268 ✗ constexpr auto background_color = rgb(11, 14, 19);
1269 ✗ constexpr auto filled_color = rgb(132, 204, 22);
1270
1271 ✗ const auto middle = std::max(left, grab_center);
1272
1273
0/8
bool eu::imgui::flat_slider<float (*)(float)>(char const*, float*, float, float, float (*)(float)):
✗ Branch 46 → 47 not taken.
✗ Branch 46 → 89 not taken.
✗ Branch 49 → 50 not taken.
✗ Branch 49 → 87 not taken.
bool eu::imgui::flat_slider<eu::imgui::simple_gamma_slider(char const*, float*, float, float, float)::{lambda(float)#1}>(char const*, float*, float, float, eu::imgui::simple_gamma_slider(char const*, float*, float, float, float)::{lambda(float)#1}):
✗ Branch 46 → 47 not taken.
✗ Branch 46 → 89 not taken.
✗ Branch 49 → 50 not taken.
✗ Branch 49 → 87 not taken.
✗ draw->AddRectFilled({ left, top }, { middle, bottom }, ImColor{filled_color}, half_circle_rounding);
1274
0/8
bool eu::imgui::flat_slider<float (*)(float)>(char const*, float*, float, float, float (*)(float)):
✗ Branch 51 → 52 not taken.
✗ Branch 51 → 92 not taken.
✗ Branch 54 → 55 not taken.
✗ Branch 54 → 90 not taken.
bool eu::imgui::flat_slider<eu::imgui::simple_gamma_slider(char const*, float*, float, float, float)::{lambda(float)#1}>(char const*, float*, float, float, eu::imgui::simple_gamma_slider(char const*, float*, float, float, float)::{lambda(float)#1}):
✗ Branch 51 → 52 not taken.
✗ Branch 51 → 92 not taken.
✗ Branch 54 → 55 not taken.
✗ Branch 54 → 90 not taken.
✗ draw->AddRectFilled({ middle, top }, { right, bottom }, ImColor{background_color}, half_circle_rounding);
1275 }
1276
1277 {
1278 ✗ constexpr auto default_color = rgb(225, 226, 233);
1279 ✗ constexpr auto hover_color = rgb(163, 230, 53);
1280 ✗ constexpr auto active_color = rgb(132, 204, 22);
1281
1282
0/8
bool eu::imgui::flat_slider<float (*)(float)>(char const*, float*, float, float, float (*)(float)):
✗ Branch 55 → 56 not taken.
✗ Branch 55 → 57 not taken.
✗ Branch 57 → 58 not taken.
✗ Branch 57 → 59 not taken.
bool eu::imgui::flat_slider<eu::imgui::simple_gamma_slider(char const*, float*, float, float, float)::{lambda(float)#1}>(char const*, float*, float, float, eu::imgui::simple_gamma_slider(char const*, float*, float, float, float)::{lambda(float)#1}):
✗ Branch 55 → 56 not taken.
✗ Branch 55 → 57 not taken.
✗ Branch 57 → 58 not taken.
✗ Branch 57 → 59 not taken.
✗ const auto grab_color = is_active ? active_color
1283 : is_hovering ? hover_color
1284 : default_color;
1285
1286 ✗ const auto center = ImVec2{ grab_center, center_y };
1287
0/8
bool eu::imgui::flat_slider<float (*)(float)>(char const*, float*, float, float, float (*)(float)):
✗ Branch 62 → 63 not taken.
✗ Branch 62 → 94 not taken.
✗ Branch 63 → 64 not taken.
✗ Branch 63 → 94 not taken.
bool eu::imgui::flat_slider<eu::imgui::simple_gamma_slider(char const*, float*, float, float, float)::{lambda(float)#1}>(char const*, float*, float, float, eu::imgui::simple_gamma_slider(char const*, float*, float, float, float)::{lambda(float)#1}):
✗ Branch 62 → 63 not taken.
✗ Branch 62 → 94 not taken.
✗ Branch 63 → 64 not taken.
✗ Branch 63 → 94 not taken.
✗ draw->AddCircleFilled(center, radius, ImColor{ grab_color });
1288
1289
0/8
bool eu::imgui::flat_slider<float (*)(float)>(char const*, float*, float, float, float (*)(float)):
✗ Branch 64 → 65 not taken.
✗ Branch 64 → 66 not taken.
✗ Branch 65 → 66 not taken.
✗ Branch 65 → 73 not taken.
bool eu::imgui::flat_slider<eu::imgui::simple_gamma_slider(char const*, float*, float, float, float)::{lambda(float)#1}>(char const*, float*, float, float, eu::imgui::simple_gamma_slider(char const*, float*, float, float, float)::{lambda(float)#1}):
✗ Branch 64 → 65 not taken.
✗ Branch 64 → 66 not taken.
✗ Branch 65 → 66 not taken.
✗ Branch 65 → 73 not taken.
✗ if (is_active || is_hovering)
1290 {
1291 ✗ constexpr auto line_thickness = 3.0f;
1292 ✗ constexpr auto ring_hover = rgb(190, 242, 100);
1293 ✗ constexpr auto ring_active = rgb(158, 233, 57);
1294
1295
0/4
bool eu::imgui::flat_slider<float (*)(float)>(char const*, float*, float, float, float (*)(float)):
✗ Branch 66 → 67 not taken.
✗ Branch 66 → 68 not taken.
bool eu::imgui::flat_slider<eu::imgui::simple_gamma_slider(char const*, float*, float, float, float)::{lambda(float)#1}>(char const*, float*, float, float, eu::imgui::simple_gamma_slider(char const*, float*, float, float, float)::{lambda(float)#1}):
✗ Branch 66 → 67 not taken.
✗ Branch 66 → 68 not taken.
✗ const auto ring_color = is_active ? ring_active : ring_hover;
1296
1297
0/8
bool eu::imgui::flat_slider<float (*)(float)>(char const*, float*, float, float, float (*)(float)):
✗ Branch 70 → 71 not taken.
✗ Branch 70 → 95 not taken.
✗ Branch 71 → 72 not taken.
✗ Branch 71 → 95 not taken.
bool eu::imgui::flat_slider<eu::imgui::simple_gamma_slider(char const*, float*, float, float, float)::{lambda(float)#1}>(char const*, float*, float, float, eu::imgui::simple_gamma_slider(char const*, float*, float, float, float)::{lambda(float)#1}):
✗ Branch 70 → 71 not taken.
✗ Branch 70 → 95 not taken.
✗ Branch 71 → 72 not taken.
✗ Branch 71 → 95 not taken.
✗ draw->AddCircle(center, ring_radius, ImColor{ ring_color }, 0, line_thickness);
1298 }
1299 }
1300
1301
0/4
bool eu::imgui::flat_slider<float (*)(float)>(char const*, float*, float, float, float (*)(float)):
✗ Branch 74 → 75 not taken.
✗ Branch 74 → 99 not taken.
bool eu::imgui::flat_slider<eu::imgui::simple_gamma_slider(char const*, float*, float, float, float)::{lambda(float)#1}>(char const*, float*, float, float, eu::imgui::simple_gamma_slider(char const*, float*, float, float, float)::{lambda(float)#1}):
✗ Branch 74 → 75 not taken.
✗ Branch 74 → 99 not taken.
✗ const auto text_size = ImGui::CalcTextSize(text.c_str());
1302
0/4
bool eu::imgui::flat_slider<float (*)(float)>(char const*, float*, float, float, float (*)(float)):
✗ Branch 77 → 78 not taken.
✗ Branch 77 → 98 not taken.
bool eu::imgui::flat_slider<eu::imgui::simple_gamma_slider(char const*, float*, float, float, float)::{lambda(float)#1}>(char const*, float*, float, float, eu::imgui::simple_gamma_slider(char const*, float*, float, float, float)::{lambda(float)#1}):
✗ Branch 77 → 78 not taken.
✗ Branch 77 → 98 not taken.
✗ draw->AddText({ total.Max.x - text_size.x, center_y - text_size.y * 0.5f }, IM_COL32(170, 230, 60, 255), text.c_str());
1303
1304 ✗ return value_was_changed;
1305 ✗ }
1306
1307
1308 ✗ bool simple_gamma_slider(const char* label, float* gamma, float curve, float min_gamma, float max_gamma)
1309 {
1310
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 5 not taken.
✗ if (curve < 0.0f)
1311 {
1312
0/2
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 13 not taken.
✗ return flat_slider(label, gamma, min_gamma, max_gamma, float_identity);
1313 }
1314
1315 // todo(Gustav): is this the correct way? it doesn't feel exactly right but perhaps that's just dear imgui
1316 ✗ const auto gamma_range = max_gamma - min_gamma;
1317 ✗ const auto t = (*gamma - min_gamma) / gamma_range;
1318
1319 ✗ const auto backf = [min_gamma, gamma_range, curve](float f)
1320 {
1321 ✗ const auto perceptual = std::pow(f, curve);
1322 ✗ return min_gamma + perceptual * gamma_range;
1323 ✗ };
1324
1325 ✗ auto slider_value = std::pow(t, 1.0f / curve);
1326
0/4
✗ Branch 6 → 7 not taken.
✗ Branch 6 → 13 not taken.
✗ Branch 7 → 8 not taken.
✗ Branch 7 → 9 not taken.
✗ if (flat_slider(label, &slider_value, 0.0f, 1.0f, backf) == false)
1327 {
1328 ✗ return false;
1329 }
1330
1331 ✗ *gamma = backf(slider_value);
1332 ✗ return true;
1333 }
1334
1335 // -----
1336 ✗ void row(const PropTable& t, const char* label)
1337 {
1338
0/6
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 10 not taken.
✗ Branch 5 → 6 not taken.
✗ Branch 5 → 10 not taken.
✗ Branch 7 → 8 not taken.
✗ Branch 7 → 15 not taken.
✗ ASSERT(t);
1339 ✗ ImGui::TableNextRow();
1340 ✗ ImGui::TableSetColumnIndex(0);
1341 ✗ eu::imgui::label(label);
1342 ✗ ImGui::TableSetColumnIndex(1);
1343 ✗ }
1344
1345 template<typename F>
1346 ✗ bool trow(const PropTable& t, const char* label, const F& f)
1347 {
1348 ✗ row(t, label);
1349 ✗ ImGui::PushID(label);
1350 ✗ ImGui::PushItemWidth(-std::numeric_limits<float>::min()); // place widget right edge on the edge of the table
1351 ✗ const auto ret = f();
1352 ✗ ImGui::PopItemWidth();
1353 ✗ ImGui::PopID();
1354 ✗ return ret;
1355 }
1356
1357
1358 ✗ PropTable::PropTable(PropTable&& r) noexcept
1359 ✗ : started(r.started)
1360 {
1361 ✗ r.started = false;
1362 ✗ }
1363
1364 ✗ PropTable PropTable::begin(const char* label)
1365 {
1366 ✗ constexpr ImGuiTableFlags table_flags = ImGuiTableFlags_SizingStretchProp;
1367
1368
0/2
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 12 not taken.
✗ const auto started = ImGui::BeginTable(label, 2, table_flags);
1369 ✗ auto ret = PropTable{ started };
1370
0/2
✗ Branch 5 → 6 not taken.
✗ Branch 5 → 7 not taken.
✗ if (started == false) return ret;
1371
1372
0/2
✗ Branch 7 → 8 not taken.
✗ Branch 7 → 13 not taken.
✗ ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed);
1373
0/2
✗ Branch 8 → 9 not taken.
✗ Branch 8 → 13 not taken.
✗ ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch);
1374
1375 // Should we display header???
1376
0/2
✗ Branch 9 → 10 not taken.
✗ Branch 9 → 13 not taken.
✗ ImGui::TableHeadersRow();
1377
1378 ✗ return ret;
1379 ✗ }
1380
1381 ✗ PropTable::~PropTable()
1382 {
1383
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 4 not taken.
✗ if (started == false) return;
1384 ✗ ImGui::EndTable();
1385 ✗ }
1386
1387 ✗ bool PropTable::check(const char* label, bool* v) const
1388 {
1389
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 6 not taken.
✗ return trow(*this, label, [&]
1390 {
1391 ✗ return ImGui::Checkbox("", v);
1392 ✗ });
1393 }
1394
1395 ✗ bool PropTable::slider(const char* label, float* v, float min, float max) const
1396 {
1397
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 6 not taken.
✗ return trow(*this, label, [&]
1398 {
1399 ✗ const auto ret = flat_slider("", v, min, max, float_identity);
1400 ✗ return ret;
1401 ✗ });
1402 }
1403 ✗ bool PropTable::slider(const char* label, float* v) const
1404 {
1405 ✗ return slider(label, v, 0.0f, 1.0f);
1406 }
1407
1408 ✗ void PropTable::text(const char* label, const std::string& str) const
1409 {
1410
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 4 not taken.
✗ trow(*this, label, [&]
1411 {
1412 ✗ eu::imgui::imgui_text(str);
1413 ✗ return false;
1414 });
1415 ✗ }
1416
1417 ✗ bool PropTable::edit(const char* label, std::string* str) const
1418 {
1419
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 6 not taken.
✗ return trow(*this, label, [&]
1420 {
1421 ✗ return eu::imgui::imgui_edit("", str);
1422 ✗ });
1423 }
1424
1425 ✗ void PropTable::display(const char* label, bool b) const
1426 {
1427
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 4 not taken.
✗ trow(*this, label, [&]
1428 {
1429
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 4 not taken.
✗ ImGui::TextUnformatted(b ? "TRUE" : "false");
1430 ✗ return false;
1431 });
1432 ✗ }
1433
1434 ✗ void PropTable::readonly(const char* label, const std::string& text) const
1435 {
1436
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 4 not taken.
✗ trow(*this, label, [&]
1437 {
1438 ✗ eu::imgui::imgui_readonly("", text);
1439 ✗ return false;
1440 });
1441 ✗ }
1442
1443 ✗ void PropTable::readonly(const char* label, const eu::v3& v) const
1444 {
1445
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 4 not taken.
✗ trow(*this, label, [&]
1446 {
1447 ✗ eu::imgui::imgui_readonly("", v);
1448 ✗ return false;
1449 });
1450 ✗ }
1451
1452 ✗ void PropTable::readonly(const char* label, const eu::Ypr& y) const
1453 {
1454
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 4 not taken.
✗ trow(*this, label, [&]
1455 {
1456 ✗ eu::imgui::imgui_readonly("", y);
1457 ✗ return false;
1458 });
1459 ✗ }
1460
1461 ✗ void PropTable::image(const char* label, const render::FrameBuffer& img, ImguiShaderCache* cache, ImageShader shader) const
1462 {
1463
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 4 not taken.
✗ trow(*this, label, [&]
1464 {
1465 ✗ imgui::imgui_image("", img, cache, shader);
1466 ✗ return false;
1467 });
1468 ✗ }
1469
1470 ✗ bool PropTable::simple_gamma_slider(const char* label, float* gamma, float curve, float min_gamma, float max_gamma) const
1471 {
1472
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 6 not taken.
✗ return trow(*this, label, [&]
1473 {
1474 ✗ return eu::imgui::simple_gamma_slider("", gamma, curve, min_gamma, max_gamma);
1475 ✗ });
1476 }
1477
1478 ✗ bool PropTable::color(const char* label, Rgb* color) const
1479 {
1480
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 6 not taken.
✗ return trow(*this, label, [&]
1481 {
1482
0/2
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 19 not taken.
✗ ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2{ 0, 0 });
1483 ✗ const auto button_size = calculate_square_button_size();
1484 // todo(Gustav): change so drag is 0-255
1485 // todo(Gustav): change colors?
1486
0/2
✗ Branch 6 → 7 not taken.
✗ Branch 6 → 20 not taken.
✗ bool changed = imgui_drag_with_reset(&color->r, &color->g, &color->b, 0.0f, button_size.x,
1487 ✗ "R", "G", "B", Drags{ .v_speed = 0.1f, .v_min = 0.0f, .v_max = 1.0f }
1488 );
1489 ✗ constexpr const char* color_popup = "color-popup";
1490
0/4
✗ Branch 9 → 10 not taken.
✗ Branch 9 → 22 not taken.
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 12 not taken.
✗ if (ImGui::ColorButton("color", ImVec4{color->r, color->g, color->b, 1.0f}))
1491 {
1492
0/2
✗ Branch 11 → 12 not taken.
✗ Branch 11 → 24 not taken.
✗ ImGui::OpenPopup(color_popup);
1493 }
1494
0/2
✗ Branch 12 → 13 not taken.
✗ Branch 12 → 24 not taken.
✗ ImGui::PopStyleVar();
1495
0/4
✗ Branch 13 → 14 not taken.
✗ Branch 13 → 24 not taken.
✗ Branch 14 → 15 not taken.
✗ Branch 14 → 17 not taken.
✗ if (ImGui::BeginPopup(color_popup))
1496 {
1497 // color dialog
1498
0/2
✗ Branch 15 → 16 not taken.
✗ Branch 15 → 24 not taken.
✗ changed = ImGui::ColorPicker3(label, &color->r);
1499
0/2
✗ Branch 16 → 17 not taken.
✗ Branch 16 → 24 not taken.
✗ ImGui::EndPopup();
1500 }
1501 ✗ return changed;
1502 ✗ });
1503 }
1504
1505 ✗ bool PropTable::drag(const char* label, eu::v3* drag) const
1506 {
1507
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 6 not taken.
✗ return trow(*this, label, [&]
1508 {
1509
0/2
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 7 not taken.
✗ return imgui_drag_with_reset(&drag->x, &drag->y, &drag->z, 0.0f, std::nullopt, "X", "Y", "Z");
1510 ✗ });
1511 }
1512
1513 ✗ bool PropTable::drag(const char* label, eu::Ypr* drag) const
1514 {
1515
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 6 not taken.
✗ return trow(*this, label, [&]
1516 {
1517 float angles[3] = {
1518
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 15 not taken.
✗ drag->yaw.as_degrees(),
1519 ✗ drag->pitch.as_degrees(),
1520 ✗ drag->roll.as_degrees()
1521
0/4
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 15 not taken.
✗ Branch 4 → 5 not taken.
✗ Branch 4 → 15 not taken.
✗ };
1522
1523 // const auto changed = ImGui::DragFloat3(label, angles, 1.0f, -360.0f, 360.0f);
1524
0/2
✗ Branch 6 → 7 not taken.
✗ Branch 6 → 13 not taken.
✗ const auto changed = imgui_drag_with_reset(&angles[0], &angles[1], &angles[2], 0.0f, std::nullopt,
1525 ✗ "Y", "P", "R", Drags{.v_speed = 1.0f, .v_min = -360.0f, .v_max = 360.0f }
1526 );
1527
1528
0/2
✗ Branch 7 → 8 not taken.
✗ Branch 7 → 11 not taken.
✗ if (changed)
1529 {
1530
0/2
✗ Branch 8 → 9 not taken.
✗ Branch 8 → 15 not taken.
✗ drag->yaw = An::from_degrees(angles[0]);
1531
0/2
✗ Branch 9 → 10 not taken.
✗ Branch 9 → 15 not taken.
✗ drag->pitch = An::from_degrees(angles[1]);
1532
0/2
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 15 not taken.
✗ drag->roll = An::from_degrees(angles[2]);
1533 }
1534
1535 ✗ return changed;
1536 ✗ });
1537 }
1538
1539 ✗ bool PropTable::drag(const char* label, float* drag, float speed) const
1540 {
1541
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 6 not taken.
✗ return trow(*this, label, [&]
1542 {
1543 ✗ return ImGui::DragFloat("", drag, speed);
1544 ✗ });
1545 }
1546 ✗ bool PropTable::drag(const char* label, int* drag) const
1547 {
1548
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 6 not taken.
✗ return trow(*this, label, [&]
1549 {
1550 ✗ return ImGui::DragInt("", drag);
1551 ✗ });
1552 }
1553
1554 ✗ bool PropTable::gear(const char* label, eu::v3* drag) const
1555 {
1556
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 6 not taken.
✗ return trow(*this, label, [&]
1557 {
1558 ✗ return eu::imgui::gear("", drag);
1559 ✗ });
1560 }
1561
1562 ✗ bool PropTable::gear(const char* label, eu::Rui* drag) const
1563 {
1564
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 6 not taken.
✗ return trow(*this, label, [&]
1565 {
1566 ✗ return eu::imgui::gear("", drag);
1567 ✗ });
1568 }
1569
1570 ✗ bool PropTable::gear(const char* label, eu::Ypr* drag) const
1571 {
1572
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 6 not taken.
✗ return trow(*this, label, [&]
1573 {
1574 ✗ return eu::imgui::gear("", drag);
1575 ✗ });
1576 }
1577
1578 ✗ void PropTable::transform(const eu::m4& m) const
1579 {
1580
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 9 not taken.
✗ const auto t = eu::transform_from_matrix(m);
1581
1582
0/2
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 9 not taken.
✗ readonly("Position", t.position);
1583
0/4
✗ Branch 4 → 5 not taken.
✗ Branch 4 → 8 not taken.
✗ Branch 5 → 6 not taken.
✗ Branch 5 → 8 not taken.
✗ readonly("Rotation", eu::Ypr::from(t.rotation));
1584
0/2
✗ Branch 6 → 7 not taken.
✗ Branch 6 → 9 not taken.
✗ readonly("Scale", t.scale);
1585 ✗ }
1586
1587 ✗ bool PropTable::transform(eu::m4* m) const
1588 {
1589
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 18 not taken.
✗ auto t = eu::transform_from_matrix(*m);
1590
1591
0/2
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 18 not taken.
✗ ImGui::PushID(m);
1592
1593 ✗ bool changed = false;
1594
1595
0/4
✗ Branch 4 → 5 not taken.
✗ Branch 4 → 18 not taken.
✗ Branch 5 → 6 not taken.
✗ Branch 5 → 7 not taken.
✗ if (gear("Position", &t.position))
1596 {
1597 ✗ changed = true;
1598
0/2
✗ Branch 6 → 7 not taken.
✗ Branch 6 → 18 not taken.
✗ *m = matrix_from_transform(t);
1599 }
1600
0/2
✗ Branch 7 → 8 not taken.
✗ Branch 7 → 18 not taken.
✗ auto y = eu::Ypr::from(t.rotation);
1601
0/4
✗ Branch 8 → 9 not taken.
✗ Branch 8 → 18 not taken.
✗ Branch 9 → 10 not taken.
✗ Branch 9 → 12 not taken.
✗ if (gear("Rotation", &y))
1602 {
1603 ✗ changed = true;
1604
0/2
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 18 not taken.
✗ t.rotation = eu::Q::from(y);
1605
0/2
✗ Branch 11 → 12 not taken.
✗ Branch 11 → 18 not taken.
✗ *m = matrix_from_transform(t);
1606 }
1607
0/4
✗ Branch 12 → 13 not taken.
✗ Branch 12 → 18 not taken.
✗ Branch 13 → 14 not taken.
✗ Branch 13 → 15 not taken.
✗ if (gear("Scale", &t.scale))
1608 {
1609 ✗ changed = true;
1610
0/2
✗ Branch 14 → 15 not taken.
✗ Branch 14 → 18 not taken.
✗ *m = matrix_from_transform(t);
1611 }
1612
1613
0/2
✗ Branch 15 → 16 not taken.
✗ Branch 15 → 18 not taken.
✗ ImGui::PopID();
1614
1615 ✗ return changed;
1616 }
1617
1618 ✗ bool PropTable::scurve(const char* label, eu::core::SCurve* scurve, eu::imgui::SCurveGuiState* gui, eu::imgui::FlipX flip_x, const eu::imgui::SCurveImguiSettings& settings, bool force_init_curve) const
1619 {
1620
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 6 not taken.
✗ return trow(*this, label, [&]
1621 {
1622 ✗ return eu::imgui::imgui_s_curve_editor("", scurve, gui, flip_x, settings, force_init_curve);
1623 ✗ });
1624 }
1625
1626 ✗ PropTable::PropTable(bool b) : started(b) {}
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636 }
1637
1638