diff --git a/Externals/imgui/imgui.h b/Externals/imgui/imgui.h index 53e172533a..18eb829c7b 100644 --- a/Externals/imgui/imgui.h +++ b/Externals/imgui/imgui.h @@ -449,7 +449,7 @@ namespace ImGui IMGUI_API bool VSliderFloat(const char* label, const ImVec2& size, float* v, float v_min, float v_max, const char* format = "%.3f", float power = 1.0f); IMGUI_API bool VSliderInt(const char* label, const ImVec2& size, int* v, int v_min, int v_max, const char* format = "%d"); IMGUI_API bool VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType data_type, void* v, const void* v_min, const void* v_max, const char* format = NULL, float power = 1.0f); - + IMGUI_API bool SliderCustom(const char* label, ImVec4 color, int* v, int v_min, int v_max, float power, const char* format = "%d"); // Widgets: Input with Keyboard // - If you want to use InputText() with a dynamic string type such as std::string or your own, see misc/cpp/imgui_stdlib.h // - Most of the ImGuiInputTextFlags flags are only useful for InputText() and not for InputFloatX, InputIntX, InputDouble etc. diff --git a/Externals/imgui/imgui_internal.h b/Externals/imgui/imgui_internal.h index 9107cfeaff..2ca301659b 100644 --- a/Externals/imgui/imgui_internal.h +++ b/Externals/imgui/imgui_internal.h @@ -1543,6 +1543,7 @@ namespace ImGui IMGUI_API bool ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool* out_held, ImGuiButtonFlags flags = 0); IMGUI_API bool DragBehavior(ImGuiID id, ImGuiDataType data_type, void* v, float v_speed, const void* v_min, const void* v_max, const char* format, float power, ImGuiDragFlags flags); IMGUI_API bool SliderBehavior(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, void* v, const void* v_min, const void* v_max, const char* format, float power, ImGuiSliderFlags flags, ImRect* out_grab_bb); + IMGUI_API bool SliderCustomBehavior(const ImRect& frame_bb, ImGuiID id, int* v, int v_min, int v_max, float power, ImGuiSliderFlags flags, ImVec4 color, ImVec2 valuesize, const char* label, char* value); IMGUI_API bool SplitterBehavior(const ImRect& bb, ImGuiID id, ImGuiAxis axis, float* size1, float* size2, float min_size1, float min_size2, float hover_extend = 0.0f, float hover_visibility_delay = 0.0f); IMGUI_API bool TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* label, const char* label_end = NULL); IMGUI_API bool TreeNodeBehaviorIsOpen(ImGuiID id, ImGuiTreeNodeFlags flags = 0); // Consume previous SetNextTreeNodeOpened() data, if any. May return true when logging diff --git a/Externals/imgui/imgui_widgets.cpp b/Externals/imgui/imgui_widgets.cpp index d1ecf3ba99..7d2abab1d9 100644 --- a/Externals/imgui/imgui_widgets.cpp +++ b/Externals/imgui/imgui_widgets.cpp @@ -2414,6 +2414,122 @@ bool ImGui::SliderBehavior(const ImRect& bb, ImGuiID id, ImGuiDataType data_type return false; } +bool ImGui::SliderCustomBehavior(const ImRect& bb, ImGuiID id, int* v, int v_min, int v_max, float power, ImGuiSliderFlags flags, ImVec4 color, ImVec2 valuesize, const char* label, char* value) +{ + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + ImGuiWindow* window = GetCurrentWindow(); + const bool hovered = ItemHoverable(bb, id); + const ImGuiAxis axis = (flags & ImGuiSliderFlags_Vertical) ? ImGuiAxis_Y : ImGuiAxis_X; + const bool is_decimal = false; // TODO handle other types + const bool is_power = (power != 1.0f) && is_decimal; + + const float grab_padding = 2.0f; + const float slider_sz = (bb.Max[axis] - bb.Min[axis]) - grab_padding * 2.0f; + float grab_sz = ImMin(ImMax(1.0f * (slider_sz / ((v_min < v_max ? v_max - v_min : v_min - v_max) + 1.0f)), style.GrabMinSize), slider_sz); + int v_range = (v_min < v_max ? v_max - v_min : v_min - v_max); + if (!is_decimal && v_range >= 0) // v_range < 0 may happen on integer overflows + grab_sz = ImMax((float)(slider_sz / (v_range + 1)), style.GrabMinSize); // For integer sliders: if possible have the grab size represent 1 unit + grab_sz = ImMin(grab_sz, slider_sz); + const float slider_usable_sz = slider_sz - grab_sz; + const float slider_usable_pos_min = bb.Min[axis] + grab_padding + grab_sz * 0.5f; + const float slider_usable_pos_max = bb.Max[axis] - grab_padding - grab_sz * 0.5f; + + float linear_zero_pos = 0.0f; + if (is_power && v_min * v_max < 0.0f) + { + const float linear_dist_min_to_0 = ImPow(v_min >= 0 ? (float)v_min : -(float)v_min, (float)1.0f / power); + const float linear_dist_max_to_0 = ImPow(v_max >= 0 ? (float)v_max : -(float)v_max, (float)1.0f / power); + linear_zero_pos = (float)(linear_dist_min_to_0 / (linear_dist_min_to_0 + linear_dist_max_to_0)); + } + else + { + linear_zero_pos = v_min < 0.0f ? 1.0f : 0.0f; + } + + bool isDown = false; + bool value_changed = false; + if (g.ActiveId == id) + { + if (!g.IO.MouseDown[0]) + { + ClearActiveID(); + } + else + { + isDown = true; + const float mouse_abs_pos = g.IO.MousePos[axis]; + float clicked_t = (slider_usable_sz > 0.0f) ? ImClamp((mouse_abs_pos - slider_usable_pos_min) / slider_usable_sz, 0.0f, 1.0f) : 0.0f; + if (axis == ImGuiAxis_Y) + clicked_t = 1.0f - clicked_t; + + int new_value; + if (is_power) + { + if (clicked_t < linear_zero_pos) + { + float a = 1.0f - (clicked_t / linear_zero_pos); + a = ImPow(a, power); + new_value = ImLerp(ImMin(v_max, 0), v_min, a); + } + else + { + float a; + if (ImFabs(linear_zero_pos - 1.0f) > 1.e-6f) + a = (clicked_t - linear_zero_pos) / (1.0f - linear_zero_pos); + else + a = clicked_t; + a = ImPow(a, power); + new_value = ImLerp(ImMax(v_min, 0), v_max, a); + } + } + else + { + new_value = ImLerp(v_min, v_max, clicked_t); + } + + if (*v != new_value) + { + *v = new_value; + value_changed = true; + } + } + + } + + float grab_t = SliderCalcRatioFromValueT(ImGuiDataType_S32, *v, v_min, v_max, power, linear_zero_pos); + + if (axis == ImGuiAxis_Y) + grab_t = 1.0f - grab_t; + const float grab_pos = ImLerp(slider_usable_pos_min, slider_usable_pos_max, grab_t); + ImRect grab_bb; + if (axis == ImGuiAxis_X) + grab_bb = ImRect(ImVec2(grab_pos - grab_sz * 0.5f, bb.Min.y + grab_padding), ImVec2(grab_pos + grab_sz * 0.5f, bb.Max.y - grab_padding)); + else + grab_bb = ImRect(ImVec2(bb.Min.x + grab_padding, grab_pos - grab_sz * 0.5f), ImVec2(bb.Max.x - grab_padding, grab_pos + grab_sz * 0.5f)); + + // Draw all the things + + // Grey background line + window->DrawList->AddLine(ImVec2(bb.Min.x, bb.Min.y + ((bb.Max.y - bb.Min.y) / 2)), ImVec2(bb.Max.x, bb.Min.y + ((bb.Max.y - bb.Min.y) / 2)), ColorConvertFloat4ToU32(ImVec4(1.0f, 1.0f, 1.0f, 0.5f)), 4); + + // Whiter, more opaque line up to mouse position + if (hovered) + window->DrawList->AddLine(ImVec2(bb.Min.x, bb.Min.y + ((bb.Max.y - bb.Min.y) / 2)), ImVec2(grab_bb.Min.x, bb.Min.y + ((bb.Max.y - bb.Min.y) / 2)), ColorConvertFloat4ToU32(ImVec4(1.0f, 1.0f, 1.0f, 1.0f)), 4); + + window->DrawList->AddLine(ImVec2(bb.Min.x, bb.Min.y + ((bb.Max.y - bb.Min.y) / 2)), ImVec2(grab_bb.Min.x, bb.Min.y + ((bb.Max.y - bb.Min.y) / 2)), ColorConvertFloat4ToU32(ImVec4(0.0f, 1.0f, 0.0f, 1.00f)), 4); + if (isDown) { + window->DrawList->AddCircleFilled(ImVec2(grab_bb.Min.x + 6, grab_bb.Min.y + ((grab_bb.Max.y - grab_bb.Min.y) / 2)), 8, ColorConvertFloat4ToU32(ImVec4(0.0f, 1.0f, 0.0f, 1.0f))); + float width = (valuesize.x + 2 * 2.f); //width = (textWidth + 2 * padding) + window->DrawList->AddText(ImVec2(grab_bb.GetCenter().x - valuesize.x / 2, bb.Min.y), ImColor(255, 255, 255), value, label); + } + //window->DrawList->AddCircleFilled(ImVec2(grab_bb.Min.x + 6, grab_bb.Min.y + ((grab_bb.Max.y - grab_bb.Min.y) / 2)), 5, ColorConvertFloat4ToU32(ImVec4(0.0f, 1.0f, 0.0f, 1.00f))); + + window->DrawList->AddRectFilled(ImVec2(grab_bb.Min.x, bb.Min.y + 2), ImVec2(grab_bb.Max.x + valuesize.x, bb.Min.y + 14), ColorConvertFloat4ToU32(ImVec4(0.21f, 0.20f, 0.21f, 1.00f)), 3); + + return value_changed; +} + bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* v, const void* v_min, const void* v_max, const char* format, float power) { ImGuiWindow* window = GetCurrentWindow(); @@ -2520,6 +2636,63 @@ bool ImGui::SliderScalarN(const char* label, ImGuiDataType data_type, void* v, i return value_changed; } +// TODO: this really should be templated but I'm lazy +bool ImGui::SliderCustom(const char* label, ImVec4 color, int* v, int v_min, int v_max, float power, const char* format) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + const float w = CalcItemWidth(); + + const ImVec2 label_size = CalcTextSize(label, NULL, true) * 2.7; + const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 0.5f)); + 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)); + + if (!ItemAdd(total_bb, id)) + { + ItemSize(total_bb, style.FramePadding.y); + return false; + } + + const bool hovered = ItemHoverable(frame_bb, id); + if (hovered) + SetHoveredID(id); + + if (!format) + format = "%d"; + + bool start_text_input = false; + const bool tab_focus_requested = FocusableItemRegister(window, g.ActiveId == id); + if (tab_focus_requested || (hovered && g.IO.MouseClicked[0])) + { + SetActiveID(id, window); + FocusWindow(window); + + if (tab_focus_requested || g.IO.KeyCtrl) + { + start_text_input = true; + g.ScalarAsInputTextId = 0; + } + } + if (start_text_input || (g.ActiveId == id && g.ScalarAsInputTextId == id)) + return InputScalarAsWidgetReplacement(frame_bb, id, label, ImGuiDataType_S32, v, format); + + ItemSize(total_bb, style.FramePadding.y); + + char value_buf[64]; + const char* value_buf_end = value_buf + ImFormatString(value_buf, IM_ARRAYSIZE(value_buf), format, *v); + const bool value_changed = SliderCustomBehavior(frame_bb, id, v, v_min, v_max, power, ImGuiSliderFlags_None, color, CalcTextSize(value_buf, NULL, true), value_buf_end, value_buf); + + if (label_size.x > 0.0f) + RenderText(ImVec2(frame_bb.Min.x + style.ItemInnerSpacing.x, frame_bb.Min.y + 25), label); + + return value_changed; +} + bool ImGui::SliderFloat(const char* label, float* v, float v_min, float v_max, const char* format, float power) { return SliderScalar(label, ImGuiDataType_Float, v, &v_min, &v_max, format, power); diff --git a/Source/Core/DolphinQt/RenderWidget.cpp b/Source/Core/DolphinQt/RenderWidget.cpp index 2f2f098d8e..ba572fbe00 100644 --- a/Source/Core/DolphinQt/RenderWidget.cpp +++ b/Source/Core/DolphinQt/RenderWidget.cpp @@ -20,6 +20,10 @@ #include #include +//test ui stuff +#include +#include + #include "imgui.h" #include "Core/Config/MainSettings.h" diff --git a/Source/Core/VideoCommon/OnScreenDisplay.cpp b/Source/Core/VideoCommon/OnScreenDisplay.cpp index 68afd3e6ab..9a8f8c6f8f 100644 --- a/Source/Core/VideoCommon/OnScreenDisplay.cpp +++ b/Source/Core/VideoCommon/OnScreenDisplay.cpp @@ -13,6 +13,7 @@ #include #include "Common/CommonTypes.h" +#include "Common/Logging/Log.h" #include "Common/Timer.h" #include "Core/ConfigManager.h" @@ -36,6 +37,7 @@ struct Message }; static std::multimap s_messages; static std::mutex s_messages_mutex; +static int value = 0; static ImVec4 RGBAToImVec4(const u32 rgba) { @@ -127,4 +129,38 @@ void ClearMessages() std::lock_guard lock{s_messages_mutex}; s_messages.clear(); } + +void DrawSlippiPlaybackControls() +{ + const auto height = ImGui::GetWindowHeight(); + // We have to provide a window name, and these shouldn't be duplicated. + // So instead, we generate a name based on the number of messages drawn. + const std::string window_name = fmt::format("Slippi Playback Controls"); + + const float alpha = 0.5f; + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, alpha); + + if (ImGui::Begin(window_name.c_str(), nullptr, + ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings | + ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoBackground | + ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoFocusOnAppearing)) + { + if (ImGui::Button("start")) { + INFO_LOG(SLIPPI, "pressed button!"); + }; + if (ImGui::Button("stop")) { + INFO_LOG(SLIPPI, "pressed button!"); + }; + + ImGui::PushItemWidth(ImGui::GetWindowWidth()); + ImGui::SliderCustom("test", ImVec4(1.0f, 0.0f, 0.0f, 1.0f), &value, 0, 8000, 1.0); + ImGui::SetWindowSize(ImVec2(ImGui::GetIO().DisplaySize.x, std::min(static_cast(50.0), ImGui::GetWindowHeight()))); + ImGui::SetWindowPos( + ImVec2(0, ImGui::GetIO().DisplaySize.y - ImGui::GetWindowHeight()), + true); + } + + ImGui::End(); + ImGui::PopStyleVar(); +} } // namespace OSD diff --git a/Source/Core/VideoCommon/OnScreenDisplay.h b/Source/Core/VideoCommon/OnScreenDisplay.h index 7179b9f4c2..190447c3ef 100644 --- a/Source/Core/VideoCommon/OnScreenDisplay.h +++ b/Source/Core/VideoCommon/OnScreenDisplay.h @@ -44,4 +44,7 @@ void AddTypedMessage(MessageType type, std::string message, u32 ms = Duration::S // Draw the current messages on the screen. Only call once per frame. void DrawMessages(); void ClearMessages(); + +// Draw playback controls when playing Slippi replays +void DrawSlippiPlaybackControls(); } // namespace OSD diff --git a/Source/Core/VideoCommon/RenderBase.cpp b/Source/Core/VideoCommon/RenderBase.cpp index 2e5709f86c..ba33f5c97d 100644 --- a/Source/Core/VideoCommon/RenderBase.cpp +++ b/Source/Core/VideoCommon/RenderBase.cpp @@ -1249,6 +1249,7 @@ void Renderer::Swap(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height, u6 DrawDebugText(); OSD::DrawMessages(); + OSD::DrawSlippiPlaybackControls(); ImGui::Render(); }