mirror of
https://github.com/MiSTer-devel/InputTest_MiSTer.git
synced 2026-04-19 03:04:18 +00:00
263 lines
10 KiB
C++
263 lines
10 KiB
C++
#include <imgui_plot.h>
|
|
#include <imgui.h>
|
|
#ifndef IMGUI_DEFINE_MATH_OPERATORS
|
|
#define IMGUI_DEFINE_MATH_OPERATORS
|
|
#endif
|
|
#include <imgui_internal.h>
|
|
|
|
namespace ImGui {
|
|
// [0..1] -> [0..1]
|
|
static float rescale(float t, float min, float max, PlotConfig::Scale::Type type) {
|
|
switch (type) {
|
|
case PlotConfig::Scale::Linear:
|
|
return t;
|
|
case PlotConfig::Scale::Log10:
|
|
return log10(ImLerp(min, max, t) / min) / log10(max / min);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// [0..1] -> [0..1]
|
|
static float rescale_inv(float t, float min, float max, PlotConfig::Scale::Type type) {
|
|
switch (type) {
|
|
case PlotConfig::Scale::Linear:
|
|
return t;
|
|
case PlotConfig::Scale::Log10:
|
|
return (pow(max/min, t) * min - min) / (max - min);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int cursor_to_idx(const ImVec2& pos, const ImRect& bb, const PlotConfig& conf, float x_min, float x_max) {
|
|
const float t = ImClamp((pos.x - bb.Min.x) / (bb.Max.x - bb.Min.x), 0.0f, 0.9999f);
|
|
const int v_idx = (int)(rescale_inv(t, x_min, x_max, conf.scale.type) * (conf.values.count - 1));
|
|
IM_ASSERT(v_idx >= 0 && v_idx < conf.values.count);
|
|
|
|
return v_idx;
|
|
}
|
|
|
|
PlotStatus Plot(const char* label, const PlotConfig& conf) {
|
|
PlotStatus status = PlotStatus::nothing;
|
|
|
|
ImGuiWindow* window = GetCurrentWindow();
|
|
if (window->SkipItems)
|
|
return status;
|
|
|
|
const float* const* ys_list = conf.values.ys_list;
|
|
int ys_count = conf.values.ys_count;
|
|
const ImU32* colors = conf.values.colors;
|
|
if (conf.values.ys != nullptr) { // draw only a single plot
|
|
ys_list = &conf.values.ys;
|
|
ys_count = 1;
|
|
colors = &conf.values.color;
|
|
}
|
|
|
|
ImGuiContext& g = *GImGui;
|
|
const ImGuiStyle& style = g.Style;
|
|
const ImGuiID id = window->GetID(label);
|
|
|
|
const ImRect frame_bb(
|
|
window->DC.CursorPos,
|
|
window->DC.CursorPos + conf.frame_size);
|
|
const ImRect inner_bb(
|
|
frame_bb.Min + style.FramePadding,
|
|
frame_bb.Max - style.FramePadding);
|
|
const ImRect total_bb = frame_bb;
|
|
ItemSize(total_bb, style.FramePadding.y);
|
|
if (!ItemAdd(total_bb, 0, &frame_bb))
|
|
return status;
|
|
const bool hovered = ItemHoverable(frame_bb, id);
|
|
|
|
RenderFrame(
|
|
frame_bb.Min,
|
|
frame_bb.Max,
|
|
GetColorU32(ImGuiCol_FrameBg),
|
|
true,
|
|
style.FrameRounding);
|
|
|
|
if (conf.values.count > 0) {
|
|
int res_w;
|
|
if (conf.skip_small_lines)
|
|
res_w = ImMin((int)conf.frame_size.x, conf.values.count);
|
|
else
|
|
res_w = conf.values.count;
|
|
res_w -= 1;
|
|
int item_count = conf.values.count - 1;
|
|
|
|
float x_min = conf.values.offset;
|
|
float x_max = conf.values.offset + conf.values.count - 1;
|
|
if (conf.values.xs) {
|
|
x_min = conf.values.xs[size_t(x_min)];
|
|
x_max = conf.values.xs[size_t(x_max)];
|
|
}
|
|
|
|
// Tooltip on hover
|
|
int v_hovered = -1;
|
|
if (conf.tooltip.show && hovered && inner_bb.Contains(g.IO.MousePos)) {
|
|
const int v_idx = cursor_to_idx(g.IO.MousePos, inner_bb, conf, x_min, x_max);
|
|
const size_t data_idx = conf.values.offset + (v_idx % conf.values.count);
|
|
const float x0 = conf.values.xs ? conf.values.xs[data_idx] : v_idx;
|
|
const float y0 = ys_list[0][data_idx]; // TODO: tooltip is only shown for the first y-value!
|
|
SetTooltip(conf.tooltip.format, x0, y0);
|
|
v_hovered = v_idx;
|
|
}
|
|
|
|
const float t_step = 1.0f / (float)res_w;
|
|
const float inv_scale = (conf.scale.min == conf.scale.max) ?
|
|
0.0f : (1.0f / (conf.scale.max - conf.scale.min));
|
|
|
|
if (conf.grid_x.show) {
|
|
int y0 = inner_bb.Min.y;
|
|
int y1 = inner_bb.Max.y;
|
|
switch (conf.scale.type) {
|
|
case PlotConfig::Scale::Linear: {
|
|
float cnt = conf.values.count / (conf.grid_x.size / conf.grid_x.subticks);
|
|
float inc = 1.f / cnt;
|
|
for (int i = 0; i <= cnt; ++i) {
|
|
int x0 = ImLerp(inner_bb.Min.x, inner_bb.Max.x, i * inc);
|
|
window->DrawList->AddLine(
|
|
ImVec2(x0, y0),
|
|
ImVec2(x0, y1),
|
|
IM_COL32(200, 200, 200, (i % conf.grid_x.subticks) ? 128 : 255));
|
|
}
|
|
break;
|
|
}
|
|
case PlotConfig::Scale::Log10: {
|
|
float start = 1.f;
|
|
while (start < x_max) {
|
|
for (int i = 1; i < 10; ++i) {
|
|
float x = start * i;
|
|
if (x < x_min) continue;
|
|
if (x > x_max) break;
|
|
float t = log10(x / x_min) / log10(x_max / x_min);
|
|
int x0 = ImLerp(inner_bb.Min.x, inner_bb.Max.x, t);
|
|
window->DrawList->AddLine(
|
|
ImVec2(x0, y0),
|
|
ImVec2(x0, y1),
|
|
IM_COL32(200, 200, 200, (i > 1) ? 128 : 255));
|
|
}
|
|
start *= 10.f;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (conf.grid_y.show) {
|
|
int x0 = inner_bb.Min.x;
|
|
int x1 = inner_bb.Max.x;
|
|
float cnt = (conf.scale.max - conf.scale.min) / (conf.grid_y.size / conf.grid_y.subticks);
|
|
float inc = 1.f / cnt;
|
|
for (int i = 0; i <= cnt; ++i) {
|
|
int y0 = ImLerp(inner_bb.Min.y, inner_bb.Max.y, i * inc);
|
|
window->DrawList->AddLine(
|
|
ImVec2(x0, y0),
|
|
ImVec2(x1, y0),
|
|
IM_COL32(0, 0, 0, (i % conf.grid_y.subticks) ? 16 : 64));
|
|
}
|
|
}
|
|
|
|
const ImU32 col_hovered = GetColorU32(ImGuiCol_PlotLinesHovered);
|
|
ImU32 col_base = GetColorU32(ImGuiCol_PlotLines);
|
|
|
|
for (int i = 0; i < ys_count; ++i) {
|
|
if (colors) {
|
|
if (colors[i]) col_base = colors[i];
|
|
else col_base = GetColorU32(ImGuiCol_PlotLines);
|
|
}
|
|
float v0 = ys_list[i][conf.values.offset];
|
|
float t0 = 0.0f;
|
|
// Point in the normalized space of our target rectangle
|
|
ImVec2 tp0 = ImVec2(t0, 1.0f - ImSaturate((v0 - conf.scale.min) * inv_scale));
|
|
|
|
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 < conf.values.count);
|
|
const float v1 = ys_list[i][conf.values.offset + (v1_idx + 1) % conf.values.count];
|
|
const ImVec2 tp1 = ImVec2(
|
|
rescale(t1, x_min, x_max, conf.scale.type),
|
|
1.0f - ImSaturate((v1 - conf.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, tp1);
|
|
|
|
if (v1_idx == v_hovered) {
|
|
window->DrawList->AddCircleFilled(pos0, 3, col_hovered);
|
|
}
|
|
|
|
window->DrawList->AddLine(
|
|
pos0,
|
|
pos1,
|
|
col_base,
|
|
conf.line_thickness);
|
|
|
|
t0 = t1;
|
|
tp0 = tp1;
|
|
}
|
|
}
|
|
|
|
if (conf.v_lines.show) {
|
|
for (size_t i = 0; i < conf.v_lines.count; ++i) {
|
|
const size_t idx = conf.v_lines.indices[i];
|
|
const float t1 = rescale(idx * t_step, x_min, x_max, conf.scale.type);
|
|
ImVec2 pos0 = ImLerp(inner_bb.Min, inner_bb.Max, ImVec2(t1, 0.f));
|
|
ImVec2 pos1 = ImLerp(inner_bb.Min, inner_bb.Max, ImVec2(t1, 1.f));
|
|
window->DrawList->AddLine(pos0, pos1, IM_COL32(0xff, 0, 0, 0x88));
|
|
}
|
|
}
|
|
|
|
if (conf.selection.show) {
|
|
if (hovered) {
|
|
if (g.IO.MouseClicked[0]) {
|
|
SetActiveID(id, window);
|
|
FocusWindow(window);
|
|
|
|
const int v_idx = cursor_to_idx(g.IO.MousePos, inner_bb, conf, x_min, x_max);
|
|
uint32_t start = conf.values.offset + (v_idx % conf.values.count);
|
|
uint32_t end = start;
|
|
if (conf.selection.sanitize_fn)
|
|
end = conf.selection.sanitize_fn(end - start) + start;
|
|
if (end < conf.values.offset + conf.values.count) {
|
|
*conf.selection.start = start;
|
|
*conf.selection.length = end - start;
|
|
status = PlotStatus::selection_updated;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (g.ActiveId == id) {
|
|
if (g.IO.MouseDown[0]) {
|
|
const int v_idx = cursor_to_idx(g.IO.MousePos, inner_bb, conf, x_min, x_max);
|
|
const uint32_t start = *conf.selection.start;
|
|
uint32_t end = conf.values.offset + (v_idx % conf.values.count);
|
|
if (end > start) {
|
|
if (conf.selection.sanitize_fn)
|
|
end = conf.selection.sanitize_fn(end - start) + start;
|
|
if (end < conf.values.offset + conf.values.count) {
|
|
*conf.selection.length = end - start;
|
|
status = PlotStatus::selection_updated;
|
|
}
|
|
}
|
|
} else {
|
|
ClearActiveID();
|
|
}
|
|
}
|
|
float fSelectionStep = 1.0 / item_count;
|
|
ImVec2 pos0 = ImLerp(inner_bb.Min, inner_bb.Max,
|
|
ImVec2(fSelectionStep * *conf.selection.start, 0.f));
|
|
ImVec2 pos1 = ImLerp(inner_bb.Min, inner_bb.Max,
|
|
ImVec2(fSelectionStep * (*conf.selection.start + *conf.selection.length), 1.f));
|
|
window->DrawList->AddRectFilled(pos0, pos1, IM_COL32(128, 128, 128, 32));
|
|
window->DrawList->AddRect(pos0, pos1, IM_COL32(128, 128, 128, 128));
|
|
}
|
|
}
|
|
|
|
// Text overlay
|
|
if (conf.overlay_text)
|
|
RenderTextClipped(ImVec2(frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y), frame_bb.Max, conf.overlay_text, NULL, NULL, ImVec2(0.5f,0.0f));
|
|
|
|
return status;
|
|
}
|
|
} |