code:
#include "Core/CommonUI/CharacterPicker.hpp"
#include "Core/ImGuiExt/ImGuiUtils.hpp"
#include "Core/ResourceManager.hpp"
#include "imgui.h"
#include "imgui_internal.h"
#include "misc/cpp/imgui_stdlib.h"

#include <IconsFontAwesome6.h>
#include <iostream>

#include "Database/Database.hpp"

static bool ContainsCaseInsensitive(std::string_view str, std::string_view val) {
  return std::search(str.begin(), str.end(), val.begin(), val.end(), [](char ch1, char ch2) { return std::toupper(ch1) == std::toupper(ch2); }) != str.end();
}

static int makeTileId(const int n, const int x, const int y) { return n * 256 + floor(x / 8) * 128 + y * 8 + x % 8; }
CharacterPicker::CharacterPicker(const PickerMode mode, const bool useTileset, const int tileId, const std::string_view sheetName, const int character, const int pattern, const Direction direction)
: IDialogController("Select an Image##character_picker")
, m_pickerMode(mode)
, m_characterIndex(character)
, m_pattern(pattern)
, m_direction(direction) {
  m_charDir.emplace("img/characters/", ".png", static_cast<std::string>(sheetName));
  m_characterSheets = m_charDir.value().getDirectoryContents();
  if (useTileset) {
    int mapId = Database::instance()->mapInfos.currentMap()->id();
    m_tileSheets = Database::instance()->tilesets.tileset(Database::instance()->mapInfos.map(mapId)->map()->tilesetId())->tilesetNames();
    // m_palette.setPageIndex(5);
    // m_palette.setTilesetNames(m_tileSheets);
  }
  m_folders = m_charDir.value().getDirectories();

  if (useTileset) {
    setTileId(tileId);
  } else {
    setCharacterInfo(sheetName, character, pattern, direction);
  }
}

void CharacterPicker::setCharacterInfo(const std::string_view sheetName, const int character, const int pattern, const Direction direction) {
  m_characterSheet.emplace(sheetName);
  if (m_characterSheet->texture()) {
    m_characterIndex = character;
    m_pattern = pattern;
    m_direction = direction;

    std::string imageName = m_charDir.value().getFileName(static_cast<std::string>(sheetName));
    if (!imageName.empty()) {
      m_selectedSheet = sheetIndexOf(imageName);

      const float charX = static_cast<float>((character % (m_characterSheet->texture().width() / m_characterSheet->characterAtlasWidth())) * m_characterSheet->characterAtlasWidth());
      const float charY = static_cast<float>((character / (m_characterSheet->texture().width() / m_characterSheet->characterAtlasWidth())) * m_characterSheet->characterAtlasHeight());

      if (m_pickerMode == PickerMode::PatternAndDirection) {
        m_selectionX = charX + (pattern * m_characterSheet->characterWidth());
        m_selectionY = charY + (((static_cast<int>(m_direction) - 2) / 2) * m_characterSheet->characterHeight());
        m_selectionWidth = m_characterSheet->characterWidth();
        m_selectionHeight = m_characterSheet->characterHeight();
      } else {
        m_selectionX = charX;
        m_selectionY = charY;
        m_selectionWidth = m_characterSheet->characterAtlasWidth();
        m_selectionHeight = m_characterSheet->characterAtlasHeight();
      }
      // m_selectionX =
      //     std::clamp(m_selectionX, 0, m_characterSheet->texture().width() - m_characterSheet->characterWidth());
      // m_selectionY =
      //     std::clamp(m_selectionY, 0, m_characterSheet->texture().height() - m_characterSheet->characterHeight());
    } else {
      m_selectedSheet = -5;
    }
    m_checkerboardTexture.setSize(m_characterSheet->texture().width(), m_characterSheet->texture().height());
  }
}

void CharacterPicker::setTileId(int tileId) {
  if (m_tileSheets.empty()) {
    int mapId = Database::instance()->mapInfos.currentMap()->id();
    m_tileSheets = Database::instance()->tilesets.tileset(Database::instance()->mapInfos.map(mapId)->map()->tilesetId())->tilesetNames();
  }
  m_tileId = tileId;
  if (m_tileId > 0) {
    m_isTile = true;
  }
  m_selectedSheet = -5 + floor(m_tileId / 256);
  std::string tileSheet = m_tileSheets[m_selectedSheet + 9];
  setCursorPos(setCursorByTile(m_tileId));
  m_selectedSheet = m_tileId < 256 ? -4 : m_tileId < 512 ? -3 : m_tileId < 768 ? -2 : -1;
  m_characterSheet.emplace(tileSheet, true, m_tileId);
  m_characterIndex = 0;
  m_pattern = 0;
  m_direction = Direction::Retain;
  m_selectionWidth = Database::instance()->system.tileSize();
  m_selectionHeight = Database::instance()->system.tileSize();

  m_checkerboardTexture.setSize(m_characterSheet->texture().width(), m_characterSheet->texture().height());
}

void CharacterPicker::setCursorPos(Point pos) {
  m_selectionX = pos.x() * 48;
  m_selectionY = pos.y() * 48;
}

int CharacterPicker::sheetIndexOf(std::string& str) {
  bool found = false;
  for (int i = 0; i < m_characterSheets.size(); ++i) {
    if (!m_characterSheets[i].compare(str)) {
      found = true;
      m_selectedSheet = i;
    }
  }
  if (!found) {
    m_characterSheet.reset();
    m_selectedSheet = -5;
    return -4;
  }
  return m_selectedSheet;
}

std::tuple<bool, bool> CharacterPicker::draw() {
  if (isOpen()) {
    ImGui::OpenPopup(m_name.c_str());
  }

  int index{0};
  if (m_sortRequest) {
    m_sortedIndexes.clear();
    for (auto& str : m_characterSheets) {
      if (!m_filter.empty()) {
        if (ContainsCaseInsensitive(str, m_filter)) {
          m_sortedIndexes.insert(std::make_pair(index, ""));
          // m_sortedList.push_back(str);
        }
      } else {
        m_sortedIndexes.insert(std::make_pair(index, ""));
        // m_sortedList.push_back(str);
      }
      index++;
    }
    m_sortRequest = false;
  }

  const ImVec2 center = ImGui::GetMainViewport()->GetCenter();
  ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
  ImGui::SetNextWindowSizeConstraints(ImGui::GetDPIScaledSize(320, 320), {FLT_MAX, FLT_MAX});
  ImGui::SetNextWindowSize(ImGui::GetDPIScaledSize(320, 320), ImGuiCond_Once);
  if (ImGui::BeginPopupModal(m_name.c_str(), &m_open)) {
    const auto calc = ImGui::CalcTextSize("OKCANCEL");
    ImGui::BeginChild("##top_child", {0, ImGui::GetContentRegionAvail().y - (calc.y + (ImGui::GetStyle().ItemSpacing.y * 2) + ImGui::GetStyle().FramePadding.y)});
    {
      ImGui::BeginVertical("selection_status_layout");
      {
        ImGui::BeginHorizontal("filter_layout");
        {
          ImGui::Text("Filter");
          if (ImGui::InputText("##object_selection_filter_input", &m_filter)) {
            m_selectedSheet = -5;
            if (m_filter.empty()) {
              m_sortedIndexes.clear();
            } else {
              m_sortRequest = true;
            }
          }
          if (ImGui::Button("Clear")) {
            m_filter.clear();
            m_sortedIndexes.clear();
          }
        }
        ImGui::EndHorizontal();
        std::string selectedTileSheet = m_selectedSheet == -5 ? "(None)" : m_selectedSheet < 0 ? m_tileSheets[m_selectedSheet + 9] : "(None)";
        std::string selectedSheet = m_selectedSheet == -5 ? "(None)" : m_characterSheets.size() > 0 && m_selectedSheet > -1 ? m_characterSheets[m_selectedSheet] : "(None)";
        ImGui::Text("Selected Sheet: %s", selectedSheet.c_str());
      }
      ImGui::EndVertical();
      ImGui::BeginChild("##character_picker_sheet_list", ImVec2{ImGui::CalcTextSize("ABCDEFGHIJKLMNOPQRS").x, ImGui::GetContentRegionAvail().y - ImGui::GetStyle().FramePadding.y},
                        ImGuiChildFlags_Borders | ImGuiChildFlags_ResizeX);
      {
        if (ImGui::BeginTable("##character_picker.characterlist", 1, ImGuiTableFlags_Resizable)) {
          ImGui::TableNextRow();
          ImGui::TableNextColumn();

          if (ImGui::Selectable("(None)", m_selectedSheet == -5, ImGuiSelectableFlags_SelectOnNav | ImGuiSelectableFlags_SelectOnClick)) {
            m_selectedSheet = -5;
            m_isTile = false;
            m_characterSheet.reset();
          }
          if (m_selectedSheet == -5) {
            ImGui::SetItemDefaultFocus();
          }

          if (m_isTile) {
            ImGui::SeparatorEx(ImGuiSeparatorFlags_Horizontal, 4);
          }
          for (int z = -4; z < 0; ++z) {
            auto& sheet = m_tileSheets[z + 9];
            if (!sheet.empty()) {
              if (ImGui::Selectable(std::format("Tileset {}", static_cast<char>('A' + (z + 5))).c_str(), m_selectedSheet == z, ImGuiSelectableFlags_SelectOnNav | ImGuiSelectableFlags_SelectOnClick)) {
                if (m_selectedSheet != z) {
                  m_selectedSheet = z;
                  m_selectionWidth = Database::instance()->system.tileSize();
                  m_selectionHeight = Database::instance()->system.tileSize();
                  m_tileId = makeTileId(m_selectedSheet + 4, m_selectionX / m_selectionWidth, m_selectionY / m_selectionHeight);
                  m_characterSheet.emplace(sheet, true, m_tileId);
                  if (m_characterSheet->texture()) {
                    m_checkerboardTexture.setSize(m_characterSheet->texture().width(), m_characterSheet->texture().height());
                  }
                  m_isTile = true;
                }
              }
              if (m_selectedSheet == z && ImGui::IsWindowAppearing()) {
                ImGui::SetScrollHereY();
              }
            }
          }

          ImGui::SeparatorEx(ImGuiSeparatorFlags_Horizontal, 4);
          ImGui::BeginDisabled(m_charDir.value().isParentDirectory());
          if (ImGui::Selectable("\u21B0 ..", false, ImGuiSelectableFlags_SelectOnNav | ImGuiSelectableFlags_SelectOnClick)) {
            if (ImGui::GetMouseClickedCount(ImGuiMouseButton_Left) >= 2) {
              m_charDir.value().moveUp();
              m_characterSheets = m_charDir->getDirectoryContents();
              m_folders = m_charDir.value().getDirectories();
              m_selectedSheet = -5;
              m_selectedFolder = -1;
              m_isTile = false;
            }
          }
          ImGui::EndDisabled();
          ImGui::BeginChild("##dir_list", {0, (ImGui::CalcTextSize("A").y * 5) + (ImGui::GetStyle().ItemSpacing.y * 2)}, ImGuiChildFlags_ResizeY,
                            ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_HorizontalScrollbar);
          {
            for (int i = 0; i < m_folders.size(); ++i) {
              const auto& folderName = std::format("{} {}", ICON_FA_FOLDER_OPEN, m_folders[i]);
              if (ImGui::Selectable(folderName.c_str(), m_selectedFolder == i, ImGuiSelectableFlags_SelectOnNav | ImGuiSelectableFlags_SelectOnClick)) {
                m_selectedFolder = i;
              }
              if (m_selectedFolder == i && ImGui::GetMouseClickedCount(ImGuiMouseButton_Left) >= 2) {
                m_charDir->setDirectory(i);
                m_folders = m_charDir.value().getDirectories();
                m_characterSheets = m_charDir.value().getDirectoryContents();
                m_selectedSheet = -5;
                m_selectedFolder = -1;
                m_isTile = false;
              }
            }
          }
          ImGui::EndChild();
          ImGui::SeparatorEx(ImGuiSeparatorFlags_Horizontal, 4);
          ImGui::BeginChild("##sheet_list", {}, 0, ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_HorizontalScrollbar);
          {
            for (int i = 0; i < m_characterSheets.size(); ++i) {
              if (m_filter.empty() == false && m_sortedIndexes.contains(i) == false) {
                continue;
              }
              const auto& sheet = m_characterSheets[i];
              ImGui::TableNextColumn();
              if (ImGui::Selectable(std::format("{0}##sheet_{0}", sheet).c_str(), m_selectedSheet == i, ImGuiSelectableFlags_SelectOnNav | ImGuiSelectableFlags_SelectOnClick)) {
                if (m_selectedSheet != i) {
                  m_characterSheet.emplace(m_charDir.value().isParentDirectory() ? sheet : m_charDir.value().getPathPrefix() + '/' + sheet);
                  if (m_characterSheet->texture()) {
                    m_checkerboardTexture.setSize(m_characterSheet->texture().width(), m_characterSheet->texture().height());
                  }
                  if (m_pickerMode == PickerMode::Character) {
                    m_selectionWidth = m_characterSheet->characterAtlasWidth();
                    m_selectionHeight = m_characterSheet->characterAtlasHeight();
                  } else if (m_pickerMode == PickerMode::PatternAndDirection) {
                    m_selectionWidth = m_characterSheet->characterWidth();
                    m_selectionHeight = m_characterSheet->characterHeight();
                  }
                }
                m_isTile = false;
                m_selectedSheet = i;
              }
              if (m_selectedSheet == i && ImGui::IsWindowAppearing()) {
                ImGui::SetScrollHereY();
              }
              if (ImGui::IsItemHovered()) {
                ImGui::SetTooltip("%s", sheet.c_str());
              }
            }
          }
          ImGui::EndChild();
          ImGui::EndTable();
        }
      }
      ImGui::EndChild();
      ImGui::SameLine();
      ImGui::BeginChild("##character_picker_sheet_panel", {}, ImGuiChildFlags_Border, ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_HorizontalScrollbar);
      {
        auto win = ImGui::GetCurrentWindow();
        if (ImGui::IsMouseClicked(ImGuiMouseButton_Left) && ImGui::IsWindowHovered()) {
          auto mouseCursor = ImGui::GetMousePos();
          mouseCursor -= win->ContentRegionRect.Min;
          int x = alignValue(mouseCursor.x, m_selectionWidth);
          int y = alignValue(mouseCursor.y, m_selectionHeight);
          if ((x >= 0 && x < m_characterSheet->texture().width()) && (y >= 0 && y < m_characterSheet->texture().height())) {
            m_selectionX = x;
            m_selectionY = y;
            if (m_isTile) {
              // On selection, set the tileId
              m_tileId = makeTileId(m_selectedSheet + 4, x / Database::instance()->system.tileSize(), y / Database::instance()->system.tileSize());
            } else {
              if (m_pickerMode == PickerMode::Character) {
                m_selectionX = std::clamp(m_selectionX, 0, m_characterSheet->texture().width() - m_characterSheet->characterAtlasWidth());
                m_selectionY = std::clamp(m_selectionY, 0, m_characterSheet->texture().height() - m_characterSheet->characterAtlasHeight());
              } else {
                m_selectionX = std::clamp(m_selectionX, 0, m_characterSheet->texture().width() - m_characterSheet->characterWidth());
                m_selectionY = std::clamp(m_selectionY, 0, m_characterSheet->texture().height() - m_characterSheet->characterHeight());
              }

              x = m_selectionX / m_characterSheet->characterAtlasWidth();
              y = m_selectionY / m_characterSheet->characterAtlasHeight();
              m_pattern = (m_selectionX / m_characterSheet->characterWidth()) % m_characterSheet->patternCountForCharacter();
              const int direction = (m_selectionY / m_characterSheet->characterHeight()) % m_characterSheet->directionCountForCharacter();
              m_direction = static_cast<Direction>((direction * 2) + 2);

              m_characterIndex = (y * (m_characterSheet->texture().width() / m_characterSheet->characterAtlasWidth())) + x;
            }
          }
        }
        if (m_characterSheet) {
          ImGui::Dummy(ImVec2{static_cast<float>(m_characterSheet->texture().width()), static_cast<float>(m_characterSheet->texture().height())});
          win->DrawList->AddImage(static_cast<ImTextureID>(m_checkerboardTexture), win->ContentRegionRect.Min + ImVec2{0.f, 0.f},
                                  win->ContentRegionRect.Min + (ImVec2{static_cast<float>(m_checkerboardTexture.width()), static_cast<float>(m_checkerboardTexture.height())}));
          win->DrawList->AddImage(m_characterSheet->texture(), win->ContentRegionRect.Min + ImVec2{0.f, 0.f},
                                  win->ContentRegionRect.Min + (ImVec2{static_cast<float>(m_characterSheet->texture().width()), static_cast<float>(m_characterSheet->texture().height())}));

          win->DrawList->AddRect(win->ContentRegionRect.Min + (ImVec2{static_cast<float>(m_selectionX), static_cast<float>(m_selectionY)}),
                                 win->ContentRegionRect.Min +
                                     (ImVec2{static_cast<float>(m_selectionX) + static_cast<float>(m_selectionWidth), static_cast<float>(m_selectionY) + static_cast<float>(m_selectionHeight)}),
                                 0xFF000000, 0.f, 0, 7.f);
          win->DrawList->AddRect(win->ContentRegionRect.Min + (ImVec2{static_cast<float>(m_selectionX), static_cast<float>(m_selectionY)}),
                                 win->ContentRegionRect.Min +
                                     (ImVec2{static_cast<float>(m_selectionX) + static_cast<float>(m_selectionWidth), static_cast<float>(m_selectionY) + static_cast<float>(m_selectionHeight)}),
                                 0xFFFFFFFF, 0.f, 0, 3.f);
        }
      }
      ImGui::EndChild();
    }
    ImGui::EndChild();
    ImGui::BeginHorizontal(std::format("##buttons_character_picker{}", reinterpret_cast<uintptr_t>(this)).c_str(), ImGui::GetContentRegionAvail());
    {
      ImGui::Spring();
      const int ret = ImGui::ButtonGroup(std::format("##ok_cancel_character_picker{}", reinterpret_cast<uintptr_t>(this)).c_str(), {
                                                                                trNOOP("OK"),
                                                                                trNOOP("Cancel"),
                                                                                });
      if (ret == 0) { // OK
        if (m_selectedSheet == -5 || m_selectedSheet >= 0) {
          m_tileId = 0;
          m_isTile = false;
        }
        m_filter.clear();
        m_sortedIndexes.clear();
        m_confirmed = true;
        m_open = false;
        ImGui::CloseCurrentPopup();
      } else if (ret == 1) { // Cancel
        m_filter.clear();
        m_sortedIndexes.clear();
        m_confirmed = false;
        m_open = false;
        ImGui::CloseCurrentPopup();
      }
    }
    ImGui::EndHorizontal();
    ImGui::EndPopup();
  }

  return {!m_open, m_confirmed};
}

Point CharacterPicker::setCursorByTile(const int tileId) {
  if (tileId > 0) {
    int tileX = floor(tileId % 256 / 128) * 8 + tileId % 8;
    int tileY = floor(tileId % 128 / 8);
    return {tileX, tileY};
  }
  return {0, 0};
}


code:
#pragma once

#include "Core/CommonUI/IDialogController.hpp"
#include "Core/Graphics/CharacterSheet.hpp"
#include "Core/Graphics/CheckerboardTexture.hpp"
#include "Core/Tilemap/TilePalette.hpp"
#include "Database/Globals.hpp"
#include "Directory.hpp"

struct CharacterPicker : IDialogController {
  enum class PickerMode {
    Character,
    PatternAndDirection,
  };

  explicit CharacterPicker(PickerMode mode = PickerMode::Character, bool useMapTiles = false, int tileId = 0,
                           std::string_view sheetName = {}, int character = 0, int pattern = 0,
                           Direction direction = Direction::Down);

  std::tuple<bool, bool> draw() override;

  Point setCursorByTile(int tileId);

  [[nodiscard]] PickerMode pickerMode() const { return m_pickerMode; }
  [[nodiscard]] int selectedPattern() const { return m_pattern; }
  [[nodiscard]] Direction selectedDirection() const { return m_direction; }
  [[nodiscard]] int character() const { return m_characterIndex; }
  [[nodiscard]] bool isTilePick() const { return m_isTile; }
  [[nodiscard]] int tileId() const { return m_tileId; }

  [[nodiscard]] std::string selectedSheet() const {
    return m_selectedSheet >= 0
             ? m_charDir.value().isParentDirectory()
                 ? m_characterSheets[m_selectedSheet]
                 : m_charDir.value().getPathPrefix() + '/' + m_characterSheets[m_selectedSheet]
             : "";
  }

  [[nodiscard]] std::string selectedTileSheet() const { return m_tileSheets[m_selectedSheet + 9]; }

  void setCharacterInfo(std::string_view sheetName, int character = 0, int pattern = 0,
                        Direction direction = Direction::Down);

  void setTileId(int tileId);

  void setCursorPos(Point pos);

  Rect getTilesetRect();

private:
  std::optional<Directory> m_charDir;
  std::vector<std::string> m_folders;
  int m_selectedFolder{-1};

  bool m_isTile{false};
  int m_tileId{0};
  //TilePalette m_palette;
  int m_tileX{0};
  int m_tileY{0};

  std::string m_filter;
  std::map<int, std::string> m_sortedIndexes;
  bool m_sortRequest{false};

  std::optional<CharacterSheet> m_characterSheet;
  std::vector<std::string> m_characterSheets;
  std::array<std::string, 9> m_tileSheets;
  PickerMode m_pickerMode;
  int m_characterIndex{0};
  CheckerboardTexture m_checkerboardTexture{};
  int m_selectedSheet{-1};
  int m_selectionWidth{48};
  int m_selectionHeight{48};
  int m_selectionX{0};
  int m_selectionY{0};
  // For PatternAndDirection mode
  int m_pattern{0};
  Direction m_direction{Direction::Down};

  int sheetIndexOf(std::string &str);
};

repo_name: Antidote/openrpgeditor

license: mit


code:
#include "Core/CommonUI/ColorFlashPicker.hpp"
#include "Core/Application.hpp"
#include "imgui.h"
#include "imgui_internal.h"

bool ColorFlashPicker::draw() {
  if (m_toneInvalid) {
    m_tonePreview.update(0.f, 0.f, 0.f, 0.f);
    m_toneInvalid = false;
  }
  ImGui::BeginGroup();
  {
    ImGui::BeginGroup();
    {
      ImGui::SetCursorPosY(ImGui::GetCursorPos().y + 5.f);
      ImGui::TextUnformatted(trNOOP("Red:"));
      ImGui::SetCursorPosY(ImGui::GetCursorPos().y + 12.f);
      ImGui::TextUnformatted(trNOOP("Green:"));
      ImGui::SetCursorPosY(ImGui::GetCursorPos().y + 14.f);
      ImGui::TextUnformatted(trNOOP("Blue:"));
      ImGui::SetCursorPosY(ImGui::GetCursorPos().y + 12.f);
      ImGui::TextUnformatted(trNOOP("Intensity:"));
    }
    ImGui::EndGroup();
    ImGui::SameLine();
    ImGui::BeginGroup();
    {
      ImGui::SetNextItemWidth(150);
      if (ImGui::SliderInt(std::format("##color_preview_red_slider_{}", reinterpret_cast<intptr_t>(this)).c_str(), &m_r, 0, 255)) {
        m_modified = true;
      }
      ImGui::SameLine();
      ImGui::SetNextItemWidth(120);
      if (ImGui::InputInt("##color_preview_int_red", &m_r, 1, 100)) {
        m_toneInvalid = m_modified = true;
      }
      ImGui::SetNextItemWidth(150);
      if (ImGui::SliderInt(std::format("##color_preview_green_slider_{}", reinterpret_cast<intptr_t>(this)).c_str(), &m_g, 0, 255)) {
        m_modified = true;
      }
      ImGui::SameLine();
      ImGui::SetNextItemWidth(120);
      if (ImGui::InputInt("##color_preview_int_green", &m_g, 1, 100)) {
        m_toneInvalid = m_modified = true;
      }
      ImGui::SetNextItemWidth(150);
      if (ImGui::SliderInt(std::format("##color_preview_blue_slider_{}", reinterpret_cast<intptr_t>(this)).c_str(), &m_b, 0, 255)) {
        m_modified = true;
      }
      ImGui::SameLine();
      ImGui::SetNextItemWidth(120);
      if (ImGui::InputInt("##color_preview_int_blue", &m_b, 1, 100)) {
        m_toneInvalid = m_modified = true;
      }
      ImGui::SetNextItemWidth(150);
      if (ImGui::SliderInt(std::format("##color_preview_intensity_slider_{}", reinterpret_cast<intptr_t>(this)).c_str(), &m_intensity, 0, 255)) {
        m_modified = true;
      }
      ImGui::SameLine();
      ImGui::SetNextItemWidth(120);
      if (ImGui::InputInt("##color_preview_int_intensity", &m_intensity, 1, 100)) {
        m_toneInvalid = m_modified = true;
      }
    }
    ImGui::EndGroup();
    ImGui::SameLine();
    ImGui::BeginChild(std::format("##color_preview_child_{}", reinterpret_cast<intptr_t>(this)).c_str(), ImVec2(0, 0),
                      ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysAutoResize | ImGuiChildFlags_FrameStyle);
    {
      ImGui::Dummy(ImVec2{static_cast<float>(m_tonePreview.width()), static_cast<float>(m_tonePreview.height())});
      auto win = ImGui::GetCurrentWindow();
      win->DrawList->AddImage(m_tonePreview, win->ContentRegionRect.Min, win->ContentRegionRect.Min + ImVec2{static_cast<float>(m_tonePreview.width()), static_cast<float>(m_tonePreview.height())});
      win->DrawList->AddRectFilled(win->ContentRegionRect.Min, win->ContentRegionRect.Min + ImVec2{static_cast<float>(m_tonePreview.width()), static_cast<float>(m_tonePreview.height())},
                                   IM_COL32(m_r, m_g, m_b, m_intensity));
    }
    ImGui::EndChild();
  }
  ImGui::EndGroup();

  return m_modified;
}

void ColorFlashPicker::setPreviewSize(int width, int height) {
  if (width == m_tonePreview.width() && height == m_tonePreview.height()) {
    return;
  }
  m_tonePreview.setSize(width, height);
}


code:
#pragma once

#include "Core/Graphics/ColorTonePreview.hpp"

class ColorFlashPicker {
public:
  ColorFlashPicker() = default;

  void setValues(const int r, const int g, const int b, const int intensity) {
    m_r = r;
    m_g = g;
    m_b = b;
    m_intensity = intensity;
    m_modified = false;
  }

  /**!
   * Sets the base size of the preview image before,
   * the values are scaled based on UI scale and applied automatically
   * @param width Unscaled width of the preview image
   * @param height Unscaled height of the preview image
   */
  void setPreviewSize(int width, int height);

  [[nodiscard]] int r() const { return m_r; }
  [[nodiscard]] int g() const { return m_g; }
  [[nodiscard]] int b() const { return m_b; }
  [[nodiscard]] int intensity() const { return m_intensity; }

  bool draw();

private:
  ColorTonePreview m_tonePreview;
  int m_r = 255;
  int m_g = 255;
  int m_b = 255;
  int m_intensity = 170;
  bool m_toneInvalid{true};
  bool m_modified{false};
};


code:
#include "Core/CommonUI/ColorTonePicker.hpp"
#include "Core/Application.hpp"
#include "imgui.h"

bool ColorTonePicker::draw() {
  ImGui::BeginGroup();
  {
    ImGui::BeginGroup();
    {
      ImGui::SetCursorPosY(ImGui::GetCursorPos().y + 5.f);
      ImGui::TextUnformatted(trNOOP("Red:"));
      ImGui::SetCursorPosY(ImGui::GetCursorPos().y + 12.f);
      ImGui::TextUnformatted(trNOOP("Green:"));
      ImGui::SetCursorPosY(ImGui::GetCursorPos().y + 14.f);
      ImGui::TextUnformatted(trNOOP("Blue:"));
      ImGui::SetCursorPosY(ImGui::GetCursorPos().y + 12.f);
      ImGui::TextUnformatted(trNOOP("Gray:"));
    }
    ImGui::EndGroup();
    ImGui::SameLine();
    ImGui::BeginGroup();
    {
      ImGui::SetNextItemWidth(150);
      if (ImGui::SliderInt(std::format("##color_preview_red_slider_{}", reinterpret_cast<intptr_t>(this)).c_str(), &m_r, -255, 255)) {
        m_toneInvalid = m_modified = true;
      }
      ImGui::SameLine();
      ImGui::SetNextItemWidth(120);
      if (ImGui::InputInt("##color_preview_int_red", &m_r, 1, 100)) {
        m_toneInvalid = m_modified = true;
      }
      ImGui::SetNextItemWidth(150);
      if (ImGui::SliderInt(std::format("##color_preview_green_slider_{}", reinterpret_cast<intptr_t>(this)).c_str(), &m_g, -255, 255)) {
        m_toneInvalid = m_modified = true;
      }
      ImGui::SameLine();
      ImGui::SetNextItemWidth(120);
      if (ImGui::InputInt("##color_preview_int_green", &m_g, 1, 100)) {
        m_toneInvalid = m_modified = true;
      }
      ImGui::SetNextItemWidth(150);
      if (ImGui::SliderInt(std::format("##color_preview_blue_slider_{}", reinterpret_cast<intptr_t>(this)).c_str(), &m_b, -255, 255)) {
        m_toneInvalid = m_modified = true;
      }
      ImGui::SameLine();
      ImGui::SetNextItemWidth(120);
      if (ImGui::InputInt("##color_preview_int_blue", &m_b, 1, 100)) {
        m_toneInvalid = m_modified = true;
      }
      ImGui::SetNextItemWidth(150);
      if (ImGui::SliderInt(std::format("##color_preview_gray_slider_{}", reinterpret_cast<intptr_t>(this)).c_str(), &m_gray, 0, 255)) {
        m_toneInvalid = m_modified = true;
      }
      ImGui::SameLine();
      ImGui::SetNextItemWidth(120);
      if (ImGui::InputInt("##color_preview_int_gray", &m_gray, 1, 100)) {
        m_toneInvalid = m_modified = true;
      }
    }

    if (m_toneInvalid) {
      m_tonePreview.update(m_r / 255.f, m_g / 255.f, m_b / 255.f, m_gray / 255.f);
      m_toneInvalid = false;
    }

    ImGui::EndGroup();
    ImGui::SameLine();
    ImGui::BeginChild(std::format("##color_preview_child_{}", reinterpret_cast<intptr_t>(this)).c_str(), ImVec2(0, 0),
                      ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysAutoResize | ImGuiChildFlags_FrameStyle);
    { ImGui::Image(m_tonePreview, ImVec2{static_cast<float>(m_tonePreview.width()), static_cast<float>(m_tonePreview.height())}); }
    ImGui::EndChild();
  }
  ImGui::EndGroup();

  return m_modified;
}

void ColorTonePicker::setPreviewSize(int width, int height) {

  if (width == m_tonePreview.width() && height == m_tonePreview.height()) {
    return;
  }
  m_tonePreview.setSize(width, height);
}


code:
#pragma once

#include "Core/Graphics/ColorTonePreview.hpp"

/**
 * @name ColorTonePicker
 * Provides a common UI tool to manipulate color tone values
 */
class ColorTonePicker {
public:
  ColorTonePicker() = default;

  /**
   *
   * @param r Red channel shift value, [-255,255] range
   * @param g Green channel shift value, [-255,255] range
   * @param b Blue channel shift value, [-255,255] range
   * @param gray Grayscale intensity, [0,255] range
   */
  void setValues(const int r, const int g, const int b, const int gray) {
    m_r = r;
    m_g = g;
    m_b = b;
    m_gray = gray;
    m_modified = false;
    m_toneInvalid = true;
  }

  /**
   * Sets the base size of the preview image.
   * The values are scaled based on UI scale and applied automatically
   * @param width Unscaled width of the preview image
   * @param height Unscaled height of the preview image
   */
  void setPreviewSize(int width, int height);
  [[nodiscard]] int r() const { return m_r; }
  [[nodiscard]] int g() const { return m_g; }
  [[nodiscard]] int b() const { return m_b; }
  [[nodiscard]] int gray() const { return m_gray; }

  /**
   * Draws the ColorTonePicker elements.
   * @return True if any of the values were modified, false otherwise
   */
  bool draw();

private:
  ColorTonePreview m_tonePreview;
  int m_r = 0;
  int m_g = 0;
  int m_b = 0;
  int m_gray = 0;
  bool m_toneInvalid{true};
  bool m_modified{false};
};


code:
#include "Core/CommonUI/CreateNewProjectDialog.hpp"

#include "Core/Application.hpp"
#include "Core/ImGuiExt/ImGuiUtils.hpp"
#include "Core/Settings.hpp"

#include <filesystem>
#include <iostream>
#include <string_view>

#include <SDL3/SDL_dialog.h>

using namespace std::string_view_literals;

void CreateNewProjectDialog::setOpen(bool open) {
  IDialogController::setOpen(open);

  if (!Settings::instance()->projectBaseDirectory.empty()) {
    int i = 1;
    std::filesystem::path projectPath;
    do {
      ++i;
      projectPath = std::filesystem::path(Settings::instance()->projectBaseDirectory) / std::filesystem::path(std::format("Project{}", i));
    } while (is_directory(projectPath));
    m_gameTitle = m_projectName = std::format("Project{}", i);
  }
}

std::tuple<bool, bool> CreateNewProjectDialog::draw() {
  if (isOpen()) {
    ImGui::OpenPopup(m_name.c_str());
  }
  const ImVec2 center = ImGui::GetMainViewport()->GetCenter();
  ImGui::SetNextWindowPos(center, ImGuiCond_Always, ImVec2(0.5f, 0.5f));
  ImGui::SetNextWindowSize(ImVec2{894, 768}, ImGuiCond_Appearing);
  if (ImGui::BeginPopupModal(m_name.c_str(), &m_open, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings)) {
    ImGui::BeginGroup();
    {
      if (!Settings::instance()->rpgMakerMVLocation.empty() || !Settings::instance()->rpgMakerMZLocation.empty()) {
        ImGui::BeginGroup();
        {
          ImGui::BeginDisabled(Settings::instance()->rpgMakerMZLocation.empty());
          if (ImGui::RadioButton(trNOOP("RPG Maker MV"), m_projectType == ProjectType::RPGMV)) {
            m_projectType = ProjectType::RPGMV;
          }
          ImGui::EndDisabled();
          ImGui::SameLine();
          ImGui::BeginDisabled(Settings::instance()->rpgMakerMZLocation.empty());
          if (ImGui::RadioButton(trNOOP("RPG Maker MZ"), m_projectType == ProjectType::RPGMZ)) {
            m_projectType = ProjectType::RPGMZ;
          }
          // TODO: corescripts combo
          ImGui::EndDisabled();
          char name[256]{};
          strncpy(name, m_projectName.c_str(), sizeof(name));
          if (ImGui::LabelOverLineEdit("##project_name_label", "Name:", name, sizeof(name), 0.f, nullptr, ImGuiInputTextFlags_None)) {
            m_projectName = name;
          }
          ImGui::SameLine();
          ImGui::SetCursorPosY(ImGui::GetCursorPosY() - ImGui::GetStyle().FramePadding.y);
          char title[4096]{};
          strncpy(title, m_gameTitle.c_str(), sizeof(title));
          if (ImGui::LabelOverLineEdit("##project_title_label", "Game Title:", title, sizeof(title), 0.f, nullptr, ImGuiInputTextFlags_None)) {
            m_gameTitle = title;
          }
        }
        ImGui::EndGroup();
        ImGui::BeginGroup();
        {
          char location[4096];
          strncpy(location, Settings::instance()->projectBaseDirectory.c_str(), 4096);
          if (ImGui::LabelOverLineEdit("##project_location_label", "Location:", location, 4096, 0.f, nullptr, ImGuiInputTextFlags_None)) {
            Settings::instance()->projectBaseDirectory = location;
          }
          ImGui::SameLine();
          ImGui::SetCursorPosY(ImGui::GetCursorPosY() - ImGui::GetStyle().FramePadding.y);
          ImGui::BeginGroup();
          {
            ImGui::NewLine();
            ImGui::SetCursorPosY(ImGui::GetCursorPosY() + ImGui::GetStyle().FramePadding.y);
            if (ImGui::Button("Choose...")) {
              SDL_ShowOpenFolderDialog(
                  [](void*, const char* const* fileList, int filter) {
                    if (!fileList || !*fileList || filter >= 1) {
                      return;
                    }
                    const std::filesystem::path path{fileList[0]};
                    Settings::instance()->projectBaseDirectory = absolute(path).generic_string();
                  },
                  nullptr, App::APP->getWindow()->getNativeWindow(), !Settings::instance()->projectBaseDirectory.empty() ? Settings::instance()->projectBaseDirectory.c_str() : nullptr, false);
            }
          }
          ImGui::EndGroup();
          ImGui::Checkbox("Copy example project from RPG Maker", &m_copyExample);
        }
        ImGui::EndGroup();
      } else {
        ImGui::TextUnformatted(
            trNOOP("RPG Maker Locations have not been specified!\n"
                   "You need to specify either an RPG Maker MV or MZ directory.\n"
                   "You can, optionally, specify both."));
      }
    }
    ImGui::EndGroup();
    ImGui::BeginGroup();
    {
      if (ImGui::Button("OK")) {
        m_confirmed = true;
        m_open = false;
        ImGui::CloseCurrentPopup();
      }
      ImGui::SameLine();
      if (ImGui::Button("Cancel")) {
        m_confirmed = false;
        m_open = false;
        ImGui::CloseCurrentPopup();
      }
    }
    ImGui::EndGroup();
    ImGui::EndPopup();
  }

  return {!m_open, m_confirmed};
}


code:
#pragma once

#include "Core/CommonUI/IDialogController.hpp"
class CreateNewProjectDialog : public IDialogController {
public:
  enum class ProjectType {
    RPGMV,
    RPGMZ,
  };
  explicit CreateNewProjectDialog(const std::string_view name)
  : IDialogController(name) {}
  std::tuple<bool, bool> draw() override;

  void setOpen(bool open) override;
  std::string_view projectName() const { return m_projectName; }
  std::string_view gameTitle() const { return m_gameTitle; }
  bool copyExample() const { return m_copyExample; }

  ProjectType projectType() const { return m_projectType; }

private:
  std::string m_projectName;
  std::string m_gameTitle;
  ProjectType m_projectType{ProjectType::RPGMV};
  bool m_copyExample{true};
};


code:
#include "Directory.hpp"

#include "Core/CommonUI/ImagePicker.hpp"
#include "Database/Database.hpp"

Directory::Directory(std::string_view path, std::string_view filter, std::string_view selectedPath) {
  m_path = Database::instance()->basePath + path.data();
  if (!selectedPath.empty()) {
    // Navigate to contents
    selectedPath = selectedPath.substr(0, selectedPath.find_last_of('/'));
    if (!path.ends_with('/')) {
      m_currentPath.assign(Database::instance()->basePath + path.data() + "/" + selectedPath.data());
    } else {
      m_currentPath.assign(Database::instance()->basePath + path.data() + selectedPath.data());
    }
    if (!filter.empty() && !is_directory(m_currentPath)) {
      m_currentPath.replace_extension(filter);
    }
  } else {
    m_currentPath.assign(m_path);
  }

  /* Find the first valid directory */
  while (!is_directory(m_currentPath) && !m_currentPath.empty()) {
    m_currentPath = m_currentPath.parent_path();
  }

  try {
    m_isParentDir = equivalent(m_path, m_currentPath);
  } catch (const std::exception&) {
    /* If we encounter an exception assume something crazy happened and use the specified path as the current path */
    m_isParentDir = false;
    m_currentPath = m_path;
  }
  setDirectoryContents(filter);
  setSubDirectories();

  // TODO: *nix path handling
}

void Directory::setDirectoryContents(std::string_view filter) {
  m_contents.clear();
  for (const auto& entry : fs::directory_iterator(m_currentPath)) {
    if (!filter.empty() && entry.path().extension().compare(filter) != 0) {
      continue;
    }

    auto filename = entry.path().filename();
    filename = filename.replace_extension();
    const auto path = filename.generic_string();
    m_extFilter = filter;
    m_contents.emplace_back(path);
  }
}
void Directory::setSubDirectories() {
  m_subDirectories.clear();
  for (const auto& entry : fs::directory_iterator(m_currentPath)) {
    if (!entry.is_directory()) {
      continue;
    }
    const auto path = entry.path().filename().generic_string();
    m_subDirectories.emplace_back(path);
  }
}
void Directory::setDirectory(int index) {
  m_currentPath.append(m_subDirectories[index]);
  m_pathPrefix += m_pathPrefix.empty() ? m_subDirectories[index] : '/' + m_subDirectories[index];
  m_isParentDir = equivalent(m_path, m_currentPath);
  setDirectoryContents(m_extFilter);
  setSubDirectories();
}
void Directory::home() {
  m_currentPath = m_path;
  m_isParentDir = equivalent(m_path, m_currentPath);
  setDirectoryContents(m_extFilter);
  setSubDirectories();
}
void Directory::moveUp() {
  if (m_pathPrefix.find('/') == std::string::npos) {
    m_pathPrefix.clear();
  } else {
    m_pathPrefix = m_pathPrefix.substr(0, m_pathPrefix.find_last_of('/'));
  }
  m_currentPath = m_currentPath.parent_path();
  m_isParentDir = equivalent(m_path, m_currentPath);
  setDirectoryContents(m_extFilter);
  setSubDirectories();
}
bool Directory::isParentDirectory() const { return m_isParentDir; }

std::vector<std::string>& Directory::getDirectories() { return m_subDirectories; }
std::string Directory::getFileName(std::string name) {
  if (name.find('/') != std::string::npos) {
    name = name.substr(name.find_last_of('/') + 1, name.size());
  }
  return name;
}

std::string& Directory::getPathPrefix() { return m_pathPrefix; }
const std::string& Directory::getPathPrefix() const { return m_pathPrefix; }

std::string& Directory::getExt() { return m_extFilter; }
const std::string& Directory::getExt() const { return m_extFilter; }

std::vector<std::string>& Directory::getDirectoryContents() { return m_contents; }


code:
#pragma once

#include "Core/CommonUI/Directory.hpp"
#include "Core/DirectoryMonitor.hpp"

#include <string>
#include <vector>

namespace fs = std::filesystem;
struct Directory {
  Directory(std::string_view path, std::string_view filter, std::string_view selectedPath);

  std::vector<std::string>& getDirectoryContents();
  std::vector<std::string>& getDirectories();
  std::string getFileName(std::string name);

  std::string& getPathPrefix();
  const std::string& getPathPrefix() const;

  std::string& getExt();
  const std::string& getExt() const;

  void setSubDirectories();
  void setDirectoryContents(std::string_view filter);
  void setDirectory(int index);

  void home();
  void moveUp();
  bool isParentDirectory() const;

private:
  bool m_isParentDir{true};

  std::filesystem::path m_path;
  std::filesystem::path m_currentPath;

  std::string m_pathPrefix;
  std::string m_extFilter;

  std::vector<std::string> m_contents;
  std::vector<std::string> m_subDirectories;
};


code:
#include "Core/CommonUI/EffectsEditor.hpp"
#include "Core/DatabaseEditor.hpp"

#include <format>

#include "imgui.h"

using namespace std::string_view_literals;

static constexpr auto EffectsEditorPopupId = "Effects Edit"sv;

template <>
inline int ObjectPicker<std::optional<CommonEvent>>::getId(const std::optional<CommonEvent>& value) {
  return value ? value->id() : 0;
}

static const std::string InvalidCommonEvent = "Invalid Common Event";
template <>
inline const std::string& ObjectPicker<std::optional<CommonEvent>>::getName(const std::optional<CommonEvent>& value) {
  return value ? value->name() : InvalidCommonEvent;
}

void EffectsEditor::draw(DatabaseEditor* dbEditor) {
  if (m_effects == nullptr) {
    return;
  }

  ImGui::BeginGroup();
  {
    ImGui::SeparatorText("Effects");
    if (ImGui::BeginTable("##orpg_effects_editor", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_ScrollY | ImGuiTableFlags_ScrollY)) {
      ImGui::TableSetupColumn("Type");
      ImGui::TableSetupColumn("Content");
      ImGui::TableHeadersRow();

      for (auto& effect : *m_effects) {
        ImGui::PushID(&effect);
        ImGui::TableNextRow();
        if (ImGui::TableNextColumn()) {
          if (ImGui::Selectable(std::format("{}##effect_{}", DecodeEnumName(effect.code()), reinterpret_cast<uintptr_t>(&effect)).c_str(), m_selectedEffect == &effect,
                                ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowDoubleClick)) {
            m_selectedEffect = &effect;
            if (ImGui::GetMouseClickedCount(ImGuiMouseButton_Left) >= 2) {
              m_isNewEntry = false;
              m_updateTab = true;
              m_tempEffectAssigned = false;
              ImGui::OpenPopup(EffectsEditorPopupId.data());
            }
          }
          if (ImGui::IsKeyPressed(ImGuiKey_Delete) && m_selectedEffect == &effect && m_deletingEffect == nullptr) {
            m_deletingEffect = &effect;
            m_selectedEffect = nullptr;
          }
          drawPopup(dbEditor);
        }
        if (ImGui::TableNextColumn()) {
          switch (effect.code()) {
          case EffectCode::Recover_HP: {
            ImGui::Text("%i%% + %i", static_cast<int>(effect.value1() * 100), effect.value2());
            break;
          }
          case EffectCode::Recover_MP: {
            ImGui::Text("%i%% + %i", static_cast<int>(effect.value1() * 100), effect.value2());
            break;
          }
          case EffectCode::Gain_TP: {
            ImGui::Text("%i", static_cast<int>(effect.value1()));
            break;
          }
          case EffectCode::Add_State: {
            ImGui::Text("%s %i%%", Database::instance()->stateNameOrId(effect.dataId()).c_str(), static_cast<int>(effect.value1() * 100));
            break;
          }
          case EffectCode::Remove_State: {
            ImGui::Text("%s %i%%", Database::instance()->stateNameOrId(effect.dataId()).c_str(), static_cast<int>(effect.value1() * 100));
            break;
          }
          case EffectCode::Add_Buff: {
            ImGui::Text("%s + %i turns", DecodeEnumName(static_cast<ParameterSource>(effect.dataId())).c_str(), static_cast<int>(effect.value1()));
            break;
          }
          case EffectCode::Add_Debuff: {
            ImGui::Text("%s + %i turns", DecodeEnumName(static_cast<ParameterSource>(effect.dataId())).c_str(), static_cast<int>(effect.value1()));
            break;
          }
          case EffectCode::Remove_Buff: {
            ImGui::Text("%s", DecodeEnumName(static_cast<ParameterSource>(effect.dataId())).c_str());
            break;
          }
          case EffectCode::Remove_Debuff: {
            ImGui::Text("%s", DecodeEnumName(static_cast<ParameterSource>(effect.dataId())).c_str());
            break;
          }
          case EffectCode::Special_Effect: {
            ImGui::Text("%s", DecodeEnumName(static_cast<SpecialEffectSource>(effect.dataId())).c_str());
            break;
          }
          case EffectCode::Grow: {
            ImGui::Text("%s + %i", DecodeEnumName(static_cast<ParameterSource>(effect.dataId())).c_str(), static_cast<int>(effect.value1()));
            break;
          }
          case EffectCode::Learn_Skill: {
            ImGui::Text("%s", Database::instance()->skillNameOrId(effect.dataId()).c_str());
            break;
          }
          case EffectCode::Common_Event: {
            ImGui::Text("%s", Database::instance()->commonEventNameOrId(effect.dataId()).c_str());
            break;
          }
          default:
            ImGui::Text("%i %g", effect.dataId(), effect.value1());
            break;
          }
        }

        ImGui::PopID();
      }

      /* Dummy entry for adding new effects */
      ImGui::TableNextRow();
      if (ImGui::TableNextColumn()) {
        if (ImGui::Selectable("##effects_editor_effect_dummy", false, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowDoubleClick)) {
          if (ImGui::GetMouseClickedCount(ImGuiMouseButton_Left) >= 2) {
            m_tempEffect = Effect();
            m_isNewEntry = true;
            m_updateTab = true;
            m_tempEffectAssigned = false;
            m_selectedEffect = &m_tempEffect;
            ImGui::OpenPopup(EffectsEditorPopupId.data());
          }
        }
      }
      drawPopup(dbEditor);
      ImGui::EndTable();
    }
  }
  ImGui::EndGroup();

  if (m_deletingEffect) {
    std::erase_if(*m_effects, [&](const auto& trait) { return &trait == m_deletingEffect; });
    m_deletingEffect = nullptr;
  }
}

void EffectsEditor::drawPopup(DatabaseEditor* dbEditor) {
  if (m_selectedEffect == nullptr || m_effects == nullptr) {
    return;
  }
  if (ImGui::BeginPopupModal(EffectsEditorPopupId.data(), nullptr, ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_AlwaysAutoResize)) {
    if (!m_isNewEntry && !m_tempEffectAssigned) {
      // We're not a new entry so copy our values so we can restore them if we cancel
      m_tempEffect = *m_selectedEffect;
      m_tempEffectAssigned = true;
    }
    ImGui::BeginGroup();
    {
      if (ImGui::BeginTabBar("##orpg_effects_edit_tab_bar")) {
        // Tab 1 - Recover HP, Recover MP, Gain TP
        if (ImGui::BeginTabItem("Rate", nullptr,
                                m_selectedEffect->code() >= EffectCode::Recover_HP && m_selectedEffect->code() <= EffectCode::Gain_TP && m_updateTab ? ImGuiTabItemFlags_SetSelected : 0)) {
          ImGui::BeginChild("##orpg_effects_rate_left_child", ImVec2(0, 0), ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysAutoResize,
                            ImGuiWindowFlags_NoBackground);
          {

            if (ImGui::RadioButton("Recover HP", m_selectedEffect->code() == EffectCode::Recover_HP)) {
              if (m_selectedEffect->code() != EffectCode::Recover_HP) {
                m_selectedEffect->setDataId(0);
                m_selectedEffect->setValue1(1);
                m_selectedEffect->setValue2(0);
                m_selectedEffect->setCode(EffectCode::Recover_HP);
              }
            }
            ImGui::NewLine();
            ImGui::Spacing();
            ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 10.f);
            if (ImGui::RadioButton("Recover MP", m_selectedEffect->code() == EffectCode::Recover_MP)) {
              if (m_selectedEffect->code() != EffectCode::Recover_MP) {
                m_selectedEffect->setDataId(0);
                m_selectedEffect->setValue1(1);
                m_selectedEffect->setValue2(0);
                m_selectedEffect->setCode(EffectCode::Recover_MP);
              }
            }
            ImGui::NewLine();
            ImGui::Spacing();
            ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 5.f);
            if (ImGui::RadioButton("Gain TP", m_selectedEffect->code() == EffectCode::Gain_TP)) {
              if (m_selectedEffect->code() != EffectCode::Gain_TP) {
                m_selectedEffect->setDataId(0);
                m_selectedEffect->setValue1(1);
                m_selectedEffect->setValue2(0);
                m_selectedEffect->setCode(EffectCode::Gain_TP);
              }
            }
          }
          ImGui::EndChild();
          ImGui::SameLine();
          ImGui::BeginChild("##orpg_effects_rate_right_child", ImVec2(0, 0), ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysAutoResize,
                            ImGuiWindowFlags_NoBackground);
          {
            // Recover HP
            ImGui::BeginDisabled(m_selectedEffect->code() != EffectCode::Recover_HP);
            {
              int tmpInt = m_selectedEffect->code() == EffectCode::Recover_HP ? static_cast<int>(m_selectedEffect->value1() * 100) : 0;
              if (ImGui::SliderInt("##effect_recover_hp_value", &tmpInt, -100, 100, m_selectedEffect->code() == EffectCode::Recover_HP ? "%d%%" : "")) {
                m_selectedEffect->setValue1(tmpInt / 100.0);
              }
              int tmpInt2 = m_selectedEffect->code() == EffectCode::Recover_HP ? static_cast<int>(m_selectedEffect->value2()) : 0;
              ImGui::SetNextItemWidth(230);
              if (ImGui::InputInt("##effect_recover_hp_flat_value", &tmpInt2)) {
                m_selectedEffect->setValue2(tmpInt);
              }
              ImGui::SameLine();
              ImGui::Text("(flat)");
            }
            ImGui::EndDisabled();
            // Recover MP
            ImGui::BeginDisabled(m_selectedEffect->code() != EffectCode::Recover_MP);
            {
              int tmpInt = m_selectedEffect->code() == EffectCode::Recover_MP ? static_cast<int>(m_selectedEffect->value1() * 100) : 0;
              if (ImGui::SliderInt("##effect_recover_mp_value", &tmpInt, -100, 100, m_selectedEffect->code() == EffectCode::Recover_MP ? "%d%%" : "")) {
                m_selectedEffect->setValue1(tmpInt / 100.0);
              }
              int tmpInt2 = m_selectedEffect->code() == EffectCode::Recover_HP ? static_cast<int>(m_selectedEffect->value2()) : 0;
              ImGui::SetNextItemWidth(230);
              if (ImGui::InputInt("##effect_recover_mp_flat_value", &tmpInt2)) {
                m_selectedEffect->setValue2(tmpInt);
              }
              ImGui::SameLine();
              ImGui::Text("(flat)");
            }
            ImGui::EndDisabled();
            // Gain TP
            ImGui::BeginDisabled(m_selectedEffect->code() != EffectCode::Gain_TP);
            {
              int tmpInt = m_selectedEffect->code() == EffectCode::Gain_TP ? static_cast<int>(m_selectedEffect->value1()) : 0;
              ImGui::SetNextItemWidth(230);
              if (ImGui::InputInt("##effect_gain_tp", &tmpInt)) {
                if (tmpInt > 100) {
                  tmpInt = 100;
                } else if (tmpInt < 0) {
                  tmpInt = 0;
                }
                m_selectedEffect->setValue1(tmpInt);
              }
            }
            ImGui::EndDisabled();
          }
          ImGui::EndChild();
          ImGui::EndTabItem();
        }
        // Tab 2 - Add State, Remove State
        if (ImGui::BeginTabItem("State", nullptr,
                                m_selectedEffect->code() >= EffectCode::Add_State && m_selectedEffect->code() <= EffectCode::Remove_State && m_updateTab ? ImGuiTabItemFlags_SetSelected : 0)) {
          ImGui::BeginChild("##orpg_effects_state_left_child", ImVec2(0, 0), ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysAutoResize,
                            ImGuiWindowFlags_NoBackground);
          {

            if (ImGui::RadioButton("Add State", m_selectedEffect->code() == EffectCode::Add_State)) {
              if (m_selectedEffect->code() != EffectCode::Add_State) {
                m_selectedEffect->setDataId(1);
                m_selectedEffect->setValue1(1);
                m_selectedEffect->setValue2(0);
                m_selectedEffect->setCode(EffectCode::Add_State);
              }
            }
            ImGui::NewLine();
            ImGui::Spacing();
            ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 10.f);
            if (ImGui::RadioButton("Remove State", m_selectedEffect->code() == EffectCode::Remove_State)) {
              if (m_selectedEffect->code() != EffectCode::Remove_State) {
                m_selectedEffect->setDataId(1);
                m_selectedEffect->setValue1(1);
                m_selectedEffect->setValue2(0);
                m_selectedEffect->setCode(EffectCode::Remove_State);
              }
            }
          }
          ImGui::EndChild();
          ImGui::SameLine();
          ImGui::BeginChild("##orpg_effects_state_right_child", ImVec2(0, 0), ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysAutoResize,
                            ImGuiWindowFlags_NoBackground);
          {
            // Add State
            ImGui::BeginDisabled(m_selectedEffect->code() != EffectCode::Add_State);
            {
              // State Button - Picker
              if (ImGui::Button(m_selectedEffect->code() == EffectCode::Add_State ? Database::instance()->stateNameOrId(m_selectedEffect->dataId()).c_str() : "##effects_state_add_selection",
                                ImVec2{ImGui::GetContentRegionAvail().x, 0})) {
                m_statePicker.emplace("States"sv, Database::instance()->states.states(), m_selectedEffect->dataId());
                m_statePicker->setOpen(true);
              }
              // State %
              ImGui::SetNextItemWidth(250);
              int tmpInt = m_selectedEffect->code() == EffectCode::Add_State ? static_cast<int>(m_selectedEffect->value1() * 100) : 0;
              if (ImGui::SliderInt("##effect_state_add_value", &tmpInt, 0, 100, m_selectedEffect->code() == EffectCode::Add_State ? "%d%%" : "")) {
                m_selectedEffect->setValue1(tmpInt / 100.0);
              }
            }
            ImGui::EndDisabled();

            // Remove State
            ImGui::BeginDisabled(m_selectedEffect->code() != EffectCode::Remove_State);
            {
              // State Button - Picker
              if (ImGui::Button(m_selectedEffect->code() == EffectCode::Remove_State ? Database::instance()->stateNameOrId(m_selectedEffect->dataId()).c_str() : "##effects_state_remove_selection",
                                ImVec2{ImGui::GetContentRegionAvail().x, 0})) {
                m_statePicker.emplace("States"sv, Database::instance()->states.states(), m_selectedEffect->dataId());
                m_statePicker->setOpen(true);
              }
              // State %;
              ImGui::SetNextItemWidth(250);
              int tmpInt = m_selectedEffect->code() == EffectCode::Remove_State ? static_cast<int>(m_selectedEffect->value1() * 100) : 0;
              if (ImGui::SliderInt("##effect_state_remove_value", &tmpInt, 0, 100, m_selectedEffect->code() == EffectCode::Remove_State ? "%d%%" : "")) {
                m_selectedEffect->setValue1(tmpInt / 100.0);
              }
            }
            ImGui::EndDisabled();
          }
          ImGui::EndChild();
          ImGui::EndTabItem();
        }

        // Tab 3 - Add Buff, Add Debuff, Remove Buff, Remove Debuff

        if (ImGui::BeginTabItem("Params", nullptr,
                                m_selectedEffect->code() >= EffectCode::Add_Buff && m_selectedEffect->code() <= EffectCode::Remove_Debuff && m_updateTab ? ImGuiTabItemFlags_SetSelected : 0)) {
          ImGui::BeginChild("##orpg_effects_params_left_child", ImVec2(0, 0), ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysAutoResize,
                            ImGuiWindowFlags_NoBackground);
          {

            if (ImGui::RadioButton("Add Buff", m_selectedEffect->code() == EffectCode::Add_Buff)) {
              if (m_selectedEffect->code() != EffectCode::Add_Buff) {
                m_selectedEffect->setDataId(0);
                m_selectedEffect->setValue1(5);
                m_selectedEffect->setValue2(0);
                m_selectedEffect->setCode(EffectCode::Add_Buff);
              }
            }
            ImGui::NewLine();
            ImGui::Spacing();
            ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 2.f);
            if (ImGui::RadioButton("Add Debuff", m_selectedEffect->code() == EffectCode::Add_Debuff)) {
              if (m_selectedEffect->code() != EffectCode::Add_Debuff) {
                m_selectedEffect->setDataId(0);
                m_selectedEffect->setValue1(5);
                m_selectedEffect->setValue2(0);
                m_selectedEffect->setCode(EffectCode::Add_Debuff);
              }
            }
            ImGui::NewLine();
            ImGui::Spacing();
            ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 10.f);
            if (ImGui::RadioButton("Remove Buff", m_selectedEffect->code() == EffectCode::Remove_Buff)) {
              if (m_selectedEffect->code() != EffectCode::Remove_Buff) {
                m_selectedEffect->setDataId(0);
                m_selectedEffect->setValue1(1);
                m_selectedEffect->setValue2(0);
                m_selectedEffect->setCode(EffectCode::Remove_Buff);
              }
            }
            ImGui::Spacing();
            // ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 5.f);
            if (ImGui::RadioButton("Remove Debuff", m_selectedEffect->code() == EffectCode::Remove_Debuff)) {
              if (m_selectedEffect->code() != EffectCode::Remove_Debuff) {
                m_selectedEffect->setDataId(0);
                m_selectedEffect->setValue1(1);
                m_selectedEffect->setValue2(0);
                m_selectedEffect->setCode(EffectCode::Remove_Debuff);
              }
            }
          }
          ImGui::EndChild();
          ImGui::SameLine();
          ImGui::BeginChild("##orpg_effects_params_right_child", ImVec2(0, 0), ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysAutoResize,
                            ImGuiWindowFlags_NoBackground);
          {
            // Add_Buff
            ImGui::BeginDisabled(m_selectedEffect->code() != EffectCode::Add_Buff);
            {
              if (ImGui::BeginCombo("##effect_addbuff_combo",
                                    m_selectedEffect->code() == EffectCode::Add_Buff ? DecodeEnumName(static_cast<ParameterSource>(m_selectedEffect->dataId())).c_str() : "")) {
                for (auto v : magic_enum::enum_values<ParameterSource>()) {
                  if (ImGui::Selectable(DecodeEnumName(v).c_str(), static_cast<ParameterSource>(m_selectedEffect->dataId()) == v)) {
                    m_selectedEffect->setDataId(static_cast<int>(v));
                  }
                }
                ImGui::EndCombo();
              }
              int tmpInt = m_selectedEffect->code() == EffectCode::Add_Buff ? m_selectedEffect->value1() : 0;
              ImGui::SetNextItemWidth(230);
              if (ImGui::InputInt("##effect_addbuff_value", &tmpInt)) {
                if (tmpInt > 1000) {
                  tmpInt = 1000;
                } else if (tmpInt < 1) {
                  tmpInt = 1;
                }
                m_selectedEffect->setValue1(tmpInt);
              }
              ImGui::SameLine();
              ImGui::Text("turns");
            }
            ImGui::EndDisabled();
            // Add Debuff
            ImGui::BeginDisabled(m_selectedEffect->code() != EffectCode::Add_Debuff);
            {
              if (ImGui::BeginCombo("##effect_adddebuff_combo",
                                    m_selectedEffect->code() == EffectCode::Add_Debuff ? DecodeEnumName(static_cast<ParameterSource>(m_selectedEffect->dataId())).c_str() : "")) {
                for (auto v : magic_enum::enum_values<ParameterSource>()) {
                  if (ImGui::Selectable(DecodeEnumName(v).c_str(), static_cast<ParameterSource>(m_selectedEffect->dataId()) == v)) {
                    m_selectedEffect->setDataId(static_cast<int>(v));
                  }
                }
                ImGui::EndCombo();
              }
              int tmpInt = m_selectedEffect->code() == EffectCode::Add_Debuff ? m_selectedEffect->value1() : 0;
              ImGui::SetNextItemWidth(230);
              if (ImGui::InputInt("##effect_adddebuff_value", &tmpInt)) {
                if (tmpInt > 1000) {
                  tmpInt = 1000;
                } else if (tmpInt < 1) {
                  tmpInt = 1;
                }
                m_selectedEffect->setValue1(tmpInt);
              }
              ImGui::SameLine();
              ImGui::Text("turns");
            }
            ImGui::EndDisabled();
            // Remove Buff
            ImGui::BeginDisabled(m_selectedEffect->code() != EffectCode::Remove_Buff);
            {
              if (ImGui::BeginCombo("##effect_removebuff_combo",
                                    m_selectedEffect->code() == EffectCode::Remove_Buff ? DecodeEnumName(static_cast<ParameterSource>(m_selectedEffect->dataId())).c_str() : "")) {
                for (auto v : magic_enum::enum_values<ParameterSource>()) {
                  if (ImGui::Selectable(DecodeEnumName(v).c_str(), static_cast<ParameterSource>(m_selectedEffect->dataId()) == v)) {
                    m_selectedEffect->setDataId(static_cast<int>(v));
                  }
                }
                ImGui::EndCombo();
              }
            }
            ImGui::EndDisabled();
            // Remove Debuff
            ImGui::BeginDisabled(m_selectedEffect->code() != EffectCode::Remove_Debuff);
            {
              if (ImGui::BeginCombo("##effect_removedebuff_combo",
                                    m_selectedEffect->code() == EffectCode::Remove_Debuff ? DecodeEnumName(static_cast<ParameterSource>(m_selectedEffect->dataId())).c_str() : "")) {
                for (auto v : magic_enum::enum_values<ParameterSource>()) {
                  if (ImGui::Selectable(DecodeEnumName(v).c_str(), static_cast<ParameterSource>(m_selectedEffect->dataId()) == v)) {
                    m_selectedEffect->setDataId(static_cast<int>(v));
                  }
                }
                ImGui::EndCombo();
              }
            }
            ImGui::EndDisabled();
          }
          ImGui::EndChild();
          ImGui::EndTabItem();
        }

        // Tab 4 - Special Effect, Grow, Learn Skill, Common Event

        if (ImGui::BeginTabItem("Other", nullptr,
                                m_selectedEffect->code() >= EffectCode::Special_Effect && m_selectedEffect->code() <= EffectCode::Common_Event && m_updateTab ? ImGuiTabItemFlags_SetSelected : 0)) {
          ImGui::BeginChild("##orpg_effects_params_left_child", ImVec2(0, 0), ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysAutoResize,
                            ImGuiWindowFlags_NoBackground);
          {

            if (ImGui::RadioButton("Special Effect", m_selectedEffect->code() == EffectCode::Special_Effect)) {
              if (m_selectedEffect->code() != EffectCode::Special_Effect) {
                m_selectedEffect->setDataId(0);
                m_selectedEffect->setValue1(1);
                m_selectedEffect->setValue2(0);
                m_selectedEffect->setCode(EffectCode::Special_Effect);
              }
            }
            ImGui::Spacing();
            if (ImGui::RadioButton("Add Grow", m_selectedEffect->code() == EffectCode::Grow)) {
              if (m_selectedEffect->code() != EffectCode::Grow) {
                m_selectedEffect->setDataId(0);
                m_selectedEffect->setValue1(1);
                m_selectedEffect->setValue2(0);
                m_selectedEffect->setCode(EffectCode::Grow);
              }
            }
            ImGui::NewLine();
            ImGui::Spacing();
            ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 3.f);
            if (ImGui::RadioButton("Learn Skill", m_selectedEffect->code() == EffectCode::Learn_Skill)) {
              if (m_selectedEffect->code() != EffectCode::Learn_Skill) {
                m_selectedEffect->setDataId(1);
                m_selectedEffect->setValue1(1);
                m_selectedEffect->setValue2(0);
                m_selectedEffect->setCode(EffectCode::Learn_Skill);
              }
            }
            ImGui::Spacing();
            ImGui::SetCursorPosY(ImGui::GetCursorPosY() - 3.f);
            if (ImGui::RadioButton("Common Event", m_selectedEffect->code() == EffectCode::Common_Event)) {
              if (m_selectedEffect->code() != EffectCode::Common_Event) {
                m_selectedEffect->setDataId(1);
                m_selectedEffect->setValue1(1);
                m_selectedEffect->setValue2(0);
                m_selectedEffect->setCode(EffectCode::Common_Event);
              }
            }
          }
          ImGui::EndChild();
          ImGui::SameLine();
          ImGui::BeginChild("##orpg_effects_params_right_child", ImVec2(0, 0), ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysAutoResize,
                            ImGuiWindowFlags_NoBackground);
          {
            // Special_Effect
            ImGui::BeginDisabled(m_selectedEffect->code() != EffectCode::Special_Effect);
            {
              if (ImGui::BeginCombo("##effect_special_effect_combo",
                                    m_selectedEffect->code() == EffectCode::Special_Effect ? DecodeEnumName(static_cast<SpecialEffectSource>(m_selectedEffect->dataId())).c_str() : "")) {
                for (auto v : magic_enum::enum_values<SpecialEffectSource>()) {
                  if (ImGui::Selectable(DecodeEnumName(v).c_str(), static_cast<SpecialEffectSource>(m_selectedEffect->dataId()) == v)) {
                    m_selectedEffect->setDataId(static_cast<int>(v));
                  }
                }
                ImGui::EndCombo();
              }
            }
            ImGui::EndDisabled();
            // Grow
            ImGui::BeginDisabled(m_selectedEffect->code() != EffectCode::Grow);
            {
              if (ImGui::BeginCombo("##effect_grow_combo", m_selectedEffect->code() == EffectCode::Grow ? DecodeEnumName(static_cast<ParameterSource>(m_selectedEffect->dataId())).c_str() : "")) {
                for (auto v : magic_enum::enum_values<ParameterSource>()) {
                  if (ImGui::Selectable(DecodeEnumName(v).c_str(), static_cast<ParameterSource>(m_selectedEffect->dataId()) == v)) {
                    m_selectedEffect->setDataId(static_cast<int>(v));
                  }
                }
                ImGui::EndCombo();
              }
              int tmpInt = m_selectedEffect->code() == EffectCode::Grow ? m_selectedEffect->value1() : 0;
              ImGui::SetNextItemWidth(230);
              if (ImGui::InputInt("##effect_grow_value", &tmpInt)) {
                if (tmpInt > 1000) {
                  tmpInt = 1000;
                } else if (tmpInt < 1) {
                  tmpInt = 1;
                }
                m_selectedEffect->setValue1(tmpInt);
              }
            }
            ImGui::EndDisabled();
            // Learn Skill
            ImGui::BeginDisabled(m_selectedEffect->code() != EffectCode::Learn_Skill);
            {
              // Skill Button - Picker
              if (ImGui::Button(m_selectedEffect->code() == EffectCode::Learn_Skill ? Database::instance()->skillNameOrId(m_selectedEffect->dataId()).c_str() : "##effects_learn_skill_selection",
                                ImVec2{ImGui::GetContentRegionAvail().x, 0})) {
                m_skillPicker.emplace("Skill"sv, Database::instance()->skills.skills(), m_selectedEffect->dataId());
                m_skillPicker->setOpen(true);
              }
            }
            ImGui::EndDisabled();
            // Common Event
            ImGui::BeginDisabled(m_selectedEffect->code() != EffectCode::Common_Event);
            {
              // Common Event Button - Picker
              if (ImGui::Button(m_selectedEffect->code() == EffectCode::Common_Event ? Database::instance()->commonEventNameOrId(m_selectedEffect->dataId()).c_str()
                                                                                : "##effects_common_event_selection",
                                ImVec2{ImGui::GetContentRegionAvail().x, 0})) {

                m_commonEventPicker.emplace("Common Event"sv, Database::instance()->commonEvents.events(), m_selectedEffect->dataId());
                m_commonEventPicker->setOpen(true);
              }
            }
            ImGui::EndDisabled();
          }
          ImGui::EndChild();
          ImGui::EndTabItem();
        }
      }
      ImGui::EndTabBar();
      ImGui::EndGroup();
    }
    ImGui::Separator();
    ImGui::BeginGroup();
    {
      if (ImGui::Button("OK")) {
        if (m_isNewEntry) {
          m_effects->push_back(m_tempEffect);
        }
        m_isNewEntry = false;
        ImGui::CloseCurrentPopup();
      }
      ImGui::SameLine();
      if (ImGui::Button("Cancel")) {
        if (!m_isNewEntry) {
          // Restore values to their unmodified state;
          *m_selectedEffect = m_tempEffect;
        }
        m_isNewEntry = false;
        ImGui::CloseCurrentPopup();
      }
    }
    ImGui::EndGroup();

    if (m_commonEventPicker) {
      if (auto [closed, confirmed] = m_commonEventPicker->draw(); closed) {
        if (confirmed) {
          m_selectedEffect->setDataId(m_commonEventPicker->selection());
        }
        m_commonEventPicker.reset();
      }
    }

    if (m_skillPicker) {
      if (auto [closed, confirmed] = m_skillPicker->draw(); closed) {
        if (confirmed) {
          m_selectedEffect->setDataId(m_skillPicker->selection());
        }
        m_skillPicker.reset();
      }
    }
    ImGui::EndPopup();
  }
  m_updateTab = false;
}


code:
#pragma once

#include "Core/CommonUI/ObjectPicker.hpp"
#include "Database/CommonEvents.hpp"
#include "Database/Skills.hpp"
#include "Database/States.hpp"

#include <vector>

class Effect;
struct DatabaseEditor;

struct EffectsEditor {
  void setEffects(std::vector<Effect>* effects) { m_effects = effects; }
  void draw(DatabaseEditor* dbEditor);

private:
  void drawPopup(DatabaseEditor* dbEditor);
  std::vector<Effect>* m_effects = nullptr;
  Effect* m_selectedEffect = nullptr;
  bool m_updateTab{false};
  bool m_isNewEntry{false};
  bool m_tempEffectAssigned{false};
  Effect m_tempEffect;
  Effect* m_deletingEffect = nullptr;
  std::optional<ObjectPicker<State>> m_statePicker;
  std::optional<ObjectPicker<Skill>> m_skillPicker;
  std::optional<ObjectPicker<std::optional<CommonEvent>>> m_commonEventPicker;
};