code:
#include "Core/CommonUI/GroupBox.hpp"
#include "Core/ImGuiExt/ImGuiUtils.hpp"

#include <format>
#include <imgui_internal.h>

GroupBox::GroupBox(const std::string_view title, const std::string_view id, const ImVec2& size, bool* checked, ImGuiChildFlags childFlags, ImGuiWindowFlags windowFlags)
: m_title(title)
, m_id(id)
, m_size(size)
, m_childFlags(childFlags)
, m_windowFlags(windowFlags)
, m_checked(checked)
, m_visible(false) {}

bool GroupBox::begin() {
  m_beginCalled = true;
  ImGui::BeginChild(std::format("{}_outer", m_id).c_str(), m_size, ImGuiChildFlags_NavFlattened | ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysAutoResize,
                    ImGuiWindowFlags_NoBackground);
  const auto id = ImGui::GetID(m_id.c_str());
  auto groupPos = ImGui::GetCursorScreenPos();
  m_groupStart = groupPos;
  if (!m_title.empty()) {
    ImGui::PushStyleVarY(ImGuiStyleVar_FramePadding, ImGui::GetStyle().FramePadding.y * 0.5);
    groupPos.y += (ImGui::GetFontSize() / 2) + ImGui::GetStyle().ItemSpacing.y;
    ImGui::SetCursorScreenPos(groupPos);
    groupPos.y += (ImGui::GetFrameHeight() / 2);
    ImGui::PopStyleVar();
  }
  m_clicked = false;

  m_visible = ImGui::BeginChild(id, m_size,
                                m_childFlags | ImGuiChildFlags_FrameStyle | ImGuiChildFlags_NavFlattened | ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysAutoResize,
                                m_windowFlags);
  if (m_visible) {
    if (!m_wasHeaderDrawn && !m_title.empty()) {
      auto size = ImGui::CalcItemSize(ImGui::CalcTextSize(m_title.c_str()), 0, 0);
      if (m_checked) {
        ImGui::PushStyleVarY(ImGuiStyleVar_FramePadding, ImGui::GetStyle().FramePadding.y * 0.5);
        size.x += (ImGui::GetFrameHeightWithSpacing() * 0.45f) * 2;
        ImGui::PopStyleVar();
      }
      ImGui::Dummy({size.x, size.y / 2});
    }
    ImGui::BeginDisabled(m_checked && !*m_checked);
  }

  return m_visible;
}

void GroupBox::end() {
  assert(m_beginCalled && "GroupBox::begin not called");

  ImRect clip;

  if (m_visible) {
    ImGui::EndDisabled();
    clip = ImGui::GetCurrentWindow()->ClipRect;
    ImGui::EndChild();
  }

  if (m_visible && !m_wasHeaderDrawn && !m_title.empty()) {
    const auto labelId = ImGui::GetID(std::format("{}_checkbox", m_id).c_str());
    const auto oldPos = ImGui::GetCursorScreenPos();
    const auto oldX = ImGui::GetCursorPosX();
    ImGui::SetCursorScreenPos({m_groupStart.x + ImGui::GetStyle().FramePadding.x + ImGui::GetStyle().FrameBorderSize, m_groupStart.y});
    clip.Min.y -= ImGui::GetFrameHeightWithSpacing() / 2;
    ImGui::PushClipRect(clip.Min, clip.Max, false);
    if (ImGui::BeginChild(labelId, {}, ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysAutoResize | ImGuiChildFlags_FrameStyle | ImGuiChildFlags_NavFlattened,
                          ImGuiWindowFlags_NoScrollbar | (!m_checked ? ImGuiWindowFlags_NoNav : 0))) {
      // Group Header text
      if (!m_checked) {
        ImGui::TextUnformatted(m_title.c_str());
      } else {
        ImGui::PushStyleVarY(ImGuiStyleVar_FramePadding, ImGui::GetStyle().FramePadding.y * 0.5);
        m_clicked = ImGui::SizeableCheckbox(m_title.c_str(), m_checked, ImGui::GetFrameHeight() * 0.45f);
        ImGui::PopStyleVar();
      }
      ImGui::EndChild();
    }
    ImGui::PopClipRect();
    ImGui::SetCursorScreenPos(oldPos);
    ImGui::SetCursorPosX(oldX);
    m_wasHeaderDrawn = true;
  }
  ImGui::EndChild();
  m_beginCalled = false;
}


code:
#pragma once

#include <cstdint>
#include <string>
#include <string_view>

#include <imgui.h>

class GroupBox {
public:
  GroupBox(std::string_view title, std::string_view id, const ImVec2& size = {0, 0}, bool* checked = nullptr, ImGuiChildFlags childFlags = 0, ImGuiWindowFlags windowFlags = 0);
  bool begin();
  void end();

  bool isChecked() const { return m_checked ? *m_checked : false; }
  bool wasClicked() const { return m_clicked; }

private:
  std::string m_title;
  std::string m_id;
  ImVec2 m_size;
  uint32_t m_childFlags;
  uint32_t m_windowFlags;

  bool* m_checked = nullptr;
  bool m_clicked = false;

  bool m_visible;
  ImVec2 m_groupStart;
  bool m_wasHeaderDrawn{false};
  bool m_beginCalled{false};
};

repo_name: Antidote/openrpgeditor

license: mit


code:
#pragma once

#include <string>
#include <tuple>

struct IDialogController {
  IDialogController() = delete;
  explicit IDialogController(const std::string_view name)
  : m_name(name) {}
  virtual ~IDialogController() = default;
  virtual std::tuple<bool, bool> draw() = 0;
  [[nodiscard]] virtual bool isOpen() const { return m_open; }
  [[nodiscard]] virtual bool isConfirmed() const { return m_confirmed; }
  virtual void setOpen(const bool open) { m_open = open; }
  virtual void accept() {
    m_open = false;
    m_confirmed = false;
  }

protected:
  bool m_confirmed{false};
  bool m_open{false};
  std::string m_name;
};


code:
#pragma once

class IWidget {
  public:
    virtual ~IWidget() = default;
    virtual void draw() = 0;
  };


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

#include <IconsFontAwesome6.h>

ImagePicker::ImagePicker(PickerMode mode, const std::string_view imageName, const std::string_view image2Name)
: IDialogController("Select an Image##image_picker")
, m_checkerboardTexture(864, 768) {
  m_pickType = mode;
  switch (m_pickType) {
  case PickerMode::Parallax:
    m_imageDir.emplace("img/parallaxes/", ".png", std::string(imageName));

    m_images = m_imageDir.value().getDirectoryContents();
    m_folderDir = m_imageDir.value().getDirectories();
    break;
  case PickerMode::Picture:
    m_imageDir.emplace("img/pictures/", ".png", std::string(imageName));

    m_images = m_imageDir.value().getDirectoryContents();
    m_folderDir = m_imageDir.value().getDirectories();
    break;
  case PickerMode::Battleback:
    m_imageDir.emplace("img/battlebacks1/", ".png", std::string(imageName));
    m_imageDir2.emplace("img/battlebacks2/", ".png", std::string(image2Name));

    m_images = m_imageDir.value().getDirectoryContents();
    m_images_2 = m_imageDir2.value().getDirectoryContents();
    m_folderDir = m_imageDir.value().getDirectories();
    m_folderDir_2 = m_imageDir2.value().getDirectories();
    break;
  case PickerMode::Title:
    m_imageDir.emplace("img/titles1/", ".png", std::string(imageName));
    m_imageDir2.emplace("img/titles2/", ".png", std::string(image2Name));

    m_images = m_imageDir.value().getDirectoryContents();
    m_images_2 = m_imageDir2.value().getDirectoryContents();
    m_folderDir = m_imageDir.value().getDirectories();
    m_folderDir_2 = m_imageDir2.value().getDirectories();
    break;
  case PickerMode::Tileset:
    m_imageDir.emplace("img/tilesets/", ".png", std::string(imageName));

    m_images = m_imageDir.value().getDirectoryContents();
    m_folderDir = m_imageDir.value().getDirectories();
    break;
  case PickerMode::Animation:
    m_imageDir.emplace("img/animations/", ".png", std::string(imageName));

    m_images = m_imageDir.value().getDirectoryContents();
    m_folderDir = m_imageDir.value().getDirectories();
    break;
  case PickerMode::SVBattler:
    m_imageDir.emplace("img/sv_actors/", ".png", std::string(imageName));

    m_images = m_imageDir.value().getDirectoryContents();
    m_folderDir = m_imageDir.value().getDirectories();
    break;
  default:
    break;
  }
  setImageInfo(imageName, image2Name);
}

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();
}

void ImagePicker::setImageInfo(std::string_view imageName, std::string_view image2Name) {
  if (!imageName.empty()) {
    bool found = false;
    for (int i = 0; i < m_images.size(); ++i) {
      if (!m_images[i].compare(imageName)) {
        found = true;
        m_selectedImage = i;
        break;
      }
    }
    if (!found) {
      m_selectedImage = -1;
      return;
    }

  } else {
    m_selectedImage = -1;
  }
  if (!image2Name.empty()) {
    bool found = false;
    for (int i = 0; i < m_images_2[i].size(); ++i) {
      if (!m_images_2[i].compare(image2Name)) {
        found = true;
        m_selectedImage2 = i;
        break;
      }
    }
    if (!found) {
      m_selectedImage2 = -1;
      return;
    }

  } else {
    m_selectedImage2 = -1;
  }
}

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

  int index{0};
  if (m_sortRequest) {
    m_sortedIndexes.clear();
    for (auto& str : m_images) {
      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::SetNextWindowSize(ImGui::GetMainViewport()->Size / 2, ImGuiCond_Appearing);
  if (ImGui::BeginPopupModal(m_name.c_str(), &m_open, ImGuiWindowFlags_NoSavedSettings)) {
    const auto calc = ImGui::CalcTextSize("OKCANCEL");
    ImGui::BeginChild("##top_child", {0, ImGui::GetContentRegionAvail().y - (calc.y + (ImGui::GetStyle().ItemSpacing.y * 3) + ImGui::GetStyle().FramePadding.y)});
    {
      ImGui::Text("Filter");
      ImGui::SameLine();
      if (ImGui::InputText("##object_selection_filter_input", &m_filter)) {
        if (m_filter.empty()) {
          m_sortedIndexes.clear();
        } else {
          m_sortRequest = true;
        }
      }
      ImGui::SameLine();
      if (ImGui::Button("Clear")) {
        m_filter.clear();
        m_sortedIndexes.clear();
      }
      if (!m_images.empty()) {
        ImGui::BeginChild("##image_picker_list##1", ImVec2{200, ImGui::GetContentRegionAvail().y - ImGui::GetStyle().FramePadding.y}, ImGuiChildFlags_Borders | ImGuiChildFlags_ResizeX);
        {
          if (ImGui::BeginTable("##image_picker.tablelist", 1)) {
            ImGui::TableNextRow();
            ImGui::TableNextColumn();
            if (ImGui::Selectable("(None)", m_selectedImage < 0, ImGuiSelectableFlags_SelectOnNav | ImGuiSelectableFlags_SelectOnClick)) {
              m_selectedImage = -1;
              m_image.reset();
            }
            ImGui::SeparatorEx(ImGuiSeparatorFlags_Horizontal, 4);
            ImGui::BeginDisabled(m_imageDir.value().isParentDirectory());
            if (ImGui::Selectable("\u21B0 ..", false, ImGuiSelectableFlags_SelectOnNav | ImGuiSelectableFlags_SelectOnClick)) {
              if (ImGui::GetMouseClickedCount(ImGuiMouseButton_Left) >= 2) {
                m_imageDir.value().moveUp();
                m_images = m_imageDir->getDirectoryContents();
                m_folderDir = m_imageDir.value().getDirectories();
                m_selectedImage = -1;
                m_selectedFolder = -1;
                m_image.reset();
              }
            }
            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_folderDir.size(); ++i) {
                const auto& folderName = std::format("{} {}", ICON_FA_FOLDER_OPEN, m_folderDir[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_imageDir->setDirectory(i);
                  m_folderDir = m_imageDir.value().getDirectories();
                  m_images = m_imageDir.value().getDirectoryContents();
                  m_selectedImage = -1;
                  m_selectedFolder = -1;
                  m_image.reset();
                }
              }
            }
            ImGui::EndChild();
            ImGui::SeparatorEx(ImGuiSeparatorFlags_Horizontal, 4);

            ImGui::BeginChild("##image_list", {}, 0, ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_HorizontalScrollbar);
            {
              for (int i = 0; i < m_images.size(); ++i) {
                if (m_filter.empty() == false && m_sortedIndexes.contains(i) == false) {
                  continue;
                }
                const auto& sheet = m_images[i];
                ImGui::TableNextColumn();
                if (ImGui::Selectable(sheet.c_str(), m_selectedImage == i, ImGuiSelectableFlags_SelectOnNav | ImGuiSelectableFlags_SelectOnClick)) {
                  m_selectedImage = i;
                  m_image.emplace(m_selectedImage == -1                    ? ""
                                  : m_imageDir.value().isParentDirectory() ? m_images.at(m_selectedImage)
                                                                           : m_imageDir.value().getPathPrefix() + '/' + m_images.at(m_selectedImage),
                                  m_pickType, false);
                  if (ImGui::IsItemHovered()) {
                    ImGui::SetTooltip("%s", sheet.c_str());
                  }
                }
                if (m_selectedImage == i && ImGui::IsWindowAppearing()) {
                  ImGui::SetScrollHereY();
                }
              }
            }
            ImGui::EndChild();
            ImGui::EndTable();
          }
        }
        ImGui::EndChild();
        ImGui::SameLine();
      }
      if (!m_images_2.empty()) {
        ImGui::BeginChild("##image_picker_list##2", ImVec2{200, ImGui::GetContentRegionAvail().y - ImGui::GetStyle().FramePadding.y}, ImGuiChildFlags_Borders | ImGuiChildFlags_ResizeX);
        {
          if (m_pickType == PickerMode::Battleback || m_pickType == PickerMode::Title) {
            if (ImGui::BeginTable("##image_picker.tablelist##2", 1)) {
              ImGui::TableNextRow();
              ImGui::TableNextColumn();
              if (ImGui::Selectable("(None)##2", m_selectedImage2 < 0, ImGuiSelectableFlags_SelectOnNav | ImGuiSelectableFlags_SelectOnClick)) {
                m_selectedImage2 = -1;
                m_image2.reset();
              }
              ImGui::SeparatorEx(ImGuiSeparatorFlags_Horizontal, 4);
              ImGui::BeginDisabled(m_imageDir2.value().isParentDirectory());
              if (ImGui::Selectable("\u21B0 ..##2", false, ImGuiSelectableFlags_SelectOnNav | ImGuiSelectableFlags_SelectOnClick)) {
                if (ImGui::GetMouseClickedCount(ImGuiMouseButton_Left) >= 2) {
                  m_imageDir2.value().moveUp();
                  m_images_2 = m_imageDir2->getDirectoryContents();
                  m_folderDir_2 = m_imageDir2.value().getDirectories();
                }
              }
              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_folderDir_2.size(); ++i) {
                  const auto& folderName = std::format("{} {}##2", ICON_FA_FOLDER_OPEN, m_folderDir_2[i]);
                  if (ImGui::Selectable(folderName.c_str(), m_selectedFolder2 == i, ImGuiSelectableFlags_SelectOnNav | ImGuiSelectableFlags_SelectOnClick)) {
                    m_selectedFolder2 = i;
                  }
                  if (m_selectedFolder2 == i && ImGui::GetMouseClickedCount(ImGuiMouseButton_Left) >= 2) {
                    m_imageDir2->setDirectory(i);
                    m_folderDir_2 = m_imageDir2.value().getDirectories();
                    m_images_2 = m_imageDir2.value().getDirectoryContents();
                    m_selectedImage2 = -1;
                    m_selectedFolder2 = -1;
                    m_image2.reset();
                  }
                }
              }
              ImGui::EndChild();
              ImGui::SeparatorEx(ImGuiSeparatorFlags_Horizontal, 4);

              ImGui::BeginChild("##image_list", {}, 0, ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_HorizontalScrollbar);
              {
                for (int i = 0; i < m_images_2.size(); ++i) {
                  const auto& sheet2 = m_images_2[i];
                  ImGui::TableNextColumn();
                  if (ImGui::Selectable(sheet2.c_str(), m_selectedImage2 == i, ImGuiSelectableFlags_SelectOnNav | ImGuiSelectableFlags_SelectOnClick)) {
                    m_selectedImage2 = i;
                    m_image2.emplace(m_selectedImage2 == -1                    ? ""
                                     : m_imageDir2.value().isParentDirectory() ? m_images_2.at(m_selectedImage2)
                                                                               : m_imageDir2.value().getPathPrefix() + '/' + m_images_2.at(m_selectedImage2),
                                     m_pickType, true);
                    if (ImGui::IsItemHovered()) {
                      ImGui::SetTooltip("%s", sheet2.c_str());
                    }
                  }
                }
              }
              ImGui::EndChild();
              ImGui::EndTable();
            }
          }
        }
        ImGui::EndChild();
        ImGui::SameLine();
      }
      ImGui::BeginChild("##image_picker_image_panel", ImVec2{ImGui::GetContentRegionAvail().x - ImGui::GetStyle().FramePadding.x, ImGui::GetContentRegionAvail().y - ImGui::GetStyle().FramePadding.y},
                        ImGuiChildFlags_Border, ImGuiWindowFlags_HorizontalScrollbar);
      {
        if (!m_image && m_selectedImage != -1) {
          m_image.emplace(m_selectedImage == -1 ? "" : m_images.at(m_selectedImage), m_pickType, false);
        }
        if (!m_image2 && m_selectedImage2 != -1) {
          m_image2.emplace(m_selectedImage2 == -1 ? "" : m_images.at(m_selectedImage2), m_pickType, false);
        }

        const auto win = ImGui::GetCurrentWindow();
        const int dummyWidth = std::max(m_image ? m_image->imageWidth() : 0, m_image2 ? m_image2->imageWidth() : 0);
        const int dummyHeight = std::max(m_image ? m_image->imageHeight() : 0, m_image2 ? m_image2->imageHeight() : 0);
        if (dummyWidth > 0 && dummyHeight > 0) {
          ImGui::Dummy({static_cast<float>(dummyWidth), static_cast<float>(dummyHeight)});
        }

        if (m_image) {
          ImGui::GetWindowDrawList()->AddImage(static_cast<ImTextureID>(m_checkerboardTexture), win->ContentRegionRect.Min + ImVec2{0.f, 0.f},
                                               win->ContentRegionRect.Min + (ImVec2{static_cast<float>(m_image->texture().width()), static_cast<float>(m_image->texture().height())}));

          ImGui::GetWindowDrawList()->AddImage(m_image->texture(), win->ContentRegionRect.Min + ImVec2{0.f, 0.f},
                                               win->ContentRegionRect.Min + (ImVec2{static_cast<float>(m_image->texture().width()), static_cast<float>(m_image->texture().height())}));
        }
        if (m_image2) {
          ImGui::GetWindowDrawList()->AddImage(m_image2->texture(), win->ContentRegionRect.Min + ImVec2{0.f, 0.f},
                                               win->ContentRegionRect.Min + (ImVec2{static_cast<float>(m_image2->texture().width()), static_cast<float>(m_image2->texture().height())}));
        }
      }
      ImGui::EndChild();
    }
    ImGui::EndChild();
    ImGui::BeginChild("##bottom_child");
    {
      ImGui::SetCursorPosX(ImGui::GetContentRegionMax().x - (calc.x + (ImGui::GetStyle().FramePadding.x * 2) + ImGui::GetStyle().ItemSpacing.x));
      if (ImGui::Button("OK")) {
        m_filter.clear();
        m_sortedIndexes.clear();
        m_confirmed = true;
        m_open = false;
        ImGui::CloseCurrentPopup();
      }
      ImGui::SameLine();
      if (ImGui::Button("Cancel")) {
        m_filter.clear();
        m_sortedIndexes.clear();
        m_confirmed = false;
        m_open = false;
        ImGui::CloseCurrentPopup();
      }
    }
    ImGui::EndChild();
    ImGui::EndPopup();
  }
  return {!m_open, m_confirmed};
}


code:
#pragma once

#include "Core/CommonUI/IDialogController.hpp"
#include "Core/Graphics/CheckerboardTexture.hpp"
#include "Core/Graphics/Image.hpp"
#include "Core/Graphics/Texture.hpp"
#include "Core/Log.hpp"
#include "Directory.hpp"

#include "Database/Globals.hpp"

namespace fs = std::filesystem;
struct Image;
struct ImagePicker : IDialogController {
  using PickerMode = Image::Mode;

  explicit ImagePicker(PickerMode mode, std::string_view imageName = {}, std::string_view image2Name = {});
  std::tuple<bool, bool> draw() override;
  [[nodiscard]] std::string selectedImage() const {
    return m_selectedImage >= 0 ? m_imageDir.value().isParentDirectory() ? m_images[m_selectedImage] : m_imageDir.value().getPathPrefix() + '/' + m_images[m_selectedImage] : "";
  }

  [[nodiscard]] std::string selectedImage2() const {
    return m_selectedImage2 >= 0 ? m_imageDir2.value().isParentDirectory() ? m_images_2[m_selectedImage2] : m_imageDir2.value().getPathPrefix() + '/' + m_images_2[m_selectedImage2] : "";
  }
  void setImageInfo(std::string_view imageName, std::string_view image2Name = {});

private:
  std::optional<Image> m_image;
  std::optional<Image> m_image2;
  std::vector<std::string> m_images;
  std::vector<std::string> m_images_2;
  std::vector<std::string> m_folderDir;
  std::vector<std::string> m_folderDir_2;
  PickerMode m_pickType;
  CheckerboardTexture m_checkerboardTexture{};
  int m_selectedImage{-1};
  int m_selectedImage2{-1};
  int m_selectedFolder{-1};
  int m_selectedFolder2{-1};

  std::optional<Directory> m_imageDir;
  std::optional<Directory> m_imageDir2;

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


code:
#pragma once

#include "Database/CommonEvents.hpp"
#include "IDialogController.hpp"

#include <optional>
#include <string>
#include <vector>

#include <string_view>

#include <Utils/Typename.hpp>

/*!
 * @struct ObjectPicker
 * @details Provides a pickable list broken up into groups of 100
 * @tparam T Object type to list
 */
template <typename T>
class ObjectPicker {
  static constexpr std::string_view Name = type_name<T>();

public:
  ObjectPicker(const std::string_view name, std::vector<T>& list, const int initialSelection)
  : m_name(name)
  , m_list(&list)
  , m_selection(initialSelection) {
    if (m_selection > 0) {
      m_navigateOnOpen = true;
    }
  }

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

  [[nodiscard]] int selection() const { return m_selection; }

  static int getId(const T& value) { return value.id(); }
  static const std::string& getName(const T& value) { return value.name(); }

  [[nodiscard]] bool isOpen() const { return m_open; }
  [[nodiscard]] bool isConfirmed() const { return m_confirmed; }
  void setOpen(const bool open) { m_open = open; }
  void accept() {
    m_open = false;
    m_confirmed = false;
  }

  void setNoSelectionMeansAdd(const bool noSelectionMeansAdd) { m_noSelectionMeansAdd = noSelectionMeansAdd; }
  void setUseDummyEntry(const std::string_view type) {
    m_useDummyEntry = true;
    m_entireText = type;
  }
  bool drawPicker(int& selectedData) {
    auto [closed, confirmed] = draw();
    if (closed) {
      if (confirmed) {
        selectedData = selection();
      }
    }
    return closed;
  }

private:
  std::string m_name;
  std::vector<T>* m_list;
  bool m_confirmed{false};
  bool m_navigateOnOpen{false};
  int m_selection{0};
  bool m_open{false};
  bool m_noSelectionMeansAdd{false};
  std::string m_filter;
  std::string m_entireText;
  bool m_useDummyEntry{false};
};

#include "ObjectPicker.inl"


code:
#include "imgui.h"
#include "misc/cpp/imgui_stdlib.h"
#include <cxxabi.h>
#include <format>

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

template <typename T>
std::tuple<bool, bool> ObjectPicker<T>::draw() {
  const std::string id = std::format("###ObjectPicker{}", std::string(Name));
  const std::string title = std::format("{} Selection{}", m_name, id);
  if (isOpen()) {
    ImGui::OpenPopup(id.c_str());
  }

  const ImVec2 center = ImGui::GetMainViewport()->GetCenter();
  ImGui::SetNextWindowPos(center, ImGuiCond_Always, ImVec2(0.5f, 0.5f));
  ImGui::SetNextWindowSize(ImGui::GetMainViewport()->Size / 2, ImGuiCond_Once);
  if (ImGui::BeginPopupModal(title.c_str(), &m_open, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings)) {
    ImGui::Text("Filter");
    ImGui::SameLine();
    ImGui::InputText("##object_selection_filter_input", &m_filter);
    ImGui::SameLine();
    if (ImGui::Button("Clear")) {
      m_filter.clear();
    }

    if (ImGui::BeginTable("Objects", 2, ImGuiTableFlags_Resizable | ImGuiTableFlags_Sortable | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | ImGuiTableFlags_ScrollY,
                          ImVec2{0, ImGui::GetContentRegionAvail().y - 32 - ImGui::GetStyle().FramePadding.y})) {

      ImGui::TableSetupColumn("ID", ImGuiTableColumnFlags_PreferSortAscending | ImGuiTableColumnFlags_DefaultSort | ImGuiTableColumnFlags_WidthFixed, 0, 1);
      ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthStretch, 0, 2);
      ImGui::TableSetupScrollFreeze(0, 1);
      ImGui::TableHeadersRow();

      ImGuiTableSortSpecs* sortSpecs = ImGui::TableGetSortSpecs();

      bool hasSortSpec = sortSpecs != nullptr && sortSpecs->SpecsCount == 1;

      std::vector<T*> sortedList;
      for (T& value : *m_list) {
        if (getId(value) == 0) {
          continue;
        }
        if (!m_filter.empty()) {
          if (ContainsCaseInsensitive(getName(value), m_filter) || ContainsCaseInsensitive(std::to_string(getId(value)), m_filter)) {
            sortedList.push_back(&value);
          }
        } else {
          sortedList.push_back(&value);
        }
      }

      if (hasSortSpec) {
        const auto& spec = sortSpecs->Specs[0];
        if (spec.ColumnUserID == 1) {
          std::sort(sortedList.begin(), sortedList.end(), [&](T* a, T* b) { return spec.SortDirection == ImGuiSortDirection_Ascending ? getId(*a) < getId(*b) : getId(*a) > getId(*b); });
        } else if (spec.ColumnUserID == 2) {
          std::sort(sortedList.begin(), sortedList.end(), [&](T* a, T* b) {
            int compare = getName(*a).compare(getName(*b));
            return spec.SortDirection == ImGuiSortDirection_Ascending ? compare < 0 : compare > 0;
          });
        }
      }

      if (m_useDummyEntry) {
        ImGui::TableNextRow();
        if (ImGui::TableNextColumn()) {
          if (ImGui::Selectable("##object_picker_dummy_entry", m_selection == 0, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowDoubleClick)) {
            m_selection = 0;
            if (ImGui::GetMouseClickedCount(ImGuiMouseButton_Left) >= 2) {
              m_open = false;
              m_confirmed = true;
            }
          }
        }
        if (ImGui::TableNextColumn()) {
          ImGui::Text("%s", m_entireText.c_str());
        }
      }

      for (T* value : sortedList) {
        ImGui::PushID(value);
        ImGui::TableNextRow();

        /* ID */
        if (ImGui::TableNextColumn()) {
          std::string id = std::format("{:04}", getId(*value));
          if (ImGui::Selectable(id.c_str(), m_selection == getId(*value), ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowDoubleClick)) {
            m_selection = getId(*value);
            if (ImGui::GetMouseClickedCount(ImGuiMouseButton_Left) >= 2) {
              m_open = false;
              m_confirmed = true;
            }
          }
          if (m_selection == getId(*value)) {
            ImGui::SetItemDefaultFocus();
            if (m_navigateOnOpen) {
              ImGui::SetScrollHereY();
              m_navigateOnOpen = false;
            }
          }
        }
        /* Name */
        if (ImGui::TableNextColumn()) {
          ImGui::Text("%s", getName(*value).c_str());
        }
        ImGui::PopID();
      }

      ImGui::EndTable();
    }
    ImGui::BeginGroup();
    {
      if (ImGui::Button(m_noSelectionMeansAdd && m_selection <= 0 ? "Add" : "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 std::make_tuple(!m_open, m_confirmed);
}


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

#include "Database/Database.hpp"
#include "Database/EventCommands/Script.hpp"
#include "Database/EventCommands/ShowPicture.hpp"
#include "Database/EventCommands/Wait.hpp"
#include "imgui.h"
#include "misc/cpp/imgui_stdlib.h"
#include <format>

#include <algorithm>
#include <regex>

Preview::Preview(int commonId)
: IDialogController("Preview") {
  m_interpreter.emplace(Database::instance()->commonEvents.event(commonId)->commands());
  for (const int frameIndex : m_interpreter.value().getKeyFrames()) {
    m_keyFrames.push_back(frameIndex);
  }
  for (const int variable : m_interpreter.value().getVariables()) {
    m_variables[variable] = 0;
  }
  for (const int sw : m_interpreter.value().getSwitches()) {
    m_switches[sw] = false;
  }
}

std::tuple<bool, bool> Preview::draw() {
  if (!m_isPaused) {
    m_interpreter.value().update();
    m_selectedKeyFrame = m_interpreter->getKeyFrameIndex();
  }

  const std::string title = std::format("{}##{}", m_name, reinterpret_cast<uintptr_t>(this));
  if (isOpen()) {
    ImGui::OpenPopup(title.c_str());
  }

  const ImVec2 center = ImGui::GetMainViewport()->GetCenter();
  ImGui::SetNextWindowPos(center, ImGuiCond_Always, ImVec2(0.5f, 0.5f));
  ImGui::SetNextWindowSize(ImVec2{ImGui::GetMainViewport()->Size.x / 2, 900}, ImGuiCond_Once);
  if (ImGui::BeginPopupModal(title.c_str(), &m_open, ImGuiWindowFlags_NoSavedSettings)) {

    ImGui::BeginChild("##orpg_preview_navigation_bar", ImVec2{ImGui::GetContentRegionAvail().x - ImGui::GetStyle().FramePadding.x, 30.f});
    {
      if (ImGui::Button(m_isPaused ? "\u25B6" : "\u23F8", ImVec2{50.f, 0})) {
        m_isPaused = !m_isPaused;
      }
      ImGui::SameLine();
      ImGui::SetCursorPosX(ImGui::GetCursorPosX() - 8.f);
      if (ImGui::Button(m_isFastForward ? "2x" : "1x", ImVec2{50.f, 0})) {
        m_isFastForward = !m_isFastForward;
        m_interpreter.value().setFastForward(m_isFastForward);
      }
      ImGui::SameLine();
      ImGui::SetCursorPosX(ImGui::GetCursorPosX() - 8.f);
      if (ImGui::Button("Test", ImVec2{50.f, 0})) {}
      if (ImGui::BeginPopupContextItem()) // <-- use last item id as popup id
      {
        if (!m_variables.empty()) {
          int index{0};
          for (auto& pair : m_variables) {
            if (ImGui::InputInt(std::format("{}##orpg_preview_context_variablePreview_{}", Database::instance()->variableName(pair.first), index).c_str(), &pair.second)) {
              m_interpreter.value().setVariable(pair.first, pair.second);
            }
            index++;
          }
        }
        if (!m_switches.empty()) {
          int index{0};
          for (auto& pair : m_switches) {
            if (ImGui::Checkbox(std::format("{}##orpg_preview_context_switchPreview_{}", Database::instance()->variableName(pair.first), index).c_str(), &pair.second)) {
              m_interpreter.value().setSwitch(pair.first, pair.second);
            }
            index++;
          }
        }
        if (m_switches.empty() && m_variables.empty()) {
          ImGui::TextUnformatted("No variables or switches found!");
        }
        // if (ImGui::Button("Close")) {
        //   ImGui::CloseCurrentPopup();
        // }
        ImGui::EndPopup();
      }
      ImGui::SetItemTooltip("Right-click to open popup");
    }
    ImGui::EndChild();
    ImGui::SetCursorPosY(ImGui::GetCursorPosY() - 2.f);
    ImGui::BeginChild("##orpg_preview_navigation_keyframes",
                      ImVec2{ImGui::GetContentRegionMax().x - ImGui::GetStyle().FramePadding.x, m_keyFrames.empty() ? 30.f : (m_keyFrames.size() > 17 ? 50.f : 30.f)}, 0,
                      ImGuiWindowFlags_HorizontalScrollbar);
    {
      for (int i = 0; i < m_keyFrames.size(); i++) {
        std::string text = m_selectedKeyFrame > m_keyFrames.size() - 1 && i == m_keyFrames.size() - 1 ? "\u25D8" : (m_selectedKeyFrame == i ? "\u25D8" : "\u25E6");

        if (i > 0) {
          ImGui::SetCursorPosX(ImGui::GetCursorPosX() - 8.f);
        }
        if (ImGui::Button(std::format("{}##{}", text, i).c_str(), ImVec2(50.f, 0.f))) {
          m_isPaused = true;
          m_interpreter.value().setIndex(m_keyFrames[i], i);
          m_selectedKeyFrame = m_interpreter->getKeyFrameIndex();
        }
        if (m_selectedKeyFrame == i) {
          // ImGui::SetScrollHereX(); // TODO: Funny behavior when using this. Need to smoothen it out.
        }
        if (i < m_keyFrames.size() - 1) {
          ImGui::SameLine();
        }
      }
    }
    ImGui::EndChild();
    ImGui::BeginGroup();
    {
      if (m_interpreter.value().image()) {
        ImGui::Image(m_interpreter.value().getImage().texture(),
                     ImVec2{static_cast<float>(m_interpreter.value().getImage().imageWidth()), static_cast<float>(m_interpreter.value().getImage().imageHeight())});
        // ImGui::SameLine();
        // ImGui::TextUnformatted(m_interpreter.value().getImage().imageName().c_str());
      }
    }
    ImGui::EndGroup();
    ImGui::SetCursorPosY(ImGui::GetContentRegionMax().y - (ImGui::GetStyle().FramePadding.y * 5));
    ImGui::BeginGroup();
    {
      if (ImGui::Button("OK")) {
        m_confirmed = true;
        m_open = false;
        ImGui::CloseCurrentPopup();
      }
    }
    ImGui::EndGroup();
    ImGui::EndPopup();
  }

  return std::make_tuple(!m_open, m_confirmed);
}


code:
#pragma once

#include "Core/Interpreter.hpp"
#include "Database/EventCommands/IEventCommand.hpp"
#include "IDialogController.hpp"

#include <vector>

class Preview : public IDialogController {
public:
  Preview(int commonId);
  std::tuple<bool, bool> draw() override;

private:
  std::optional<Interpreter> m_interpreter;

  bool m_isPaused{false};
  bool m_isFastForward{false};
  int m_selectedKeyFrame{1};
  int m_inputInt{0};

  std::vector<int> m_keyFrames;
  std::map<int, int> m_variables;
  std::map<int, bool> m_switches;
};


code:
#include "Core/CommonUI/SoundPicker.hpp"
#include "Core/Application.hpp"
#include "Core/ImGuiExt/ImGuiUtils.hpp"
#include "imgui.h"
#include "imgui_internal.h"
#include <IconsFontAwesome6.h>

bool SoundPicker::draw() {
  if (m_open) {
    ImGui::OpenPopup("Audio");
  }

  ImVec2 center = ImGui::GetMainViewport()->GetCenter();
  ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
  ImGui::SetNextWindowSize(ImVec2{610, 380}, ImGuiCond_Appearing);
  if (ImGui::BeginPopupModal("Audio", &m_open, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_AlwaysAutoResize)) {
    ImGui::BeginGroup();
    {
      // Audio List
      if (ImGui::BeginTable("##se_audio_contents", 1, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY, ImVec2{500, 500})) {

        ImGui::TableSetupScrollFreeze(1, 0);
        ImGui::TableSetupColumn("File");
        ImGui::TableNextRow();
        ImGui::TableNextColumn();

        ImGui::BeginDisabled(m_audioDir.value().isParentDirectory());
        if (ImGui::Selectable("\u21B0 ..", false, static_cast<int>(ImGuiSelectableFlags_SelectOnNav) | ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_SelectOnClick)) {
          if (ImGui::GetMouseClickedCount(ImGuiMouseButton_Left) >= 2) {
            m_audioDir.value().moveUp();
            m_audios = m_audioDir->getDirectoryContents();
            m_folders = m_audioDir.value().getDirectories();
            m_selectedFolder = -1;
          }
        }
        ImGui::EndDisabled();
        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, static_cast<int>(ImGuiSelectableFlags_SelectOnNav) | ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_SelectOnClick)) {
            m_selectedFolder = i;
          }
          if (m_selectedFolder == i && ImGui::GetMouseClickedCount(ImGuiMouseButton_Left) >= 2) {
            m_audioDir->setDirectory(i);
            m_folders = m_audioDir.value().getDirectories();
            m_audios = m_audioDir.value().getDirectoryContents();
            m_selected = 0;
            m_selectedFolder = -1;
          }
        }

        if (ImGui::SelectableWithBorder("None", m_selected == 0, ImGuiSelectableFlags_AllowOverlap | ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowDoubleClick)) {
          m_selected = 0;
          m_audio.setName("");
        }

        for (int n = 0; n < m_audios.size(); n++) {
          ImGui::TableNextColumn();
          const bool isSelected = (m_selected == n + 1);
          if (ImGui::SelectableWithBorder(std::format("{}##{}", m_audios.at(n), n).c_str(), isSelected,
                                          ImGuiSelectableFlags_AllowOverlap | ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowDoubleClick)) {

            m_selected = n + 1;
            m_audio.setName(m_audioDir.value().isParentDirectory() ? m_audios.at(m_selected - 1) : m_audioDir.value().getPathPrefix() + '/' + m_audios.at(m_selected - 1));
            if (ImGui::GetMouseClickedCount(ImGuiMouseButton_Left) >= 2) {
              playAudio(m_audio.name().c_str());
            }
            if (isSelected)
              ImGui::SetItemDefaultFocus();
          }
        }
        ImGui::EndTable();
      }
    }
    ImGui::EndGroup();
    ImGui::SameLine();
    ImGui::BeginGroup();
    {
      if (ImGui::Button("Play", ImVec2(100, 0))) {
        if (m_selected > 0)
          playAudio(m_audio.name().c_str());
      }
      if (ImGui::Button("Stop", ImVec2(100, 0))) {
        stopAudio();
      }

      ImGui::SeparatorText("Volume");
      ImGui::SetNextItemWidth(100);
      int volume = m_audio.volume();
      if (ImGui::DragInt("##playse_audio.volume", &volume, 0.5f, 0, 100)) {
        m_audio.setVolume(volume);
        setVolume(m_audio.volume());
      }
      ImGui::SeparatorText("Pitch");
      ImGui::SetNextItemWidth(100);
      int pitch = m_audio.pitch();
      if (ImGui::DragInt("##playse_audio.pitch", &pitch, 0.5f, 0, 100)) {
        m_audio.setPitch(pitch);
        setPitch(m_audio.pitch());
      }
      ImGui::SeparatorText("Pan");
      ImGui::SetNextItemWidth(100);
      int pan = m_audio.pan();
      if (ImGui::DragInt("##playse_audio.pan", &pan, 0.5f, -100, 100)) {
        m_audio.setPan(pan);
        setPanning(m_audio.pan());
      }
    }

    ImGui::EndGroup();
    if (ImGui::Button("OK")) {
      m_toneInvalid = m_modified = true;
      m_open = false;
      ImGui::CloseCurrentPopup();
    }
    ImGui::SameLine();
    if (ImGui::Button("Cancel")) {
      m_open = false;
    }
    ImGui::EndPopup();
  }
  return m_modified;
}


code:
#pragma once
#include "Database/Audio.hpp"
#include "Database/Database.hpp"
#include "Directory.hpp"

#include <SFML/Audio.hpp>
#include <iostream>

/**
 * @name SoundPicker
 * Provides a common UI tool to select a sound file
 */
namespace fs = std::filesystem;
class SoundPicker {
public:
  SoundPicker() = default;

  [[nodiscard]] Audio& selectedAudio() { return m_audio; }

  /**
   * Draws the SoundPicker elements.
   * @return True if any of the values were modified, false otherwise
   */
  bool draw();
  void show(SEType type) {
    m_audios = m_audioDir.value().getDirectoryContents();
    m_folders = m_audioDir.value().getDirectories();
    m_type = type;
    m_open = true;
    m_audioDir.emplace("audio/se", ".ogg", m_audio.name());
  };

private:
  Audio m_audio;
  std::vector<std::string> m_audios;
  int m_selected{0};
  bool m_toneInvalid{true};
  bool m_modified{false};
  bool m_open{false};

  SEType m_type;

  sf::SoundBuffer buffer;
  sf::Sound sound;

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

  bool playAudio(const char* path) {
    // Load and play music
    if (!buffer.loadFromFile(Database::instance()->basePath + path + m_audioDir.value().getExt())) {
      // error loading file
      return false;
    }
    sf::Listener::setPosition(0.f, 0.f, 0.f); // Set listener position
    sound.setRelativeToListener(true);        // Ensure sound is not relative to listener
    sound.setBuffer(buffer);
    sound.play();
    return true;
  }
  void setVolume(int volume) {
    sound.setVolume(volume); // 0% to 100%
  }
  void setPanning(int value) {
    // TODO -- how can we pan this thing
    /*
    *(-1,0,0)' is to the left of the listener
    (1,0,0)' is to the right of the listener
    (0,0,1)' is in front of the listener
    (0,0,-1)' is behind the listener
    */
    if (value > 0) {
      // To the right (+)
      // sound.setPosition(sf::Vector3f{ static_cast<float>(value), 0.f, 0.f});
      sound.setPosition(sf::Vector3f(static_cast<float>(value) / 100.f, 0, 0));
    } else { // To the left (-)
      sound.setPosition(sf::Vector3f(static_cast<float>(value) / 100.f, 0, 0));
    }
  }
  void setPitch(int value) { sound.setPitch(value / 100.f); }
  void stopAudio() { sound.stop(); }
};