Euphoria
imgui_extra.cc
Go to the documentation of this file.
1 #include "window/imgui_extra.h"
2 
3 #include "assert/assert.h"
4 
5 #include "base/angle.h"
6 #include "base/rgb.h"
7 #include "base/numeric.h"
8 
9 #include "render/texture.h"
10 
11 #include "window/imgui_icons.h"
12 
13 
14 #include <iomanip>
15 
16 #include "fmt/format.h"
17 
18 #include "imgui/imgui_internal.h"
19 #include "imgui_stdlib.h"
20 
21 #include "euph_generated_config.h"
22 
23 
24 namespace eu::window
25 {
26  ImVec2
27  con(const vec2f& v)
28  {
29  return {v.x, v.y};
30  }
31 
32 
33  vec2f
34  con(const ImVec2& v)
35  {
36  return {v.x, v.y};
37  }
38 }
39 
40 namespace eu::window::imgui
41 {
42  void
44  {
45  ImGui::SameLine();
46  ImGui::TextDisabled(ICON_MD_INFO);
48  }
49 
50 
51  void
53  {
54  if (ImGui::IsItemHovered())
55  {
56  ImGui::BeginTooltip();
57  ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);
58  ImGui::TextUnformatted(desc);
59  ImGui::PopTextWrapPos();
60  ImGui::EndTooltip();
61  }
62  }
63 
64 
65  // todo(Gustav): rename to imgui_label
66  void
67  imgui_label(const std::string& str)
68  {
69  ImGui::TextUnformatted(str.c_str());
70  }
71 
72 
73  bool
75  (
76  const char* name,
77  Angle* angle,
78  const Angle& mindeg,
79  const Angle& maxdeg
80  )
81  {
82  ASSERT(angle);
83 
84  float degrees = angle->as_degrees();
85  const auto value_was_changed = ImGui::SliderFloat
86  (
87  name,
88  &degrees,
89  mindeg.as_degrees(),
90  maxdeg.as_degrees()
91  );
92 
93  if(value_was_changed)
94  {
95  *angle = Angle::from_degrees(degrees);
96  }
97 
98  return value_was_changed;
99  }
100 
101 
102  bool
104  (
105  const char* name,
106  Angle* angle
107  )
108  {
109  ASSERT(angle);
110 
111  float degrees = angle->as_degrees();
112  const auto value_was_changed = ImGui::DragFloat
113  (
114  name,
115  &degrees
116  );
117 
118  if(value_was_changed)
119  {
120  *angle = Angle::from_degrees(degrees);
121  angle->wrap();
122  }
123 
124  return value_was_changed;
125  }
126 
127 
128  bool
129  imgui_toggle_button(const char* label, bool down, const ImVec2& size)
130  {
131  if (down)
132  {
133  const auto c = ImGui::GetStyle().Colors[ImGuiCol_ButtonActive];
134  ImGui::PushStyleColor(ImGuiCol_Button, c);
135  const auto r = ImGui::Button(label, size);
136  ImGui::PopStyleColor(1);
137 
138  return r;
139  }
140  else
141  {
142  return ImGui::Button(label, size);
143  }
144  }
145 
146  bool
147  imgui_color_edit(const char* name, Rgb* c)
148  {
149  return ImGui::ColorEdit3(name, &c->r);
150  }
151 
152  bool
153  imgui_color_edit(const char* name, Rgba* c)
154  {
155  return ImGui::ColorEdit4(name, &c->r);
156  }
157 
158  bool
159  imgui_color_edit(const char* name, Rgbai* c)
160  {
161  auto cc = to_rgba(*c);
162  const auto changed = ImGui::ColorEdit4(name, &cc.r);
163  if(changed)
164  {
165  *c = to_rgbai(cc);
166  }
167  return changed;
168  }
169 
170 
171  ImTextureID
173  {
174 #if EU_ARCH_32 == 1
175  std::int32_t
176 #elif EU_ARCH_64 == 1
177  std::int64_t
178 #else
179 #error unknown arch
180 #endif
181  id = texture->get_id();
182  return reinterpret_cast<ImTextureID>(id);
183  }
184 
185 
186  // todo(Gustav): rename to imgui_image
187  void
189  {
190  auto tex_w = static_cast<float>(texture->width);
191  auto tex_h = static_cast<float>(texture->height);
192  ImTextureID tex_id = c_texture_to_imgui(texture); // NOLINT: auto is preferred but a texture is a hidden pointer
193 
194  ImVec2 tex_screen_pos = ImGui::GetCursorScreenPos();
195  imgui_label(fmt::format("{:.0}x{:.0}", tex_w, tex_h));
196  ImGui::Image(
197  tex_id,
198  ImVec2(tex_w, tex_h),
199  ImVec2(0, 0),
200  ImVec2(1, 1),
201  ImColor(255, 255, 255, 255),
202  ImColor(255, 255, 255, 128));
203  if(ImGui::IsItemHovered())
204  {
205  ImGui::BeginTooltip();
206  float focus_sz = 32.0f;
207  float focus_x = ImGui::GetMousePos().x - tex_screen_pos.x - focus_sz * 0.5f;
208  if(focus_x < 0.0f)
209  {
210  focus_x = 0.0f;
211  }
212  else if(focus_x > tex_w - focus_sz)
213  {
214  focus_x = tex_w - focus_sz;
215  }
216  float focus_y = ImGui::GetMousePos().y - tex_screen_pos.y
217  - focus_sz * 0.5f;
218  if(focus_y < 0.0f)
219  {
220  focus_y = 0.0f;
221  }
222  else if(focus_y > tex_h - focus_sz)
223  {
224  focus_y = tex_h - focus_sz;
225  }
226  imgui_label(fmt::format("Min: ({:.2f}, {:.2f})", focus_x, focus_y));
227  imgui_label(fmt::format("Max: ({:.2f}, {:.2f})", focus_x + focus_sz, focus_y + focus_sz));
228  ImVec2 uv0 = ImVec2((focus_x) / tex_w, (focus_y) / tex_h);
229  ImVec2 uv1 = ImVec2(
230  (focus_x + focus_sz) / tex_w, (focus_y + focus_sz) / tex_h);
231  ImGui::Image(
232  tex_id,
233  ImVec2(128, 128),
234  uv0,
235  uv1,
236  ImColor(255, 255, 255, 255),
237  ImColor(255, 255, 255, 128));
238  ImGui::EndTooltip();
239  }
240  }
241 
242 
243  // stolen from ShowExampleAppFixedOverlay function in imgui_demo
244  bool
246  (
247  Corner corner,
248  const std::string& title,
249  float a_distance,
250  float a_distance_y
251  )
252  {
253  const int corner_int = static_cast<int>(corner);
254  // const float distance = 10.0f;
255  const auto distance_x = a_distance;
256  const auto distance_y = a_distance_y > 0 ? a_distance_y : a_distance;
257  const auto size = ImGui::GetIO().DisplaySize;
258  const ImVec2 window_pos = corner == Corner::center
259  ? ImVec2(size.x / 2, size.y / 2)
260  : ImVec2
261  (
262  (corner_int & 1) != 0
263  ? size.x - distance_x
264  : distance_x,
265  (corner_int & 2) != 0
266  ? size.y - distance_y
267  : distance_y
268  );
269  const ImVec2 window_pos_pivot = corner == Corner::center
270  ? ImVec2(0.5f, 0.5f)
271  : ImVec2
272  (
273  (corner_int & 1) != 0 ? 1.0f : 0.0f,
274  (corner_int & 2) != 0 ? 1.0f : 0.0f
275  );
276  ImGui::SetNextWindowPos(window_pos, ImGuiCond_Always, window_pos_pivot);
277 
278  return ImGui::Begin(
279  title.c_str(),
280  nullptr,
281  ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize
282  | ImGuiWindowFlags_AlwaysAutoResize
283  | ImGuiWindowFlags_NoMove
284  | ImGuiWindowFlags_NoSavedSettings);
285  }
286 
287 
288  // from https://github.com/ocornut/imgui/issues/211
290  {
291  // todo(Gustav): change to use BeginDisabled / EndDisabled instead?
292  // ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true);
293  ImGui::PushStyleVar(
294  ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5f);
295  }
296 
297 
299  {
300  // ImGui::PopItemFlag();
301  ImGui::PopStyleVar();
302  }
303 
304 
305  bool
306  imgui_selectable_or_disabled(bool enabled, const char* label)
307  {
308  if(enabled)
309  {
310  if(ImGui::Selectable(label))
311  {
312  return true;
313  }
314  }
315  else
316  {
317  VisuallyDisabledWidgets disabled;
318  ImGui::TextUnformatted(label);
319  }
320 
321  return false;
322  }
323 
324 
325 
326  void
328  (
329  ImDrawList* draw_list,
330  const ImVec2& centre,
331  float radius,
332  ImU32 col,
333  int num_segments,
334  float angle_offset
335  )
336  {
337  ASSERT(draw_list);
338 
339  // implementation modified from ImDrawList AddCircleFilled
340 
341  if((col & IM_COL32_A_MASK) == 0)
342  { return; }
343 
344  const float a_max = IM_PI * 2.0f * (c_int_to_float(num_segments) - 1.0f) / c_int_to_float(num_segments);
345  draw_list->PathArcTo
346  (
347  centre,
348  radius,
349  angle_offset,
350  a_max + angle_offset,
351  num_segments
352  );
353  draw_list->PathFillConvex(col);
354  }
355 
356  bool
358  (
359  const char* label,
360  float* p_value,
361  float v_min,
362  float v_max,
363  KnobStyle style
364  )
365  {
366  // constexpr auto rad2deg = 180 / pi;
367  constexpr auto pi2 = pi * 2;
368  constexpr float angle_min = pi * 0.75f;
369  constexpr float angle_max = pi * 2.25f;
370  constexpr float angle_step = 20 * (pi / 180);
371  constexpr float size_outer = 20;
372  constexpr float peg_max_end = size_outer;
373  constexpr float peg_end = 19;
374  constexpr float peg_start = 15;
375  constexpr float knob_size = 15;
376  constexpr float knob_mark_start = 15;
377  constexpr float knob_mark_end = 8;
378  constexpr int seg = 16;
379 
380  ImGuiIO& io = ImGui::GetIO();
381  ImGuiStyle& imstyle = ImGui::GetStyle();
382  ImDrawList* draw_list = ImGui::GetWindowDrawList();
383 
384  const ImVec2 start = ImGui::GetCursorScreenPos();
385  const ImVec2 center
386  = ImVec2(start.x + size_outer, start.y + size_outer);
387  const float line_height = ImGui::GetTextLineHeight();
388 
389  ImGui::InvisibleButton
390  (
391  label,
392  ImVec2
393  (
394  size_outer * 2,
395  size_outer * 2 + line_height + imstyle.ItemInnerSpacing.y
396  )
397  );
398  const bool is_active = ImGui::IsItemActive();
399  const bool is_hovered = ImGui::IsItemHovered();
400 
401  const float t = (*p_value - v_min) / (v_max - v_min);
402  const float current_angle = angle_min + (angle_max - angle_min) * t;
403 
404  // changing value
405  bool value_changed = false;
406 
407  if((style & knob_style_ui_aim) != 0)
408  {
409  ImVec2 direct(io.MousePos.x - center.x, io.MousePos.y - center.y);
410  const float directl = sqrtf(direct.x * direct.x + direct.y * direct.y);
411  direct.x = direct.x / directl;
412  direct.y = direct.y / directl;
413 
414  // todo(Gustav): flip y based on x
415  const auto acos = acosf(direct.x);
416  const auto ang = direct.y > 0 ? pi2 - acos : acos;
417  float input_angle = -ang;
418  input_angle += pi2;
419  const bool b = input_angle < pi / 2;
420  if(b)
421  {
422  input_angle += pi2;
423  }
424 
425  const float input_angle_t
426  = (input_angle - angle_min) / (angle_max - angle_min);
427 
428  if(is_active && (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f))
429  {
430  *p_value = v_min + (v_max - v_min) * input_angle_t;
431 
432  if(*p_value < v_min) { *p_value = v_min; }
433  if(*p_value > v_max) { *p_value = v_max; }
434  }
435  }
436  else
437  {
438  const auto val = (style & knob_style_ui_drag_x) != 0 ? io.MouseDelta.x : io.MouseDelta.y;
439  if(is_active && val != 0.0f)
440  {
441  float step = (v_max - v_min) / 200.0f;
442  *p_value += val * step;
443  if(*p_value < v_min) { *p_value = v_min; }
444  if(*p_value > v_max) { *p_value = v_max; }
445 
446  value_changed = true;
447  }
448  }
449 
450  // colors
451  const auto label_color = ImGui::GetColorU32(ImGuiCol_Text);
452  const auto fill_color = ImGui::GetColorU32(ImGuiCol_FrameBg);
453  const auto knob_color = ImGui::GetColorU32(ImGuiCol_SliderGrab);
454  const auto indicator_color = ImGui::GetColorU32(ImGuiCol_Text);
455  const auto peg_color_off = ImGui::GetColorU32(ImGuiCol_TextDisabled);
456  const auto peg_color_on = ImGui::GetColorU32(ImGuiCol_Text);
457  const auto peg_color_max = ImGui::GetColorU32(ImGuiCol_Text);
458 
459  // util function
460  const auto calculate_position = [=](float ang, float rad) -> ImVec2
461  {
462  return {center.x + cosf(ang) * rad, center.y + sinf(ang) * rad};
463  };
464 
465  // ----------------- visualization
466  // background
467  if((style & knob_style_vis_draw_background) != 0)
468  {
469  draw_list->AddCircleFilled(center, size_outer, fill_color, seg);
470  }
471 
472  // peg indicators
473  if((style & knob_style_vis_markers_visible) != 0)
474  {
475  const auto marker_stop = ((style & knob_style_vis_off_marker_hidden) != 0) ? current_angle : angle_max;
476  for(float angle = angle_min; angle <= marker_stop; angle += angle_step)
477  {
478  const auto c = (style & knob_style_vis_off_marker_hidden) != 0
479  ? peg_color_off
480  : angle <= current_angle ? peg_color_on : peg_color_off
481  ;
482  draw_list->AddLine(calculate_position(angle, peg_start), calculate_position(angle, peg_end), c, 1.0f);
483  }
484  }
485 
486  if((style & knob_style_vis_max_and_min_visible) != 0)
487  {
488  draw_list->AddLine(calculate_position(angle_max, peg_start), calculate_position(angle_max, peg_max_end), peg_color_max, 1.0f);
489  draw_list->AddLine(calculate_position(angle_min, peg_start), calculate_position(angle_min, peg_max_end), peg_color_max, 1.0f);
490  }
491 
492  // the knob
493  add_circle_filled(draw_list, center, knob_size, knob_color, 6, current_angle);
494  draw_list->AddLine(calculate_position(current_angle, knob_mark_start), calculate_position(current_angle, knob_mark_end), indicator_color, 2.0f);
495 
496  const bool display_value = (style & knob_style_vis_display_value_on_hover) != 0
497  ? is_active || is_hovered
498  : is_active
499  ;
500 
501  // knob control name
502  const auto label_position = ImVec2 {start.x, start.y + size_outer * 2 + imstyle.ItemInnerSpacing.y / 4};
503 
504  const auto value_to_str = [](float f) -> std::string
505  {
506  std::stringstream ss;
507  ss << std::fixed << std::setprecision(3) << f;
508  return ss.str();
509  };
510 
511  if((style & knob_style_vis_value_instead_of_name) != 0 && display_value)
512  {
513  const auto v = value_to_str(*p_value);
514  draw_list->AddText(label_position, label_color, v.c_str());
515  }
516  else
517  {
518  draw_list->AddText(label_position, label_color, label);
519  }
520 
521  // tooltip
522  if((style & knob_style_vis_value_as_tooltip) != 0 && display_value)
523  {
524  ImGui::SetNextWindowPos
525  (
526  ImVec2
527  (
528  start.x - 0,
529  start.y - line_height - imstyle.ItemInnerSpacing.y - imstyle.WindowPadding.y
530  )
531  );
532  ImGui::BeginTooltip();
533  const auto v = value_to_str(*p_value);
534  ImGui::Text("%s", v.c_str());
535  ImGui::EndTooltip();
536  }
537 
538  return value_changed;
539  }
540 
541  bool
542  begin_canvas_widget(const ImVec4& background_color, const char* title)
543  {
544  ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(1, 1));
545  ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
546  ImGui::PushStyleColor(ImGuiCol_ChildBg, background_color);
547 
548  return ImGui::BeginChild
549  (
550  title,
551  ImVec2(0, 0),
552  true,
553  ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoMove
554  );
555  }
556 
557  void
559  {
560  ImGui::EndChild();
561 
562  ImGui::PopStyleColor();
563  ImGui::PopStyleVar(2);
564  }
565 
566  bool
567  begin_combo(const char* label, const char* preview)
568  {
569  return ImGui::BeginCombo(label, preview);
570  }
571 
572  bool
573  imgui_selectable(const char* label, bool is_selected)
574  {
575  return ImGui::Selectable(label, is_selected);
576  }
577 
578  void
580  {
581  return ImGui::EndCombo();
582  }
583 }
#define ASSERT(x)
Definition: assert.h:29
bool imgui_angle_slider(const char *name, Angle *angle, const Angle &mindeg, const Angle &maxdeg)
Definition: imgui_extra.cc:75
bool imgui_selectable(const char *label, bool is_selected)
Definition: imgui_extra.cc:573
bool imgui_knob(const char *label, float *p_value, float v_min, float v_max, KnobStyle style)
Definition: imgui_extra.cc:358
void add_help_marker_for_previous_widget(const char *desc)
Definition: imgui_extra.cc:43
bool imgui_toggle_button(const char *label, bool down, const ImVec2 &size)
Definition: imgui_extra.cc:129
void add_circle_filled(ImDrawList *draw_list, const ImVec2 &centre, float radius, ImU32 col, int num_segments, float angle_offset)
Definition: imgui_extra.cc:328
void imgui_label(const std::string &str)
Definition: imgui_extra.cc:67
void end_canvas_widget()
Definition: imgui_extra.cc:558
bool begin_combo(const char *label, const char *preview)
Definition: imgui_extra.cc:567
bool begin_canvas_widget(const ImVec4 &background_color, const char *title)
Definition: imgui_extra.cc:542
@ knob_style_vis_display_value_on_hover
Definition: imgui_extra.h:66
@ knob_style_vis_draw_background
Definition: imgui_extra.h:62
@ knob_style_vis_value_as_tooltip
Definition: imgui_extra.h:64
@ knob_style_vis_value_instead_of_name
Definition: imgui_extra.h:65
@ knob_style_vis_markers_visible
Definition: imgui_extra.h:59
@ knob_style_vis_off_marker_hidden
Definition: imgui_extra.h:61
@ knob_style_vis_max_and_min_visible
Definition: imgui_extra.h:60
bool begin_fixed_overlay(Corner corner, const std::string &title, float a_distance, float a_distance_y)
Definition: imgui_extra.cc:246
bool imgui_selectable_or_disabled(bool enabled, const char *label)
Definition: imgui_extra.cc:306
void imgui_image(render::Texture2 *texture)
Definition: imgui_extra.cc:188
ImTextureID c_texture_to_imgui(render::Texture2 *texture)
Definition: imgui_extra.cc:172
bool imgui_color_edit(const char *name, Rgb *c)
Definition: imgui_extra.cc:147
void add_help_text_for_previous_widget(const char *desc)
Definition: imgui_extra.cc:52
ImVec2 con(const vec2f &v)
Definition: imgui_extra.cc:27
Rgbai to_rgbai(const Rgba &c)
Definition: rgb.cc:395
Angle acos(float v)
Definition: angle.cc:91
constexpr float pi
Definition: numeric.h:127
constexpr float c_int_to_float(int i)
Definition: cint.h:50
Rgba to_rgba(const Rgbai &c)
Definition: rgb.cc:381
void wrap()
Definition: angle.cc:19
constexpr float as_degrees() const
Definition: angle.h:44
constexpr static Angle from_degrees(float degrees)
Definition: angle.h:16
Definition: rgb.h:62
Definition: rgb.h:143
Definition: rgb.h:45
gl::Uint get_id() const
Definition: texture.cc:118
Definition: vec2.h:33