// Copyright 2020, Collabora, Ltd. // SPDX-License-Identifier: BSL-1.0 /*! * @file * @brief Custom imgui elements. * @author Christoph Haag * * based on ImGui::PlotEx() from dear imgui, v1.76 WIP */ #include "../imgui/imgui.h" #ifndef IMGUI_DEFINE_MATH_OPERATORS #define IMGUI_DEFINE_MATH_OPERATORS #endif #include "../imgui/imgui_internal.h" #include #include "cimgui_monado.h" using namespace ImGui; static void _draw_line(ImGuiWindow *window, int values_count, float scale_min, float scale_max, float val, const char *unit, const ImRect inner_bb, ImVec2 frame_size, ImU32 color) { const float inv_scale = (scale_min == scale_max) ? 0.0f : (1.0f / (scale_max - scale_min)); ImVec2 tp0 = ImVec2( 0.0f, 1.0f - ImSaturate((val - scale_min) * inv_scale)); // Point in the normalized space of ImVec2 pos0 = ImLerp(inner_bb.Min, inner_bb.Max, tp0); const ImVec2 tp1 = ImVec2(1.0, 1.0f - ImSaturate((val - scale_min) * inv_scale)); ImVec2 pos1 = ImLerp(inner_bb.Min, inner_bb.Max, tp1); window->DrawList->AddLine(pos0, pos1, color); char text[100]; snprintf(text, 60, "%.2f %s", val, unit); ImVec2 text_size = ImGui::CalcTextSize(text); ImVec2 text_pos = {pos1.x - text_size.x, pos1.y}; ImGui::PushStyleColor(ImGuiCol_Text, color); ImGui::RenderText(text_pos, text); ImGui::PopStyleColor(1); } static void _draw_grid(ImGuiWindow *window, int values_count, float scale_min, float scale_max, float reference_timing, const char *unit, const ImRect inner_bb, ImVec2 frame_size) { const ImVec4 target_color{1.0f, 1.0f, 0.0f, .75f}; _draw_line(window, values_count, scale_min, scale_max, reference_timing, unit, inner_bb, frame_size, GetColorU32(target_color)); const ImVec4 passive_color{0.35f, 0.35f, 0.35f, 1.00f}; // always draw ~5 lines const uint8_t max_lines = 5; float step = (scale_max - scale_min) / (float)max_lines; for (uint8_t i = 0; i < max_lines; ++i) { float val = scale_min + step * (float)i; _draw_line(window, values_count, scale_min, scale_max, val, unit, inner_bb, frame_size, GetColorU32(passive_color)); } } static void PlotTimings(const char *label, float (*values_getter)(void *data, int idx), void *data, int values_count, int values_offset, const char *overlay_text, ImVec2 frame_size, float reference_timing, bool center_reference_timing, float range, const char *unit, bool dynamic_rescale) { ImGuiWindow *window = GetCurrentWindow(); if (window->SkipItems) return; ImGuiContext &g = *GImGui; const ImGuiStyle &style = g.Style; const ImGuiID id = window->GetID(label); if (frame_size.x == 0.0f) frame_size.x = CalcItemWidth(); if (frame_size.y == 0.0f) frame_size.y = (style.FramePadding.y * 2); const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size); const ImRect inner_bb(frame_bb.Min + style.FramePadding, frame_bb.Max - style.FramePadding); const ImRect total_bb(frame_bb.Min, frame_bb.Max); ItemSize(total_bb, style.FramePadding.y); if (!ItemAdd(total_bb, 0, &frame_bb)) return; const bool hovered = ItemHoverable(frame_bb, id, g.LastItemData.InFlags); float v_min = FLT_MAX; float v_max = -FLT_MAX; for (int i = 0; i < values_count; i++) { const float v = values_getter(data, i); if (v != v) // Ignore NaN values continue; v_min = ImMin(v_min, v); v_max = ImMax(v_max, v); } float scale_min = center_reference_timing ? reference_timing - range : reference_timing; float scale_max = reference_timing + range; if (dynamic_rescale) { if (v_max > scale_max) { scale_max = v_max; } scale_max = (floorf(scale_max / 10 + 1)) * 10; if (center_reference_timing) { if (v_min < scale_min) { scale_min = v_min; } scale_min = (floorf(scale_min / 10)) * 10; // make sure reference timing stays centered float lower_range = reference_timing - scale_min; float upper_range = scale_max - reference_timing; if (lower_range > upper_range) { scale_max = reference_timing + lower_range; } else if (upper_range > lower_range) { scale_min = reference_timing - upper_range; } } } RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding); _draw_grid(window, values_count, scale_min, scale_max, reference_timing, unit, inner_bb, frame_size); ImGuiPlotType plot_type = ImGuiPlotType_Lines; const int values_count_min = (plot_type == ImGuiPlotType_Lines) ? 2 : 1; if (values_count >= values_count_min) { int res_w = ImMin((int)frame_size.x, values_count) + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0); int item_count = values_count + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0); // Tooltip on hover int v_hovered = -1; if (hovered && inner_bb.Contains(g.IO.MousePos)) { const float t = ImClamp((g.IO.MousePos.x - inner_bb.Min.x) / (inner_bb.Max.x - inner_bb.Min.x), 0.0f, 0.9999f); const int v_idx = (int)(t * item_count); IM_ASSERT(v_idx >= 0 && v_idx < values_count); const float v0 = values_getter(data, (v_idx + values_offset) % values_count); const float v1 = values_getter(data, (v_idx + 1 + values_offset) % values_count); if (plot_type == ImGuiPlotType_Lines) SetTooltip("%d: %8.4g\n%d: %8.4g", v_idx, v0, v_idx + 1, v1); else if (plot_type == ImGuiPlotType_Histogram) SetTooltip("%d: %8.4g", v_idx, v0); v_hovered = v_idx; } const float t_step = 1.0f / (float)res_w; const float inv_scale = (scale_min == scale_max) ? 0.0f : (1.0f / (scale_max - scale_min)); float v0 = values_getter(data, (0 + values_offset) % values_count); float t0 = 0.0f; ImVec2 tp0 = ImVec2( t0, 1.0f - ImSaturate((v0 - scale_min) * inv_scale)); // Point in the normalized space of // our target rectangle float histogram_zero_line_t = (scale_min * scale_max < 0.0f) ? (-scale_min * inv_scale) : (scale_min < 0.0f ? 0.0f : 1.0f); // Where does the zero line stands const ImU32 col_base = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLines : ImGuiCol_PlotHistogram); const ImU32 col_hovered = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLinesHovered : ImGuiCol_PlotHistogramHovered); for (int n = 0; n < res_w; n++) { const float t1 = t0 + t_step; const int v1_idx = (int)(t0 * item_count + 0.5f); IM_ASSERT(v1_idx >= 0 && v1_idx < values_count); const float v1 = values_getter(data, (v1_idx + values_offset + 1) % values_count); const ImVec2 tp1 = ImVec2(t1, 1.0f - ImSaturate((v1 - scale_min) * inv_scale)); // NB: Draw calls are merged together by the DrawList system. Still, we // should render our batch are lower level to save a bit of CPU. ImVec2 pos0 = ImLerp(inner_bb.Min, inner_bb.Max, tp0); ImVec2 pos1 = ImLerp(inner_bb.Min, inner_bb.Max, (plot_type == ImGuiPlotType_Lines) ? tp1 : ImVec2(tp1.x, histogram_zero_line_t)); if (plot_type == ImGuiPlotType_Lines) { window->DrawList->AddLine(pos0, pos1, v_hovered == v1_idx ? col_hovered : col_base); } else if (plot_type == ImGuiPlotType_Histogram) { if (pos1.x >= pos0.x + 2.0f) pos1.x -= 1.0f; window->DrawList->AddRectFilled( pos0, pos1, v_hovered == v1_idx ? col_hovered : col_base); } t0 = t1; tp0 = tp1; } } // Text overlay if (overlay_text) RenderTextClipped( ImVec2(frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y), frame_bb.Max, overlay_text, NULL, NULL, ImVec2(0.5f, 0.0f)); const float v = values_getter(data, (values_offset)); ImGui::LabelText(label, "%6.2f %s [%6.2f, %6.2f]", v, unit, v_min, v_max); } extern "C" void igPlotTimings(const char *label, float (*values_getter)(void *data, int idx), void *data, int values_count, int values_offset, const char *overlay_text, float scale_min, float scale_max, ImVec2 frame_size, float reference_timing, bool center_reference_timing, float range, const char *unit, bool dynamic_rescale) { PlotTimings(label, values_getter, data, values_count, values_offset, overlay_text, frame_size, reference_timing, center_reference_timing, range, unit, dynamic_rescale); } extern "C" void igToggleButton(const char *str_id, bool *v) { ImVec2 p = ImGui::GetCursorScreenPos(); ImDrawList *draw_list = ImGui::GetWindowDrawList(); float height = ImGui::GetFrameHeight(); float width = height * 1.55f; float radius = height * 0.50f; ImGui::InvisibleButton(str_id, ImVec2(width, height)); if (ImGui::IsItemClicked()) *v = !*v; float t = *v ? 1.0f : 0.0f; ImGuiContext &g = *GImGui; float ANIM_SPEED = 0.08f; if (g.LastActiveId == g.CurrentWindow->GetID(str_id)) // && g.LastActiveIdTimer < ANIM_SPEED) { float t_anim = ImSaturate(g.LastActiveIdTimer / ANIM_SPEED); t = *v ? (t_anim) : (1.0f - t_anim); } ImU32 col_bg; if (ImGui::IsItemHovered()) col_bg = ImGui::GetColorU32(ImLerp(ImVec4(0.78f, 0.78f, 0.78f, 1.0f), ImVec4(0.64f, 0.83f, 0.34f, 1.0f), t)); else col_bg = ImGui::GetColorU32(ImLerp(ImVec4(0.85f, 0.85f, 0.85f, 1.0f), ImVec4(0.56f, 0.83f, 0.26f, 1.0f), t)); draw_list->AddRectFilled(p, ImVec2(p.x + width, p.y + height), col_bg, height * 0.5f); draw_list->AddCircleFilled( ImVec2(p.x + radius + t * (width - radius * 2.0f), p.y + radius), radius - 1.5f, IM_COL32(255, 255, 255, 255)); } extern "C" void igImageBg(ImTextureID user_texture_id, const ImVec2 size, const ImVec2 uv0, const ImVec2 uv1, const ImVec4 tint_col, const ImVec4 border_col, const ImVec4 bg_col) { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) { return; } ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); auto bbImg = bb; if (border_col.w > 0.0f) { bb.Max += ImVec2(2, 2); // Move the image pixel down and right, to be inside of the frame. bbImg.Min += ImVec2(1, 1); bbImg.Max += ImVec2(1, 1); } ItemSize(bb); if (!ItemAdd(bb, 0)) { return; } // Do we have a border? if (border_col.w > 0.0f) { window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(border_col), 0.0f); } // Should we clear the background? if (bg_col.w > 0.0f) { window->DrawList->AddRectFilled(bbImg.Min, bbImg.Max, GetColorU32(bg_col)); } // Finally the image. window->DrawList->AddImage(user_texture_id, bbImg.Min, bbImg.Max, uv0, uv1, GetColorU32(tint_col)); }