#include "my_imgui_internal_widgets.h" #include "imgui.h" #ifndef IMGUI_DEFINE_MATH_OPERATORS #define IMGUI_DEFINE_MATH_OPERATORS #endif #include "imgui_internal.h" // A modded version of ScalarSlider allowing for the minimum and maximum parts // of the slider to be draggable by two other buttons. p_mid is from range -1 // to 1, and s_min and max are from 0-1. bool ImGui::SliderLevels(const char* label, const char* label2, const char* label3, void* p_mid, void* p_left, void* p_right) { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; const float SliderMin = 0.1; const float SliderMax = 10; const float OtherMin = 0; const float OtherMax = 1; const void* p_min = &SliderMin; const void* p_max = &SliderMax; const void* o_min = &OtherMin; const void* o_max = &OtherMax; ImGuiDataType data_type = ImGuiDataType_Float; const char *format = "%f"; ImGuiSliderFlags flags = ImGuiSliderFlags_NoInput; ImGuiContext& g = *GImGui; const ImGuiStyle& style = g.Style; // I'm not well-versed in exactly what ImGui's id system does, but I'm // pretty sure it's one clickable object equals one ImGui ID. const ImGuiID id_L = window->GetID(label); const ImGuiID id_R = window->GetID(label2); const ImGuiID id_mid = window->GetID(label3); const float w = CalcItemWidth(); const ImVec2 label_size = CalcTextSize(label, NULL, true); const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f)); const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); ItemSize(total_bb, style.FramePadding.y); if (!ItemAdd(total_bb, id_L, &frame_bb, 0)) return false; if (!ItemAdd(total_bb, id_R, &frame_bb, 0)) return false; if (!ItemAdd(total_bb, id_mid, &frame_bb, 0)) return false; bool any_val_changed; // Slider behavior ImRect grab_bb; const bool value_changed = SliderBehavior(frame_bb, id_L, data_type, p_left, o_min, o_max, format, flags, &grab_bb); if (value_changed) { MarkItemEdited(id_L); any_val_changed = true; } ImRect grab_bb2; const bool value_changed2 = SliderBehavior(frame_bb, id_R, data_type, p_right, o_min, o_max, format, flags, &grab_bb2); if (value_changed2) { MarkItemEdited(id_R); any_val_changed = true; } const ImRect mid_bb(ImVec2(grab_bb.Max.x, frame_bb.Min.y), ImVec2(grab_bb2.Min.x, frame_bb.Max.y)); // Slider behavior ImRect grab_bb3; const bool value_changed3 = SliderBehavior(mid_bb, id_mid, data_type, p_mid, p_min, p_max, format, flags | ImGuiSliderFlags_Logarithmic, &grab_bb3); if (value_changed3) { MarkItemEdited(id_mid); any_val_changed = true; } const bool hovered = ItemHoverable(frame_bb, id_L); const bool input_requested_by_tabbing = (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_FocusedByTabbing) != 0; const bool clicked = (hovered && g.IO.MouseClicked[0]); const bool make_active = (input_requested_by_tabbing || clicked || g.NavActivateId == id_L || g.NavActivateInputId == id_L); if (make_active) { if (g.IO.MousePos.x < grab_bb.Max.x) { SetActiveID(id_L, window); SetFocusID(id_L, window); } else if (g.IO.MousePos.x > grab_bb2.Min.x) { SetActiveID(id_R, window); SetFocusID(id_R, window); } else { SetActiveID(id_mid, window); SetFocusID(id_mid, window); } FocusWindow(window); g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right); } // Draw frame const ImU32 frame_col = GetColorU32(g.ActiveId == id_L ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); RenderNavHighlight(frame_bb, id_L); RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, g.Style.FrameRounding); // Render grab if (grab_bb.Max.x > grab_bb.Min.x) window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, GetColorU32(g.ActiveId == id_L ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding); // Render grab if (grab_bb2.Max.x > grab_bb2.Min.x) window->DrawList->AddRectFilled(grab_bb2.Min, grab_bb2.Max, GetColorU32(g.ActiveId == id_R ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding); // Render grab if (grab_bb3.Max.x > grab_bb3.Min.x) window->DrawList->AddRectFilled(grab_bb3.Min, grab_bb3.Max, GetColorU32(g.ActiveId == id_mid ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding); // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. char value_buf[64]; const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, p_mid, format); if (g.LogEnabled) LogSetNextTextDecoration("{", "}"); RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f)); if (label_size.x > 0.0f) RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags); return any_val_changed; } bool ImGui::TestLine(ImVec2 p0, ImVec2 p1) { ImGuiContext& g = *GImGui; ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; bool Toggle = false; ImDrawList* draw_list = window->DrawList; // ImGuiStyle& style = g.Style; ImVec2 point = ImLineClosestPoint(p0, p1, g.IO.MousePos); ImU32 col = ImGui::GetColorU32(ImGuiCol_ScrollbarGrab); if (abs(g.IO.MousePos.x - point.x) < 3 && abs(g.IO.MousePos.y - point.y) < 3 && point.x != p0.x && point.y != p0.y && point.x != p1.x && point.y != p1.y) { col = ImGui::GetColorU32(ImGuiCol_Button); Toggle = true; } draw_list->AddLine(p0, p1, col, 2.0f); return Toggle; } // Slightly modified version of the bezier closest point lookup code that // additionally outputs the ratio of the closest point along the curve for use // in constructing the handles of new points. // The ratio here is just the dot product divided by the squared length. ImVec2 ImLineClosestPoint2(const ImVec2& a, const ImVec2& b, const ImVec2& p, float& ratio) { ImVec2 ap = p - a; ImVec2 ab_dir = b - a; float dot = ap.x * ab_dir.x + ap.y * ab_dir.y; if (dot < 0.0f) { ratio = 0.0f; return a; } float ab_len_sqr = ab_dir.x * ab_dir.x + ab_dir.y * ab_dir.y; if (dot > ab_len_sqr) { ratio = 1.0f; return b; } ratio = dot / ab_len_sqr; return a + ab_dir * dot / ab_len_sqr; } // Function to convert a ratio back into a point for the bezier handles. ImVec2 ImGui::RatioToPoint(const ImVec2& a, const ImVec2& b, float ratio) { ImVec2 ab_dir = b - a; float ab_len_sqr = ab_dir.x * ab_dir.x + ab_dir.y * ab_dir.y; float dot = ratio*ab_len_sqr; return a + ab_dir * dot / ab_len_sqr; } // Following the algorithm, we take the ratio from the _leftmost_ point in each // subdivision of the cubic spline until we're within tess_tol, and then we // interpolate it with the subdivision's rightmost point in the ClosestPoint call. // The pow(0.5, level) represents the ratio of the next subdivision's leftmost // point (AKA the rightmost point of the current subdivision). static void ImBezierCubicClosestPointCasteljauStep2(const ImVec2& p, ImVec2& p_closest, float ratio, float& r_closest, ImVec2& p_last, float& p_closest_dist2, float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4, float tess_tol, int level) { float dx = x4 - x1; float dy = y4 - y1; float d2 = ((x2 - x4) * dy - (y2 - y4) * dx); float d3 = ((x3 - x4) * dy - (y3 - y4) * dx); d2 = (d2 >= 0) ? d2 : -d2; d3 = (d3 >= 0) ? d3 : -d3; if ((d2 + d3) * (d2 + d3) < tess_tol * (dx * dx + dy * dy)) { ImVec2 p_current(x4, y4); float added_ratio; ImVec2 p_line = ImLineClosestPoint2(p_last, p_current, p, added_ratio); float dist2 = ImLengthSqr(p - p_line); if (dist2 < p_closest_dist2) { p_closest = p_line; p_closest_dist2 = dist2; r_closest = ratio + pow(0.5, level)*added_ratio; } p_last = p_current; } else if (level < 10) { float x12 = (x1 + x2)*0.5f, y12 = (y1 + y2)*0.5f; float x23 = (x2 + x3)*0.5f, y23 = (y2 + y3)*0.5f; float x34 = (x3 + x4)*0.5f, y34 = (y3 + y4)*0.5f; float x123 = (x12 + x23)*0.5f, y123 = (y12 + y23)*0.5f; float x234 = (x23 + x34)*0.5f, y234 = (y23 + y34)*0.5f; float x1234 = (x123 + x234)*0.5f, y1234 = (y123 + y234)*0.5f; ImBezierCubicClosestPointCasteljauStep2(p, p_closest, ratio, r_closest, p_last, p_closest_dist2, x1, y1, x12, y12, x123, y123, x1234, y1234, tess_tol, level + 1); ImBezierCubicClosestPointCasteljauStep2(p, p_closest, ratio + pow(0.5, level+1), r_closest, p_last, p_closest_dist2, x1234, y1234, x234, y234, x34, y34, x4, y4, tess_tol, level + 1); } } ImVec2 ImBezierCubicClosestPointCasteljau2(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, const ImVec2& p, float& ratio, float tess_tol) { IM_ASSERT(tess_tol > 0.0f); ImVec2 p_last = p1; ImVec2 p_closest; float p_closest_dist2 = FLT_MAX; float r_closest; ImBezierCubicClosestPointCasteljauStep2(p, p_closest, 0, r_closest, p_last, p_closest_dist2, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, p4.x, p4.y, tess_tol, 0); ratio = r_closest; return p_closest; } // Returns true when cursor is close to the curve but not too close to the beginning/end points. bool ImGui::BezierInteractive(ImVec2 p0, ImVec2 p1, ImVec2 p2, ImVec2 p3, float& ratio) { bool hovered = false; ImGuiContext& g = *GImGui; ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; ImDrawList* draw_list = window->DrawList; ImU32 col = ImGui::GetColorU32(ImGuiCol_ScrollbarGrab); ImVec2 point = ImBezierCubicClosestPointCasteljau2(p0, p1, p2, p3, g.IO.MousePos, ratio, GetStyle().CurveTessellationTol); if (abs(g.IO.MousePos.x - point.x) < 3 && abs(g.IO.MousePos.y - point.y) < 3 && abs(p0.x - point.x) > 3 && abs(p0.y - point.y) > 3 && abs(p1.x - point.x) > 3 && abs(p1.y - point.y) > 3) { col = GetColorU32(ImGuiCol_Button); hovered = true; } draw_list->AddBezierCubic(p0, p1, p2, p3, col, 2.0f, 0); return hovered; }