code:
//
// Created by Matty on 2022-01-28.
//
#ifndef IMGUI_DEFINE_MATH_OPERATORS
#define IMGUI_DEFINE_MATH_OPERATORS
#endif

#include "imgui_neo_sequencer.h"
#include "imgui_internal.h"
#include "imgui_neo_internal.h"

#include <unordered_map>

namespace ImGui {
// Internal state, used for deletion of old keyframes.
struct ImGuiNeoTimelineKeyframes {
  ImGuiID TimelineID;
  ImVector<int32_t> KeyframesToDelete;
};

// Internal struct holding how many times was keyframe on certain frame rendered, used as offset for duplicates
struct ImGuiNeoKeyframeDuplicate {
  int32_t Frame;
  uint32_t Count;
};

enum class SelectionState {
  Idle,      // Doing nothing related
  Selecting, // Selecting selection
  Dragging   // Dragging selection
};

struct ImGuiNeoSequencerInternalData {
  ImVec2 TopLeftCursor = {0, 0};     // Cursor on top of whole widget
  ImVec2 TopBarStartCursor = {0, 0}; // Cursor on top, below Zoom slider
  ImVec2 StartValuesCursor = {0, 0}; // Cursor on top of values
  ImVec2 ValuesCursor = {0, 0};      // Current cursor position, used for values drawing

  ImVec2 Size = {0, 0};       // Size of whole sequencer
  ImVec2 TopBarSize = {0, 0}; // Size of top bar without Zoom

  FrameIndexType StartFrame = 0;
  FrameIndexType EndFrame = 0;
  FrameIndexType OffsetFrame = 0; // Offset from start

  float ValuesWidth = 32.0f; // Width of biggest label in timeline, used for offset of timeline

  float FilledHeight = 0.0f; // Height of whole sequencer

  float Zoom = 1.0f;

  ImGuiID Id;

  ImGuiID LastSelectedTimeline = 0;
  ImGuiID SelectedTimeline = 0;
  bool LastTimelineOpenned = false;

  ImVector<ImGuiID> TimelineStack;
  ImVector<ImGuiID> GroupStack;

  FrameIndexType CurrentFrame = 0;
  bool HoldingCurrentFrame = false; // Are we draging current frame?
  ImVec4 CurrentFrameColor;         // Color of current frame, we have to save it because we render on EndNeoSequencer, but process at BeginneoSequencer

  bool HoldingZoomSlider = false;

  // Selection
  ImVector<ImGuiID> Selection; // Contains ids of keyframes
  ImVec2 SelectionMouseStart = {0, 0};
  SelectionState StateOfSelection = SelectionState::Idle;
  ImVec2 DraggingMouseStart = {0, 0};
  bool StartDragging = true;
  ImVector<int32_t> DraggingSelectionStart; // Contains start values of all selection elements
  bool DraggingEnabled = true;
  bool SelectionEnabled = true;
  bool IsSelectionRightClicked = false;

  // Last keyframe data
  bool IsLastKeyframeHovered = false;
  bool IsLastKeyframeSelected = false;
  bool IsLastKeyframeRightClicked = false;

  // Deletion
  bool DeleteDataDirty = false;
  bool DeleteEnabled = true;
  ImVector<ImGuiNeoTimelineKeyframes> SelectionData;
};

static ImGuiNeoSequencerStyle style; // NOLINT(cert-err58-cpp)

// Global context stuff
static bool inSequencer = false;

// Height of timeline right now
static float currentTimelineHeight = 0.0f;

// Current active sequencer
static ImGuiID currentSequencer;

// Current timeline depth, used for offset of label
static uint32_t currentTimelineDepth = 0;

static ImVector<ImGuiColorMod> sequencerColorStack;

// Data of all sequencers, this is main c++ part and I should create C alternative or use imgui ImVector or something
static std::unordered_map<ImGuiID, ImGuiNeoSequencerInternalData> sequencerData;

static ImVector<ImGuiNeoKeyframeDuplicate> keyframeDuplicates;

///////////// STATIC HELPERS ///////////////////////

static float getPerFrameWidth(ImGuiNeoSequencerInternalData& context) { return GetPerFrameWidth(context.Size.x, context.ValuesWidth, context.EndFrame, context.StartFrame, context.Zoom); }

static float getKeyframePositionX(FrameIndexType frame, ImGuiNeoSequencerInternalData& context) {
  const auto perFrameWidth = getPerFrameWidth(context);
  return (float)(frame - context.OffsetFrame - context.StartFrame) * perFrameWidth;
}

static float getWorkTimelineWidth(ImGuiNeoSequencerInternalData& context) {
  const auto perFrameWidth = getPerFrameWidth(context);
  return context.Size.x - context.ValuesWidth - perFrameWidth;
}

// Dont pull frame from context, its used for dragging
static ImRect getCurrentFrameBB(FrameIndexType frame, ImGuiNeoSequencerInternalData& context) {
  const auto& imStyle = GetStyle();
  const auto width = style.CurrentFramePointerSize * GetIO().FontGlobalScale;
  const auto cursor = context.TopBarStartCursor + ImVec2{context.ValuesWidth + imStyle.FramePadding.x - width / 2.0f, 0};
  const auto currentFrameCursor = cursor + ImVec2{getKeyframePositionX(frame, context), 0};

  float pointerHeight = style.CurrentFramePointerSize * 2.5f;
  ImRect rect{currentFrameCursor, currentFrameCursor + ImVec2{width, pointerHeight * GetIO().FontGlobalScale}};

  return rect;
}

static void processCurrentFrame(FrameIndexType* frame, ImGuiNeoSequencerInternalData& context) {
  auto pointerRect = getCurrentFrameBB(*frame, context);
  pointerRect.Min -= ImVec2{2.0f, 2.0f};
  pointerRect.Max += ImVec2{2.0f, 2.0f};

  const auto& imStyle = GetStyle();

  const auto timelineXmin = context.TopBarStartCursor.x + context.ValuesWidth + imStyle.FramePadding.x;

  const ImVec2 timelineXRange = {timelineXmin, // min
                                 timelineXmin + context.Size.x - context.ValuesWidth};

  const auto hovered = ItemHoverable(pointerRect, GetCurrentWindow()->GetID("##_top_selector_neo"), ImGuiItemFlags_None);

  context.CurrentFrameColor = GetStyleNeoSequencerColorVec4(ImGuiNeoSequencerCol_FramePointer);

  if (hovered) {
    context.CurrentFrameColor = GetStyleNeoSequencerColorVec4(ImGuiNeoSequencerCol_FramePointerHovered);
  }

  if (context.HoldingCurrentFrame) {
    if (IsMouseDragging(ImGuiMouseButton_Left, 0.0f)) {
      const auto mousePosX = GetMousePos().x;
      const auto v = mousePosX - timelineXRange.x; // Subtract min

      const auto normalized = v / getWorkTimelineWidth(context); // Divide by width to remap to 0 - 1 range

      const auto clamped = ImClamp(normalized, 0.0f, 1.0f);

      const auto viewSize = (float)(context.EndFrame - context.StartFrame) / context.Zoom;

      const auto frameViewVal = (float)context.StartFrame + (clamped * (float)viewSize);

      const auto finalFrame = (FrameIndexType)round(frameViewVal) + context.OffsetFrame;

      context.CurrentFrameColor = GetStyleNeoSequencerColorVec4(ImGuiNeoSequencerCol_FramePointerPressed);

      *frame = finalFrame;
    }

    if (!IsMouseDown(ImGuiMouseButton_Left)) {
      context.HoldingCurrentFrame = false;
      context.CurrentFrameColor = GetStyleNeoSequencerColorVec4(ImGuiNeoSequencerCol_FramePointer);
    }
  }

  if (hovered && IsMouseDown(ImGuiMouseButton_Left) && !context.HoldingCurrentFrame) {
    context.HoldingCurrentFrame = true;
    context.CurrentFrameColor = GetStyleNeoSequencerColorVec4(ImGuiNeoSequencerCol_FramePointerPressed);
  }

  context.CurrentFrame = *frame;
}

static void finishPreviousTimeline(ImGuiNeoSequencerInternalData& context) {
  context.ValuesCursor = {context.TopBarStartCursor.x, context.ValuesCursor.y};
  currentTimelineHeight = 0.0f;
}

static ImColor getKeyframeColor(ImGuiNeoSequencerInternalData& context, bool hovered, bool inSelection) {
  if (inSelection) {
    return ColorConvertFloat4ToU32(GetStyleNeoSequencerColorVec4(ImGuiNeoSequencerCol_KeyframeSelected));
  }

  return hovered ? ColorConvertFloat4ToU32(GetStyleNeoSequencerColorVec4(ImGuiNeoSequencerCol_KeyframeHovered)) : ColorConvertFloat4ToU32(GetStyleNeoSequencerColorVec4(ImGuiNeoSequencerCol_Keyframe));
}

static void addKeyframeToDeleteData(int32_t value, ImGuiNeoSequencerInternalData& context, const ImGuiID timelineId) {
  bool foundTimeline = false;
  for (auto&& val : context.SelectionData) {
    if (val.TimelineID == timelineId) {
      foundTimeline = true;
      if (!val.KeyframesToDelete.contains(value))
        val.KeyframesToDelete.push_back(value);
      break;
    }
  }

  if (!foundTimeline) {
    context.SelectionData.push_back({});
    auto& data = context.SelectionData.back();
    data.TimelineID = timelineId;
    data.KeyframesToDelete.push_back(value);
  }
}

static bool getKeyframeInSelection(int32_t value, ImGuiID id, ImGuiNeoSequencerInternalData& context, const ImRect bb) {
  // TODO(matej.vrba): This is kinda slow, it works for smaller data sample, but for bigger sample it should be changed to hashset
  const ImGuiID timelineId = context.TimelineStack.back();

  if (context.DeleteDataDirty && context.Selection.contains(id)) {
    addKeyframeToDeleteData(value, context, timelineId);
  }

  if (context.StateOfSelection != SelectionState::Selecting) {
    return context.Selection.contains(id);
  }

  ImRect sel = {context.SelectionMouseStart, GetMousePos()};

  if (sel.Min.y > sel.Max.y) {
    ImVec2 tmp = sel.Min;
    sel.Min = sel.Max;
    sel.Max = tmp;
  }

  if (sel.Min.x > sel.Max.x) {
    float tmp = sel.Min.x;
    sel.Min.x = sel.Max.x;
    sel.Max.x = tmp;
  }

  const bool overlaps = bb.Overlaps(sel);

  const bool forceRemove = IsKeyDown(style.ModRemoveKey);
  const bool forceAdd = IsKeyDown(style.ModAddKey);

  auto removeKeyframe = [&]() {
    for (auto&& val : context.SelectionData) {
      if (val.TimelineID == timelineId) {
        val.KeyframesToDelete.find_erase(value);
        break;
      }
    }
    context.Selection.find_erase(id);
  };

  if (overlaps) {
    if (forceRemove) {
      removeKeyframe();
      return context.Selection.contains(id);
    } else {
      if (!context.Selection.contains(id)) {
        addKeyframeToDeleteData(value, context, timelineId);

        context.Selection.push_back(id);
      }
    }
  } else {
    if (!forceRemove && !forceAdd) {
      removeKeyframe();
    } else {
      return context.Selection.contains(id);
    }
  }
  return overlaps;
}

static ImGuiID getKeyframeID(int32_t* frame) { return GetCurrentWindow()->GetID(frame); }

static bool createKeyframe(int32_t* frame) {
  const auto& imStyle = GetStyle();
  auto& context = sequencerData[currentSequencer];

  const auto timelineOffset = getKeyframePositionX(*frame, context);

  float offset = 0.0f;

  for (auto&& duplicateData : keyframeDuplicates) {
    if (duplicateData.Frame == *frame) {
      offset = (float)duplicateData.Count * style.CollidedKeyframeOffset;
      duplicateData.Count++;
    }
  }

  if (offset < style.CollidedKeyframeOffset) {
    keyframeDuplicates.push_back({});
    keyframeDuplicates.back().Frame = *frame;
    keyframeDuplicates.back().Count = 1;
  }

  const auto pos = ImVec2{context.StartValuesCursor.x + imStyle.FramePadding.x, context.ValuesCursor.y} + ImVec2{timelineOffset + context.ValuesWidth + offset, 0};

  const auto bbPos = pos - ImVec2{currentTimelineHeight / 2, 0};

  const ImRect bb = {bbPos, bbPos + ImVec2{currentTimelineHeight, currentTimelineHeight}};

  const auto drawList = ImGui::GetWindowDrawList();

  const ImGuiID id = getKeyframeID(frame);

  bool hovered = ItemHoverable(bb, id, ImGuiItemFlags_None);

  if (context.SelectionEnabled && context.Selection.contains(id) && (context.StateOfSelection != SelectionState::Selecting)) {
    // process dragging
    if (bb.Contains(GetMousePos()) && IsMouseClicked(ImGuiMouseButton_Left) && context.StateOfSelection != SelectionState::Dragging && context.DraggingEnabled) {
      // Start dragging
      context.StartDragging = true;
    }

    if (context.StateOfSelection == SelectionState::Dragging) {
      ImGuiID* it = context.Selection.find(id);
      int32_t index = context.Selection.index_from_ptr(it);

      if (context.DraggingSelectionStart.size() < index + 1 || context.DraggingSelectionStart[index] == -1) {
        if (context.DraggingSelectionStart.size() < index + 1) {
          context.DraggingSelectionStart.resize(index + 1, -1);
        }

        context.DraggingSelectionStart[index] = *frame;
      }
      float mouseDelta = GetMousePos().x - context.DraggingMouseStart.x;

      auto offsetA = int32_t(mouseDelta / (context.Size.x / (float)context.EndFrame - (float)context.StartFrame));

      *frame = context.DraggingSelectionStart[index] + offsetA;
    }
  }

  const bool inSelection = getKeyframeInSelection(*frame, id, context, bb);

  context.IsLastKeyframeSelected = inSelection;

  if (timelineOffset >= 0.0f) {

    ImColor color = getKeyframeColor(context, hovered, inSelection);

    drawList->AddCircleFilled(pos + ImVec2{0, currentTimelineHeight / 2.f}, currentTimelineHeight / 3.0f, color, 4);
  }

  context.IsLastKeyframeHovered = hovered;
  context.IsLastKeyframeRightClicked = hovered && IsMouseClicked(ImGuiMouseButton_Right);

  if (context.Selection.contains(id) && context.IsLastKeyframeRightClicked) {
    context.IsSelectionRightClicked = true;
  }

  return true;
}

static uint32_t idCounter = 0;
static char idBuffer[16];

const char* generateID() {
  idBuffer[0] = '#';
  idBuffer[1] = '#';
  memset(idBuffer + 2, 0, 14);
  snprintf(idBuffer + 2, 14, "%o", idCounter++);

  return &idBuffer[0];
}

void resetID() { idCounter = 0; }

static void renderCurrentFrame(ImGuiNeoSequencerInternalData& context) {
  const auto bb = getCurrentFrameBB(context.CurrentFrame, context);

  const auto drawList = ImGui::GetWindowDrawList();

  RenderNeoSequencerCurrentFrame(GetStyleNeoSequencerColorVec4(ImGuiNeoSequencerCol_FramePointerLine), context.CurrentFrameColor, bb, context.Size.y - context.TopBarSize.y,
                                 style.CurrentFrameLineWidth, drawList);
}

static float calculateZoomBarHeight() {
  const auto& imStyle = GetStyle();
  return GetFontSize() * style.ZoomHeightScale + imStyle.FramePadding.y * 2.0f;
}

static void processAndRenderZoom(ImGuiNeoSequencerInternalData& context, const ImVec2& cursor, bool allowEditingLength, FrameIndexType* start, FrameIndexType* end) {
  const auto& imStyle = GetStyle();
  ImGuiWindow* window = GetCurrentWindow();

  const auto zoomHeight = calculateZoomBarHeight();

  auto* drawList = GetWindowDrawList();

  // Input width
  const auto inputWidth = CalcTextSize("123456").x;

  const auto inputWidthWithPadding = inputWidth + imStyle.ItemSpacing.x;

  const auto cursorV = allowEditingLength ? cursor + ImVec2{inputWidthWithPadding, 0} : cursor;

  const auto size = allowEditingLength ? context.Size.x - 2 * inputWidthWithPadding : context.Size.x;

  const ImRect bb{cursorV, cursorV + ImVec2{size, zoomHeight}};

  const auto zoomBarEndWithSpacing = ImVec2{bb.Max.x + imStyle.ItemSpacing.x, bb.Min.y};

  FrameIndexType startFrameVal = *start;
  FrameIndexType endFrameVal = *end;

  if (allowEditingLength) {
    const float sideOffset = imStyle.ItemSpacing.x / 2.0f;
    auto prevWindowCursor = window->DC.CursorPos;

    window->DC.CursorPos = cursor;

    window->DC.CursorPos.x += sideOffset;

    PushItemWidth(inputWidth);
    InputScalar("##input_start_frame", ImGuiDataType_U32, &startFrameVal, NULL, NULL, "%i", allowEditingLength ? 0 : ImGuiInputTextFlags_ReadOnly);

    window->DC.CursorPos = ImVec2{zoomBarEndWithSpacing.x, cursor.y};
    window->DC.CursorPos.x -= sideOffset;

    PushItemWidth(inputWidth);
    InputScalar("##input_end_frame", ImGuiDataType_U32, &endFrameVal, NULL, NULL, "%i", allowEditingLength ? 0 : ImGuiInputTextFlags_ReadOnly);

    window->DC.CursorPos = prevWindowCursor;
  }

  // if (startFrameVal < 0)
  //     startFrameVal = (int32_t) *start;
  //
  // if (endFrameVal < 0)
  //    endFrameVal = (int32_t) *end;

  if (endFrameVal <= startFrameVal)
    endFrameVal = (int32_t)*end;

  *start = startFrameVal;
  *end = endFrameVal;

  // drawList->AddText(startFrameTextCursor + ImVec2{frameNumberBorderSize.x, 0} - ImVec2{numberTextWidth,0},IM_COL32_WHITE,numberText);

  // Background
  drawList->AddRectFilled(bb.Min, bb.Max, ColorConvertFloat4ToU32(GetStyleNeoSequencerColorVec4(ImGuiNeoSequencerCol_ZoomBarBg)), 10.0f);

  const auto baseWidth = bb.GetSize().x - imStyle.ItemInnerSpacing.x; // There is just half spacing applied, doing it normally makes big gap on sides

  const auto sliderHeight = bb.GetSize().y - imStyle.ItemInnerSpacing.y;

  const auto sliderWidth = baseWidth / context.Zoom;

  const auto sliderMin = bb.Min + imStyle.ItemInnerSpacing / 2.0f;

  // const auto sliderMax = bb.Max - imStyle.ItemInnerSpacing / 2.0f;

  const auto sliderMaxWidth = baseWidth;

  const auto totalFrames = (*end - *start);

  const auto singleFrameWidthOffset = sliderMaxWidth / (float)totalFrames;

  const auto zoomSliderOffset = singleFrameWidthOffset * (float)context.OffsetFrame;

  const auto sliderStart = sliderMin + ImVec2{zoomSliderOffset, 0};

  const float sideSize = sliderHeight;

  const ImRect finalSliderBB{sliderStart, sliderStart + ImVec2{sliderWidth, sliderHeight}};

  const ImRect finalSliderInteractBB = {finalSliderBB.Min + ImVec2{sideSize, 0}, finalSliderBB.Max - ImVec2{sideSize, 0}};

  const auto viewWidth = (uint32_t)((float)totalFrames / context.Zoom);

  const bool hovered = ItemHoverable(bb, GetCurrentWindow()->GetID("##zoom_slider"), ImGuiItemFlags_None);

  if (hovered) {
    SetKeyOwner(ImGuiKey_MouseWheelY, GetItemID());
    const float currentScroll = GetIO().MouseWheel;

    context.Zoom = ImClamp(context.Zoom + float(currentScroll) * 0.3f, 1.0f, (float)viewWidth);
    const auto newZoomWidth = (FrameIndexType)ceil((float)totalFrames / (context.Zoom));

    if (*start + context.OffsetFrame + newZoomWidth > *end)
      context.OffsetFrame = ImMax(0U, totalFrames - viewWidth);
  }

  if (context.HoldingZoomSlider) {
    if (IsMouseDragging(ImGuiMouseButton_Left, 0.01f)) {
      const auto currentX = GetMousePos().x;

      const auto v = currentX - bb.Min.x; // Subtract min

      const auto normalized = v / bb.GetWidth(); // Divide by width to remap to 0 - 1 range

      const auto sliderWidthNormalized = 1.0f / context.Zoom;

      const auto singleFrameWidthOffsetNormalized = singleFrameWidthOffset / bb.GetWidth();

      FrameIndexType finalFrame = (FrameIndexType)((float)(normalized - sliderWidthNormalized / 2.0f) / singleFrameWidthOffsetNormalized);

      if (normalized - sliderWidthNormalized / 2.0f < 0.0f) {
        finalFrame = 0;
      }

      if (normalized + sliderWidthNormalized / 2.0f > 1.0f) {
        finalFrame = totalFrames - viewWidth;
      }

      context.OffsetFrame = finalFrame;
    }

    if (!IsMouseDown(ImGuiMouseButton_Left)) {
      context.HoldingZoomSlider = false;
    }
  }

  if (hovered && IsMouseDown(ImGuiMouseButton_Left)) {
    context.HoldingZoomSlider = true;
  }

  const auto res = ItemAdd(finalSliderInteractBB, 0);

  const auto viewStart = *start + context.OffsetFrame;
  const auto viewEnd = viewStart + viewWidth;

  if (res) {
    auto sliderColor = GetStyleNeoSequencerColorVec4(ImGuiNeoSequencerCol_ZoomBarSlider);

    if (IsItemHovered()) {
      sliderColor = GetStyleNeoSequencerColorVec4(ImGuiNeoSequencerCol_ZoomBarSliderHovered);
    }

    // Render bar
    drawList->AddRectFilled(finalSliderBB.Min, finalSliderBB.Max, ColorConvertFloat4ToU32(sliderColor), 10.0f);

    const auto sliderCenter = finalSliderBB.GetCenter();

    char overlayTextBuffer[128];

    snprintf(overlayTextBuffer, sizeof(overlayTextBuffer), "%i - %i", viewStart, viewEnd);

    const auto overlaySize = CalcTextSize(overlayTextBuffer);

    drawList->AddText(sliderCenter - overlaySize / 2.0f, IM_COL32_WHITE, overlayTextBuffer);
  }
}

static void processSelection(ImGuiNeoSequencerInternalData& context) {
  context.DeleteDataDirty = false;

  if (context.StartDragging) {
    context.StateOfSelection = SelectionState::Dragging;
    context.DraggingMouseStart = GetMousePos();
    context.StartDragging = false;
    return;
  }

  const auto windowWorkRect = GetCurrentWindow()->ClipRect;

  const auto sequencerWorkRect = ImRect{context.TopBarStartCursor + ImVec2{context.ValuesWidth, context.TopBarSize.y}, context.TopBarStartCursor + context.Size - ImVec2{0, context.TopBarSize.y}};

  if (IsMouseDown(ImGuiMouseButton_Left) && windowWorkRect.Contains(GetMousePos()) && sequencerWorkRect.Contains(GetMousePos())) {
    // Not dragging yet
    switch (context.StateOfSelection) {
    case SelectionState::Idle: {
      if (!IsMouseClicked(ImGuiMouseButton_Left))
        return;

      context.SelectionMouseStart = GetMousePos();
      context.StateOfSelection = SelectionState::Selecting;
      break;
    }
    case SelectionState::Selecting: {
      break;
    }
    case SelectionState::Dragging: {

      break;
    }
    }
  } else {
    switch (context.StateOfSelection) {
    case SelectionState::Idle: {
      break;
    }
    case SelectionState::Selecting: {
      context.SelectionMouseStart = {0, 0};
      context.StateOfSelection = SelectionState::Idle;
      break;
    }
    case SelectionState::Dragging: {
      context.DraggingSelectionStart.resize(0);
      context.StateOfSelection = SelectionState::Idle;
      context.DraggingMouseStart = {0, 0};
      context.DeleteDataDirty = true;
      for (auto&& t : context.SelectionData)
        t.KeyframesToDelete.resize(0);
      break;
    }
    }
  }
}

static void renderSelection(ImGuiNeoSequencerInternalData& context) {
  if (context.StateOfSelection != SelectionState::Selecting) {
    return;
  }
  const ImVec2 currentMousePosition = GetMousePos();

  auto* drawList = GetWindowDrawList();

  ImRect sel{context.SelectionMouseStart, currentMousePosition};

  if (sel.Min.y > sel.Max.y) {
    ImVec2 tmp = sel.Min;
    sel.Min = sel.Max;
    sel.Max = tmp;
  }

  if (sel.Min.x > sel.Max.x) {
    float tmp = sel.Min.x;
    sel.Min.x = sel.Max.x;
    sel.Max.x = tmp;
  }

  if (sel.GetArea() < 32.0f)
    return;

  // Inner
  drawList->AddRectFilled(context.SelectionMouseStart, currentMousePosition, ColorConvertFloat4ToU32(style.Colors[ImGuiNeoSequencerCol_Selection]));

  // border
  drawList->AddRect(context.SelectionMouseStart, currentMousePosition, ColorConvertFloat4ToU32(style.Colors[ImGuiNeoSequencerCol_SelectionBorder]), 0.0f, 0, 0.5f);
}

static bool groupBehaviour(const ImGuiID id, bool* open, const ImVec2 labelSize) {
  auto& context = sequencerData[currentSequencer];
  ImGuiWindow* window = GetCurrentWindow();

  const bool closable = open != nullptr;

  auto drawList = ImGui::GetWindowDrawList();
  const float arrowWidth = drawList->_Data->FontSize;
  const ImVec2 arrowSize = {arrowWidth, arrowWidth};
  const ImRect arrowBB = {context.ValuesCursor, context.ValuesCursor + arrowSize};
  const ImVec2 groupBBMin = {context.ValuesCursor + ImVec2{arrowSize.x, 0.0f}};
  const ImRect groupBB = {groupBBMin, groupBBMin + labelSize};
  const ImGuiID arrowID = window->GetID(generateID());
  const auto addArrowRes = ItemAdd(arrowBB, arrowID);
  if (addArrowRes) {
    if (IsItemClicked() && closable)
      (*open) = !(*open);
  }

  const auto addGroupRes = ItemAdd(groupBB, id);
  if (addGroupRes) {
    if (IsItemClicked()) {
      context.LastSelectedTimeline = context.SelectedTimeline;
      context.SelectedTimeline = context.SelectedTimeline == id ? 0 : id;
    }
  }
  const float width = groupBB.Max.x - arrowBB.Min.x;
  context.ValuesWidth = std::max(context.ValuesWidth, width); // Make left panel wide enough
  return addGroupRes && addArrowRes;
}

static bool timelineBehaviour(const ImGuiID id, const ImVec2 labelSize) {
  auto& context = sequencerData[currentSequencer];
  // ImGuiWindow *window = GetCurrentWindow();

  const ImRect groupBB = {context.ValuesCursor, context.ValuesCursor + labelSize};

  const auto addGroupRes = ItemAdd(groupBB, id);
  if (addGroupRes) {
    if (IsItemClicked()) {
      context.LastSelectedTimeline = context.SelectedTimeline;
      context.SelectedTimeline = context.SelectedTimeline == id ? 0 : id;
    }
  }
  const float width = groupBB.Max.x - groupBB.Min.x;
  context.ValuesWidth = std::max(context.ValuesWidth, width); // Make left panel wide enough

  return addGroupRes;
}

////////////////////////////////////

const ImVec4& GetStyleNeoSequencerColorVec4(ImGuiNeoSequencerCol idx) { return GetNeoSequencerStyle().Colors[idx]; }

ImGuiNeoSequencerStyle& GetNeoSequencerStyle() { return style; }

bool BeginNeoSequencer(const char* idin, FrameIndexType* frame, FrameIndexType* startFrame, FrameIndexType* endFrame, const ImVec2& size, ImGuiNeoSequencerFlags flags) {
  IM_ASSERT(!inSequencer && "Called when while in other NeoSequencer, that won't work, call End!");
  IM_ASSERT(*startFrame < *endFrame && "Start frame must be smaller than end frame");

  static char childNameStorage[64];
  snprintf(childNameStorage, sizeof(childNameStorage), "##%s_child_wrapper", idin);
  const bool openChild = BeginChild(childNameStorage);

  if (!openChild) {
    EndChild();
    return openChild;
  }

  // ImGuiContext &g = *GImGui;
  ImGuiWindow* window = GetCurrentWindow();
  const auto& imStyle = GetStyle();
  // auto &neoStyle = GetNeoSequencerStyle();

  if (inSequencer)
    return false;

  if (window->SkipItems)
    return false;

  const auto drawList = GetWindowDrawList();

  const auto cursor = GetCursorScreenPos();
  const auto area = ImGui::GetContentRegionAvail();

  const auto cursorBasePos = GetCursorScreenPos() + window->Scroll;

  PushID(idin);
  const auto id = window->IDStack[window->IDStack.size() - 1];

  inSequencer = true;

  auto& context = sequencerData[id];
  context.Id = id;

  auto realSize = ImFloor(size);
  if (realSize.x <= 0.0f)
    realSize.x = ImMax(4.0f, area.x);
  if (realSize.y <= 0.0f)
    realSize.y = ImMax(4.0f, context.FilledHeight);

  const bool showZoom = !(flags & ImGuiNeoSequencerFlags_HideZoom);
  const bool headerAlwaysVisible = (flags & ImGuiNeoSequencerFlags_AlwaysShowHeader);
  context.SelectionEnabled = (flags & ImGuiNeoSequencerFlags_EnableSelection);
  context.DraggingEnabled = context.SelectionEnabled && (flags & ImGuiNeoSequencerFlags_Selection_EnableDragging);
  context.DeleteEnabled = context.SelectionEnabled && (flags & ImGuiNeoSequencerFlags_Selection_EnableDeletion);

  context.TopLeftCursor = headerAlwaysVisible ? cursorBasePos : cursor;

  // If Zoom is shown, we offset it by height of Zoom bar + padding
  context.TopBarStartCursor = showZoom ? context.TopLeftCursor + ImVec2{0, calculateZoomBarHeight()} : context.TopLeftCursor;
  context.StartFrame = *startFrame;
  context.EndFrame = *endFrame;
  context.Size = realSize;

  context.TopBarSize = ImVec2(context.Size.x, style.TopBarHeight);

  if (context.TopBarSize.y <= 0.0f)
    context.TopBarSize.y = CalcTextSize("100").y + imStyle.FramePadding.y * 2.0f;

  currentSequencer = window->IDStack[window->IDStack.size() - 1];

  auto backgroundSize = context.Size;
  const float topCut = abs(context.TopLeftCursor.y - cursor.y);
  backgroundSize.y = backgroundSize.y - (topCut);

  RenderNeoSequencerBackground(GetStyleNeoSequencerColorVec4(ImGuiNeoSequencerCol_Bg), context.TopLeftCursor, backgroundSize, drawList, style.SequencerRounding);

  RenderNeoSequencerTopBarBackground(GetStyleNeoSequencerColorVec4(ImGuiNeoSequencerCol_TopBarBg), context.TopBarStartCursor, context.TopBarSize, drawList, style.SequencerRounding);

  RenderNeoSequencerTopBarOverlay(context.Zoom, context.ValuesWidth, context.StartFrame, context.EndFrame, context.OffsetFrame, context.TopBarStartCursor, context.TopBarSize, drawList,
                                  style.TopBarShowFrameLines, style.TopBarShowFrameTexts, style.MaxSizePerTick);

  if (showZoom)
    processAndRenderZoom(context, context.TopLeftCursor, flags & ImGuiNeoSequencerFlags_AllowLengthChanging, startFrame, endFrame);

  if (context.Size.y < context.FilledHeight)
    context.Size.y = context.FilledHeight;

  context.FilledHeight = context.TopBarSize.y + style.TopBarSpacing + (showZoom ? calculateZoomBarHeight() : 0.0f);

  context.StartValuesCursor = cursor + ImVec2{0, context.TopBarSize.y + style.TopBarSpacing};
  if (showZoom)
    context.StartValuesCursor = context.StartValuesCursor + ImVec2{0, calculateZoomBarHeight()};
  context.ValuesCursor = context.StartValuesCursor;

  processCurrentFrame(frame, context);

  // if (enableSelection)
  // processSelection(context);

  const auto clipMin = context.TopBarStartCursor + ImVec2(0, context.TopBarSize.y);

  drawList->PushClipRect(clipMin, clipMin + backgroundSize - ImVec2(0, context.TopBarSize.y) - ImVec2{0, GetFontSize() * style.ZoomHeightScale}, true);

  return true;
}

void EndNeoSequencer() {
  IM_ASSERT(inSequencer && "Called end sequencer when BeginSequencer didnt return true or wasn't called at all!");
  IM_ASSERT(sequencerData.count(currentSequencer) != 0 && "Ended sequencer has no context!");

  auto& context = sequencerData[currentSequencer];
  IM_ASSERT(context.TimelineStack.empty() && "Missmatch in timeline Begin / End");

  if (context.SelectionEnabled)
    processSelection(context);

  context.LastSelectedTimeline = context.SelectedTimeline;
  context.IsSelectionRightClicked = false;

  if (context.SelectionEnabled)
    renderSelection(context);

  renderCurrentFrame(context);

  inSequencer = false;

  const ImVec2 min = {0, 0};
  context.Size.y = context.FilledHeight;
  const auto max = context.Size;

  ItemSize({min, max});
  PopID();
  resetID();

  EndChild();
}

IMGUI_API bool BeginNeoGroup(const char* label, bool* open) { return BeginNeoTimeline(label, nullptr, 0, open, ImGuiNeoTimelineFlags_Group); }

IMGUI_API void EndNeoGroup() { return EndNeoTimeLine(); }

#ifdef __cplusplus

bool BeginNeoTimeline(const char* label, std::vector<int32_t>& keyframes, bool* open, ImGuiNeoTimelineFlags flags) {
  std::vector<int32_t*> c_keyframes{keyframes.size()};
  for (uint32_t i = 0; i < keyframes.size(); i++)
    c_keyframes[i] = &keyframes[i];

  return BeginNeoTimeline(label, c_keyframes.data(), c_keyframes.size(), open, flags);
}

#endif

void PushNeoSequencerStyleColor(ImGuiNeoSequencerCol idx, ImU32 col) {
  ImGuiColorMod backup;
  backup.Col = idx;
  backup.BackupValue = style.Colors[idx];
  sequencerColorStack.push_back(backup);
  style.Colors[idx] = ColorConvertU32ToFloat4(col);
}

void PushNeoSequencerStyleColor(ImGuiNeoSequencerCol idx, const ImVec4& col) {
  ImGuiColorMod backup;
  backup.Col = idx;
  backup.BackupValue = style.Colors[idx];
  sequencerColorStack.push_back(backup);
  style.Colors[idx] = col;
}

void PopNeoSequencerStyleColor(int count) {
  while (count > 0) {
    ImGuiColorMod& backup = sequencerColorStack.back();
    style.Colors[backup.Col] = backup.BackupValue;
    sequencerColorStack.pop_back();
    count--;
  }
}

void SetSelectedTimeline(const char* timelineLabel) {
  IM_ASSERT(inSequencer && "Not in active sequencer!");

  auto& context = sequencerData[currentSequencer];

  ImGuiWindow* window = GetCurrentWindow();

  ImGuiID timelineID = 0;

  if (timelineLabel) {
    timelineID = window->GetID(timelineLabel);
  }
  context.LastSelectedTimeline = context.SelectedTimeline;
  context.SelectedTimeline = timelineID;
}

bool IsNeoTimelineSelected(ImGuiNeoTimelineIsSelectedFlags flags) {
  IM_ASSERT(inSequencer && "Not in active sequencer!");
  auto& context = sequencerData[currentSequencer];

  IM_ASSERT(!context.TimelineStack.empty() && "No active timelines are present!");

  const bool newly = flags & ImGuiNeoTimelineIsSelectedFlags_NewlySelected;

  const auto openTimeline = context.TimelineStack[context.TimelineStack.size() - 1];

  if (!newly) {
    return context.SelectedTimeline == openTimeline;
  }

  return (context.SelectedTimeline != context.LastSelectedTimeline) && context.SelectedTimeline == openTimeline;
}

bool BeginNeoTimelineEx(const char* label, bool* open, ImGuiNeoTimelineFlags flags) {
  IM_ASSERT(inSequencer && "Not in active sequencer!");

  const bool closable = open != nullptr;

  auto& context = sequencerData[currentSequencer];
  const auto& imStyle = GetStyle();
  ImGuiWindow* window = GetCurrentWindow();
  const ImGuiID id = window->GetID(label);
  auto labelSize = CalcTextSize(label);

  labelSize.y += imStyle.FramePadding.y * 2 + style.ItemSpacing.y * 2;
  labelSize.x += imStyle.FramePadding.x * 2 + style.ItemSpacing.x * 2 + (float)currentTimelineDepth * style.DepthItemSpacing;

  bool isGroup = flags & ImGuiNeoTimelineFlags_Group && closable;
  bool addRes = false;
  if (isGroup) {
    labelSize.x += imStyle.ItemSpacing.x + GetFontSize();
    addRes = groupBehaviour(id, open, labelSize);
  } else {
    addRes = timelineBehaviour(id, labelSize);
  }

  if (currentTimelineDepth > 0) {
    context.ValuesCursor = {context.TopBarStartCursor.x, context.ValuesCursor.y};
  }

  currentTimelineHeight = labelSize.y;
  context.FilledHeight += currentTimelineHeight;
  const auto result = !closable || (*open);
  context.LastTimelineOpenned = result;

  if (addRes) {
    RenderNeoTimelane(id == context.SelectedTimeline, context.ValuesCursor + ImVec2{context.ValuesWidth, 0}, ImVec2{context.Size.x - context.ValuesWidth, currentTimelineHeight},
                      GetStyleNeoSequencerColorVec4(ImGuiNeoSequencerCol_SelectedTimeline));

    ImVec4 color = GetStyleColorVec4(ImGuiCol_Text);
    if (IsItemHovered())
      color.w *= 0.7f;

    RenderNeoTimelineLabel(label, context.ValuesCursor + imStyle.FramePadding + ImVec2{(float)currentTimelineDepth * style.DepthItemSpacing, 0}, labelSize, color, isGroup, isGroup && (*open));
  }

  if (result)
    context.TimelineStack.push_back(id);

  if (isGroup) { // Group requires special behaviour if its closed
    context.ValuesCursor.y += currentTimelineHeight;
    if (result) {
      currentTimelineDepth++;
      context.GroupStack.push_back(id);
    }
  }

  keyframeDuplicates.resize(0);

  return result;
}

bool BeginNeoTimeline(const char* label, FrameIndexType** keyframes, uint32_t keyframeCount, bool* open, ImGuiNeoTimelineFlags flags) {
  if (!BeginNeoTimelineEx(label, open, flags))
    return false;

  for (uint32_t i = 0; i < keyframeCount; i++) {
    NeoKeyframe(keyframes[i]);
  }

  return true;
}

void EndNeoTimeLine() {
  IM_ASSERT(inSequencer && "Not in active sequencer!");

  auto& context = sequencerData[currentSequencer];
  const auto& imStyle = GetStyle();

  IM_ASSERT(context.TimelineStack.size() > 0 && "Timeline stack push/pop missmatch!");

  context.ValuesCursor.x += imStyle.FramePadding.x + (float)currentTimelineDepth * style.DepthItemSpacing;
  context.ValuesCursor.y += currentTimelineHeight;

  finishPreviousTimeline(context);

  if (!context.TimelineStack.empty() && !context.GroupStack.empty() && context.TimelineStack.back() == context.GroupStack.back()) {
    currentTimelineDepth--;
    context.GroupStack.pop_back();
  }

  context.TimelineStack.pop_back();
}

void NeoKeyframe(int32_t* value) {
  IM_ASSERT(inSequencer && "Not in active sequencer!");
  auto& context = sequencerData[currentSequencer];
  IM_ASSERT(!context.TimelineStack.empty() && "Not in timeline!");

  createKeyframe(value);
}

bool IsNeoKeyframeHovered() {
  IM_ASSERT(inSequencer && "Not in active sequencer!");
  auto& context = sequencerData[currentSequencer];

  return context.IsLastKeyframeHovered;
}

bool IsNeoKeyframeSelected() {
  IM_ASSERT(inSequencer && "Not in active sequencer!");
  auto& context = sequencerData[currentSequencer];

  return context.IsLastKeyframeSelected;
}

bool IsNeoKeyframeRightClicked() {
  IM_ASSERT(inSequencer && "Not in active sequencer!");
  auto& context = sequencerData[currentSequencer];

  return context.IsLastKeyframeRightClicked;
}

void NeoClearSelection() {
  IM_ASSERT(inSequencer && "Not in active sequencer!");
  auto& context = sequencerData[currentSequencer];

  context.Selection.resize(0);
  context.SelectionData.resize(0);
}

bool NeoIsSelecting() {
  IM_ASSERT(inSequencer && "Not in active sequencer!");
  auto& context = sequencerData[currentSequencer];

  return context.StateOfSelection == SelectionState::Selecting;
}

bool NeoHasSelection() {
  IM_ASSERT(inSequencer && "Not in active sequencer!");
  auto& context = sequencerData[currentSequencer];

  return !context.Selection.empty();
}

bool NeoIsDraggingSelection() {
  IM_ASSERT(inSequencer && "Not in active sequencer!");
  auto& context = sequencerData[currentSequencer];

  return context.StateOfSelection == SelectionState::Dragging;
}

uint32_t GetNeoKeyframeSelectionSize() {
  IM_ASSERT(inSequencer && "Not in active sequencer!");
  auto& context = sequencerData[currentSequencer];

  if (!context.DeleteEnabled)
    return 0;

  IM_ASSERT(!context.TimelineStack.empty() && "Not in timeline!");
  const ImGuiID timelineId = context.TimelineStack.back();

  for (auto&& deleteSelection : context.SelectionData) {
    if (deleteSelection.TimelineID == timelineId)
      return deleteSelection.KeyframesToDelete.size();
  }

  return 0;
}

void GetNeoKeyframeSelection(FrameIndexType* selection) {
  IM_ASSERT(inSequencer && "Not in active sequencer!");
  auto& context = sequencerData[currentSequencer];

  if (!context.DeleteEnabled)
    return;

  IM_ASSERT(!context.TimelineStack.empty() && "Not in timeline!");
  const ImGuiID timelineId = context.TimelineStack.back();

  for (auto&& deleteSelection : context.SelectionData) {
    if (deleteSelection.TimelineID == timelineId) {
      for (int32_t i = 0; i < deleteSelection.KeyframesToDelete.size(); i++) {
        selection[i] = deleteSelection.KeyframesToDelete[i];
      }
      return;
    }
  }
}

bool IsNeoKeyframeSelectionRightClicked() {
  IM_ASSERT(inSequencer && "Not in active sequencer!");
  auto& context = sequencerData[currentSequencer];

  return context.IsSelectionRightClicked;
}

bool NeoCanDeleteSelection() {
  IM_ASSERT(inSequencer && "Not in active sequencer!");
  auto& context = sequencerData[currentSequencer];

  return context.DeleteEnabled && NeoHasSelection() && !NeoIsSelecting() && !NeoIsDraggingSelection();
}
} // namespace ImGui

ImGuiNeoSequencerStyle::ImGuiNeoSequencerStyle() {
  Colors[ImGuiNeoSequencerCol_Bg] = ImVec4{0.31f, 0.31f, 0.31f, 1.00f};
  Colors[ImGuiNeoSequencerCol_TopBarBg] = ImVec4{0.22f, 0.22f, 0.22f, 0.84f};
  Colors[ImGuiNeoSequencerCol_SelectedTimeline] = ImVec4{0.98f, 0.706f, 0.322f, 0.88f};
  Colors[ImGuiNeoSequencerCol_TimelinesBg] = Colors[ImGuiNeoSequencerCol_TopBarBg];
  Colors[ImGuiNeoSequencerCol_TimelineBorder] = Colors[ImGuiNeoSequencerCol_Bg] * ImVec4{0.5f, 0.5f, 0.5f, 1.0f};

  Colors[ImGuiNeoSequencerCol_FramePointer] = ImVec4{0.98f, 0.24f, 0.24f, 0.50f};
  Colors[ImGuiNeoSequencerCol_FramePointerHovered] = ImVec4{0.98f, 0.15f, 0.15f, 1.00f};
  Colors[ImGuiNeoSequencerCol_FramePointerPressed] = ImVec4{0.98f, 0.08f, 0.08f, 1.00f};

  Colors[ImGuiNeoSequencerCol_Keyframe] = ImVec4{0.59f, 0.59f, 0.59f, 0.50f};
  Colors[ImGuiNeoSequencerCol_KeyframeHovered] = ImVec4{0.98f, 0.39f, 0.36f, 1.00f};
  Colors[ImGuiNeoSequencerCol_KeyframePressed] = ImVec4{0.98f, 0.39f, 0.36f, 1.00f};
  Colors[ImGuiNeoSequencerCol_KeyframeSelected] = ImVec4{0.32f, 0.23f, 0.98f, 1.00f};

  Colors[ImGuiNeoSequencerCol_FramePointerLine] = ImVec4{0.98f, 0.98f, 0.98f, 0.8f};

  Colors[ImGuiNeoSequencerCol_ZoomBarBg] = ImVec4{0.59f, 0.59f, 0.59f, 0.90f};
  Colors[ImGuiNeoSequencerCol_ZoomBarSlider] = ImVec4{0.8f, 0.8f, 0.8f, 0.60f};
  Colors[ImGuiNeoSequencerCol_ZoomBarSliderHovered] = ImVec4{0.98f, 0.98f, 0.98f, 0.80f};
  Colors[ImGuiNeoSequencerCol_ZoomBarSliderEnds] = ImVec4{0.59f, 0.59f, 0.59f, 0.90f};
  Colors[ImGuiNeoSequencerCol_ZoomBarSliderEndsHovered] = ImVec4{0.93f, 0.93f, 0.93f, 0.93f};

  Colors[ImGuiNeoSequencerCol_SelectionBorder] = ImVec4{0.98f, 0.706f, 0.322f, 0.61f};
  Colors[ImGuiNeoSequencerCol_Selection] = ImVec4{0.98f, 0.706f, 0.322f, 0.33f};
}


code:
//
// Created by Matty on 2022-01-28.
//

#ifndef IMGUI_NEO_SEQUENCER_H
#define IMGUI_NEO_SEQUENCER_H

#include "imgui.h"
#include <vector>
#include <cstdint>

typedef int ImGuiNeoSequencerFlags;
typedef int ImGuiNeoSequencerCol;
typedef int ImGuiNeoTimelineFlags;
typedef int ImGuiNeoTimelineIsSelectedFlags;

// Flags for ImGui::BeginNeoSequencer()
enum ImGuiNeoSequencerFlags_
{
    ImGuiNeoSequencerFlags_None                 = 0     ,
    ImGuiNeoSequencerFlags_AllowLengthChanging  = 1 << 0, // Allows changing length of sequence
    ImGuiNeoSequencerFlags_EnableSelection      = 1 << 1, // Enables selection of keyframes
    ImGuiNeoSequencerFlags_HideZoom             = 1 << 2, // Disables zoom bar
    //ImGuiNeoSequencerFlags_PH                 = 1 << 3, // PLACEHOLDER
    ImGuiNeoSequencerFlags_AlwaysShowHeader     = 1 << 4, // Enables overlay header, keeping it visible when scrolling

    // Selection options, only work with enable selection flag
    ImGuiNeoSequencerFlags_Selection_EnableDragging = 1 << 5,
    ImGuiNeoSequencerFlags_Selection_EnableDeletion = 1 << 6,

};

// Flags for ImGui::BeginNeoTimeline()
enum ImGuiNeoTimelineFlags_
{
    ImGuiNeoTimelineFlags_None                 = 0     ,
    ImGuiNeoTimelineFlags_AllowFrameChanging   = 1 << 0,
    ImGuiNeoTimelineFlags_Group                = 1 << 1,
};

// Flags for ImGui::IsNeoTimelineSelected()
enum ImGuiNeoTimelineIsSelectedFlags_
{
    ImGuiNeoTimelineIsSelectedFlags_None            = 0     ,
    ImGuiNeoTimelineIsSelectedFlags_NewlySelected   = 1 << 0,
};

enum ImGuiNeoSequencerCol_
{
    ImGuiNeoSequencerCol_Bg,
    ImGuiNeoSequencerCol_TopBarBg,
    ImGuiNeoSequencerCol_SelectedTimeline,
    ImGuiNeoSequencerCol_TimelineBorder,
    ImGuiNeoSequencerCol_TimelinesBg,
    ImGuiNeoSequencerCol_FramePointer,
    ImGuiNeoSequencerCol_FramePointerHovered,
    ImGuiNeoSequencerCol_FramePointerPressed,
    ImGuiNeoSequencerCol_Keyframe,
    ImGuiNeoSequencerCol_KeyframeHovered,
    ImGuiNeoSequencerCol_KeyframePressed,
    ImGuiNeoSequencerCol_KeyframeSelected,
    ImGuiNeoSequencerCol_FramePointerLine,

    ImGuiNeoSequencerCol_ZoomBarBg,
    ImGuiNeoSequencerCol_ZoomBarSlider,
    ImGuiNeoSequencerCol_ZoomBarSliderHovered,
    ImGuiNeoSequencerCol_ZoomBarSliderEnds,
    ImGuiNeoSequencerCol_ZoomBarSliderEndsHovered,

    ImGuiNeoSequencerCol_SelectionBorder,
    ImGuiNeoSequencerCol_Selection,

    ImGuiNeoSequencerCol_COUNT
};

struct ImGuiNeoSequencerStyle {
    float       SequencerRounding       = 2.5f;                 // Corner rounding around whole sequencer
    float       TopBarHeight            = 0.0f;                 // Value <= 0.0f = Height is calculated by FontSize + FramePadding.y * 2.0f
    bool        TopBarShowFrameLines    = true;                 // Show line for every frame in top bar
    bool        TopBarShowFrameTexts    = true;                 // Show frame number every 10th frame
    ImVec2      ItemSpacing             = {4.0f,0.5f};
    float       DepthItemSpacing        = 10.0f;                // Amount of text offset per depth level in timeline values
    float       TopBarSpacing           = 3.0f;                 // Space between top bar and timeline
    float       TimelineBorderSize      = 1.0f;
    float       CurrentFramePointerSize = 7.0f;                 // Size of pointing arrow above current frame line
    float       CurrentFrameLineWidth   = 1.0f;                 // Width of line showing current frame over timeline
    float       ZoomHeightScale         = 1.0f;                 // Scale of Zoom bar, base height is font size
    float       CollidedKeyframeOffset  = 3.5f;                 // Offset on which colliding keyframes are rendered

    float       MaxSizePerTick          = 4.0f;                 // Maximum amount of pixels per tick on timeline (if less pixels is present, ticks are not rendered)

    ImVec4      Colors[ImGuiNeoSequencerCol_COUNT];

    ImGuiKey    ModRemoveKey            = ImGuiMod_Ctrl;        // Key mod which when held removes selected keyframes from present selection
    ImGuiKey    ModAddKey               = ImGuiMod_Shift;       // Key mod which when held adds selected keyframes to present selection

    ImGuiNeoSequencerStyle();
};

namespace ImGui {
    typedef int32_t FrameIndexType;

    IMGUI_API const ImVec4& GetStyleNeoSequencerColorVec4(ImGuiNeoSequencerCol idx);
    IMGUI_API ImGuiNeoSequencerStyle& GetNeoSequencerStyle();

    IMGUI_API void PushNeoSequencerStyleColor(ImGuiNeoSequencerCol idx, ImU32 col);
    IMGUI_API void PushNeoSequencerStyleColor(ImGuiNeoSequencerCol idx, const ImVec4& col);
    IMGUI_API void PopNeoSequencerStyleColor(int count = 1);

    IMGUI_API bool BeginNeoSequencer(const char* id, FrameIndexType * frame, FrameIndexType * startFrame, FrameIndexType * endFrame,const ImVec2& size = ImVec2(0, 0),ImGuiNeoSequencerFlags flags = ImGuiNeoSequencerFlags_None);
    IMGUI_API void EndNeoSequencer(); //Call only when BeginNeoSequencer() returns true!!

    IMGUI_API bool BeginNeoGroup(const char* label, bool* open = nullptr);
    IMGUI_API void EndNeoGroup();

    IMGUI_API bool BeginNeoTimeline(const char* label,FrameIndexType ** keyframes, uint32_t keyframeCount, bool * open = nullptr, ImGuiNeoTimelineFlags flags = ImGuiNeoTimelineFlags_None);
    IMGUI_API void EndNeoTimeLine(); //Call only when BeginNeoTimeline() returns true!!

    // Fully customizable timeline with per key callback
    IMGUI_API bool BeginNeoTimelineEx(const char* label, bool * open = nullptr, ImGuiNeoTimelineFlags flags = ImGuiNeoTimelineFlags_None);
    IMGUI_API void NeoKeyframe(int32_t* value);

    IMGUI_API bool IsNeoKeyframeHovered();
    IMGUI_API bool IsNeoKeyframeSelected();
    IMGUI_API bool IsNeoKeyframeRightClicked();

    // Selection API
    // DON'T delete keyframes while dragging, internal buffer will get corrupted
    // Order for deletion is generally:
    // CanDelete? -> DataSize? -> GetData() -> Delete your data -> ClearSelection()
    IMGUI_API void NeoClearSelection(); // Clears selection
    IMGUI_API bool NeoIsSelecting(); // Are we currently selecting?
    IMGUI_API bool NeoHasSelection(); // Is anything selected?
    IMGUI_API bool NeoIsDraggingSelection(); // Are we dragging selection?
    IMGUI_API bool NeoCanDeleteSelection(); // Can selection deletion be done?
    IMGUI_API bool IsNeoKeyframeSelectionRightClicked(); // Is selection rightclicked?

    // Call only in BeginNeoTimeline / EndNeoTimeLine scope, returns selection per timeline and size per timeline
    IMGUI_API uint32_t GetNeoKeyframeSelectionSize();
    IMGUI_API void GetNeoKeyframeSelection(FrameIndexType  * selection);

    // Sets currently selected timeline inside BeginNeoSequencer scope
    IMGUI_API void SetSelectedTimeline(const char* timelineLabel);

    IMGUI_API bool IsNeoTimelineSelected(ImGuiNeoTimelineIsSelectedFlags flags = ImGuiNeoTimelineIsSelectedFlags_None);

#ifdef __cplusplus
    // C++ helper
    IMGUI_API bool BeginNeoTimeline(const char* label,std::vector<int32_t> & keyframes ,bool * open = nullptr, ImGuiNeoTimelineFlags flags = ImGuiNeoTimelineFlags_None);
#endif
}

#endif //IMGUI_NEO_SEQUENCER_H

repo_name: Antidote/openrpgeditor

license: mit


code:
#include "Core/Interpreter.hpp"

#include "Database/EventCommands/ConditionalBranch.hpp"
#include "Database/EventCommands/ControlSwitches.hpp"
#include "Database/EventCommands/Script.hpp"
#include "Database/EventCommands/ShowPicture.hpp"
#include "Database/EventCommands/Wait.hpp"

#include <regex>

Interpreter::Interpreter(std::vector<std::shared_ptr<IEventCommand>> commands) { m_commands = commands; }

bool Interpreter::update() {
  if (m_wait > 0) {
    m_wait--;
  } else {
    if (m_index < m_commands.size() - 1) {
      if (m_commands.at(m_index)->code() == EventCode::Conditional_Branch) {
        std::shared_ptr<ConditionalBranchCommand> cmd = std::dynamic_pointer_cast<ConditionalBranchCommand>(m_commands.at(m_index));
        if (cmd->type == ConditionType::Switch && m_switchMap.contains(cmd->globalSwitch.switchIdx)) {
          if (m_switchMap[cmd->globalSwitch.switchIdx] == false && cmd->globalSwitch.checkIfOn == ValueControl::ON) {
            m_isSkip = true;
          }
          if (m_switchMap[cmd->globalSwitch.switchIdx] == true && cmd->globalSwitch.checkIfOn == ValueControl::OFF) {
            m_isSkip = true;
          }
        }
        if (cmd->type == ConditionType::Variable && m_variableMap.contains(cmd->variable.id) && cmd->variable.source == VariableComparisonSource::Constant) {
          // This condition is satisfied for CONSTANT commands only. Variable commands are not supported.
          if (m_variableMap[cmd->variable.id] != cmd->variable.constant) {
            m_isSkip = true;
          }
        }
      }
      if (!m_isSkip) {
        if (m_commands.at(m_index)->code() == EventCode::Show_Picture) {
          std::shared_ptr<ShowPictureCommand> cmd = std::dynamic_pointer_cast<ShowPictureCommand>(m_commands.at(m_index));
          m_image.emplace(cmd->imageName, Image::Mode::Picture, false);
          m_keyFrame++;
        }
        if (m_commands.at(m_index)->code() == EventCode::Script) {
          std::shared_ptr<ScriptCommand> cmd = std::dynamic_pointer_cast<ScriptCommand>(m_commands.at(m_index));
          m_image.emplace(findStringMatch(cmd->script), Image::Mode::Picture, false);
          m_keyFrame++;
        }
        if (m_commands.at(m_index)->code() == EventCode::Wait) {
          std::shared_ptr<WaitCommand> wait = std::dynamic_pointer_cast<WaitCommand>(m_commands.at(m_index));
          m_wait = wait->duration;
          if (m_isFastForward) {
            m_wait = m_wait / 2;
          }
        }
      } else {
        // Skip all commands until we reach an else or an end. This signals that the branch condition has been processed.
        if (m_commands.at(m_index)->code() == EventCode::End) {
          m_isSkip = false;
        }
        if (m_commands.at(m_index)->code() == EventCode::Else) {
          m_isSkip = false; // If => Else, this should all run
        }
      }

      m_index++;
    } else {
      m_index = 0;
      m_wait = 0;
      m_keyFrame = 0;
    }
  }
  return true;
}
std::vector<int> Interpreter::getKeyFrames() const {
  std::vector<int> keys;
  for (int i = 0; i < m_commands.size(); i++) {
    if (m_commands.at(i)->code() == EventCode::Show_Picture) {
      keys.push_back(i);
    }
    if (m_commands.at(i)->code() == EventCode::Script) {
      std::shared_ptr<ScriptCommand> cmd = std::dynamic_pointer_cast<ScriptCommand>(m_commands.at(i));
      if (isStringMatch(cmd->script)) {
        keys.push_back(i);
      }
    }
  }
  return keys;
}
std::vector<int> Interpreter::getSwitches() {
  std::vector<int> switches;
  for (int i = 0; i < m_commands.size(); i++) {
    if (m_commands.at(i)->code() == EventCode::Conditional_Branch) {
      std::shared_ptr<ConditionalBranchCommand> cmd = std::dynamic_pointer_cast<ConditionalBranchCommand>(m_commands.at(i));
      if (cmd->type == ConditionType::Switch) {
        if (!m_switchMap.contains(cmd->globalSwitch.switchIdx)) {
          switches.push_back(cmd->globalSwitch.switchIdx);
          m_switchMap[cmd->globalSwitch.switchIdx] = false;
        }
      }
    }
  }
  return switches;
}
std::vector<int> Interpreter::getVariables() {
  std::vector<int> variables;
  for (int i = 0; i < m_commands.size(); i++) {
    if (m_commands.at(i)->code() == EventCode::Conditional_Branch) {
      std::shared_ptr<ConditionalBranchCommand> cmd = std::dynamic_pointer_cast<ConditionalBranchCommand>(m_commands.at(i));
      if (cmd->type == ConditionType::Variable && cmd->variable.source == VariableComparisonSource::Constant) {
        if (!m_variableMap.contains(cmd->variable.id)) {
          m_variableMap[cmd->variable.id] = 0;
          variables.push_back(cmd->variable.id);
        }
      } else {
        // Variable comparison... we need another variable check?
        // Unlikely that we'll ever need to compare this, but the section exists just in case
      }
    }
  }
  return variables;
}
void Interpreter::setVariable(int index, int value) { m_variableMap[index] = value; }
void Interpreter::setSwitch(int index, bool value) { m_switchMap[index] = value; }
void Interpreter::setIndex(int index, int keyFrame) {
  // Sets the interpreter to a keyframe index and emplaces the image.
  // Keyframe indexes are built in Preview from a compiled vector based on image results in the command list
  m_index = index;
  m_keyFrame = keyFrame;
  if (m_commands.at(m_index)->code() == EventCode::Show_Picture) {
    std::shared_ptr<ShowPictureCommand> cmd = std::dynamic_pointer_cast<ShowPictureCommand>(m_commands.at(m_index));
    m_image.emplace(cmd->imageName, Image::Mode::Picture, false);
  }
  if (m_commands.at(m_index)->code() == EventCode::Script) {
    std::shared_ptr<ScriptCommand> cmd = std::dynamic_pointer_cast<ScriptCommand>(m_commands.at(m_index));
    m_image.emplace(findStringMatch(cmd->script), Image::Mode::Picture, false);
  }
}
std::string Interpreter::findStringMatch(std::string text) {
  std::regex re("\"([^\"]*)\""); // Regex to match anything inside quotes
  std::smatch match;
  if (std::regex_search(text, match, re) && match.size() > 1) {
    return match.str(1);
  }
  return ""; // String not found?
}
bool Interpreter::isStringMatch(std::string text) const {
  std::regex re("\"([^\"]*)\""); // Regex to match anything inside quotes
  std::smatch match;
  if (std::regex_search(text, match, re) && match.size() > 1) {
    return true;
  }
  return false; // String not found?
}


code:
#pragma once

#include "Core/Graphics/Image.hpp"
#include "Database/EventCommands/IEventCommand.hpp"

#include <string>
#include <vector>

class Interpreter {
public:
  explicit Interpreter(std::vector<std::shared_ptr<IEventCommand>> commands);
  bool update();
  Image& getImage() { return m_image.value(); }
  [[nodiscard]] bool image() const { return m_image.has_value(); }
  std::vector<int> getKeyFrames() const;
  std::vector<int> getSwitches();
  std::vector<int> getVariables();

  bool hasVariable(int index);
  bool hasSwitch(int index);
  void setVariable(int index, int value);
  void setSwitch(int index, bool value);
  void setIndex(int index, int keyFrame);
  std::string findStringMatch(std::string text);
  bool isStringMatch(std::string text) const;
  int getKeyFrameIndex() const { return m_keyFrame; }
  void setFastForward(bool cond) { m_isFastForward = cond; }

private:
  int m_index{0};
  int m_keyFrame{0};
  int m_wait{0};

  bool m_isFastForward{false};
  bool m_isSkip{false}; // For branch condition parsing
  std::optional<Image> m_image;
  std::vector<std::shared_ptr<IEventCommand>> m_commands;

  std::map<int, int> m_variableMap;
  std::map<int, bool> m_switchMap;
};


code:
#include "Core/LCF_Importer/LCF_Importer.hpp"
#include "Database/Database.hpp"
#include "LCF_Mapping.hpp"

#include <algorithm>

bool startsWithInsensitive(const std::string& str, const std::string& prefix) {
  if (str.length() < prefix.length()) {
    return false;
  }

  std::string str_lower(str.begin(), str.begin() + prefix.length());
  std::string prefix_lower(prefix);

  std::transform(str_lower.begin(), str_lower.end(), str_lower.begin(), ::tolower);
  std::transform(prefix_lower.begin(), prefix_lower.end(), prefix_lower.begin(), ::tolower);

  return std::equal(str_lower.begin(), str_lower.end(), prefix_lower.begin());
}

bool LCF_Importer::loadProject() {
  if (m_projectPath.empty()) {
    return false;
  }
  if (m_database && m_treeMap) {
    return true;
  }

  if (!exists(m_projectPath / "RPG_RT.ldb") || !exists(m_projectPath / "RPG_RT.lmt")) {
    return false;
  }

  m_database = lcf::LDB_Reader::Load((m_projectPath / "RPG_RT.ldb").generic_string(), "SJIS");
  m_treeMap = lcf::LMT_Reader::Load((m_projectPath / "RPG_RT.lmt").generic_string(), "SJIS");

  if (!std::filesystem::exists(Database::instance()->basePath + "editor/LCFMapping.json")) {
    m_mapper.serialize(Database::instance()->basePath + "editor/LCFMapping.json"); // Make new file if it doesn't exist
  } else {
    m_mapper = LCF_Mapping::load(Database::instance()->basePath + "editor/LCFMapping.json");
    m_mapper.hasUnresolvedPairs();
  }

  return m_database && m_treeMap;
}

std::unique_ptr<lcf::rpg::Map> LCF_Importer::loadMap(const int id) const {
  std::string mapName = std::format("Map{:04}.lmu", id);
  if (exists(m_projectPath / mapName)) {
    return lcf::LMU_Reader::Load((m_projectPath / mapName).generic_string().c_str(), "SJIS");
  }
  return nullptr;
}


code:
#pragma once

#include "LCF_Mapping.hpp"

#include <filesystem>
#include <lcf/ldb/reader.h>
#include <lcf/lmt/reader.h>
#include <lcf/lmu/reader.h>
#include <map>
#include <memory>

class LCF_Importer {
public:
  explicit LCF_Importer() = default;

  bool loadProject();

  const lcf::rpg::Database* database() const { return m_database.get(); }
  const lcf::rpg::TreeMap* treeMap() const { return m_treeMap.get(); }
  std::unique_ptr<lcf::rpg::Map> loadMap(int id) const;

  void setProject(const std::filesystem::path& projectPath) { m_projectPath = projectPath; }

  LCF_Mapping* mapper() { return &m_mapper; }

private:
  std::filesystem::path m_projectPath;
  std::unique_ptr<lcf::rpg::Database> m_database;
  std::unique_ptr<lcf::rpg::TreeMap> m_treeMap;
  LCF_Mapping m_mapper;
};


code:
#include "Core/LCF_Importer/LCF_Mapping.hpp"

#include "Database/Database.hpp"

#include <fstream>
void to_json(nlohmann::ordered_json& json, const LCF_Mapping& mapping) {
  json = {
      {"switch_mapping", mapping.switch_mapping}, {"variable_mapping", mapping.variable_mapping}, {"common_mapping", mapping.common_mapping}, {"actor_mapping", mapping.actor_mapping},
      {"state_mapping", mapping.state_mapping},   {"image_mapping", mapping.image_mapping},       {"sound_mapping", mapping.sound_mapping},   {"characterName_mapping", mapping.characterName_mapping},
  };
}
void from_json(const nlohmann::ordered_json& json, LCF_Mapping& mapping) {
  mapping.switch_mapping = json.value("switch_mapping", mapping.switch_mapping);
  mapping.variable_mapping = json.value("variable_mapping", mapping.variable_mapping);
  mapping.common_mapping = json.value("common_mapping", mapping.common_mapping);
  mapping.actor_mapping = json.value("actor_mapping", mapping.actor_mapping);
  mapping.state_mapping = json.value("state_mapping", mapping.state_mapping);
  mapping.image_mapping = json.value("image_mapping", mapping.image_mapping);
  mapping.sound_mapping = json.value("sound_mapping", mapping.sound_mapping);
  mapping.characterName_mapping = json.value("characterName_mapping", mapping.characterName_mapping);
}

LCF_Mapping LCF_Mapping::load(std::string_view path) {
  if (std::ifstream file(path.data()); file.is_open()) {
    try {
      nlohmann::json data = nlohmann::ordered_json::parse(file);
      LCF_Mapping ret;
      data.get_to(ret);
      return ret;
    } catch (...) {}
  }

  return {};
}

bool LCF_Mapping::serialize(const std::string_view path) {
  std::ofstream file(path.data());
  try {
    if (file.is_open()) {
      const nlohmann::ordered_json data = *this;
      file << data.dump(4);
      return true;
    }
  } catch (...) {}
  return false;
}

bool LCF_Mapping::hasUnresolvedPairs() {
  bool result{false};
  for (auto& val : switch_mapping) {
    if (val.second == 0) {
      result = true;
    }
  }
  for (auto& val : variable_mapping) {
    if (val.second == 0) {
      result = true;
    }
  }
  for (auto& val : common_mapping) {
    if (val.second == 0) {
      result = true;
    }
  }
  for (auto& val : actor_mapping) {
    if (val.second == 0) {
      result = true;
    }
  }
  for (auto& val : state_mapping) {
    if (val.second == 0) {
      result = true;
    }
  }
  for (auto& val : sound_mapping) {
    if (val.second.empty()) {
      result = true;
    }
  }
  for (auto& val : image_mapping) {
    if (val.second.empty()) {
      result = true;
    }
  }
  for (auto& val : characterName_mapping) {
    if (val.second.empty()) {
      result = true;
    }
  }
  m_hasUnresolved = result;
  return result;
}
bool LCF_Mapping::isUnresolved() { return m_hasUnresolved; }
void LCF_Mapping::loadDatabase(const lcf::rpg::Database* database) {
  for (auto& val : database->switches) {
    if (!val.name.empty() && !switch_mapping.contains(val.ID)) {
      switch_mapping.insert(std::make_pair(val.ID, 0));
    }
  }
  for (auto& val : database->variables) {
    if (!val.name.empty() && !variable_mapping.contains(val.ID)) {
      variable_mapping.insert(std::make_pair(val.ID, 0));
    }
  }
  for (auto& val : database->commonevents) {
    if (!val.name.empty() && !common_mapping.contains(val.ID)) {
      common_mapping.insert(std::make_pair(val.ID, 0));
    }
  }
  for (auto& val : database->actors) {
    if (!val.name.empty() && !actor_mapping.contains(val.ID)) {
      actor_mapping.insert(std::make_pair(val.ID, 0));
    }
  }
  for (auto& val : database->states) {
    if (!val.name.empty() && !state_mapping.contains(val.ID)) {
      state_mapping.insert(std::make_pair(val.ID, 0));
    }
  }
}
void LCF_Mapping::addEmptySwitch(int id) {
  switch_mapping.insert(std::make_pair(id, 0));
  m_hasUnresolved = true;
}
void LCF_Mapping::addEmptyVariable(int id) {
  variable_mapping.insert(std::make_pair(id, 0));
  m_hasUnresolved = true;
}
void LCF_Mapping::addEmptyCommonEvent(int id) {
  common_mapping.insert(std::make_pair(id, 0));
  m_hasUnresolved = true;
}
void LCF_Mapping::addEmptyActor(int id) {
  actor_mapping.insert(std::make_pair(id, 0));
  m_hasUnresolved = true;
}
void LCF_Mapping::addEmptyState(int id) {
  state_mapping.insert(std::make_pair(id, 0));
  m_hasUnresolved = true;
}
void LCF_Mapping::addEmptySound(std::string& name) {
  sound_mapping.insert(std::make_pair(name, ""));
  m_hasUnresolved = true;
}
void LCF_Mapping::addEmptyImage(std::string& name) {
  image_mapping.insert(std::make_pair(name, ""));
  m_hasUnresolved = true;
}
void LCF_Mapping::addEmptyCharacterName(std::string& name) {
  characterName_mapping.insert(std::make_pair(name, ""));
  m_hasUnresolved = true;
}
std::string LCF_Mapping::replaceText(std::string& text) {
  if (text.contains("N[0]")) {
    text.replace(text.find("N[0]"), 4, "V[111]");
  }
  if (text.contains("N[25]")) {
    text.replace(text.find("N[25]"), 5, "V[296]");
  }
  if (text.contains("N[26]")) {
    text.replace(text.find("N[26]"), 5, "V[297]");
  }
  if (text.contains("N[27]")) {
    text.replace(text.find("N[27]"), 5, "V[298]");
  }
  if (text.contains("N[28]")) {
    text.replace(text.find("N[28]"), 5, "V[299]");
  }
  if (text.contains("　")) {
    text.replace(text.find("　"), 1, " ");
  }
  if (text.contains("（")) {
    text.replace(text.find("（"), 1, "(");
  }
  if (text.contains("）")) {
    text.replace(text.find("）"), 1, ")");
  }
  if (text.contains("$g")) {
    text.replace(text.find("$g"), 2, "♡");
  }
  if (text.contains("$k")) {
    text.replace(text.find("$k"), 2, "♥");
  }
  if (text.contains("$v")) {
    text.replace(text.find("$v"), 2, "☜");
  }
  // if (text.contains("♪")) {
  //   text.replace(text.find("♪"), 1, "♪");
  // }
  if (text.contains("$t")) {
    text.replace(text.find("$t"), 2, "☞");
  }
  if (text.contains("$e")) {
    text.replace(text.find("$e"), 2, "");
  }
  if (text.contains("$d")) {
    text.replace(text.find("$e"), 2, "");
  }
  if (text.contains("C[11]")) {
    text.replace(text.find("C[11]"), 5, "C[18]");
  }
  if (text.contains("C[15]")) {
    text.replace(text.find("C[15]"), 5, "C[24]");
  }
  if (text.contains("C[19]")) {
    text.replace(text.find("C[19]"), 5, "C[20]");
  }
  if (text.contains("C[17]")) {
    text.replace(text.find("C[17]"), 5, "C[36]");
  }
  if (text.contains("C[13]")) {
    text.replace(text.find("C[13]"), 5, "C[30]");
  }
  // if (text.contains("～")) {
  //   text.replace(text.find("～"), 1, "~");
  // }
  auto pos1 = text.find("「");
  auto pos2 = text.find("」");
  if (pos1 != std::string::npos && pos2 != std::string::npos) {
    std::string prefix = text.substr(0, pos1 + 3);
    std::string suffix = text.substr(pos2);
    std::string substring = text.substr(pos1 + 3, text.size() - (prefix.size() + suffix.size()));

    return "";
  }
  auto pos3 = text.find('(');
  auto pos4 = text.find(')');

  if (pos3 != std::string::npos && pos4 != std::string::npos) {
    std::string prefix = text.substr(0, pos3);
    std::string substring = text.substr(pos3, text.size() - prefix.size());

    return prefix + substring;
  }
  return text;
}

int LCF_Mapping::switchValue(int key) {
  if (switch_mapping.empty() || !switch_mapping.contains(key)) {
    addEmptySwitch(key);
    return 5000;
  }
  int ret = switch_mapping.at(key);
  if (ret == 0) {
    m_hasUnresolved = true;
    return 5000;
  }
  return ret;
}
int LCF_Mapping::variableValue(int key) {
  if (variable_mapping.empty() || !variable_mapping.contains(key)) {
    addEmptyVariable(key);
    return 5000;
  }
  int ret = variable_mapping.at(key);
  if (ret == 0) {
    m_hasUnresolved = true;
    return 5000;
  }
  return ret;
}
int LCF_Mapping::commonEventValue(int key) {
  if (common_mapping.empty() || !common_mapping.contains(key)) {
    addEmptyCommonEvent(key);
    return 1;
  }
  int ret = common_mapping.at(key);
  if (ret == 0) {
    m_hasUnresolved = true;
    return 1;
  }
  return ret;
}
int LCF_Mapping::actorValue(int key) {
  if (actor_mapping.empty() || !actor_mapping.contains(key)) {
    addEmptyCommonEvent(key);
    return key;
  }
  int ret = actor_mapping.at(key);
  if (ret == 0) {
    m_hasUnresolved = true;
    return key;
  }
  return ret;
}
int LCF_Mapping::stateValue(int key) {
  if (state_mapping.empty() || !state_mapping.contains(key)) {
    addEmptyCommonEvent(key);
    return key;
  }
  int ret = state_mapping.at(key);
  if (ret == 0) {
    m_hasUnresolved = true;
    return key;
  }
  return ret;
}
std::string LCF_Mapping::localeValue(std::string& text, int mapId, int evId, int page, int command) { return "{}"; }
std::string LCF_Mapping::soundValue(std::string key) {
  if (sound_mapping.empty() || !sound_mapping.contains(key)) {
    addEmptySound(key);
    return key;
  }
  std::string ret = sound_mapping.at(key);
  if (ret.empty()) {
    m_hasUnresolved = true;
    return key;
  }
  return ret;
}
std::string LCF_Mapping::imageValue(std::string& key) {
  if (image_mapping.empty() || !image_mapping.contains(key)) {
    addEmptyImage(key);
    return key;
  }
  std::string ret = image_mapping.at(key);
  if (ret.empty()) {
    m_hasUnresolved = true;
    return ret;
  }
  return ret;
}
std::string LCF_Mapping::characterNameValue(std::string& key) {
  if (key.empty()) {
    return key;
  }
  if (characterName_mapping.empty() || !characterName_mapping.contains(key)) {
    addEmptyCharacterName(key);
    return key;
  }
  std::string ret = characterName_mapping.at(key);
  if (ret.empty()) {
    m_hasUnresolved = true;
    return ret;
  }
  return ret;
}


code:
#pragma once

#include "Core/LCF_Importer/LCF_Mapping.hpp"
#include "lcf/rpg/database.h"

#include <nlohmann/json.hpp>

#include <filesystem>
#include <map>
#include <memory>

class LCF_Mapping {
public:
  static LCF_Mapping load(std::string_view path);
  bool serialize(std::string_view path);
  void save(std::string path) {
    serialize(path.append("editor/LCFMapping.json")); // Make new file if it doesn't exist
  }

  void addEmptySwitch(int id);
  void addEmptyVariable(int id);
  void addEmptyCommonEvent(int id);
  void addEmptyActor(int id);
  void addEmptyState(int id);
  void addEmptySound(std::string& name);
  void addEmptyImage(std::string& name);
  void addEmptyCharacterName(std::string& name);

  std::string replaceText(std::string& text);

  int switchValue(int key);
  int variableValue(int key);
  int commonEventValue(int key);
  int actorValue(int key);
  int stateValue(int key);

  std::string localeValue(std::string& text, int mapId, int evId, int page, int command);
  std::string soundValue(std::string key);
  std::string imageValue(std::string& key);
  std::string characterNameValue(std::string& key);

  bool hasUnresolvedPairs();
  bool isUnresolved();
  void loadDatabase(const lcf::rpg::Database* database);

  std::map<int, int> switch_mapping;
  std::map<int, int> variable_mapping;
  std::map<int, int> common_mapping;
  std::map<int, int> actor_mapping;
  std::map<int, int> state_mapping;
  std::map<std::string, std::string> image_mapping;
  std::map<std::string, std::string> sound_mapping;
  std::map<std::string, std::string> characterName_mapping;

  std::map<std::string, std::string> dialogue_errors;
  int keyCount{0};

private:
  bool m_hasUnresolved{false};
};

void to_json(nlohmann::ordered_json& j, const LCF_Mapping& mapper);
void from_json(const nlohmann::ordered_json& j, LCF_Mapping& mapper);