From 2c92e07f337e42cf58970443f9de678f85a9b2a4 Mon Sep 17 00:00:00 2001 From: rtk0c Date: Thu, 19 Oct 2023 22:50:07 -0700 Subject: The great renaming: switch to "module style" --- src/brussel.engine/EditorUtils.cpp | 447 +++++++++++++++++++++++++++++++++++++ 1 file changed, 447 insertions(+) create mode 100644 src/brussel.engine/EditorUtils.cpp (limited to 'src/brussel.engine/EditorUtils.cpp') diff --git a/src/brussel.engine/EditorUtils.cpp b/src/brussel.engine/EditorUtils.cpp new file mode 100644 index 0000000..20caef7 --- /dev/null +++ b/src/brussel.engine/EditorUtils.cpp @@ -0,0 +1,447 @@ +#include "EditorUtils.hpp" + +#define IMGUI_DEFINE_MATH_OPERATORS +#include + +#include +#include +#include +#include +#include + +const char* ImGui::GetKeyNameGlfw(int key) { + return GetKeyName(ImGui_ImplGlfw_KeyToImGuiKey(key)); +} + +void ImGui::SetNextWindowSizeRelScreen(float xPercent, float yPercent, ImGuiCond cond) { + auto vs = ImGui::GetMainViewport()->Size; + ImGui::SetNextWindowSize({ vs.x * xPercent, vs.y * yPercent }, cond); +} + +void ImGui::SetNextWindowCentered(ImGuiCond cond) { + auto vs = ImGui::GetMainViewport()->Size; + ImGui::SetNextWindowPos({ vs.x / 2, vs.y / 2 }, cond, { 0.5f, 0.5f }); +} + +void ImGui::PushDisabled() { + ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true); + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.5f * ImGui::GetStyle().Alpha); +} + +void ImGui::PopDisabled() { + ImGui::PopItemFlag(); + ImGui::PopStyleVar(); +} + +bool ImGui::Button(const char* label, bool disabled) { + return Button(label, ImVec2{}, disabled); +} + +bool ImGui::Button(const char* label, const ImVec2& sizeArg, bool disabled) { + if (disabled) PushDisabled(); + bool res = ImGui::Button(label, sizeArg); + if (disabled) PopDisabled(); + + return res; +} + +#define EDIT_RGBA_COLOR(EditorFunction, kUsesAlpha) \ + float components[4]; \ + components[0] = color->GetNormalizedRed(); \ + components[1] = color->GetNormalizedGreen(); \ + components[2] = color->GetNormalizedBlue(); \ + if constexpr (kUsesAlpha) components[3] = color->GetNormalizedAlpha(); \ + if (EditorFunction(label, components, flags)) { \ + color->r = components[0] * 255; \ + color->g = components[1] * 255; \ + color->b = components[2] * 255; \ + if constexpr (kUsesAlpha) color->a = components[3] * 255; \ + return true; \ + } else { \ + return false; \ + } + +bool ImGui::ColorEdit3(const char* label, RgbaColor* color, ImGuiColorEditFlags flags) { + EDIT_RGBA_COLOR(ColorEdit3, false); +} + +bool ImGui::ColorEdit4(const char* label, RgbaColor* color, ImGuiColorEditFlags flags) { + EDIT_RGBA_COLOR(ColorEdit4, true); +} + +bool ImGui::ColorPicker3(const char* label, RgbaColor* color, ImGuiColorEditFlags flags) { + EDIT_RGBA_COLOR(ColorPicker3, false); +} + +bool ImGui::ColorPicker4(const char* label, RgbaColor* color, ImGuiColorEditFlags flags) { + EDIT_RGBA_COLOR(ColorPicker4, true); +} + +#undef EDIT_RGBA_COLOR + +struct InputTextCallbackUserData { + std::string* str; + ImGuiInputTextCallback chainCallback; + void* chainCallbackUserData; +}; + +static int InputTextCallback(ImGuiInputTextCallbackData* data) { + auto user_data = (InputTextCallbackUserData*)data->UserData; + if (data->EventFlag == ImGuiInputTextFlags_CallbackResize) { + // Resize string callback + // If for some reason we refuse the new length (BufTextLen) and/or capacity (BufSize) we need to set them back to what we want. + auto str = user_data->str; + IM_ASSERT(data->Buf == str->c_str()); + str->resize(data->BufTextLen); + data->Buf = (char*)str->c_str(); + } else if (user_data->chainCallback) { + // Forward to user callback, if any + data->UserData = user_data->chainCallbackUserData; + return user_data->chainCallback(data); + } + return 0; +} + +bool ImGui::Splitter(bool splitVertically, float thickness, float* size1, float* size2, float minSize1, float minSize2, float splitterLongAxisSize) { + // Adapted from https://github.com/thedmd/imgui-node-editor/blob/master/examples/blueprints-example/blueprints-example.cpp + // ::Splitter + + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + ImGuiID id = window->GetID("##Splitter"); + ImRect bb; + bb.Min = window->DC.CursorPos + (splitVertically ? ImVec2(*size1, 0.0f) : ImVec2(0.0f, *size1)); + bb.Max = bb.Min + CalcItemSize(splitVertically ? ImVec2(thickness, splitterLongAxisSize) : ImVec2(splitterLongAxisSize, thickness), 0.0f, 0.0f); + + // Adapted from ImGui::SplitterBehavior, changes: + // - Simplified unneeded logic (hover_extend and hover_visibility_delay) + // - Changed clamped delta to clamping result size1 and deriving size2 from size1, allowing automatically adapting to the latest window content region width + + auto itemFlagsBackup = g.CurrentItemFlags; + g.CurrentItemFlags |= ImGuiItemFlags_NoNav | ImGuiItemFlags_NoNavDefaultFocus; + bool itemAdd = ItemAdd(bb, id); + g.CurrentItemFlags = itemFlagsBackup; + if (!itemAdd) { + return false; + } + + bool hovered, held; + auto bbInteract = bb; + ButtonBehavior(bbInteract, id, &hovered, &held, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_AllowItemOverlap); + if (hovered) { + g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredRect; + } // for IsItemHovered(), because bbInteract is larger than bb + if (g.ActiveId != id) { + SetItemAllowOverlap(); + } + + if (held || (hovered && g.HoveredIdPreviousFrame == id && g.HoveredIdTimer >= 0.0f)) { + SetMouseCursor((splitVertically ? ImGuiAxis_X : ImGuiAxis_Y) == ImGuiAxis_Y ? ImGuiMouseCursor_ResizeNS : ImGuiMouseCursor_ResizeEW); + } + + float contentSize = splitVertically ? window->ContentRegionRect.GetWidth() : window->ContentRegionRect.GetHeight(); + if (held) { + ImVec2 mouseDelta2D = g.IO.MousePos - g.ActiveIdClickOffset - bbInteract.Min; + float mouseDelta = ((splitVertically ? ImGuiAxis_X : ImGuiAxis_Y) == ImGuiAxis_Y) ? mouseDelta2D.y : mouseDelta2D.x; + + // Apply resize + if (mouseDelta != 0.0f) { + *size1 = ImClamp(*size1 + mouseDelta, minSize1, contentSize - minSize2 - thickness); + *size2 = contentSize - *size1 - thickness; + MarkItemEdited(id); + } + } + + ImU32 col; + if (held) { + col = GetColorU32(ImGuiCol_SeparatorActive); + } else if (hovered && g.HoveredIdTimer >= 0.0f) { + col = GetColorU32(ImGuiCol_SeparatorHovered); + } else { + col = GetColorU32(ImGuiCol_Separator); + } + window->DrawList->AddRectFilled(bb.Min, bb.Max, col, 0.0f); + + return held; +} + +void ImGui::AddUnderLine(ImColor col) { + auto min = ImGui::GetItemRectMin(); + auto max = ImGui::GetItemRectMax(); + min.y = max.y; + ImGui::GetWindowDrawList()->AddLine(min, max, col, 1.0f); +} + +void ImGui::DrawIcon(ImDrawList* drawList, const ImVec2& a, const ImVec2& b, IconType type, bool filled, ImU32 color, ImU32 innerColor) { + // Taken from https://github.com/thedmd/imgui-node-editor/blob/master/examples/blueprints-example/utilities/drawing.cpp + // ax::NodeEditor::DrawIcon + + auto rect = ImRect(a, b); + auto rect_x = rect.Min.x; + auto rect_y = rect.Min.y; + auto rect_w = rect.Max.x - rect.Min.x; + auto rect_h = rect.Max.y - rect.Min.y; + auto rect_center_x = (rect.Min.x + rect.Max.x) * 0.5f; + auto rect_center_y = (rect.Min.y + rect.Max.y) * 0.5f; + auto rect_center = ImVec2(rect_center_x, rect_center_y); + const auto outline_scale = rect_w / 24.0f; + const auto extra_segments = static_cast(2 * outline_scale); // for full circle + + if (type == IconType::Flow) { + const auto origin_scale = rect_w / 24.0f; + + const auto offset_x = 1.0f * origin_scale; + const auto offset_y = 0.0f * origin_scale; + const auto margin = 2.0f * origin_scale; + const auto rounding = 0.1f * origin_scale; + const auto tip_round = 0.7f; // percentage of triangle edge (for tip) + // const auto edge_round = 0.7f; // percentage of triangle edge (for corner) + const auto canvas = ImRect( + rect.Min.x + margin + offset_x, + rect.Min.y + margin + offset_y, + rect.Max.x - margin + offset_x, + rect.Max.y - margin + offset_y); + const auto canvas_x = canvas.Min.x; + const auto canvas_y = canvas.Min.y; + const auto canvas_w = canvas.Max.x - canvas.Min.x; + const auto canvas_h = canvas.Max.y - canvas.Min.y; + + const auto left = canvas_x + canvas_w * 0.5f * 0.3f; + const auto right = canvas_x + canvas_w - canvas_w * 0.5f * 0.3f; + const auto top = canvas_y + canvas_h * 0.5f * 0.2f; + const auto bottom = canvas_y + canvas_h - canvas_h * 0.5f * 0.2f; + const auto center_y = (top + bottom) * 0.5f; + // const auto angle = AX_PI * 0.5f * 0.5f * 0.5f; + + const auto tip_top = ImVec2(canvas_x + canvas_w * 0.5f, top); + const auto tip_right = ImVec2(right, center_y); + const auto tip_bottom = ImVec2(canvas_x + canvas_w * 0.5f, bottom); + + drawList->PathLineTo(ImVec2(left, top) + ImVec2(0, rounding)); + drawList->PathBezierCubicCurveTo( + ImVec2(left, top), + ImVec2(left, top), + ImVec2(left, top) + ImVec2(rounding, 0)); + drawList->PathLineTo(tip_top); + drawList->PathLineTo(tip_top + (tip_right - tip_top) * tip_round); + drawList->PathBezierCubicCurveTo( + tip_right, + tip_right, + tip_bottom + (tip_right - tip_bottom) * tip_round); + drawList->PathLineTo(tip_bottom); + drawList->PathLineTo(ImVec2(left, bottom) + ImVec2(rounding, 0)); + drawList->PathBezierCubicCurveTo( + ImVec2(left, bottom), + ImVec2(left, bottom), + ImVec2(left, bottom) - ImVec2(0, rounding)); + + if (!filled) { + if (innerColor & 0xFF000000) { + drawList->AddConvexPolyFilled(drawList->_Path.Data, drawList->_Path.Size, innerColor); + } + + drawList->PathStroke(color, true, 2.0f * outline_scale); + } else { + drawList->PathFillConvex(color); + } + } else { + auto triangleStart = rect_center_x + 0.32f * rect_w; + + auto rect_offset = -static_cast(rect_w * 0.25f * 0.25f); + + rect.Min.x += rect_offset; + rect.Max.x += rect_offset; + rect_x += rect_offset; + rect_center_x += rect_offset * 0.5f; + rect_center.x += rect_offset * 0.5f; + + if (type == IconType::Circle) { + const auto c = rect_center; + + if (!filled) { + const auto r = 0.5f * rect_w / 2.0f - 0.5f; + + if (innerColor & 0xFF000000) + drawList->AddCircleFilled(c, r, innerColor, 12 + extra_segments); + drawList->AddCircle(c, r, color, 12 + extra_segments, 2.0f * outline_scale); + } else { + drawList->AddCircleFilled(c, 0.5f * rect_w / 2.0f, color, 12 + extra_segments); + } + } + + if (type == IconType::Square) { + if (filled) { + const auto r = 0.5f * rect_w / 2.0f; + const auto p0 = rect_center - ImVec2(r, r); + const auto p1 = rect_center + ImVec2(r, r); + + drawList->AddRectFilled(p0, p1, color, 0, 15 + extra_segments); + } else { + const auto r = 0.5f * rect_w / 2.0f - 0.5f; + const auto p0 = rect_center - ImVec2(r, r); + const auto p1 = rect_center + ImVec2(r, r); + + if (innerColor & 0xFF000000) + drawList->AddRectFilled(p0, p1, innerColor, 0, 15 + extra_segments); + + drawList->AddRect(p0, p1, color, 0, 15 + extra_segments, 2.0f * outline_scale); + } + } + + if (type == IconType::Grid) { + const auto r = 0.5f * rect_w / 2.0f; + const auto w = ceilf(r / 3.0f); + + const auto baseTl = ImVec2(floorf(rect_center_x - w * 2.5f), floorf(rect_center_y - w * 2.5f)); + const auto baseBr = ImVec2(floorf(baseTl.x + w), floorf(baseTl.y + w)); + + auto tl = baseTl; + auto br = baseBr; + for (int i = 0; i < 3; ++i) { + tl.x = baseTl.x; + br.x = baseBr.x; + drawList->AddRectFilled(tl, br, color); + tl.x += w * 2; + br.x += w * 2; + if (i != 1 || filled) + drawList->AddRectFilled(tl, br, color); + tl.x += w * 2; + br.x += w * 2; + drawList->AddRectFilled(tl, br, color); + + tl.y += w * 2; + br.y += w * 2; + } + + triangleStart = br.x + w + 1.0f / 24.0f * rect_w; + } + + if (type == IconType::RoundSquare) { + if (filled) { + const auto r = 0.5f * rect_w / 2.0f; + const auto cr = r * 0.5f; + const auto p0 = rect_center - ImVec2(r, r); + const auto p1 = rect_center + ImVec2(r, r); + + drawList->AddRectFilled(p0, p1, color, cr, 15); + } else { + const auto r = 0.5f * rect_w / 2.0f - 0.5f; + const auto cr = r * 0.5f; + const auto p0 = rect_center - ImVec2(r, r); + const auto p1 = rect_center + ImVec2(r, r); + + if (innerColor & 0xFF000000) + drawList->AddRectFilled(p0, p1, innerColor, cr, 15); + + drawList->AddRect(p0, p1, color, cr, 15, 2.0f * outline_scale); + } + } else if (type == IconType::Diamond) { + if (filled) { + const auto r = 0.607f * rect_w / 2.0f; + const auto c = rect_center; + + drawList->PathLineTo(c + ImVec2(0, -r)); + drawList->PathLineTo(c + ImVec2(r, 0)); + drawList->PathLineTo(c + ImVec2(0, r)); + drawList->PathLineTo(c + ImVec2(-r, 0)); + drawList->PathFillConvex(color); + } else { + const auto r = 0.607f * rect_w / 2.0f - 0.5f; + const auto c = rect_center; + + drawList->PathLineTo(c + ImVec2(0, -r)); + drawList->PathLineTo(c + ImVec2(r, 0)); + drawList->PathLineTo(c + ImVec2(0, r)); + drawList->PathLineTo(c + ImVec2(-r, 0)); + + if (innerColor & 0xFF000000) + drawList->AddConvexPolyFilled(drawList->_Path.Data, drawList->_Path.Size, innerColor); + + drawList->PathStroke(color, true, 2.0f * outline_scale); + } + } else { + const auto triangleTip = triangleStart + rect_w * (0.45f - 0.32f); + + drawList->AddTriangleFilled( + ImVec2(ceilf(triangleTip), rect_y + rect_h * 0.5f), + ImVec2(triangleStart, rect_center_y + 0.15f * rect_h), + ImVec2(triangleStart, rect_center_y - 0.15f * rect_h), + color); + } + } +} + +void ImGui::Icon(const ImVec2& size, IconType type, bool filled, const ImVec4& color, const ImVec4& innerColor) { + // Taken from https://github.com/thedmd/imgui-node-editor/blob/master/examples/blueprints-example/utilities/widgets.cpp + // ax::NodeEditor::Icon + + if (ImGui::IsRectVisible(size)) { + auto cursorPos = ImGui::GetCursorScreenPos(); + auto drawList = ImGui::GetWindowDrawList(); + DrawIcon(drawList, cursorPos, cursorPos + size, type, filled, ImColor(color), ImColor(innerColor)); + } + + ImGui::Dummy(size); +} + +void ImGui::DrawArrow(ImDrawList* drawList, ImVec2 from, ImVec2 to, ImU32 color, float lineThickness) { + // Adapted from https://stackoverflow.com/a/6333775 + + using namespace std::numbers; + constexpr float kHeadLength = 10; + + auto angle = std::atan2(to.y - from.y, to.x - from.x); + drawList->AddLine(from, to, color, lineThickness); + drawList->AddLine(to, ImVec2(to.x - kHeadLength * std::cos(angle - pi / 6), to.y - kHeadLength * std::sin(angle - pi / 6)), color, lineThickness); + drawList->AddLine(to, ImVec2(to.x - kHeadLength * std::cos(angle + pi / 6), to.y - kHeadLength * std::sin(angle + pi / 6)), color, lineThickness); +} + +struct DialogObject { + std::string message; + std::function callback; +}; + +static DialogObject gConfirmationDialog{}; + +void ImGui::DialogConfirmation(std::string message, std::function callback) { + gConfirmationDialog.message = std::move(message); + // TODO is converting void(bool) to void(int) fine? + gConfirmationDialog.callback = std::move(callback); +} + +void ImGui::ShowDialogs() { + if (gConfirmationDialog.callback) { + if (ImGui::BeginPopupModal("Confirmation")) { + ImGui::Text("%s", gConfirmationDialog.message.c_str()); + if (ImGui::Button("Cancel")) { + gConfirmationDialog.callback(false); + gConfirmationDialog.callback = {}; + ImGui::CloseCurrentPopup(); + } + ImGui::SameLine(); + if (ImGui::Button("Confirm")) { + gConfirmationDialog.callback(true); + gConfirmationDialog.callback = {}; + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } + } +} + +float Utils::CalcImageHeight(glm::vec2 original, int targetWidth) { + // Xorig / Yorig = Xnew / Ynew + // Ynew = Xnew * Yorig / Xorig + return targetWidth * original.y / original.x; +} + +float Utils::CalcImageWidth(glm::vec2 original, float targetHeight) { + // Xorig / Yorig = Xnew / Ynew + // Xnew = Xorig / Yorig * Ynew + return original.x / original.y * targetHeight; +} + +ImVec2 Utils::FitImage(glm::vec2 original) { + float newWidth = ImGui::GetContentRegionAvail().x; + return ImVec2(newWidth, CalcImageHeight(original, newWidth)); +} -- cgit v1.2.3-70-g09d2