code:
#include "Core/DatabaseEditor/DBAnimationsTab.hpp"
#include "Core/Application.hpp"
#include "Database/Animations.hpp"

#include "Core/ImGuiExt/ImGuiUtils.hpp"
#include "Core/ImGuiExt/imgui_neo_sequencer.h"
#include "imgui.h"
#include "imgui_internal.h"

DBAnimationsTab::DBAnimationsTab(Animations& animations, DatabaseEditor* parent)
: IDBEditorTab(parent)
, m_animations(animations) {

  m_selectedAnimation = m_animations.animation(1);

  m_selectedTimings = m_selectedAnimation->timings();
  m_colorPicker.setValues(255, 255, 255, 255);
  m_selectedAudio.setName("");
  m_selectedAudio.setVolume(100);
  m_selectedAudio.setPan(0);
  m_selectedAudio.setPitch(100);

  m_selectedAnimation->setTimeLine(std::vector(m_selectedAnimation->frames().size(), false));

  m_soundPicker.selectedAudio().nameModified().connect<&DBAnimationsTab::onNameModified>(this);
  m_soundPicker.selectedAudio().volumeModified().connect<&DBAnimationsTab::onVolModified>(this);
  m_soundPicker.selectedAudio().panModified().connect<&DBAnimationsTab::onPanModified>(this);
  m_soundPicker.selectedAudio().pitchModified().connect<&DBAnimationsTab::onPitchModified>(this);
}

void DBAnimationsTab::draw() {
  if (m_selectedAnimation) {
    if (!m_imagePicker) {
      m_imagePicker.emplace(ImagePicker::PickerMode::Animation, "", "");
    }
  }
  if (const auto [closed, confirmed] = m_imagePicker->draw(); closed) {
    if (confirmed) {
      m_imagePicker->accept();
      if (m_pickerSelection == 0) {
        m_selectedAnimation->setAnimation1Name(m_imagePicker->selectedImage());
      } else {
        m_selectedAnimation->setAnimation2Name(m_imagePicker->selectedImage());
      }
    }
  }
  m_soundPicker.draw();

  ImGui::BeginChild("#orpg_animations_editor");
  {
    const auto calc = ImGui::CalcTextSize("ABCDEFGHIJKLMNOPQRSTUV");
    ImGui::BeginChild("##orpg_actors_editor_actors", ImVec2{calc.x + (ImGui::GetStyle().ItemSpacing.x * 2), 0}, 0, ImGuiWindowFlags_NoScrollbar);
    {
      ImGui::BeginGroup();
      {
        ImGui::SeparatorText(trNOOP("Animations"));
        ImGui::BeginChild("##orpg_animations_editor_animations_list", ImVec2{0, ImGui::GetContentRegionMax().y - ((calc.y + ImGui::GetStyle().ItemSpacing.y) * 4)});
        {
          ImGui::BeginGroup();
          {
            for (auto& animation : m_animations.animations()) {
              if (animation.id() == 0) {
                continue;
              }
              if (ImGui::Selectable(Database::instance()->animationNameAndId(animation.id()).c_str(), &animation == m_selectedAnimation) ||
                  (ImGui::IsItemFocused() && m_selectedAnimation != &animation)) {

                m_selectedAnimation = &animation;
                m_frameCursor = 0; // Reset cursor on animation change
                m_selectedTimings = m_selectedAnimation->timings();
              }
            }
          }
          ImGui::EndGroup();
        }
        ImGui::EndChild();
        ImGui::SeparatorText(trFormat("Max Animations {}", m_animations.count()).c_str());
        if (ImGui::Button(trNOOP("Change Max"), ImVec2{ImGui::GetContentRegionMax().x - 8, 0})) {
          m_changeIntDialogOpen = true;
          m_editMaxAnimations = m_animations.count();
        }
      }
      ImGui::EndGroup();
    }
    ImGui::EndChild();
    ImGui::SameLine();
    ImGui::BeginChild("##orpg_animations_editor_animations_animation_properties");
    {
      if (m_selectedAnimation) {
        ImGui::BeginChild("##orpg_animations_animation_panel_topleft", ImVec2{ImGui::GetContentRegionMax().x, ImGui::GetContentRegionMax().y / 5});
        {
          ImGui::BeginGroup();
          {
            ImGui::SeparatorText(trNOOP("General Settings"));
            // Name
            ImGui::BeginGroup();
            {
              char name[4096];
              strncpy(name, m_selectedAnimation->name().c_str(), 4096);
              if (ImGui::LabelOverLineEdit("##orpg_animations_editor_name", trNOOP("Name:"), name, 4096, 200)) {
                m_selectedAnimation->setName(name);
              }
            }
            ImGui::EndGroup();
            ImGui::SameLine();
            ImGui::SetCursorPosY(ImGui::GetCursorPosY() - 6.f);
            ImGui::BeginGroup();
            {
              ImGui::TextUnformatted(trNOOP("Images:"));
              ImGui::PushID("##orpg_animations_name1_selection");
              if (ImGui::Button(m_selectedAnimation->animation1Name().data(), ImVec2{200, 0})) {
                m_pickerSelection = 0;
                m_imagePicker->setImageInfo(m_selectedAnimation->animation1Name(), "");
                m_imagePicker->setOpen(true);
              }
              ImGui::PopID();
              ImGui::SameLine();
              ImGui::PushID("##orpg_animations_name2_selection");
              if (ImGui::Button(m_selectedAnimation->animation2Name().data(), ImVec2{200, 0})) {
                m_pickerSelection = 1;
                m_imagePicker->setImageInfo(m_selectedAnimation->animation2Name(), "");
                m_imagePicker->setOpen(true);
              }
              ImGui::PopID();
              ImGui::SameLine();
              ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 40.f);
              ImGui::PushID("##orpg_animations_play");
              if (ImGui::Button(trNOOP("Play"), ImVec2{150, 0})) {
                // Play Animation
              }
              ImGui::PopID();
            }
            ImGui::EndGroup();
            ImGui::BeginGroup();
            {
              ImGui::TextUnformatted(trNOOP("Frame:"));
              ImGui::SetNextItemWidth(ImGui::GetContentRegionMax().x - 50);
              ImGui::SliderInt("##orpg_animations_frames_slider", m_isApplyingChanges ? &m_selectedFrameNumber : &m_frameCursor, 1, m_selectedAnimation->frames().size());
              ImGui::EndGroup();
            }
          }
          ImGui::EndGroup();
        }
        ImGui::EndChild();

        ImGui::BeginChild("##orpg_animations_animation_panel_middle");
        {
          ImGui::Dummy(ImVec2{ImGui::GetContentRegionMax().x, 300});
          ImGui::BeginGroup();
          {
            int currentFrame = 1;
            int startFrame = 1;
            int endFrame = m_selectedAnimation->frames().size();
            if (ImGui::BeginNeoSequencer("Group", &currentFrame, &startFrame, &endFrame, {0.0f, 400.0f},
                                         ImGuiNeoSequencerFlags_EnableSelection | ImGuiNeoSequencerFlags_Selection_EnableDragging | ImGuiNeoSequencerFlags_Selection_EnableDeletion |
                                             ImGuiNeoSequencerFlags_HideZoom)) {

              int timingIndex{0};
              int se_timingTotal{0};
              int flash_timingTotal{0};

              for (auto& timing : m_selectedAnimation->timings()) {
                bool is_selected = m_selectedAnimation->timing(timingIndex).showTimeLine();
                if (timing.frame() == m_frameCursor - 1) {
                  if (ImGui::BeginNeoGroup("Timing", &is_selected)) {
                    m_selectedAnimation->timing(timingIndex).setTimeLine(true);
                    if (ImGui::BeginNeoTimelineEx("Sound Effect")) {
                      for (auto& timing : m_selectedAnimation->timings()) {
                        int timingFrame = timing.frame();
                        ImGui::PushID(std::format("##timing_se_{}_{}", timingIndex, se_timingTotal).c_str());
                        ImGui::NeoKeyframe(&timingFrame);
                        ImGui::PopID();

                        if (ImGui::IsNeoKeyframeHovered()) {
                          ImGui::BeginTooltip();
                          ImGui::TextUnformatted(timing.getToolTip(false).c_str());
                          ImGui::EndTooltip();
                        }
                        if (ImGui::IsNeoKeyframeSelected()) {
                          // Edit the data
                        }
                        se_timingTotal++;
                      }
                      ImGui::EndNeoTimeLine();
                    }
                    if (ImGui::BeginNeoTimelineEx("Flash Color")) {
                      for (auto& timing : m_selectedAnimation->timings()) {
                        int timingFrame = timing.frame();
                        ImGui::PushID(std::format("##timing_flash_{}_{}", timingIndex, flash_timingTotal).c_str());
                        ImGui::NeoKeyframe(&timingFrame);
                        ImGui::PopID();
                        if (ImGui::IsNeoKeyframeHovered()) {
                          ImGui::BeginTooltip();
                          ImGui::TextUnformatted(timing.getToolTip(true).c_str());
                          ImGui::EndTooltip();
                        }
                        if (ImGui::IsNeoKeyframeSelected()) {
                          // Edit the data
                        }
                        flash_timingTotal++;
                      }
                      ImGui::EndNeoTimeLine();
                    }
                    ImGui::EndNeoGroup();
                  }
                  if (is_selected != m_selectedAnimation->timing(timingIndex).showTimeLine()) {
                    m_selectedAnimation->timing(timingIndex).setTimeLine(is_selected);
                  }
                }
                timingIndex++;
              }

              // End Timing Section

              // Begin Image Section

              int imageIndex{1};
              for (auto& frame : m_selectedAnimation->frames().at(m_frameCursor - 1)) {
                // This has all the image frames
                if (ImGui::BeginNeoTimelineEx(std::format("Image {}", imageIndex).c_str())) {
                  ImGui::NeoKeyframe(&imageIndex);
                  if (ImGui::IsNeoKeyframeHovered()) {
                    ImGui::BeginTooltip();
                    ImGui::TextUnformatted(frame.getToolTip().c_str());
                    ImGui::EndTooltip();
                  }
                  if (ImGui::IsNeoKeyframeSelected()) {
                    // Edit the data
                  }
                  ImGui::EndNeoTimeLine();
                }
                imageIndex++;
              }
              ImGui::EndNeoSequencer();
            }
            ImGui::EndGroup();
          }
        }
        ImGui::EndChild();
        /*
                ImGui::BeginChild("##orpg_animations_animation_panel_middleleft", ImVec2{ImGui::GetContentRegionMax().x / 2 - 50, 360});
                {
                  ImGui::BeginGroup();
                  {
                    ImGui::SeparatorText(trNOOP("Sound Effects and Flash Timing"));
                    if (ImGui::BeginTable("##orpg_animations_se_flash_timings", 3,
                                          ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_ScrollY | ImGuiTableFlags_ScrollY)) {
                      ImGui::TableSetupColumn(trNOOP("Frame #"));
                      ImGui::TableSetupColumn(trNOOP("Sound Effect"));
                      ImGui::TableSetupColumn(trNOOP("Flash"));
                      ImGui::TableHeadersRow();

                      int timingIndex{0};
                      for (int i = 0; i < m_selectedAnimation->timings().size(); ++i) {
                        ImGui::PushID(&m_selectedAnimation->timings().at(i));
                        ImGui::TableNextRow();
                        if (ImGui::TableNextColumn()) {
                          if (ImGui::Selectable(
                                  std::format("#{:03}##timing_{}", m_isApplyingChanges && i == m_selectedTiming ? m_selectedFrameNumber + 1 : m_selectedAnimation->timing(i).frame() + 1,
           timingIndex).c_str(), i == m_selectedTiming, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowDoubleClick)) { if (!m_isApplyingChanges) { m_selectedTiming = timingIndex; if
           (ImGui::GetMouseClickedCount(ImGuiMouseButton_Left) >= 2) { m_isApplyingChanges = true; m_selectedAudio = *m_selectedAnimation->timing(m_selectedTiming).se(); m_selectedColor =
           m_selectedAnimation->timing(m_selectedTiming).flashColor(); m_selectedScope = static_cast<int>(m_selectedAnimation->timing(m_selectedTiming).flashScope()); m_frameType = m_selectedScope;
                                m_selectedDuration = m_selectedAnimation->timing(m_selectedTiming).flashDuration();
                                m_selectedFrameNumber = m_selectedAnimation->timing(m_selectedTiming).frame();

                                m_colorPicker.setValues(m_selectedColor.r(), m_selectedColor.g(), m_selectedColor.b(), m_selectedColor.intensity());
                              }
                            }
                          }
                          timingIndex++;
                        }
                        if (ImGui::TableNextColumn()) {
                          if (m_isApplyingChanges && m_selectedTiming == i) {
                            ImGui::Text("%s", m_selectedAudio.name().c_str());
                          } else {
                            ImGui::Text("%s", m_selectedAnimation->timing(i).se()->name().c_str());
                          }
                        }
                        if (ImGui::TableNextColumn()) {
                          if (m_selectedAnimation->timing(i).flashScope() == FlashScope::Hide_Target) {
                            if (m_isApplyingChanges && m_selectedTiming == i) {
                              ImGui::Text("%s, %i frames", DecodeEnumName(static_cast<FlashScope>(m_selectedScope)).c_str(), m_selectedFrameNumber);
                            } else {
                              ImGui::Text("%s, %i frames", DecodeEnumName(m_selectedAnimation->timing(i).flashScope()).c_str(), m_selectedAnimation->timing(i).frame());
                            }
                          } else {
                            int red = m_selectedAnimation->timing(i).flashColor().r();
                            int green = m_selectedAnimation->timing(i).flashColor().g();
                            int blue = m_selectedAnimation->timing(i).flashColor().b();
                            int intensity = m_selectedAnimation->timing(i).flashColor().intensity();

                            if (m_isApplyingChanges && m_selectedTiming == i) {
                              ImGui::Text("%s(%i,%i,%i,%i), %i frames", DecodeEnumName(static_cast<FlashScope>(m_selectedScope)).c_str(), m_selectedColor.r(), m_selectedColor.g(),
           m_selectedColor.b(), m_selectedColor.intensity(), m_selectedDuration); } else { ImGui::Text("%s(%i,%i,%i,%i), %i frames",
           DecodeEnumName(m_selectedAnimation->timing(i).flashScope()).c_str(), red, green, blue, intensity, m_selectedAnimation->timing(i).flashDuration());
                            }
                          }
                        }
                        ImGui::PopID();
                      }

                      ImGui::TableNextRow();
                      ImGui::EndTable();
                    }
                  }
                  ImGui::EndGroup();
                }
                ImGui::EndChild();
                ImGui::SameLine();
                ImGui::SetCursorPosY(ImGui::GetCursorPos().y + 8.f);

                ImGui::BeginChild("##orpg_animations_animation_panel_middle_buttons", ImVec2{50, 320});
                {
                  ImGui::Dummy(ImVec2{0, 140});
                  ImGui::BeginDisabled(m_isApplyingChanges == true);
                  ImGui::PushID("##orpg_animations_timings_add");
                  if (ImGui::Button("+", ImVec2{50, 0})) {
                    Animation::Timing timing;
                    Audio SE;
                    Animation::Color color;

                    if (m_selectedAudio.name() != "") {
                      SE = m_selectedAudio;
                    } else {
                      SE.setName("");
                      SE.setVolume(100);
                      SE.setPan(0);
                      SE.setPitch(100);
                    }

                    color.setR(255);
                    color.setG(255);
                    color.setB(255);
                    color.setIntensity(255);

                    timing.setFrame(m_frameCursor - 1);
                    timing.setSe(SE);
                    timing.setFlashColor(color);
                    timing.setFlashScope(static_cast<FlashScope>(m_frameType));
                    timing.setFlashDuration(m_duration);

                    m_selectedTimings.emplace_back(timing);

                    std::sort(m_selectedTimings.begin(), m_selectedTimings.end(), [](const auto& a, const auto& b) { return a.frame() < b.frame(); });

                    m_selectedAnimation->setTimings(m_selectedTimings);
                  }
                  ImGui::PopID();
                  ImGui::EndDisabled();
                  ImGui::BeginDisabled(m_selectedAnimation->timings().size() < 1 || m_isApplyingChanges == true);
                  ImGui::PushID("##orpg_animations_timings_remove");
                  if (ImGui::Button("-", ImVec2{50, 0})) {
                    m_selectedTimings.erase(m_selectedTimings.begin() + m_selectedTiming, m_selectedTimings.begin() + m_selectedTiming + 1);
                    m_selectedAnimation->setTimings(m_selectedTimings);
                    if (m_selectedTiming > 0) {
                      m_selectedTiming--;
                    }
                  }
                  ImGui::PopID();
                  ImGui::EndDisabled();
                  ImGui::BeginDisabled(m_isApplyingChanges == false);
                  ImGui::PushID("##orpg_animations_timings_apply");
                  if (ImGui::Button("✓", ImVec2{50, 0})) {
                    m_selectedAnimation->setTiming(m_selectedTiming, getTiming(m_selectedFrameNumber, m_selectedAudio, m_selectedColor, static_cast<FlashScope>(m_selectedScope),
           m_selectedDuration)); m_isApplyingChanges = false;
                  }
                  ImGui::PopID();
                  ImGui::EndDisabled();
                }

                ImGui::EndChild();
                ImGui::SameLine();
                ImGui::SetCursorPosY(ImGui::GetCursorPos().y + 8.f);
                ImGui::BeginChild("##orpg_animations_animation_panel_middleright", ImVec2{ImGui::GetContentRegionMax().x, 0});
                {
                  ImGui::BeginGroup();
                  {
                    ImGui::BeginGroup();
                    {
                      ImGui::Text(trNOOP("Frame:"));
                      ImGui::EndGroup();
                    }
                    ImGui::BeginGroup();
                    {
                      ImGui::SetCursorPosY(ImGui::GetCursorPos().y + 3.f);
                      ImGui::Text(std::string("\t" + std::to_string(m_frameCursor)).c_str());
                      ImGui::EndGroup();
                    }
                    ImGui::EndGroup();
                  }
                  ImGui::SameLine();
                  ImGui::BeginGroup();
                  {
                    ImGui::Text(trNOOP("Sound Effect:"));
                    ImGui::PushID("##orpg_animations_timings_soundeffect");
                    if (ImGui::Button(m_selectedAudio.name().c_str(), ImVec2{200, 0})) {
                      m_soundPicker.show(SEType::SE);
                    }
                    ImGui::PopID();
                    ImGui::EndGroup();
                  }
                  ImGui::BeginGroup();
                  {
                    ImGui::SeparatorText("Flash Type");
                    if (ImGui::RadioButton(trNOOP("None"), m_isApplyingChanges ? m_selectedScope == 0 : m_frameType == 0)) {

                      m_selectedScope = 0;
                      m_frameType = 0;
                      if (m_isApplyingChanges) {
                      } else {
                      }
                    }
                    ImGui::SameLine();
                    if (ImGui::RadioButton(trNOOP("Target"), m_isApplyingChanges ? m_selectedScope == 1 : m_frameType == 1)) {

                      m_selectedScope = 1;
                      m_frameType = 1;
                      if (m_isApplyingChanges) {
                      } else {
                      }
                    }
                    ImGui::SameLine();
                    if (ImGui::RadioButton(trNOOP("Screen"), m_isApplyingChanges ? m_selectedScope == 2 : m_frameType == 2)) {

                      m_selectedScope = 2;
                      m_frameType = 2;
                      if (m_isApplyingChanges) {
                      } else {
                      }
                    }
                    ImGui::SameLine();
                    if (ImGui::RadioButton(trNOOP("Hide Target"), m_isApplyingChanges ? m_selectedScope == 3 : m_frameType == 3)) {

                      m_selectedScope = 3;
                      m_frameType = 3;
                      if (m_isApplyingChanges) {
                      } else {
                      }
                    }

                    ImGui::EndGroup(); // End Group: Flash
                  }
                  ImGui::BeginDisabled((m_frameType == static_cast<int>(FlashScope::None) || m_frameType == static_cast<int>(FlashScope::Hide_Target)) && m_isApplyingChanges == false);

                  // Color Picker
                  m_colorPicker.draw();
                  ImGui::SeparatorText(trNOOP("Duration"));
                  ImGui::SetNextItemWidth(100);
                  if (ImGui::InputInt("##orpg_animations_timings_duration", m_isApplyingChanges ? &m_selectedDuration : &m_duration)) {

                    if (m_isApplyingChanges) {
                      m_selectedDuration = std::clamp(m_selectedDuration, 1, 255);
                    } else {
                      m_duration = std::clamp(m_duration, 1, 255);
                    }
                  }
                  ImGui::SameLine();
                  ImGui::Text(trNOOP("frames 1/60 sec"));

                  ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 4.f);
                  ImGui::PushItemWidth(390);
                  if (ImGui::BeginCombo("##orpg_animations_timings_presets", "Future presets go in this list")) {
                    // TODO: Template system for tint screen
                    ImGui::EndCombo();
                  }
                  ImGui::EndDisabled();
                }
                ImGui::EndChild();
                */
      }
      ImGui::EndChild();
    }
    ImGui::EndChild();

    if (m_changeIntDialogOpen) {
      if (ImGui::Begin(trNOOP("Change Max Animations"), &m_changeIntDialogOpen,
                       ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_Modal | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDocking)) {
        ImGui::TextUnformatted(
            trNOOP("Specify an amount to resize the animation list to\n"
                   "This cannot be undone!"));
        ImGui::InputInt("##value_input", &m_editMaxAnimations);
        ImGui::SameLine();
        if (ImGui::Button(trNOOP("Accept"))) {
          m_changeConfirmDialogOpen = true;
        }
        ImGui::SameLine();
        if (ImGui::Button(trNOOP("Cancel"))) {
          m_changeIntDialogOpen = false;
        }
      }
      ImGui::End();

      if (m_changeConfirmDialogOpen) {
        if (ImGui::Begin(trNOOP("Confirm Change"), &m_changeConfirmDialogOpen,
                         ImGuiWindowFlags_NoResize | ImGuiWindowFlags_Modal | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDocking)) {
          ImGui::TextUnformatted(trNOOP("Are you sure?"));
          if (ImGui::Button(trNOOP("Yes"))) {
            const int tmpId = m_selectedAnimation->id();
            m_animations.resize(m_editMaxAnimations);
            m_selectedAnimation = m_animations.animation(tmpId);
            m_changeIntDialogOpen = false;
            m_changeConfirmDialogOpen = false;
          }
          ImGui::SameLine();
          if (ImGui::Button(trNOOP("Cancel"))) {
            m_changeIntDialogOpen = false;
            m_changeConfirmDialogOpen = false;
          }
        }
        ImGui::End();
      }
    }
  }
}
Animation::Timing DBAnimationsTab::getTiming(int frame, Audio audio, Animation::Color color, FlashScope scope, int duration) {
  Animation::Timing m_timing;

  color.setR(m_colorPicker.r());
  color.setG(m_colorPicker.g());
  color.setB(m_colorPicker.b());
  color.setIntensity(m_colorPicker.intensity());

  m_timing.setFrame(frame);
  m_timing.setSe(audio);
  m_timing.setFlashColor(color);
  m_timing.setFlashScope(scope);
  m_timing.setFlashDuration(duration);
  return m_timing;
}
void DBAnimationsTab::resetSelection(Animation::Timing& timing) {
  timing.se()->setName("");
  timing.se()->setVolume(100);
  timing.se()->setPan(0);
  timing.se()->setPitch(100);

  timing.flashColor().setR(255);
  timing.flashColor().setG(255);
  timing.flashColor().setB(255);
  timing.flashColor().setIntensity(255);

  timing.setFlashScope(FlashScope::None);
  timing.setFlashDuration(5);
}


code:
#pragma once
#include "Core/CommonUI/ColorFlashPicker.hpp"
#include "Core/CommonUI/ImagePicker.hpp"
#include "Core/CommonUI/SoundPicker.hpp"
#include "Core/DatabaseEditor/IDBEditorTab.hpp"
#include "DBCommonEventsTab.hpp"
#include "Database/Animations.hpp"

class DatabaseEditor;
class Animations;
class DBAnimationsTab final : public IDBEditorTab {
public:
  DBAnimationsTab() = delete;
  explicit DBAnimationsTab(Animations& animations, DatabaseEditor* parent);
  void draw() override;

  [[nodiscard]] std::vector<Animation>& animations() { return m_animations.animations(); }
  [[nodiscard]] const std::vector<Animation>& animations() const { return m_animations.animations(); }
  [[nodiscard]] Animation* animation(int id) { return m_animations.animation(id); }
  [[nodiscard]] const Animation* animation(int id) const { return m_animations.animation(id); }
  std::vector<int>& getHeaders() override { return m_headers; }
  int getHeader(int index) override { return m_headers.at(index); }
  bool hasHeader() override { return !m_headers.empty(); }
  void setHeaderRange(int start, int end) override {
    m_categoryStart = start;
    m_categoryEnd = end;
  }
  std::string getName(int index) override { return m_animations.animation(index)->name(); }
  int getCount() override { return m_animations.count(); }

private:
  int m_categoryStart;
  int m_categoryEnd;
  std::vector<int> m_headers;
  Animations& m_animations;
  Animation* m_selectedAnimation{};
  int m_editMaxAnimations{};
  float m_splitterWidth = 300.f;
  bool m_changeIntDialogOpen = false;
  bool m_changeConfirmDialogOpen = false;

  int m_frameCursor{1};
  int m_timingSE{0};
  int m_frameType{0};
  int m_selectedTiming{0};

  bool m_isApplyingChanges{false};

  std::vector<Animation::Timing> m_selectedTimings;

  int m_duration{5};
  Audio m_selectedAudio;
  Animation::Color m_selectedColor;
  int m_selectedScope;
  int m_selectedDuration;
  int m_selectedFrameNumber;

  std::optional<ImagePicker> m_imagePicker;
  ColorFlashPicker m_colorPicker;
  int m_pickerSelection;
  SoundPicker m_soundPicker;

  void onNameModified(Audio*, const std::string_view name) { m_selectedAudio.setName(name.data()); }
  void onVolModified(Audio*, const int volume) { m_selectedAudio.setVolume(volume); }
  void onPanModified(Audio*, const int pan) { m_selectedAudio.setPan(pan); }
  void onPitchModified(Audio*, const int pitch) { m_selectedAudio.setPitch(pitch); }
  void resetSelection(Animation::Timing& timing);

  Animation::Timing getTiming(int frame, Audio audio, Animation::Color color, FlashScope scope, int duration);
};

repo_name: Antidote/openrpgeditor

license: mit


code:
#include "Core/DatabaseEditor/DBArmorsTab.hpp"
#include "Core/Application.hpp"
#include "Database/Armors.hpp"

#include "Core/ImGuiExt/ImGuiUtils.hpp"
#include "imgui.h"
#include "imgui_internal.h"

DBArmorsTab::DBArmorsTab(Armors& Armors, DatabaseEditor* parent)
: IDBEditorTab(parent)
, m_armors(Armors) {
  m_selectedArmor = m_armors.armor(1);
  if (m_selectedArmor) {
    m_traitsEditor.setTraits(&m_selectedArmor->traits());
  }
}

void DBArmorsTab::draw() {
  if (!m_itemButtonTexture) {
    m_itemButtonTexture.emplace();
    m_itemButtonTexture->setSize(38, 38);
    if (!m_itemSheet) {
      m_itemSheet.emplace(*m_parent->getIconSheet());
    }
    const auto& [uv0, uv1] = m_itemSheet.value().rectForId(m_selectedArmor->iconIndex());
    const Point offset{static_cast<int>(uv0.x() * m_itemSheet.value().texture().width()), static_cast<int>(uv0.y() * m_itemSheet.value().texture().height())};
    m_itemButtonTexture->setTexturesToComposite({{m_itemSheet.value().texture(), {m_itemSheet.value().iconWidth(), m_itemSheet.value().iconHeight()}, offset}});
  }
  ImGui::BeginChild("#orpg_armors_editor");
  {
    const auto calc = ImGui::CalcTextSize("ABCDEFGHIJKLMNOPQRSTUV");
    ImGui::BeginChild("##orpg_armors_editor_armors", ImVec2{calc.x + (ImGui::GetStyle().ItemSpacing.x * 2), 0}, 0, ImGuiWindowFlags_NoScrollbar);
    {
      ImGui::BeginGroup();
      {
        ImGui::SeparatorText("Armors");
        ImGui::BeginChild("##orpg_armors_editor_armors_list", ImVec2{0, ImGui::GetContentRegionMax().y - ((calc.y + ImGui::GetStyle().ItemSpacing.y) * 4)});
        {
          ImGui::BeginGroup();
          {
            for (auto& armor : m_armors.armors()) {
              if (armor.id() == 0) {
                continue;
              }

              char name[4096];
              snprintf(name, 4096, "%04i %s", armor.id(), armor.name().c_str());
              if (ImGui::Selectable(name, &armor == m_selectedArmor) || (ImGui::IsItemFocused() && m_selectedArmor != &armor)) {

                m_selectedArmor = &armor;
                m_traitsEditor.setTraits(&m_selectedArmor->traits());
              }
            }
          }
          ImGui::EndGroup();
        }
        ImGui::EndChild();
        char str[4096];
        snprintf(str, 4096, "Max Armors %i", m_armors.count());
        ImGui::SeparatorText(str);
        if (ImGui::Button("Change Max", ImVec2{ImGui::GetContentRegionMax().x - (8), 0})) {
          m_changeIntDialogOpen = true;
          m_editMaxArmors = m_armors.count();
        }
      }
      ImGui::EndGroup();
    }
    ImGui::EndChild();
    ImGui::SameLine();
    ImGui::BeginChild("##orpg_armors_editor_armors_armor_properties");
    {
      if (m_selectedArmor) {
        ImGui::BeginChild("##orpg_armors_armor_panel_left", ImVec2{ImGui::GetContentRegionMax().x / 2, 0.f});
        {
          ImGui::BeginGroup();
          {
            ImGui::SeparatorText("General Settings");
            // Name
            ImGui::BeginGroup();
            {
              char name[4096];
              strncpy(name, m_selectedArmor->name().c_str(), 4096);
              if (ImGui::LabelOverLineEdit("##orpg_armors_editor_name", "Name:", name, 4096, ImGui::GetContentRegionMax().x / 2 - 16)) {
                m_selectedArmor->setName(name);
              }
            }
            ImGui::EndGroup();
            ImGui::SameLine();
            // Icon
            ImGui::SetCursorPosY(ImGui::GetCursorPosY() - 6.f);
            ImGui::BeginGroup();
            {
              ImGui::TextUnformatted("Icon:");
              if (ImGui::ImageButtonEx(ImGui::GetID("##orpg_database_armors_image"), m_itemButtonTexture->get(), ImVec2(m_itemButtonTexture->size()), {0.f, 0.f}, {1.f, 1.f}, {}, {1.f, 1.f, 1.f, 1.f},
                                       ImGuiButtonFlags_PressedOnDoubleClick)) {
                m_currentSheet = &m_itemSheet.value();
                // m_characterPicker->setCharacterInfo("", m_selectedItem->iconIndex);
                // m_characterPicker->setOpen(true);
              }
            }
            ImGui::EndGroup();
            // Description
            ImGui::BeginGroup();
            {
              char description[4096];
              strncpy(description, m_selectedArmor->description().c_str(), 4096);
              ImGui::Text("Description:");
              ImGui::InputTextMultiline("##orpg_database_armors_description", description, 4096, ImVec2{360, 60});
            }
            ImGui::EndGroup();
            // Weapon Type
            ImGui::BeginGroup();
            {
              ImGui::Text("Armor Type:");
              ImGui::SetNextItemWidth(170);
              if (ImGui::BeginCombo("##orpg_database_armors_atype", Database::instance()->system.armorType(m_selectedArmor->atypeId()).c_str())) {
                int index{0};
                for (auto v : Database::instance()->system.armorTypes()) {
                  bool selected = index == m_selectedArmor->atypeId();
                  if (index == 0) {
                    if (ImGui::Selectable(std::format("None##_{}", index).c_str(), selected)) {
                      m_selectedArmor->setAtypeId(index);
                    }
                  } else {
                    if (ImGui::Selectable(Database::instance()->system.armorType(index) == "" ? std::format("#{:02}", index).c_str() : std::format("{}##_{}", v, index).c_str(), selected)) {
                      m_selectedArmor->setAtypeId(index);
                    }
                  }
                  index++;
                }
                ImGui::EndCombo();
              }
            }
            ImGui::EndGroup();
            ImGui::SameLine();
            ImGui::SetCursorPosY(ImGui::GetCursorPosY() - 6.f);
            // Price
            ImGui::BeginGroup();
            {
              ImGui::Text("Price:");
              ImGui::SetNextItemWidth(170);
              int price = m_selectedArmor->price();
              if (ImGui::InputInt("##orpg_database_armors_price", &price, 1, 100)) {
                m_selectedArmor->setPrice(std::clamp(price, 0, 999999));
              }
            }
            ImGui::EndGroup();
            ImGui::BeginGroup();
            {
              ImGui::Text("Equipment Type:");
              // ImGui::SetNextItemWidth(160);
              ImGui::PushID("##orpg_database_armors_equipmenttype");
              // TODO: Equipment Type combobox
              ImGui::PopID();
            }
            ImGui::EndGroup();
          }
          ImGui::EndGroup();
          ImGui::BeginGroup();
          {
            ImGui::EndGroup();
            ImGui::SeparatorText("Parameter Changes");
            ImGui::BeginGroup();
            {
              ImGui::Text("Attack:");
              ImGui::SetNextItemWidth(170);
              int param = m_selectedArmor->param(0);
              if (ImGui::InputInt("##orpg_database_armor_attack", &param, 1, 100)) {
                m_selectedArmor->setParam(0, std::clamp(param, -999, 999));
              }
            }
            ImGui::EndGroup();
            ImGui::SameLine();
            ImGui::SetCursorPosY(ImGui::GetCursorPosY() - 6.f);
            ImGui::BeginGroup();
            {
              ImGui::Text("Defense:");
              ImGui::SetNextItemWidth(170);
              int param = m_selectedArmor->param(1);
              if (ImGui::InputInt("##orpg_database_armor_defense", &param, 1, 100)) {
                m_selectedArmor->setParam(1, std::clamp(param, -999, 999));
              }
            }
            ImGui::EndGroup();
            ImGui::BeginGroup();
            {
              ImGui::Text("M.Attack:");
              ImGui::SetNextItemWidth(170);
              int param = m_selectedArmor->param(2);
              if (ImGui::InputInt("##orpg_database_armor_mattack", &param, 1, 100)) {
                m_selectedArmor->setParam(2, std::clamp(param, -999, 999));
              }
            }
            ImGui::EndGroup();
            ImGui::SameLine();
            ImGui::SetCursorPosY(ImGui::GetCursorPosY() - 6.f);
            ImGui::BeginGroup();
            {
              ImGui::Text("M.Defense:");
              ImGui::SetNextItemWidth(170);
              int param = m_selectedArmor->param(3);
              if (ImGui::InputInt("##orpg_database_armor_mdefense", &param, 1, 100)) {
                m_selectedArmor->setParam(3, std::clamp(param, -999, 999));
              }
            }
            ImGui::EndGroup();
            ImGui::BeginGroup();
            {
              ImGui::Text("Agility:");
              ImGui::SetNextItemWidth(170);
              int param = m_selectedArmor->param(4);
              if (ImGui::InputInt("##orpg_database_armor_agility", &param, 1, 100)) {
                m_selectedArmor->setParam(4, std::clamp(param, -999, 999));
              }
            }
            ImGui::EndGroup();
            ImGui::SameLine();
            ImGui::SetCursorPosY(ImGui::GetCursorPosY() - 6.f);
            ImGui::BeginGroup();
            {
              ImGui::Text("Luck:");
              ImGui::SetNextItemWidth(170);
              int param = m_selectedArmor->param(5);
              if (ImGui::InputInt("##orpg_database_armor_luck", &param, 1, 100)) {
                m_selectedArmor->setParam(5, std::clamp(param, -999, 999));
              }
            }
            ImGui::EndGroup();
            ImGui::BeginGroup();
            {
              ImGui::Text("Max HP:");
              ImGui::SetNextItemWidth(170);
              int param = m_selectedArmor->param(6);
              if (ImGui::InputInt("##orpg_database_armor_maxhp", &param, 1, 100)) {
                m_selectedArmor->setParam(6, std::clamp(param, -9999, 9999));
              }
            }
            ImGui::EndGroup();
            ImGui::SameLine();
            ImGui::SetCursorPosY(ImGui::GetCursorPosY() - 6.f);
            ImGui::BeginGroup();
            {
              ImGui::Text("Max MP:");
              ImGui::SetNextItemWidth(170);
              int param = m_selectedArmor->param(7);
              if (ImGui::InputInt("##orpg_database_armor_maxmp", &param, 1, 100)) {
                m_selectedArmor->setParam(7, std::clamp(param, -9999, 9999));
              }
            }
            ImGui::EndGroup();
          }
        }
        ImGui::EndChild();
        ImGui::SameLine();
        ImGui::BeginChild("##orpg_armors_armor_panel_right");
        { m_traitsEditor.draw(m_parent); }
        ImGui::EndChild();
        ImGui::BeginChild("##orpg_armors_armor_panel_bottomright");
        {
          ImGui::SeparatorText("Note:");
          char note[8192];
          strncpy(note, m_selectedArmor->note().c_str(), IM_ARRAYSIZE(note));
          if (ImGui::InputTextMultiline(std::format("##orpg_database_armor_note{}", m_selectedArmor->id()).c_str(), note, IM_ARRAYSIZE(note), ImVec2{ImGui::GetContentRegionMax().x - 16, 400})) {
            m_selectedArmor->setNote(note);
          }
        }
        ImGui::EndChild();
      }
      ImGui::EndChild();
    }
    ImGui::EndChild();

    if (m_changeIntDialogOpen) {
      if (ImGui::Begin("Change Max Armors", &m_changeIntDialogOpen,
                       ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_Modal | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDocking)) {
        ImGui::Text(
            "Specify an amount to resize the armor list to\n"
            "This cannot be undone!");
        ImGui::InputInt("##value_input", &m_editMaxArmors);
        ImGui::SameLine();
        if (ImGui::Button("Accept")) {
          m_changeConfirmDialogOpen = true;
        }
        ImGui::SameLine();
        if (ImGui::Button("Cancel")) {
          m_changeIntDialogOpen = false;
        }
      }
      ImGui::End();

      if (m_changeConfirmDialogOpen) {
        if (ImGui::Begin("Confirm Change", &m_changeConfirmDialogOpen,
                         ImGuiWindowFlags_NoResize | ImGuiWindowFlags_Modal | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDocking)) {
          ImGui::Text("Are you sure?");
          if (ImGui::Button("Yes")) {
            const int tmpId = m_selectedArmor->id();
            m_armors.resize(m_editMaxArmors);
            m_selectedArmor = m_armors.armor(tmpId);
            m_changeIntDialogOpen = false;
            m_changeConfirmDialogOpen = false;
          }
          ImGui::SameLine();
          if (ImGui::Button("Cancel")) {
            m_changeIntDialogOpen = false;
            m_changeConfirmDialogOpen = false;
          }
        }
        ImGui::End();
      }
    }
  }
}


code:
#pragma once
#include "Core/CommonUI/TraitsEditor.hpp"
#include "Core/DatabaseEditor/IDBEditorTab.hpp"
#include "Core/Graphics/CheckeredCompositeTexture.hpp"
#include "Core/Graphics/IconSheet.hpp"
#include "DBCommonEventsTab.hpp"
#include "Database/Armors.hpp"

struct DBArmorsTab : IDBEditorTab {
  DBArmorsTab() = delete;
  explicit DBArmorsTab(Armors& armors, DatabaseEditor* parent);
  void draw() override;

  [[nodiscard]] std::vector<Armor>& armors() { return m_armors.armors(); }
  [[nodiscard]] const std::vector<Armor>& armors() const { return m_armors.armors(); }
  Armor* armor(int id) { return m_armors.armor(id); }
  const Armor* armor(int id) const { return m_armors.armor(id); }
  std::vector<int>& getHeaders() override { return m_headers; }
  int getHeader(int index) override { return m_headers.at(index); }
  bool hasHeader() override { return !m_headers.empty(); }
  void setHeaderRange(int start, int end) override {
    m_categoryStart = start;
    m_categoryEnd = end;
  }

  std::string getName(int index) override { return m_armors.armor(index)->name(); }
  int getCount() override { return m_armors.count(); }

private:
  int m_categoryStart;
  int m_categoryEnd;
  std::vector<int> m_headers;
  Armors& m_armors;
  Armor* m_selectedArmor{};
  int m_editMaxArmors{};
  float m_splitterWidth = 300.f;
  bool m_changeIntDialogOpen = false;
  bool m_changeConfirmDialogOpen = false;
  IconSheet* m_currentSheet = nullptr;
  std::optional<CheckeredCompositeTexture> m_itemButtonTexture;
  std::optional<IconSheet> m_itemSheet;
  std::optional<IconSheet> m_itemPicker;
  TraitsEditor m_traitsEditor;
};


code:
#include "Core/DatabaseEditor/DBClassesTab.hpp"

#include "Core/Application.hpp"

#include "Core/ImGuiExt/ImGuiParsedText.hpp"
#include "Core/ImGuiExt/ImGuiUtils.hpp"
#include "Database/Class.hpp"
#include "Database/RPGEquations.hpp"
#include "imgui_internal.h"
#include "implot.h"

#include <format>
#include <imgui.h>

constexpr ImVec2 ParameterGraphSize{92, 92};

DBClassesTab::DBClassesTab(Classes& classes, DatabaseEditor* parent)
: IDBEditorTab(parent)
, m_categoryStart(0)
, m_categoryEnd(0)
, m_classes(classes)
, m_expWorkValues() {
  m_selectedClass = m_classes.classType(1);
  if (m_selectedClass) {
    m_traitsEditor.setTraits(&m_selectedClass->traits());
  }
}

void DBClassesTab::drawExperienceGraph(const ExperienceGraphMode mode) const {
  if (!m_selectedClass) {
    return;
  }

  std::array<uint32_t, 99> levels;
  std::array<uint32_t, 99> totalExp;
  std::array<uint32_t, 99> nextExp;
  for (int i = 0; i < 99; ++i) {
    const int level = i + 1;
    levels[i] = level;
    totalExp[i] = rpg::equations::experienceForLevel(level, m_expWorkValues[0], m_expWorkValues[1], m_expWorkValues[2], m_expWorkValues[3]);
    if (level < 99) {
      nextExp[i] = rpg::equations::experienceForLevel(level + 1, m_expWorkValues[0], m_expWorkValues[1], m_expWorkValues[2], m_expWorkValues[3]) - totalExp[i];
    }
  }

  ImGui::BeginGroup();
  {
    const auto& array = mode == ExperienceGraphMode::Total ? totalExp : nextExp;
    const int maxLoops = (mode == ExperienceGraphMode::Total ? 19 : 18);
    constexpr std::string_view red = "&push-color=255,0,0;";
    constexpr std::string_view green = "&push-color=0,255,0;";
    const std::string_view color = mode == ExperienceGraphMode::Total ? red : green;
    ImGui::PushFont(App::APP->getMonoFont());
    for (int i = 0; i < 20; ++i) {
      std::string labelText = std::format("L{:2}: {}{:7}&pop-color; L{:2}: {}{:7}&pop-color; L{:2}: {}{:7}&pop-color; L{:2}: {}{:7}&pop-color;", levels[i + 0], color, array[i + 0], levels[i + 20],
                                          color, array[i + 20], levels[i + 40], color, array[i + 40], levels[i + 60], color, array[i + 60]);

      if (i < maxLoops) {
        labelText += std::format(" L{:2}: {}{:7}&pop-color;", levels[i + 80], color, array[i + 80]);
      }
      ImGui::TextParsed("%s", labelText.c_str());
    }
    ImGui::PopFont();
  }
  ImGui::EndGroup();

  if (ImPlot::BeginPlot("EXP Curve", ImVec2{-1, 0}, ImPlotFlags_CanvasOnly)) {
    ImPlot::SetupAxis(ImAxis_X1, nullptr, ImPlotAxisFlags_NoDecorations | ImPlotAxisFlags_AutoFit);
    ImPlot::SetupAxis(ImAxis_Y1, nullptr, ImPlotAxisFlags_NoDecorations);
    ImPlot::SetupAxesLimits(0, mode == ExperienceGraphMode::Next ? 98 : 99, 0, mode == ExperienceGraphMode::Next ? 180000 : 7800000, ImPlotCond_Always);
    if (mode == ExperienceGraphMode::Next) {
      ImPlot::PlotShaded("##Next", levels.data(), nextExp.data(), 98);
    } else {
      ImPlot::PlotShaded("##Total", levels.data(), totalExp.data(), 99);
    }
    ImPlot::EndPlot();
  }
}

void DBClassesTab::drawExpPopup() {
  ImGui::SetNextWindowSize(ImVec2{720, 878}, ImGuiCond_Once);
  if (ImGui::BeginPopupModal("EXP Curve##curve_dialog", nullptr,
                             ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_AlwaysAutoResize |
                                 ImGuiWindowFlags_NoScrollbar)) {
    if (ImGui::BeginTabBar("##exp_graph_tabbar")) {
      if (ImGui::BeginTabItem("To Next Level")) {
        drawExperienceGraph(ExperienceGraphMode::Next);
        ImGui::EndTabItem();
      }
      if (ImGui::BeginTabItem("Total")) {
        drawExperienceGraph(ExperienceGraphMode::Total);
        ImGui::EndTabItem();
      }
      ImGui::EndTabBar();
    }
    float cursorY = ImGui::GetCursorPosY();
    ImGui::BeginGroup();
    {
      ImGui::BeginGroup();
      {
        ImGui::Text("Base Value:");
        ImGui::SetNextItemWidth((ImGui::GetContentRegionMax().x / 2) - ImGui::GetStyle().FramePadding.x);
        ImGui::SliderInt("##exp_base_value_drag", &m_expWorkValues[0], 10, 50);
      }
      ImGui::EndGroup();
      ImGui::BeginGroup();
      {
        ImGui::Text("Acceleration A:");
        ImGui::SetNextItemWidth((ImGui::GetContentRegionMax().x / 2) - ImGui::GetStyle().FramePadding.x);
        ImGui::SliderInt("##exp_accel_a_value_drag", &m_expWorkValues[2], 10, 50);
      }
      ImGui::EndGroup();
    }
    ImGui::EndGroup();
    ImGui::SameLine();
    ImGui::SetCursorPosY(cursorY - 4);
    ImGui::BeginGroup();
    {
      ImGui::BeginGroup();
      {
        ImGui::Text("Extra Value:");
        ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - ImGui::GetStyle().FramePadding.x);
        ImGui::SliderInt("##exp_extra_value_drag", &m_expWorkValues[1], 0, 40);
      }
      ImGui::EndGroup();
      ImGui::BeginGroup();
      {
        ImGui::Text("Acceleration B:");
        ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - ImGui::GetStyle().FramePadding.x);
        ImGui::SliderInt("##exp_accel_b_value_drag", &m_expWorkValues[3], 10, 50);
      }
      ImGui::EndGroup();
    }
    ImGui::EndGroup();
    ImGui::Separator();
    ImGui::BeginGroup();
    {
      if (ImGui::Button("OK")) {
        m_selectedClass->setExpParams(m_expWorkValues);
        ImGui::CloseCurrentPopup();
      }
      ImGui::SameLine();
      if (ImGui::Button("Cancel")) {
        ImGui::CloseCurrentPopup();
      }
    }
    ImGui::EndGroup();
    ImGui::EndPopup();
  }
}
void DBClassesTab::draw() {
  ImGui::BeginChild("#orpg_classes_editor");
  {
    const auto calc = ImGui::CalcTextSize("ABCDEFGHIJKLMNOPQRSTUV");
    ImGui::BeginChild("##orpg_classes_editor_classes", ImVec2{calc.x + (ImGui::GetStyle().ItemSpacing.x * 2), 0}, ImGuiChildFlags_Border | ImGuiChildFlags_ResizeX, ImGuiWindowFlags_NoScrollbar);
    {
      ImGui::BeginGroup();
      {
        ImGui::SeparatorText("Classes");
        ImGui::BeginChild("##orpg_classes_editor_class_list", ImVec2{0, ImGui::GetContentRegionMax().y - ((calc.y + ImGui::GetStyle().ItemSpacing.y) * 4)});
        {
          ImGui::BeginGroup();
          {
            for (auto& class_ : m_classes.classes()) {
              if (class_.id() == 0) {
                continue;
              }

              if (ImGui::Selectable(Database::instance()->classNameAndId(class_.id()).c_str(), &class_ == m_selectedClass) || (ImGui::IsItemFocused() && m_selectedClass != &class_)) {

                m_selectedClass = &class_;
                m_traitsEditor.setTraits(&m_selectedClass->traits());
              }
            }
          }
          ImGui::EndGroup();
        }
        ImGui::EndChild();
        char str[4096];
        snprintf(str, 4096, "Max Classes %i", m_classes.count());
        ImGui::SeparatorText(str);
        if (ImGui::Button("Change Max", ImVec2{ImGui::GetContentRegionMax().x - (8), 0})) {
          m_changeIntDialogOpen = true;
          m_editMaxClasses = m_classes.count();
        }
      }
      ImGui::EndGroup();
    }
    ImGui::EndChild();
    ImGui::SameLine();
    ImGui::BeginChild("##orpg_classes_editor_classes_class_properties");
    {
      if (m_selectedClass) {
        ImGui::BeginChild("##orpg_classes_class_panel_left", ImVec2{ImGui::GetContentRegionMax().x / 2, 0.f}, ImGuiChildFlags_Border | ImGuiChildFlags_ResizeX, ImGuiWindowFlags_HorizontalScrollbar);
        {
          ImGui::BeginGroup();
          {
            ImGui::SeparatorText("General Settings");
            char name[4096];
            strncpy(name, m_selectedClass->name().c_str(), 4096);
            if (ImGui::LabelOverLineEdit("##orpg_classes_editor_actors_actor_name", "Name:", name, 4096, (ImGui::GetContentRegionMax().x / 2) - 16)) {
              m_selectedClass->setName(name);
            }
            ImGui::SameLine();
            ImGui::BeginGroup();
            {
              ImGui::Text("EXP Curve:");
              char curvePreview[4096];
              snprintf(curvePreview, 4096, "[%i, %i, %i, %i]##exp_curve_button", m_selectedClass->expParams()[0], m_selectedClass->expParams()[1], m_selectedClass->expParams()[2],
                       m_selectedClass->expParams()[3]);
              if (ImGui::Button(curvePreview)) {
                ImGui::OpenPopup("EXP Curve##curve_dialog");
                m_expWorkValues = m_selectedClass->expParams();
              }
            }
            ImGui::EndGroup();
          }
          ImGui::EndGroup();
          ImGui::BeginChild("##orpg_classes_class_parameter_curves", ImVec2(), ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysAutoResize);
          {
            ImGui::SeparatorText("Parameter Curves");
            if (ImPlot::BeginPlot("Max HP", ImGui::GetDPIScaledSize(ParameterGraphSize), ImPlotFlags_NoLegend | ImPlotFlags_NoMenus | ImPlotFlags_NoBoxSelect | ImPlotFlags_NoMouseText)) {
              ImPlot::SetNextFillStyle(ImVec4{.81f, .5f, .37f, 1.f});
              ImPlot::SetupAxis(ImAxis_X1, nullptr, ImPlotAxisFlags_NoDecorations | ImPlotAxisFlags_AutoFit);
              ImPlot::SetupAxis(ImAxis_Y1, nullptr, ImPlotAxisFlags_NoDecorations);
              ImPlot::SetupAxesLimits(0.f, 99.f, 0.f, 9999.f, ImPlotCond_Always);
              if (ImGui::IsItemClicked() && ImGui::GetMouseClickedCount(ImGuiMouseButton_Left) >= 2) {}
              ImPlot::PlotShaded("##MaxHP", m_selectedClass->params()[0].data(), m_selectedClass->params()[0].size());
              ImPlot::EndPlot();
            }
            ImGui::SameLine();
            if (ImPlot::BeginPlot("Max MP", ImGui::GetDPIScaledSize(ParameterGraphSize), ImPlotFlags_NoLegend | ImPlotFlags_NoMenus | ImPlotFlags_NoBoxSelect | ImPlotFlags_NoMouseText)) {

              ImPlot::SetNextFillStyle(ImVec4{.31f, .5f, .87f, 1.f});
              ImPlot::SetupAxis(ImAxis_X1, nullptr, ImPlotAxisFlags_NoDecorations | ImPlotAxisFlags_AutoFit);
              ImPlot::SetupAxis(ImAxis_Y1, nullptr, ImPlotAxisFlags_NoDecorations);
              ImPlot::SetupAxesLimits(0.f, 99.f, 0.f, 9999.f, ImPlotCond_Always);
              if (ImGui::IsItemClicked() && ImGui::GetMouseClickedCount(ImGuiMouseButton_Left) >= 2) {}
              ImPlot::PlotShaded("##MaxMP", m_selectedClass->params()[1].data(), m_selectedClass->params()[1].size());
              ImPlot::EndPlot();
            }
            ImGui::SameLine();
            if (ImPlot::BeginPlot("Attack", ImGui::GetDPIScaledSize(ParameterGraphSize), ImPlotFlags_NoLegend | ImPlotFlags_NoMenus | ImPlotFlags_NoBoxSelect | ImPlotFlags_NoMouseText)) {
              ImPlot::SetNextFillStyle(ImVec4{.75f, .25f, .37f, 1.f});
              ImPlot::SetupAxis(ImAxis_X1, nullptr, ImPlotAxisFlags_NoDecorations | ImPlotAxisFlags_AutoFit);
              ImPlot::SetupAxis(ImAxis_Y1, nullptr, ImPlotAxisFlags_NoDecorations);
              ImPlot::SetupAxesLimits(0.f, 99.f, 0.f, 999.f, ImPlotCond_Always);
              if (ImGui::IsItemClicked() && ImGui::GetMouseClickedCount(ImGuiMouseButton_Left) >= 2) {}
              ImPlot::PlotShaded("##Attack", m_selectedClass->params()[2].data(), m_selectedClass->params()[2].size());
              ImPlot::EndPlot();
            }
            ImGui::SameLine();
            if (ImPlot::BeginPlot("Defense", ImGui::GetDPIScaledSize(ParameterGraphSize), ImPlotFlags_NoLegend | ImPlotFlags_NoMenus | ImPlotFlags_NoBoxSelect | ImPlotFlags_NoMouseText)) {
              ImPlot::SetNextFillStyle(ImVec4{.56f, .69f, .37f, 1.f});
              ImPlot::SetupAxis(ImAxis_X1, nullptr, ImPlotAxisFlags_NoDecorations | ImPlotAxisFlags_AutoFit);
              ImPlot::SetupAxis(ImAxis_Y1, nullptr, ImPlotAxisFlags_NoDecorations);
              ImPlot::SetupAxesLimits(0.f, 99.f, 0.f, 999.f, ImPlotCond_Always);
              if (ImGui::IsItemClicked() && ImGui::GetMouseClickedCount(ImGuiMouseButton_Left) >= 2) {}
              ImPlot::PlotShaded("##Defense", m_selectedClass->params()[3].data(), m_selectedClass->params()[3].size());
              ImPlot::EndPlot();
            }
            if (ImPlot::BeginPlot("M.Attack", ImGui::GetDPIScaledSize(ParameterGraphSize), ImPlotFlags_NoLegend | ImPlotFlags_NoMenus | ImPlotFlags_NoBoxSelect | ImPlotFlags_NoMouseText)) {
              ImPlot::SetNextFillStyle(ImVec4{.75f, .31f, .69f, 1.f});
              ImPlot::SetupAxis(ImAxis_X1, nullptr, ImPlotAxisFlags_NoDecorations | ImPlotAxisFlags_AutoFit);
              ImPlot::SetupAxis(ImAxis_Y1, nullptr, ImPlotAxisFlags_NoDecorations);
              ImPlot::SetupAxesLimits(0.f, 99.f, 0.f, 999.f, ImPlotCond_Always);
              if (ImGui::IsItemClicked() && ImGui::GetMouseClickedCount(ImGuiMouseButton_Left) >= 2) {}
              ImPlot::PlotShaded("##M.Attack", m_selectedClass->params()[4].data(), m_selectedClass->params()[4].size());
              ImPlot::EndPlot();
            }
            ImGui::SameLine();
            if (ImPlot::BeginPlot("M.Defense", ImGui::GetDPIScaledSize(ParameterGraphSize), ImPlotFlags_NoLegend | ImPlotFlags_NoMenus | ImPlotFlags_NoBoxSelect | ImPlotFlags_NoMouseText)) {
              ImPlot::SetNextFillStyle(ImVec4{.25f, .62f, .25f, 1.f});
              ImPlot::SetupAxis(ImAxis_X1, nullptr, ImPlotAxisFlags_NoDecorations | ImPlotAxisFlags_AutoFit);
              ImPlot::SetupAxis(ImAxis_Y1, nullptr, ImPlotAxisFlags_NoDecorations);
              ImPlot::SetupAxesLimits(0.f, 99.f, 0.f, 999.f, ImPlotCond_Always);
              if (ImGui::IsItemClicked() && ImGui::GetMouseClickedCount(ImGuiMouseButton_Left) >= 2) {}
              ImPlot::PlotShaded("##M.Defense", m_selectedClass->params()[5].data(), m_selectedClass->params()[5].size());
              ImPlot::EndPlot();
            }
            ImGui::SameLine();
            if (ImPlot::BeginPlot("Agility", ImGui::GetDPIScaledSize(ParameterGraphSize), ImPlotFlags_NoLegend | ImPlotFlags_NoMenus | ImPlotFlags_NoBoxSelect | ImPlotFlags_NoMouseText)) {
              ImPlot::SetNextFillStyle(ImVec4{.31f, .69f, .87f, 1.f});
              ImPlot::SetupAxis(ImAxis_X1, nullptr, ImPlotAxisFlags_NoDecorations | ImPlotAxisFlags_AutoFit);
              ImPlot::SetupAxis(ImAxis_Y1, nullptr, ImPlotAxisFlags_NoDecorations);
              ImPlot::SetupAxesLimits(0.f, 99.f, 0.f, 999.f, ImPlotCond_Always);
              if (ImGui::IsItemClicked() && ImGui::GetMouseClickedCount(ImGuiMouseButton_Left) >= 2) {}
              ImPlot::PlotShaded("##Agility", m_selectedClass->params()[6].data(), m_selectedClass->params()[6].size());
              ImPlot::EndPlot();
            }
            ImGui::SameLine();
            if (ImPlot::BeginPlot("Luck", ImGui::GetDPIScaledSize(ParameterGraphSize), ImPlotFlags_NoLegend | ImPlotFlags_NoMenus | ImPlotFlags_NoBoxSelect | ImPlotFlags_NoMouseText)) {
              ImPlot::SetNextFillStyle(ImVec4{.81f, .69f, .25f, 1.f});
              ImPlot::SetupAxis(ImAxis_X1, nullptr, ImPlotAxisFlags_NoDecorations | ImPlotAxisFlags_AutoFit);
              ImPlot::SetupAxis(ImAxis_Y1, nullptr, ImPlotAxisFlags_NoDecorations);
              ImPlot::SetupAxesLimits(0.f, 99.f, 0.f, 999.f, ImPlotCond_Always);
              if (ImGui::IsItemClicked() && ImGui::GetMouseClickedCount(ImGuiMouseButton_Left) >= 2) {}
              ImPlot::PlotShaded("##Luck", m_selectedClass->params()[7].data(), m_selectedClass->params()[7].size());
              ImPlot::EndPlot();
            }
          }
          ImGui::EndChild();
          ImGui::BeginGroup();
          {
            ImGui::SeparatorText("Skills to Learn");
            if (ImGui::BeginTable("##orpg_class_skills_list", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_ScrollY)) {
              ImGui::TableSetupColumn("Level");
              ImGui::TableSetupColumn("Skill");
              ImGui::TableSetupColumn("Note");
              ImGui::TableHeadersRow();

              for (int i = 0; auto& learning : m_selectedClass->learnings()) {
                const auto skill = Database::instance()->skills.skill(learning.skillId());
                if (!skill) {
                  continue;
                }
                ImGui::TableNextRow();
                ImGui::TableNextColumn();
                if (ImGui::Selectable(std::format("Lv {:3}##{}", learning.level(), i).c_str(), false, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowDoubleClick)) {
                  if (ImGui::GetMouseClickedCount(ImGuiMouseButton_Left) >= 2) {
                    // open learning skill modal
                  }
                }
                ImGui::TableNextColumn();
                ImGui::Text("%s", skill->name().c_str());
                ImGui::TableNextColumn();
                ImGui::Text("%s", learning.note().c_str());
                ++i;
              }

              ImGui::TableNextRow();
              if (ImGui::TableNextColumn()) {
                if (ImGui::Selectable("##classes_editor_learning_dummy", false, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowDoubleClick)) {
                  if (ImGui::GetMouseClickedCount(ImGuiMouseButton_Left) >= 2) {
                    // m_tempTrait = Trait();
                    // m_isNewEntry = true;
                    // m_updateTab = true;
                    // m_tempTraitAssigned = false;
                    // m_selectedTrait = &m_tempTrait;
                    // ImGui::OpenPopup(TraitsEditorPopupId.data());
                  }
                }
              }
              ImGui::EndTable();
            }
          }
          ImGui::EndGroup();
        }
        drawExpPopup();
        ImGui::EndChild();
        ImGui::SameLine();
        ImGui::BeginChild("##orpg_classes_class_panel_right");
        {
          ImGui::BeginChild("##traits_editor", {0, ImGui::GetContentRegionAvail().y / 2}, ImGuiChildFlags_ResizeY);
          m_traitsEditor.draw(m_parent);
          ImGui::EndChild();
          ImGui::BeginChild("##note");
          {
            ImGui::SeparatorText("Note:");
            char note[8192];
            strncpy(note, m_selectedClass->note().c_str(), IM_ARRAYSIZE(note));
            if (ImGui::InputTextMultiline("##orpg_classes_note", note, IM_ARRAYSIZE(note), ImVec2{ImGui::GetContentRegionMax().x - 16, ImGui::GetContentRegionAvail().y - 16})) {
              m_selectedClass->setNote(note);
            }
          }
          ImGui::EndChild();
        }
        ImGui::EndChild();
      }
    }
    ImGui::EndChild();
  }
  ImGui::EndChild();

  if (m_changeIntDialogOpen) {
    if (ImGui::Begin("Change Max Classes", &m_changeIntDialogOpen,
                     ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_Modal | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDocking)) {
      ImGui::Text(
          "Specify an amount to resize the class list to\n"
          "This cannot be undone!");
      ImGui::InputInt("##value_input", &m_editMaxClasses);
      ImGui::SameLine();
      if (ImGui::Button("Accept")) {
        m_changeConfirmDialogOpen = true;
      }
      ImGui::SameLine();
      if (ImGui::Button("Cancel")) {
        m_changeIntDialogOpen = false;
      }
    }
    ImGui::End();

    if (m_changeConfirmDialogOpen) {
      if (ImGui::Begin("Confirm Change", &m_changeConfirmDialogOpen,
                       ImGuiWindowFlags_NoResize | ImGuiWindowFlags_Modal | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDocking)) {
        ImGui::Text("Are you sure?");
        if (ImGui::Button("Yes")) {
          const int tmpId = m_selectedClass->id();
          m_classes.resize(m_editMaxClasses);
          m_selectedClass = m_classes.classType(tmpId);
          m_changeIntDialogOpen = false;
          m_changeConfirmDialogOpen = false;
        }
        ImGui::SameLine();
        if (ImGui::Button("Cancel")) {
          m_changeIntDialogOpen = false;
          m_changeConfirmDialogOpen = false;
        }
      }
      ImGui::End();
    }
  }
}


code:
#pragma once
#include "Core/CommonUI/TraitsEditor.hpp"
#include "Core/DatabaseEditor/IDBEditorTab.hpp"
#include "DBCommonEventsTab.hpp"

#include "Database/Classes.hpp"

#include <array>

struct DBClassesTab : IDBEditorTab {
  enum class ExperienceGraphMode {
    Total,
    Next,
  };

  DBClassesTab() = delete;
  explicit DBClassesTab(Classes& classes, DatabaseEditor* parent);
  void draw() override;

  Classes& classes() { return m_classes; }
  const Classes& classes() const { return m_classes; }

  Class* classType(int id) { return m_classes.classType(id); }
  const Class* classType(int id) const { return m_classes.classType(id); }
  std::vector<int>& getHeaders() override { return m_headers; }
  int getHeader(int index) override { return m_headers.at(index); }
  bool hasHeader() override { return !m_headers.empty(); }
  void setHeaderRange(int start, int end) override {
    m_categoryStart = start;
    m_categoryEnd = end;
  }
  std::string getName(int index) override { return m_classes.classType(index)->name(); }
  int getCount() override { return m_classes.count(); }

private:
  int m_categoryStart;
  int m_categoryEnd;
  std::vector<int> m_headers;
  void drawExperienceGraph(ExperienceGraphMode mode) const;
  void drawExpPopup();
  Classes& m_classes;
  Class* m_selectedClass{};
  int m_editMaxClasses{};
  float m_splitterWidth = 300.f;
  bool m_changeIntDialogOpen = false;
  bool m_changeConfirmDialogOpen = false;
  bool m_showEquipEdit = false;
  TraitsEditor m_traitsEditor;
  std::array<int, 4> m_expWorkValues;
};


code:
#include "Core/DatabaseEditor/DBCommonEventsTab.hpp"

#include "Core/Application.hpp"

#include "Core/DatabaseEditor.hpp"
#include "Core/ImGuiExt/ImGuiUtils.hpp"
#include "Core/Utils.hpp"

#include "Database/CommonEvents.hpp"

#include "imgui.h"

#include <string>

DBCommonEventsTab::DBCommonEventsTab(CommonEvents& commonEvents, DatabaseEditor* parent)
: IDBEditorTab(parent)
, m_events(commonEvents) {
  m_selectedCommonEvent = m_events.event(1);
  m_commandEditor.setCommands(&m_selectedCommonEvent->commands());

  for (const auto& cmn : m_events.events()) {
    if (cmn.has_value()) {
      if (hasUnicodeFormatting(cmn.value().name())) {
        m_headers.push_back(cmn.value().id());
      }
    }
  }
}

void DBCommonEventsTab::draw() {
  if (picker) {
    auto [closed, confirmed] = picker->draw();
    if (closed) {
      if (confirmed) {
        m_selectedCommonEvent->setSwitchId(picker->selection());
      }
      picker.reset();
    }
  }
  if (preview) {
    auto [closed, confirmed] = preview->draw();
    if (closed) {
      if (confirmed) {}
      preview.reset();
    }
  }

  ImGui::BeginChild("##orpg_commonevents_editor");
  {
    const auto calc = ImGui::CalcTextSize("ABCDEFGHIJKLMNOPQRSTUV");
    ImGui::BeginChild("##orpg_commonevents_editor_commonevents", ImVec2{calc.x + (ImGui::GetStyle().ItemSpacing.x * 2), 0}, 0, ImGuiWindowFlags_NoScrollbar);
    {
      ImGui::BeginGroup();
      {
        ImGui::SeparatorText("Common Events");
        ImGui::BeginChild("##orpg_commonevents_editor_commonevent_list", ImVec2{0, ImGui::GetContentRegionMax().y - ((calc.y + ImGui::GetStyle().ItemSpacing.y) * 4)});
        {
          ImGui::BeginGroup();
          {
            ImGuiListClipper clipper;
            int itemCount = m_events.count() + 1;
            // if (m_parent->isFilteredByCategory()) {
            //   // if (commonEvent.id() <= m_categoryStart || m_categoryEnd == -1 ? true : commonEvent.id() >= m_categoryEnd) {
            //   //   continue;
            //   // }
            // } else {
            //   std::string searchString = m_parent->getFilterString();
            //   if (!searchString.empty()) {
            //     if (commonEvent.name().contains(searchString)) {
            //       continue;
            //     }
            //   }
            // }

            if (m_parent->isFilteredByCategory()) {
              itemCount = (m_categoryEnd - m_categoryStart);
            } else {
              if (m_categoryStart > 0 || m_categoryEnd > 0) {
                m_categoryEnd = 0;
                m_categoryStart = 0;
              }
            }
            clipper.Begin(itemCount, 22.f);
            while (clipper.Step()) {

              for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) {
                if (m_parent->isFilteredByCategory()) {
                  if (i + m_categoryStart > m_categoryEnd || i + m_categoryStart > m_events.count()) {
                    continue;
                  }
                }
                auto& event = m_events.events()[i + m_categoryStart];

                if (!event) {
                  continue;
                }
                CommonEvent& commonEvent = event.value();

                std::string id = "##orpg_commonevent_editor_unnamed_commonevent_" + std::to_string(commonEvent.id());
                ImGui::PushID(id.c_str());

                bool isFormatted{false};
                if (hasUnicodeFormatting(commonEvent.name())) {
                  auto col = ImGui::GetStyleColorVec4(ImGuiCol_Text);
                  ImGui::PushStyleColor(ImGuiCol_Text, ImColor(1.f - col.x, 1.f - col.y, 1.f - col.z, col.w).Value);
                  col = ImGui::GetStyleColorVec4(ImGuiCol_Header);
                  ImGui::PushStyleColor(ImGuiCol_Header, ImColor(1.f - col.x, 1.f - col.y, 1.f - col.z, col.w * .75f).Value);
                  col = ImGui::GetStyleColorVec4(ImGuiCol_HeaderHovered);
                  ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImColor(1.f - col.x, 1.f - col.y, 1.f - col.z, col.w * .75f).Value);
                  col = ImGui::GetStyleColorVec4(ImGuiCol_HeaderActive);
                  ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImColor(1.f - col.x, 1.f - col.y, 1.f - col.z, col.w * .75f).Value);
                  isFormatted = true;
                }
                if (ImGui::Selectable(Database::instance()->commonEventNameAndId(commonEvent.id()).c_str(), &commonEvent == m_selectedCommonEvent || isFormatted) ||
                    (ImGui::IsItemFocused() && m_selectedCommonEvent != &commonEvent)) {
                  m_selectedCommonEvent = &commonEvent;
                  m_commandEditor.setCommands(&m_selectedCommonEvent->commands());
                }
                if (isFormatted) {
                  ImGui::PopStyleColor(4);
                }

                ImGui::PopID();
              }
            }
          }
          ImGui::EndGroup();
        }
        ImGui::EndChild();

        char str[4096];
        snprintf(str, 4096, "Max Common Events %i", m_events.count());
        ImGui::SeparatorText(str);
        if (ImGui::Button("Change Max", ImVec2{ImGui::GetContentRegionMax().x - 8, 0})) {
          m_changeIntDialogOpen = true;
        }
      }
      ImGui::EndGroup();
    }
    ImGui::EndChild();
    ImGui::SameLine();
    ImGui::BeginChild("##orpg_commonevents_editor_commonevents_commonevent_properties");
    {
      if (m_selectedCommonEvent) {
        ImGui::BeginChild("##orpg_commonevents_commonevent_panel", ImVec2{ImGui::GetContentRegionMax().x, 0.f});
        {
          ImGui::BeginGroup();
          {
            ImGui::SeparatorText("General Settings");
            char name[4096];
            strncpy(name, m_selectedCommonEvent->name().c_str(), 4096);
            if (ImGui::LabelOverLineEdit("##orpg_commonevents_editor_commonevents_commonevent_name", "Name:", name, 4096, ImGui::GetContentRegionMax().x / 2 / 2 - 16, nullptr,
                                         ImGuiInputTextFlags_None)) {
              m_selectedCommonEvent->setName(name);
            }
            ImGui::SameLine();
            ImGui::SetCursorPosY(ImGui::GetCursorPosY() - 4);
            ImGui::BeginGroup();
            {
              ImGui::Text("Trigger:");
              char buf[4096];
              strncpy(buf, DecodeEnumName(m_selectedCommonEvent->trigger()).c_str(), 4096);
              ImGui::SetNextItemWidth(ImGui::GetContentRegionMax().x / 2 / 2 - 16);
              if (ImGui::BeginCombo("##orpg_commonevents_editor_trigger_combo", buf)) {
                for (const auto& e : magic_enum::enum_values<CommonEventTriggerType>()) {
                  strncpy(buf, DecodeEnumName(e).c_str(), 4096);
                  if (ImGui::Selectable(buf, e == m_selectedCommonEvent->trigger())) {
                    m_selectedCommonEvent->setTrigger(e);
                  }
                }
                ImGui::EndCombo();
              }
            }
            ImGui::EndGroup();

            ImGui::SameLine();
            ImGui::SetCursorPosY(ImGui::GetCursorPosY() - 4);
            ImGui::BeginGroup();
            {
              ImGui::Text("Switch:");
              const bool isSwitchEnabled = m_selectedCommonEvent->trigger() == CommonEventTriggerType::None;
              const std::string text = isSwitchEnabled ? "##commonevent_switch_empty" : Database::instance()->system.switche(m_selectedCommonEvent->switchId());
              ImGui::PushID("##commonevent_button");
              ImGui::SetNextItemWidth(ImGui::GetContentRegionMax().x / 2 / 2 - 16);
              ImGui::BeginDisabled(isSwitchEnabled);
              if (ImGui::Button(text.c_str(), ImVec2{ImGui::GetWindowContentRegionMax().x / 2 / 2 - 15, 0})) {
                picker.emplace("Switches", Database::instance()->system.switches(), m_selectedCommonEvent->switchId());
                picker->setOpen(true);
              }
              ImGui::PopID();
              ImGui::EndDisabled();
            }
            ImGui::SameLine();
            // ImGui::SetCursorPosY(ImGui::GetCursorPosY());
            ImGui::BeginGroup();
            {
              if (ImGui::Button("\u25B6", ImVec2{50.f, 0})) {
                preview.emplace(m_selectedCommonEvent->id());
                preview->setOpen(true);
              }
            }
            ImGui::EndGroup();
            ImGui::EndGroup();
            m_commandEditor.draw();
          }
          ImGui::EndGroup();
        }
        ImGui::EndChild();
      }
    }
    ImGui::EndChild();
  }
  ImGui::EndChild();

  if (m_changeIntDialogOpen) {
    if (ImGui::Begin("Change Max Common Events", &m_changeIntDialogOpen,
                     ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_Modal | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDocking)) {
      ImGui::Text(
          "Specify an amount to resize the common event list to\n"
          "This cannot be undone!");
      ImGui::InputInt("##value_input", &m_editMaxCommonEvents);
      ImGui::SameLine();
      if (ImGui::Button("Accept")) {
        m_changeConfirmDialogOpen = true;
      }
      ImGui::SameLine();
      if (ImGui::Button("Cancel")) {
        m_changeIntDialogOpen = false;
      }
    }
    ImGui::End();

    ImGui::SetNextWindowPos(ImGui::GetWindowSize() / 2);
    if (m_changeConfirmDialogOpen) {
      if (ImGui::Begin("Confirm Change", &m_changeConfirmDialogOpen,
                       ImGuiWindowFlags_NoResize | ImGuiWindowFlags_Modal | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDocking)) {
        ImGui::Text("Are you sure?");
        if (ImGui::Button("Yes")) {
          const int tmpId = m_selectedCommonEvent->id();
          m_events.resize(m_editMaxCommonEvents);
          m_selectedCommonEvent = m_events.event(tmpId);
          m_commandEditor.setCommands(&m_selectedCommonEvent->commands());
          m_changeIntDialogOpen = false;
          m_changeConfirmDialogOpen = false;
        }
        ImGui::SameLine();
        if (ImGui::Button("Cancel")) {
          m_changeIntDialogOpen = false;
          m_changeConfirmDialogOpen = false;
        }
      }
      ImGui::End();
    }
  }
}


code:
#pragma once
#include "Core/CommonUI/EventCommandEditor.hpp"
#include "Core/CommonUI/Preview.hpp"
#include "Core/DatabaseEditor/IDBEditorTab.hpp"
#include "Database/CommonEvents.hpp"

struct DBCommonEventsTab : IDBEditorTab {
  DBCommonEventsTab() = delete;
  explicit DBCommonEventsTab(CommonEvents& commonEvents, DatabaseEditor* parent);
  void draw() override;

  [[nodiscard]] std::vector<std::optional<CommonEvent>>& events() { return m_events.events(); }
  [[nodiscard]] const std::vector<std::optional<CommonEvent>>& events() const { return m_events.events(); }

  [[nodiscard]] CommonEvent* event(int id) { return m_events.event(id); }
  [[nodiscard]] const CommonEvent* event(int id) const { return m_events.event(id); }
  std::vector<int>& getHeaders() override { return m_headers; }
  int getHeader(int index) override { return m_headers.at(index); }
  bool hasHeader() override { return !m_headers.empty(); }
  void setHeaderRange(int start, int end) override {
    m_categoryStart = start;
    m_categoryEnd = end;
  }
  std::string getName(int index) override { return m_events.event(index)->name(); }
  int getCount() override { return m_events.count(); }
  int getSelectedIndex() { return m_selectedCommonEvent ? m_selectedCommonEvent->id() : 0; }

private:
  CommonEvents& m_events;
  CommonEvent* m_selectedCommonEvent{};
  EventCommandEditor m_commandEditor;
  std::optional<VariableSwitchPicker> picker;
  std::optional<Preview> preview;

  std::vector<int> m_headers;
  int m_categoryStart{0};
  int m_categoryEnd{0};

  int m_editMaxCommonEvents{};
  float m_splitterWidth = 300.f;
  bool m_changeIntDialogOpen = false;
  bool m_changeConfirmDialogOpen = false;
};


code:
#include "Core/DatabaseEditor/DBDocTab.hpp"
#include "Core/Application.hpp"
#include "Core/DatabaseEditor.hpp"
#include "Core/ImGuiExt/ImGuiNotify.hpp"
#include "Core/ImGuiExt/ImGuiUtils.hpp"
#include "Core/MainWindow.hpp"
#include "imgui.h"
#include <cstring>
#include <misc/cpp/imgui_stdlib.h>
#include <numeric>
DBDocTab::DBDocTab(Docs& docs, DatabaseEditor* parent)
: IDBEditorTab(parent)
, m_docs(&docs) {
  if (m_docs->docs.size() == 0) {
    Doc doc;

    doc.setNames({"Example Name 1", "Example Name 2", "Example Name 3"});
    doc.setTexts({" Example Text 1", "Example Text 2", "Example Test 3"});

    m_docs->docs.emplace_back(doc);

    Save();
  }
  m_names = m_docs->docs.front().names();
  m_texts = m_docs->docs.front().texts();

  createHeaders();
}

void DBDocTab::draw() {
  const auto calc = ImGui::CalcTextSize("ABCDEFGHIJKLMNOPQRSTUV");
  ImGui::BeginChild("##orpg_docs_navigation", ImVec2{calc.x + (ImGui::GetStyle().ItemSpacing.x * 2), 0}, 0, ImGuiWindowFlags_NoScrollbar);
  {
    ImGui::BeginGroup();
    {
      ImGui::BeginChild("##orpg_docs_navigation_headers", ImVec2{calc.x + (ImGui::GetStyle().ItemSpacing.x * 2), ImGui::GetContentRegionMax().y - 35});
      {
        ImGui::BeginGroup();
        {
          if (ImGui::Button("+##orpg_docs_add_category", ImVec2(40, 0))) {
            m_selectedCategory = m_names.size();
            m_names.emplace_back(trNOOP("New Category"));
            m_texts.emplace_back(trNOOP("Begin adding text!"));
            m_docs->docs.front().setNames(m_names);
            m_docs->docs.front().setTexts(m_texts);
            Save();
            setDoc(m_selectedCategory);
          }
          if (ImGui::IsItemHovered()) {
            ImGui::BeginTooltip();
            {
              ImGui::TextUnformatted(trNOOP("Adds a new category."));
              ImGui::EndTooltip();
            }
          }
          ImGui::SameLine();
          ImGui::BeginDisabled(m_names.empty());
          if (ImGui::Button("-##orpg_docs_remove_category", ImVec2(40, 0))) {
            // int indexToRemove = m_selectedCategory;
            // if (m_selectedCategory > 0) {
            //   m_selectedCategory--;
            // }
            // if (indexToRemove >= 0 && indexToRemove < m_names.size()) {
            //   m_names.erase(m_names.begin() + indexToRemove);
            // }
            // if (indexToRemove >= 0 && indexToRemove < m_texts.size()) {
            //   m_texts.erase(m_texts.begin() + indexToRemove);
            // }
            // m_docs->docs.front().setNames(m_names);
            // m_docs->docs.front().setTexts(m_texts);
            // Save();
            // setDoc(m_selectedCategory);
          }
          if (ImGui::IsItemHovered()) {
            ImGui::BeginTooltip();
            {
              ImGui::TextUnformatted(trNOOP("Removes a category."));
              ImGui::EndTooltip();
            }
          }
          ImGui::EndDisabled();
          ImGui::SameLine();
          ImGui::BeginDisabled(m_selectedCategory == 0);
          if (ImGui::Button("↑##orpg_docs_sort_up", ImVec2(40, 0))) {
            std::swap(m_names.at(m_selectedCategory), m_names.at(m_selectedCategory - 1));
            std::swap(m_texts.at(m_selectedCategory), m_texts.at(m_selectedCategory - 1));
            m_docs->docs.front().setNames(m_names);
            m_docs->docs.front().setTexts(m_texts);
            setDoc(m_selectedCategory - 1);
          }
          if (ImGui::IsItemHovered()) {
            ImGui::BeginTooltip();
            {
              ImGui::TextUnformatted(trNOOP("Moves the selected category up by one on its index."));
              ImGui::EndTooltip();
            }
          }
          ImGui::EndDisabled();
          ImGui::SameLine();
          ImGui::BeginDisabled(m_selectedCategory == m_names.size() - 1);
          if (ImGui::Button("↓##orpg_docs_sort_down", ImVec2(40, 0))) {
            std::swap(m_names.at(m_selectedCategory), m_names.at(m_selectedCategory + 1));
            std::swap(m_texts.at(m_selectedCategory), m_texts.at(m_selectedCategory + 1));
            m_docs->docs.front().setNames(m_names);
            m_docs->docs.front().setTexts(m_texts);
            setDoc(m_selectedCategory + 1);
          }
          ImGui::EndDisabled();
          if (ImGui::IsItemHovered()) {
            ImGui::BeginTooltip();
            {
              ImGui::TextUnformatted(trNOOP("Moves the selected category down by one on its index."));
              ImGui::EndTooltip();
            }
          }
          ImGui::EndGroup();
        }
        ImGui::BeginGroup();
        {
          ImGui::SetNextItemWidth(ImGui::GetContentRegionMax().x);
          if (ImGui::BeginCombo(std::format("##orpg_navigation_combo_{}", m_selectedCategory).c_str(),
                                m_names.empty() ? "Add a new category" : m_docs->docs.at(0).names().at(m_selectedCategory).c_str(), ImGuiComboFlags_NoArrowButton)) {
            for (int i = 0; i < m_docs->docs.at(0).names().size(); i++) {
              if (ImGui::Selectable(std::format("{}##{}", m_docs->docs.at(0).names().at(i), i).c_str(), i == m_selectedCategory)) {
                setDoc(i);
                // SetScroll
              }
            }
            ImGui::EndCombo();
          }
          ImGui::SeparatorText("Headers");
          if (headerTexts.size() == 0) {
            ImGui::TextUnformatted(trNOOP("No headers available"));
          } else {
            int index{0};
            for (auto& header : headerTexts) {
              char name[4096];
              snprintf(name, 4096, "%s", header.front().c_str());
              if (ImGui::Selectable(std::format("{}##headerText_{}", name, index).c_str(), index == m_selectedHeader) || (ImGui::IsItemFocused() && m_selectedHeader != index)) {

                m_selectedHeader = index;
              }
              if (ImGui::GetMouseClickedCount(ImGuiMouseButton_Left) >= 2) {
                std::string text;
                for (auto iter = headerTexts.at(m_selectedHeader).begin() + 1; iter != headerTexts.at(m_selectedHeader).end(); ++iter) {
                  text += *iter + "\n";
                }
                headerText = text;
                referenceText = text;
                m_isEdittingHeader = true;
              }
              index++;
            }
          }
        }
        ImGui::EndGroup();
      }
      ImGui::EndChild();
    }
    ImGui::EndGroup();
  }
  ImGui::EndChild();
  ImGui::SameLine();
  ImGui::BeginChild("##orpg_docs_editor");
  {
    // Menu Bar above editor
    ImGui::BeginGroup();
    {
      ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 6.f);
      ImGui::TextUnformatted(trNOOP("Name:"));
      ImGui::SameLine();
      ImGui::SetCursorPosY(ImGui::GetCursorPosY() - 6.f);
      char name[4096];
      strncpy(name, m_names.empty() ? "" : m_names.at(m_selectedCategory).c_str(), 4096);
      ImGui::SetNextItemWidth(200);
      ImGui::BeginDisabled(!m_isEdittingText);
      if (ImGui::InputText(std::format("##orpg_doc_category_editor_name{}", m_selectedCategory).c_str(), name, 4096)) {
        m_names.at(m_selectedCategory) = name;
        m_isEdittingNames = true;
      }
      ImGui::EndDisabled();
      ImGui::SameLine();
      ImGui::BeginDisabled(m_isEdittingText);
      ImGui::SetCursorPosY(ImGui::GetCursorPosY() - 6.f);
      ImGui::BeginDisabled(m_texts.empty());
      if (ImGui::Button(trNOOP("Edit"), ImVec2(100, 30))) {
        m_isEdittingText = !m_isEdittingText;
      }
      ImGui::EndDisabled();
      ImGui::EndDisabled();
      ImGui::SameLine();
      ImGui::BeginDisabled(!(m_isEdittingText || m_isEdittingHeader));
      ImGui::SetCursorPosY(ImGui::GetCursorPosY() - 6.f);
      if (ImGui::Button(trNOOP("Apply"), ImVec2(100, 30))) {
        if (m_isEdittingHeader) {
          std::vector<std::string> text = m_docs->docs.front().texts();
          if (text.at(m_selectedCategory).contains(referenceText)) {
            size_t beginPos = text.at(m_selectedCategory).find(referenceText);
            size_t count = 1;
            size_t pos = 0;

            // Use find to locate each occurrence of referenceText
            if (text.at(m_selectedCategory).find(referenceText, beginPos + referenceText.length()) != std::string::npos) {
              count++;
            }
            if (count < 2) {
              // If found, replace the substring with "this is a test"
              if (headerText.back() != '\n') {
                headerText += '\n';
              }
              text.at(m_selectedCategory).replace(beginPos, referenceText.length(), "");
              text.at(m_selectedCategory).insert(beginPos, headerText);
              m_docs->docs.front().setTexts(text);
              Save();
              createHeaders();
            }
            m_isEdittingHeader = false;
          }
        } else {

          if (m_isEdittingText) {
            m_docs->docs.front().setTexts(m_texts);
            m_isEdittingText = false;
          }
          if (m_isEdittingNames) {
            m_docs->docs.front().setNames(m_names);
            m_isEdittingNames = false;
          }
          Save();
          createHeaders();
        }
      }
      ImGui::EndDisabled();
      ImGui::SameLine();
      ImGui::SetCursorPosY(ImGui::GetCursorPosY() - 6.f);
      ImGui::TextDisabled("(?)");
      if (ImGui::BeginItemTooltip()) {
        ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);
        ImGui::TextUnformatted(GetFormattingHelpText().c_str());
        ImGui::PopTextWrapPos();
        ImGui::EndTooltip();
      }
      ImGui::EndGroup();
    }
    // Editor
    ImGui::BeginChild("##orpg_docs_editor_text", ImVec2(0, 0), 0, ImGuiWindowFlags_NoScrollbar);
    {
      if (m_isEdittingHeader) {
        std::string text = headerText;
        // headerTexts.at(m_selectedHeader);
        if (ImGui::InputTextMultiline("##orpg_docs_text", &text, ImVec2{ImGui::GetContentRegionMax().x, ImGui::GetContentRegionMax().y - 30})) {
          headerText = text;
          //  m_isEdittingTexts = true;
        }
      } else if (m_isEdittingText) {
        // Text Editor Mode
        std::string text = m_texts.at(m_selectedCategory);
        if (ImGui::InputTextMultiline("##orpg_docs_text", &text, ImVec2{ImGui::GetContentRegionMax().x, ImGui::GetContentRegionMax().y - 30})) {
          m_texts.at(m_selectedCategory) = text;
          // m_isEdittingTexts = true;
        }
      } else {
        // Text Viewing Mode

        if (m_docs->docs.at(0).texts().empty()) {
          ImGui::TextUnformatted(trNOOP("\tNo documentation available."));
        } else {
          // Documentation available

          int headerIndex{0};
          float xPos = ImGui::GetCursorPosX();

          for (auto& text : splitString(m_docs->docs.at(0).texts().at(m_selectedCategory), '\n')) {
            if (text.contains("##") && inHeader == false) {
              inHeader = true;
            } else if (text.contains("##") && inHeader) {
              inHeader = false;
              // TODO: Header Status
              if (ImGui::CollapsingHeader(std::format("{}##header{}", headerTexts.at(headerIndex).front(), m_selectedCategory).c_str())) {
                for (auto it = headerTexts.at(headerIndex).begin() + (headerTexts.at(headerIndex).size() == 1 ? 0 : 1); it != headerTexts.at(headerIndex).end(); ++it) {
                  std::string itText = *it;
                  ImGui::SetCursorPosX(xPos + 20);
                  if (isSeperatorFormatting(itText)) {
                    // Seperators have no extra formatting
                    if (itText.size() > 1) {
                      // We use text
                      ImGui::Text("");
                      ImGui::SeparatorText(itText.substr(1, it->size() - 1).c_str());
                      ImGui::Text("");
                    } else {
                      // Just seperator
                      ImGui::SeparatorEx(ImGuiSeparatorFlags_Horizontal, 3);
                    }
                  } else {
                    // Process through formatting
                    ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 20);
                    while (ProcessFormatting(itText)) {}
                    if (isArrowBullet) {
                      itText = "→ " + itText;
                    }
                    if (isFontFormat) {
                      ImFont font = *App::APP->getMainFont();
                      font.Scale = font.Scale + m_formatScale;

                      ImGui::PushFont(&font);

                      if (isPictureFormat) {
                        // Show Picture
                        Texture texture = ResourceManager::instance()->loadEditorTexture(itText);
                        ImGui::Image(texture, ImVec2{static_cast<float>(texture.width()), static_cast<float>(texture.height())}, ImVec2{0, 0}, ImVec2{1, 1});
                      } else {
                        if (isBulletFormat) {
                          ImGui::BulletText("%s", itText.c_str());
                        } else {
                          ImGui::Text("%s", itText.c_str());
                        }
                      }
                      ImGui::PopFont();
                    } else {
                      if (isPictureFormat) {
                        // Show Picture
                        Texture texture = ResourceManager::instance()->loadEditorTexture(itText);
                        ImGui::Image(texture, ImVec2{static_cast<float>(texture.width()), static_cast<float>(texture.height())}, ImVec2{0, 0}, ImVec2{1, 1});

                      } else {
                        if (isBulletFormat) {
                          ImGui::BulletText("%s", itText.c_str());
                        } else {
                          ImGui::Text("%s", itText.c_str());
                        }
                      }
                    }

                    // Reset bools per line
                    isFontFormat = false;
                    isArrowBullet = false;
                    isIdenting = false;
                    isBulletFormat = false;
                    isPictureFormat = false;
                  }
                }
              }
              headerIndex++;
            } else {
              if (!inHeader) {
                if (isSeperatorFormatting(text)) {
                  // Seperators don't get any extra formatting
                  if (text.size() > 1) {
                    // We use text
                    ImGui::SeparatorText(text.substr(1, text.size() - 1).c_str());
                  } else {
                    // Just seperator
                    ImGui::Text("");
                    ImGui::SeparatorEx(ImGuiSeparatorFlags_Horizontal, 3);
                    ImGui::Text("");
                  }
                } else {
                  // Basic text formatting branching
                  ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 20);
                  while (ProcessFormatting(text)) {}
                  if (isArrowBullet) {
                    text = "→ " + text;
                  }
                  if (isFontFormat) {
                    ImFont font = *App::APP->getMainFont();
                    font.Scale = font.Scale + m_formatScale;

                    ImGui::PushFont(&font);

                    if (isPictureFormat) {
                      // Show Picture
                      Texture texture = ResourceManager::instance()->loadEditorTexture(text);
                      ImGui::Image(texture, ImVec2{static_cast<float>(texture.width()), static_cast<float>(texture.height())}, ImVec2{0, 0}, ImVec2{1, 1});
                    } else {
                      if (isBulletFormat) {
                        ImGui::BulletText("%s", text.c_str());
                      } else {
                        ImGui::Text("%s", text.c_str());
                      }
                    }
                    ImGui::PopFont();
                  } else {
                    if (isPictureFormat) {
                      // Show Picture
                      Texture texture = ResourceManager::instance()->loadEditorTexture(text);
                      ImGui::Image(texture, ImVec2{static_cast<float>(texture.width()), static_cast<float>(texture.height())}, ImVec2{0, 0}, ImVec2{1, 1});

                    } else {
                      if (isBulletFormat) {
                        ImGui::BulletText("%s", text.c_str());
                      } else {
                        ImGui::Text("%s", text.c_str());
                      }
                    }
                  }

                  // Reset bools per line
                  isFontFormat = false;
                  isArrowBullet = false;
                  isIdenting = false;
                  isBulletFormat = false;
                  isPictureFormat = false;
                }
              }
            }
          }
        }
      }
      ImGui::EndChild();
    }
  }
  ImGui::EndChild();
}
void DBDocTab::Save() const {

  fs::path editorPath = fs::path(Database::instance()->basePath) / "editor";

  if (!exists(editorPath)) {
    create_directories(editorPath);
  }

  if (m_docs->serialize(Database::instance()->basePath + "editor/Documentation.json")) {
    ImGui::InsertNotification(ImGuiToast{ImGuiToastType::Success, "Serialized editor/Documentation.json successfully!"});
  } else {
    ImGui::InsertNotification(ImGuiToast{ImGuiToastType::Error, "Failed to serialize editor/Documentation.json!"});
  }
}
// ##Example Text 1
// Example Text in Header 2231
/// ##
void DBDocTab::createHeaders() {
  headerTexts.clear();
  referenceHeaderTexts.clear();
  if (m_names.empty()) {
    return;
  }
  std::vector<std::string> texts = splitString(m_docs->docs.front().texts().at(m_selectedCategory), '\n');
  bool isHeaderText{false};
  int headerIndex{0};
  for (auto& text : texts) {
    if (text.contains("/##")) {
      isHeaderText = false;
      headerIndex++;
    } else if (text.contains("##")) {
      isHeaderText = true;
      headerTexts.push_back(std::vector<std::string>{});
      headerTexts.at(headerIndex).push_back(text.substr(2, text.size() - 1).data());

      referenceHeaderTexts.push_back(std::vector<std::string>{});
      referenceHeaderTexts.at(headerIndex).push_back(text.substr(2, text.size() - 1).data());
    }
    if (isHeaderText) {
      if (!text.contains("##")) {
        headerTexts.at(headerIndex).push_back(text);
        referenceHeaderTexts.at(headerIndex).push_back(text);
      }
    }
  }
}
void DBDocTab::setDoc(int index) {
  m_selectedCategory = index;
  createHeaders();
}
std::string DBDocTab::GetFormattingHelpText() {
  return trNOOP("Headers: \n##\n<text>\n/##\nBullets: -text\nSeperators: ~ or ~text\nArrow Bullets: >text\nIndent: \\t<text>\nPicture: \\p<name>\nFont Scale: \\f0.5<text>");
}

bool DBDocTab::ProcessFormatting(std::string& text) {
  bool containsFormatting{false};
  if (isArrowBulletFormatting(text)) {
    isArrowBullet = true;
    containsFormatting = true;
    text = text.substr(1, text.size() - 1);
  }
  if (isIndentFormatting(text)) {
    isIdenting = true;
    containsFormatting = true;
    ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 20);
    text = text.substr(2, text.size() - 1);
  }
  if (isFontFormatting(text)) {
    isFontFormat = true;
    containsFormatting = true;
    text = std::string(text.substr(2, text.size() - 1));

    std::string fontScale = text.substr(0, 3);

    m_formatScale = std::stof(fontScale);

    text = std::string(text.substr(3, text.size() - 1));
  }
  if (isBulletFormatting(text)) {
    isBulletFormat = true;
    containsFormatting = true;
    text = text.substr(1, text.size() - 1).data();
  }
  if (isPictureFormatting(text)) {
    isPictureFormat = true;
    containsFormatting = true;
    text = std::string(text.substr(2, text.size() - 1)) + ".png";
  }
  return containsFormatting;
}
bool DBDocTab::isNumericOnly(const std::string& str) {
  // Check if all characters in the string are alphabetic
  return std::all_of(str.begin(), str.end(), [](char c) {
    return std::isdigit(c); // Returns true if c is an alphabetic character
  });
}


code:
#pragma once
#include "Core/DatabaseEditor/IDBEditorTab.hpp"
#include "Database/Docs.hpp"

struct DBDocTab : IDBEditorTab {
  DBDocTab() = delete;
  explicit DBDocTab(Docs& docs, DatabaseEditor* parent);
  void draw() override;
  std::vector<int>& getHeaders() override { return m_headers; }
  int getHeader(int index) override { return m_headers.at(index); }
  bool hasHeader() override { return !m_headers.empty(); }
  void setHeaderRange(int start, int end) override {
    m_categoryStart = start;
    m_categoryEnd = end;
  }
  std::string getName(int index) override { return ""; }
  int getCount() override { return 0; }

private:
  int m_categoryStart;
  int m_categoryEnd;
  std::vector<int> m_headers;
  Docs* m_docs = nullptr;
  int m_id{0};
  bool inHeader{false};
  int m_selectedHeader{0};
  int m_selectedCategory{0};

  std::vector<std::vector<std::string>> headerTexts;          // All categorized sections
  std::vector<std::vector<std::string>> referenceHeaderTexts; // Referenced for replacement
  std::vector<std::string> m_names;
  std::vector<std::string> m_texts;
  std::string headerText;
  std::string referenceText;

  bool m_isEdittingText{false};
  bool m_isEdittingNames{false};
  // bool m_isEdittingTexts{false};
  bool m_isEdittingHeader{false};

  bool isArrowBullet{false};
  bool isIdenting{false};
  bool isFontFormat{false};
  bool isBulletFormat{false};
  bool isPictureFormat{false};

  float m_formatScale{0.0};

  void Save() const;
  bool isHeader() const { return inHeader; }
  bool isBulletFormatting(std::string text) { return text.starts_with("-"); }
  bool isSeperatorFormatting(std::string text) { return text.starts_with("~"); }
  bool isArrowBulletFormatting(std::string text) { return text.starts_with(">"); }
  bool isIndentFormatting(std::string text) { return text.starts_with("\\t"); }
  bool isFormatting() const { return (isHeader() == false); }
  bool isFontFormatting(std::string text) const { return text.starts_with("\\f"); }
  bool isPictureFormatting(std::string text) const { return text.starts_with("\\p"); }
  void createHeaders();
  void setDoc(int index);
  std::string GetFormattingHelpText();
  bool ProcessFormatting(std::string& text);
  bool isNumericOnly(const std::string& text);
};