The open source OpenXR runtime
1// Copyright 2020, Collabora, Ltd.
2// SPDX-License-Identifier: BSL-1.0
3/*!
4 * @file
5 * @brief Custom imgui elements.
6 * @author Christoph Haag <christoph.haag@collabora.com>
7 *
8 * based on ImGui::PlotEx() from dear imgui, v1.76 WIP
9 */
10
11#include "../imgui/imgui.h"
12
13#ifndef IMGUI_DEFINE_MATH_OPERATORS
14#define IMGUI_DEFINE_MATH_OPERATORS
15#endif
16#include "../imgui/imgui_internal.h"
17
18#include <stdint.h>
19
20#include "cimgui_monado.h"
21
22using namespace ImGui;
23
24static void _draw_line(ImGuiWindow *window, int values_count, float scale_min,
25 float scale_max, float val, const char *unit,
26 const ImRect inner_bb, ImVec2 frame_size, ImU32 color) {
27 const float inv_scale =
28 (scale_min == scale_max) ? 0.0f : (1.0f / (scale_max - scale_min));
29 ImVec2 tp0 = ImVec2(
30 0.0f, 1.0f - ImSaturate((val - scale_min) *
31 inv_scale)); // Point in the normalized space of
32 ImVec2 pos0 = ImLerp(inner_bb.Min, inner_bb.Max, tp0);
33 const ImVec2 tp1 =
34 ImVec2(1.0, 1.0f - ImSaturate((val - scale_min) * inv_scale));
35 ImVec2 pos1 = ImLerp(inner_bb.Min, inner_bb.Max, tp1);
36 window->DrawList->AddLine(pos0, pos1, color);
37
38 char text[100];
39 snprintf(text, 60, "%.2f %s", val, unit);
40 ImVec2 text_size = ImGui::CalcTextSize(text);
41 ImVec2 text_pos = {pos1.x - text_size.x, pos1.y};
42 ImGui::PushStyleColor(ImGuiCol_Text, color);
43 ImGui::RenderText(text_pos, text);
44 ImGui::PopStyleColor(1);
45}
46
47static void _draw_grid(ImGuiWindow *window, int values_count, float scale_min,
48 float scale_max, float reference_timing, const char *unit,
49 const ImRect inner_bb, ImVec2 frame_size) {
50
51 const ImVec4 target_color{1.0f, 1.0f, 0.0f, .75f};
52 _draw_line(window, values_count, scale_min, scale_max, reference_timing, unit,
53 inner_bb, frame_size, GetColorU32(target_color));
54
55 const ImVec4 passive_color{0.35f, 0.35f, 0.35f, 1.00f};
56
57 // always draw ~5 lines
58 const uint8_t max_lines = 5;
59 float step = (scale_max - scale_min) / (float)max_lines;
60 for (uint8_t i = 0; i < max_lines; ++i) {
61 float val = scale_min + step * (float)i;
62 _draw_line(window, values_count, scale_min, scale_max, val, unit, inner_bb,
63 frame_size, GetColorU32(passive_color));
64 }
65}
66
67static void PlotTimings(const char *label,
68 float (*values_getter)(void *data, int idx), void *data,
69 int values_count, int values_offset,
70 const char *overlay_text, ImVec2 frame_size,
71 float reference_timing, bool center_reference_timing,
72 float range, const char *unit, bool dynamic_rescale) {
73 ImGuiWindow *window = GetCurrentWindow();
74 if (window->SkipItems)
75 return;
76
77 ImGuiContext &g = *GImGui;
78 const ImGuiStyle &style = g.Style;
79 const ImGuiID id = window->GetID(label);
80
81 if (frame_size.x == 0.0f)
82 frame_size.x = CalcItemWidth();
83 if (frame_size.y == 0.0f)
84 frame_size.y = (style.FramePadding.y * 2);
85
86 const ImRect frame_bb(window->DC.CursorPos,
87 window->DC.CursorPos + frame_size);
88 const ImRect inner_bb(frame_bb.Min + style.FramePadding,
89 frame_bb.Max - style.FramePadding);
90 const ImRect total_bb(frame_bb.Min, frame_bb.Max);
91 ItemSize(total_bb, style.FramePadding.y);
92 if (!ItemAdd(total_bb, 0, &frame_bb))
93 return;
94 const bool hovered = ItemHoverable(frame_bb, id, g.LastItemData.InFlags);
95
96 float v_min = FLT_MAX;
97 float v_max = -FLT_MAX;
98 for (int i = 0; i < values_count; i++) {
99 const float v = values_getter(data, i);
100 if (v != v) // Ignore NaN values
101 continue;
102 v_min = ImMin(v_min, v);
103 v_max = ImMax(v_max, v);
104 }
105
106 float scale_min =
107 center_reference_timing ? reference_timing - range : reference_timing;
108 float scale_max = reference_timing + range;
109
110 if (dynamic_rescale) {
111 if (v_max > scale_max) {
112 scale_max = v_max;
113 }
114 scale_max = (floorf(scale_max / 10 + 1)) * 10;
115
116 if (center_reference_timing) {
117 if (v_min < scale_min) {
118 scale_min = v_min;
119 }
120 scale_min = (floorf(scale_min / 10)) * 10;
121
122 // make sure reference timing stays centered
123 float lower_range = reference_timing - scale_min;
124 float upper_range = scale_max - reference_timing;
125 if (lower_range > upper_range) {
126 scale_max = reference_timing + lower_range;
127 } else if (upper_range > lower_range) {
128 scale_min = reference_timing - upper_range;
129 }
130 }
131 }
132
133 RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true,
134 style.FrameRounding);
135
136 _draw_grid(window, values_count, scale_min, scale_max, reference_timing, unit,
137 inner_bb, frame_size);
138
139 ImGuiPlotType plot_type = ImGuiPlotType_Lines;
140 const int values_count_min = (plot_type == ImGuiPlotType_Lines) ? 2 : 1;
141 if (values_count >= values_count_min) {
142 int res_w = ImMin((int)frame_size.x, values_count) +
143 ((plot_type == ImGuiPlotType_Lines) ? -1 : 0);
144 int item_count =
145 values_count + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0);
146
147 // Tooltip on hover
148 int v_hovered = -1;
149 if (hovered && inner_bb.Contains(g.IO.MousePos)) {
150 const float t = ImClamp((g.IO.MousePos.x - inner_bb.Min.x) /
151 (inner_bb.Max.x - inner_bb.Min.x),
152 0.0f, 0.9999f);
153 const int v_idx = (int)(t * item_count);
154 IM_ASSERT(v_idx >= 0 && v_idx < values_count);
155
156 const float v0 =
157 values_getter(data, (v_idx + values_offset) % values_count);
158 const float v1 =
159 values_getter(data, (v_idx + 1 + values_offset) % values_count);
160 if (plot_type == ImGuiPlotType_Lines)
161 SetTooltip("%d: %8.4g\n%d: %8.4g", v_idx, v0, v_idx + 1, v1);
162 else if (plot_type == ImGuiPlotType_Histogram)
163 SetTooltip("%d: %8.4g", v_idx, v0);
164 v_hovered = v_idx;
165 }
166
167 const float t_step = 1.0f / (float)res_w;
168 const float inv_scale =
169 (scale_min == scale_max) ? 0.0f : (1.0f / (scale_max - scale_min));
170
171 float v0 = values_getter(data, (0 + values_offset) % values_count);
172 float t0 = 0.0f;
173 ImVec2 tp0 = ImVec2(
174 t0, 1.0f - ImSaturate((v0 - scale_min) *
175 inv_scale)); // Point in the normalized space of
176 // our target rectangle
177 float histogram_zero_line_t =
178 (scale_min * scale_max < 0.0f)
179 ? (-scale_min * inv_scale)
180 : (scale_min < 0.0f ? 0.0f
181 : 1.0f); // Where does the zero line stands
182
183 const ImU32 col_base = GetColorU32((plot_type == ImGuiPlotType_Lines)
184 ? ImGuiCol_PlotLines
185 : ImGuiCol_PlotHistogram);
186 const ImU32 col_hovered = GetColorU32((plot_type == ImGuiPlotType_Lines)
187 ? ImGuiCol_PlotLinesHovered
188 : ImGuiCol_PlotHistogramHovered);
189
190 for (int n = 0; n < res_w; n++) {
191 const float t1 = t0 + t_step;
192 const int v1_idx = (int)(t0 * item_count + 0.5f);
193 IM_ASSERT(v1_idx >= 0 && v1_idx < values_count);
194 const float v1 =
195 values_getter(data, (v1_idx + values_offset + 1) % values_count);
196 const ImVec2 tp1 =
197 ImVec2(t1, 1.0f - ImSaturate((v1 - scale_min) * inv_scale));
198
199 // NB: Draw calls are merged together by the DrawList system. Still, we
200 // should render our batch are lower level to save a bit of CPU.
201 ImVec2 pos0 = ImLerp(inner_bb.Min, inner_bb.Max, tp0);
202 ImVec2 pos1 = ImLerp(inner_bb.Min, inner_bb.Max,
203 (plot_type == ImGuiPlotType_Lines)
204 ? tp1
205 : ImVec2(tp1.x, histogram_zero_line_t));
206
207 if (plot_type == ImGuiPlotType_Lines) {
208 window->DrawList->AddLine(pos0, pos1,
209 v_hovered == v1_idx ? col_hovered : col_base);
210 } else if (plot_type == ImGuiPlotType_Histogram) {
211 if (pos1.x >= pos0.x + 2.0f)
212 pos1.x -= 1.0f;
213 window->DrawList->AddRectFilled(
214 pos0, pos1, v_hovered == v1_idx ? col_hovered : col_base);
215 }
216
217 t0 = t1;
218 tp0 = tp1;
219 }
220 }
221
222 // Text overlay
223 if (overlay_text)
224 RenderTextClipped(
225 ImVec2(frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y),
226 frame_bb.Max, overlay_text, NULL, NULL, ImVec2(0.5f, 0.0f));
227
228 const float v = values_getter(data, (values_offset));
229 ImGui::LabelText(label, "%6.2f %s [%6.2f, %6.2f]", v, unit, v_min, v_max);
230}
231
232extern "C"
233void igPlotTimings(const char *label,
234 float (*values_getter)(void *data, int idx), void *data,
235 int values_count, int values_offset,
236 const char *overlay_text, float scale_min, float scale_max,
237 ImVec2 frame_size, float reference_timing,
238 bool center_reference_timing, float range, const char *unit,
239 bool dynamic_rescale) {
240 PlotTimings(label, values_getter, data, values_count, values_offset,
241 overlay_text, frame_size, reference_timing,
242 center_reference_timing, range, unit, dynamic_rescale);
243}
244
245extern "C"
246void igToggleButton(const char *str_id, bool *v) {
247 ImVec2 p = ImGui::GetCursorScreenPos();
248 ImDrawList *draw_list = ImGui::GetWindowDrawList();
249
250 float height = ImGui::GetFrameHeight();
251 float width = height * 1.55f;
252 float radius = height * 0.50f;
253
254 ImGui::InvisibleButton(str_id, ImVec2(width, height));
255 if (ImGui::IsItemClicked())
256 *v = !*v;
257
258 float t = *v ? 1.0f : 0.0f;
259
260 ImGuiContext &g = *GImGui;
261 float ANIM_SPEED = 0.08f;
262 if (g.LastActiveId ==
263 g.CurrentWindow->GetID(str_id)) // && g.LastActiveIdTimer < ANIM_SPEED)
264 {
265 float t_anim = ImSaturate(g.LastActiveIdTimer / ANIM_SPEED);
266 t = *v ? (t_anim) : (1.0f - t_anim);
267 }
268
269 ImU32 col_bg;
270 if (ImGui::IsItemHovered())
271 col_bg = ImGui::GetColorU32(ImLerp(ImVec4(0.78f, 0.78f, 0.78f, 1.0f),
272 ImVec4(0.64f, 0.83f, 0.34f, 1.0f), t));
273 else
274 col_bg = ImGui::GetColorU32(ImLerp(ImVec4(0.85f, 0.85f, 0.85f, 1.0f),
275 ImVec4(0.56f, 0.83f, 0.26f, 1.0f), t));
276
277 draw_list->AddRectFilled(p, ImVec2(p.x + width, p.y + height), col_bg,
278 height * 0.5f);
279 draw_list->AddCircleFilled(
280 ImVec2(p.x + radius + t * (width - radius * 2.0f), p.y + radius),
281 radius - 1.5f, IM_COL32(255, 255, 255, 255));
282}
283
284extern "C"
285void igImageBg(ImTextureID user_texture_id,
286 const ImVec2 size,
287 const ImVec2 uv0, const ImVec2 uv1,
288 const ImVec4 tint_col, const ImVec4 border_col, const ImVec4 bg_col)
289{
290 ImGuiWindow* window = GetCurrentWindow();
291 if (window->SkipItems) {
292 return;
293 }
294
295 ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
296 auto bbImg = bb;
297 if (border_col.w > 0.0f) {
298 bb.Max += ImVec2(2, 2);
299
300 // Move the image pixel down and right, to be inside of the frame.
301 bbImg.Min += ImVec2(1, 1);
302 bbImg.Max += ImVec2(1, 1);
303 }
304
305 ItemSize(bb);
306 if (!ItemAdd(bb, 0)) {
307 return;
308 }
309
310 // Do we have a border?
311 if (border_col.w > 0.0f) {
312 window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(border_col), 0.0f);
313 }
314
315 // Should we clear the background?
316 if (bg_col.w > 0.0f) {
317 window->DrawList->AddRectFilled(bbImg.Min, bbImg.Max, GetColorU32(bg_col));
318 }
319
320 // Finally the image.
321 window->DrawList->AddImage(user_texture_id, bbImg.Min, bbImg.Max, uv0, uv1, GetColorU32(tint_col));
322}