code:
#include "Core/EventEditor/EVPage.hpp"

#include "Core/EventEditor.hpp"
#include "Core/ImGuiExt/ImGuiUtils.hpp"
#include "Core/MainWindow.hpp"

#include <format>
#include <string>

#include "imgui.h"
#include "imgui_internal.h"

#include "Database/EventPage.hpp"

int EVPage::mNextID = 0;

EVPage::EVPage(EventPage* page)
: IPageEditor(page) {
  m_commandEditor.setCommands(&m_page->list());
  if (m_page->name().empty()) {
    strncpy(m_pageNameBuf, m_page->name().data(), 4096);
  }
  if (page->image().tileId() != 0) {
    m_characterSheet = CharacterSheet("", true, page->image().tileId());
  } else if (!page->image().characterName().empty()) {
    m_characterSheet = CharacterSheet(page->image().characterName());
  }
  m_uid = mNextID++;
}

std::tuple<bool, bool> EVPage::draw(bool canDelete, int index) {
  if (m_routeDialog) {
    if (auto [closed, confirmed] = m_routeDialog->draw(); closed) {
      if (confirmed) {
        // m_page->conditions().setActorId(m_actorPicker->selection());
      }
      m_routeDialog.reset();
    }
  }

  if (!m_actorButton) {
    m_actorButton.emplace();
    m_actorButton->setSize(80, 102);
  }

  if (!m_actorButton->hasCompositeTextures() && m_characterSheet) {
    auto [min, _] = m_characterSheet.getRectForCharacter(m_page->image().characterIndex(), m_page->image().pattern(), m_page->image().direction());

    if (m_characterSheet.isTileSheet()) {
      const auto [newMin, _] = m_characterSheet.getRectForTile(m_page->image().tileId());
      min = newMin;
    } else {
      m_page->image().setTileId(0);
    }
    const auto size = !m_characterSheet.isTileSheet() ? Point{m_characterSheet.characterWidth(), m_characterSheet.characterHeight()} : Point{48, 48};
    m_actorButton->setTexturesToComposite(
        {{m_characterSheet.texture(), size, {static_cast<int>(min.x() * m_characterSheet.texture().width()), static_cast<int>(min.y() * m_characterSheet.texture().height())}}});
  } else if (!m_characterSheet) {
    m_actorButton->clear();
  }

  bool open = true;
  bool* p_open = canDelete ? &open : nullptr;
  std::string title;
  if (!m_page->name().empty()) {
    title = std::format("{}###page_{}", m_page->name(), m_uid);
  } else {
    title = std::format("Page {0}###page_{1}", index + 1, m_uid);
  }

  int flags = 0;
  if (m_selected && m_selectedDirty) {
    flags |= ImGuiTabItemFlags_SetSelected;
    m_selectedDirty = false;
  }
  if (m_page->isModified()) {
    flags |= ImGuiTabItemFlags_UnsavedDocument;
  }
  bool selected = ImGui::BeginTabItem(title.c_str(), p_open, flags);
  m_tabItem = ImGui::TabBarGetCurrentTab(ImGui::GetCurrentTabBar());
  m_layoutIndex = m_tabItem->IndexDuringLayout;
  if (selected) {
    if (ImGui::BeginChild("##event_page_settings_panel", {ImGui::CalcTextSize("ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHI").x + (ImGui::GetStyle().FramePadding.x * 2) + ImGui::GetStyle().ItemSpacing.x, 0},
                          ImGuiChildFlags_ResizeX, 0)) {
      if (ImGui::LabelOverLineEdit("##event_page_name_edit", trNOOP("Page Name:"), m_pageNameBuf, 4096,
                                   std::max(ImGui::GetContentRegionAvail().x - (ImGui::CalcTextSize(trNOOP("Clear Page")).x + ImGui::GetStyle().FramePadding.x * 4), ImGui::GetDPIScaledValue(200.f)),
                                   trNOOP("Page names are an Open RPG Editor addition and will not be viewable in RPG Maker MV/MZ"), ImGuiInputTextFlags_None)) {
        m_page->setName(m_pageNameBuf);
      }
      ImGui::SameLine();
      ImGui::BeginGroup();
      {
        ImGui::NewLine();
        if (ImGui::Button(trNOOP("Clear Page"))) {
          m_page->clear();
        }
      }
      ImGui::EndGroup();
      ImGui::BeginGroup();
      {
        ImGui::SeparatorText(trNOOP("Conditions"));
        ImGui::BeginGroup();
        {
          bool v = m_page->conditions().switch1Valid();
          if (ImGui::Checkbox(std::format("{}##event_page_switch1_selection_check", trNOOP("Switch")).c_str(), &v)) {
            m_page->conditions().setSwitch1Valid(v);
          }
          v = m_page->conditions().switch2Valid();
          if (ImGui::Checkbox(std::format("{}##event_page_switch2_selection_check", trNOOP("Switch")).c_str(), &v)) {
            m_page->conditions().setSwitch2Valid(v);
          }
          v = m_page->conditions().variableValid();
          if (ImGui::Checkbox(std::format("{}##event_page_variable_selection_check", trNOOP("Variable")).c_str(), &v)) {
            m_page->conditions().setVariableValid(v);
          }
          ImGui::NewLine();
          v = m_page->conditions().selfSwitchValid();
          if (ImGui::Checkbox(trNOOP("Self Switch"), &v)) {
            m_page->conditions().setSelfSwitchValid(v);
          }
          v = m_page->conditions().itemValid();
          if (ImGui::Checkbox(trNOOP("Item"), &v)) {
            m_page->conditions().setItemValid(v);
          }
          v = m_page->conditions().actorValid();
          if (ImGui::Checkbox(trNOOP("Actor"), &v)) {
            m_page->conditions().setActorValid(v);
          }
        }
        ImGui::EndGroup();
        ImGui::SameLine();
        ImGui::BeginGroup();
        {
          ImGui::BeginDisabled(!m_page->conditions().switch1Valid());
          {
            const std::string label = m_page->conditions().switch1Valid() ? Database::instance()->switchNameOrId(m_page->conditions().switch1Id()) : "";
            if (ImGui::Button((label + "##event_page_switch1_selection_button").c_str(), ImVec2{ImGui::GetContentRegionAvail().x - ImGui::GetStyle().FramePadding.x, 0})) {
              m_variableSwitchSelection = Switch1;
              m_variableSwitchPicker.emplace(trNOOP("Switch"), Database::instance()->system.switches(), m_page->conditions().switch1Id());
              m_variableSwitchPicker->setOpen(true);
            }
          }
          ImGui::EndDisabled();
          ImGui::BeginDisabled(!m_page->conditions().switch2Valid());
          {
            const std::string label = m_page->conditions().switch2Valid() ? Database::instance()->switchNameOrId(m_page->conditions().switch2Id()) : "";
            if (ImGui::Button((label + "##event_page_switch2_selection_button").c_str(), ImVec2{ImGui::GetContentRegionAvail().x - ImGui::GetStyle().FramePadding.x, 0})) {
              m_variableSwitchSelection = Switch2;
              m_variableSwitchPicker.emplace(trNOOP("Switch"), Database::instance()->system.switches(), m_page->conditions().switch2Id());
              m_variableSwitchPicker->setOpen(true);
            }
          }
          ImGui::EndDisabled();
          ImGui::BeginGroup();
          {
            ImGui::BeginDisabled(!m_page->conditions().variableValid());
            {
              const std::string label = m_page->conditions().variableValid() ? Database::instance()->variableNameOrId(m_page->conditions().variableId()) : "";
              if (ImGui::Button((label + "##event_page_variable_selection_button").c_str(), ImVec2{ImGui::GetContentRegionAvail().x - ImGui::GetStyle().FramePadding.x, 0})) {
                m_variableSwitchSelection = Variable;
                m_variableSwitchPicker.emplace(trNOOP("Variable"), Database::instance()->system.variables(), m_page->conditions().variableId());
                m_variableSwitchPicker->setOpen(true);
              }
              auto oldX = ImGui::GetCursorPosX();
              ImGui::SetCursorPosX(oldX - (ImGui::CalcTextSize("≥").x + ImGui::GetStyle().ItemSpacing.x));
              ImGui::Text("≥");
              ImGui::SetCursorPosX(oldX);
              ImGui::SameLine();
              int v = m_page->conditions().variableValue();
              ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - ImGui::GetStyle().FramePadding.x);
              if (ImGui::SpinInt("##event_page_variable_value_input_text", &v, 1, 100, m_page->conditions().variableValid() ? nullptr : "")) {
                m_page->conditions().setVariableValue(std::clamp(v, 0, 99999999));
              }
            }
            ImGui::EndDisabled();
            ImGui::BeginDisabled(!m_page->conditions().selfSwitchValid());
            {
              const std::string sw = m_page->conditions().selfSwitchValid() ? m_page->conditions().selfSwitchCh().data() : "";
              ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - ImGui::GetStyle().FramePadding.x);
              if (ImGui::BeginCombo("##event_page_self_switch_selection_combo", sw.c_str())) {
                for (int i = 0; i < 4; ++i) {
                  std::string label = std::format("{}", static_cast<char>('A' + i));
                  if (ImGui::Selectable(label.c_str(), !m_page->conditions().selfSwitchCh().compare(label))) {
                    m_page->conditions().setSelfSwitchCh(label);
                  }
                }
                ImGui::EndCombo();
              }
            }
            ImGui::EndDisabled();
            ImGui::BeginDisabled(!m_page->conditions().itemValid());
            {
              auto it = Database::instance()->items.item(m_page->conditions().itemId());
              const std::string item = m_page->conditions().itemValid() && it != nullptr ? it->name() : "";
              if (ImGui::Button(std::format("{}##event_page_item_selection_button_text", item).c_str(), ImVec2{ImGui::GetContentRegionAvail().x - ImGui::GetStyle().FramePadding.x, 0})) {
                m_itemPicker.emplace(trNOOP("Items"), Database::instance()->items.items(), m_page->conditions().itemId());
                m_itemPicker->setOpen(true);
              }
            }
            ImGui::EndDisabled();
            ImGui::BeginDisabled(!m_page->conditions().actorValid());
            {
              auto ac = Database::instance()->actors.actor(m_page->conditions().actorId());
              const std::string actor = m_page->conditions().actorValid() && ac != nullptr ? ac->name() : "";
              if (ImGui::Button(std::format("{}##event_page_actor_selection_button_text", actor).c_str(), ImVec2{ImGui::GetContentRegionAvail().x - ImGui::GetStyle().FramePadding.x, 0})) {
                m_actorPicker.emplace(trNOOP("Actors"), Database::instance()->actors.actorList(), m_page->conditions().actorId());
                m_actorPicker->setOpen(true);
              }
            }
            ImGui::EndDisabled();
          }
          ImGui::EndGroup();
        }
        ImGui::EndGroup();
      }
      ImGui::EndGroup();
      ImGui::BeginGroup();
      {
        ImGui::SeparatorText(trNOOP("Image"));
        if (ImGui::ImageButton("##event_image", m_actorButton->get(), static_cast<ImVec2>(m_actorButton->size()))) {
          if (m_page->image().tileId() > 0) {
            m_characterPicker.setTileId(m_page->image().tileId());
          } else {
            m_characterPicker.setCharacterInfo(m_page->image().characterName(), m_page->image().characterIndex(), m_page->image().pattern(), m_page->image().direction());
          }
          m_characterPicker.setOpen(true);
        }
      }
      ImGui::EndGroup();
      ImGui::SameLine();
      ImGui::BeginGroup();
      {
        ImGui::SeparatorText(trNOOP("Autonomous Movement"));
        ImGui::BeginGroup();
        {
          ImGui::BeginGroup();
          {
            ImGui::TextUnformatted(trNOOP("Type"));
            ImGui::NewLine();
            ImGui::NewLine();
            ImGui::TextUnformatted(trNOOP("Speed"));
            ImGui::TextUnformatted(trNOOP("Freq"));
          }
          ImGui::EndGroup();
          ImGui::SameLine();
          ImGui::BeginGroup();
          {
            ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - ImGui::GetStyle().FramePadding.x);
            auto preview = DecodeEnumName(m_page->moveType());
            if (ImGui::BeginCombo("##event_page_auto_movement_type_combo", preview.c_str())) {
              for (auto v : magic_enum::enum_values<MoveType>()) {
                if (ImGui::Selectable(DecodeEnumName(v).c_str(), v == m_page->moveType())) {
                  m_page->setMoveType(v);
                }
              }
              ImGui::EndCombo();
            }

            ImGui::BeginDisabled(m_page->moveType() != MoveType::Custom);
            if (ImGui::Button(trNOOP("Route..."), {ImGui::GetContentRegionAvail().x - ImGui::GetStyle().FramePadding.x, 0})) {
              if (!m_routeDialog) {
                m_routeDialog.emplace(DecodeEnumName(EventCode::Set_Movement_Route), m_page);
              }
              m_routeDialog->setOpen(true);
              m_routeDialog->setPage(m_page);
            }
            ImGui::EndDisabled();
            ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - ImGui::GetStyle().FramePadding.x);
            preview = std::format("{}: {}", magic_enum::enum_index(m_page->moveSpeed()).value() + 1, DecodeEnumName(m_page->moveSpeed()));
            if (ImGui::BeginCombo("##event_page_auto_movement_speed_combo", preview.c_str())) {
              for (auto v : magic_enum::enum_values<MovementSpeed>()) {
                std::string label = std::format("{}: {}", magic_enum::enum_index(v).value() + 1, DecodeEnumName(v));
                if (ImGui::Selectable(label.c_str(), v == m_page->moveSpeed())) {
                  m_page->setMoveSpeed(v);
                }
              }
              ImGui::EndCombo();
            }
            ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - ImGui::GetStyle().FramePadding.x);
            preview = std::format("{}: {}", magic_enum::enum_index(m_page->moveFrequency()).value() + 1, DecodeEnumName(m_page->moveFrequency()));
            if (ImGui::BeginCombo("##event_page_auto_movement_freq_combo", preview.c_str())) {
              for (auto v : magic_enum::enum_values<MovementFrequency>()) {
                std::string label = std::format("{}: {}", magic_enum::enum_index(v).value() + 1, DecodeEnumName(v));
                if (ImGui::Selectable(label.c_str(), v == m_page->moveFrequency())) {
                  m_page->setMoveFrequency(v);
                }
              }
              ImGui::EndCombo();
            }
          }
          ImGui::EndGroup();
        }
        ImGui::EndGroup();
      }
      ImGui::EndGroup();
      ImGui::BeginGroup();
      {
        ImGui::SeparatorText(trNOOP("Options"));
        bool v = m_page->walkAnime();
        if (ImGui::Checkbox(trNOOP("Walking"), &v)) {
          m_page->setWalkAnime(v);
        }
        v = m_page->stepAnime();
        if (ImGui::Checkbox(trNOOP("Stepping"), &v)) {
          m_page->setStepAnime(v);
        }
        v = m_page->directionFix();
        if (ImGui::Checkbox(trNOOP("Direction Fix"), &v)) {
          m_page->setDirectionFix(v);
        }
        v = m_page->through();
        if (ImGui::Checkbox(trNOOP("Through"), &v)) {
          m_page->setThrough(v);
        }
      }
      ImGui::EndGroup();
      ImGui::SameLine();
      ImGui::BeginGroup();
      {
        ImGui::BeginGroup();
        {
          ImGui::SeparatorText(trNOOP("Priority"));
          ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - ImGui::GetStyle().FramePadding.x);
          auto preview = DecodeEnumName(m_page->priorityType());
          if (ImGui::BeginCombo("##event_page_auto_priority_type_combo", preview.c_str())) {
            for (auto v : magic_enum::enum_values<EventPriority>()) {
              if (ImGui::Selectable(DecodeEnumName(v).c_str(), v == m_page->priorityType())) {
                m_page->setPriorityType(v);
              }
            }
            ImGui::EndCombo();
          }
        }
        ImGui::EndGroup();
        ImGui::BeginGroup();
        {
          ImGui::SeparatorText(trNOOP("Trigger"));
          ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - ImGui::GetStyle().FramePadding.x);
          auto preview = DecodeEnumName(m_page->trigger());
          if (ImGui::BeginCombo("##event_page_auto_trigger_type_combo", preview.c_str())) {
            for (auto v : magic_enum::enum_values<EventTriggerType>()) {
              if (ImGui::Selectable(DecodeEnumName(v).c_str(), v == m_page->trigger())) {
                m_page->setTrigger(v);
              }
            }
            ImGui::EndCombo();
          }
        }
        ImGui::EndGroup();
      }
      ImGui::EndGroup();
    }
    ImGui::EndChild();
    ImGui::SameLine();
    ImGui::BeginChild("##orpg_event_page_commands");
    { m_commandEditor.draw(); }
    ImGui::EndChild();
    if (const auto [closed, confirmed] = m_characterPicker.draw(); closed) {
      if (confirmed) {
        m_characterPicker.accept();
        if (m_characterPicker.isTilePick()) {
          m_page->image().setTileId(m_characterPicker.tileId());
          m_page->image().setCharacterName("");
          m_page->image().setPattern(m_characterPicker.selectedPattern());
          m_page->image().setDirection(m_characterPicker.selectedDirection());
          m_characterSheet = CharacterSheet(m_characterPicker.selectedTileSheet(), true, m_page->image().tileId());
        } else {
          m_page->image().setCharacterIndex(m_characterPicker.character());
          const auto tmpName = m_characterPicker.selectedSheet();
          m_page->image().setPattern(m_characterPicker.selectedPattern());
          m_page->image().setDirection(m_characterPicker.selectedDirection());
          if (m_page->image().characterName() != tmpName) {
            m_page->image().setCharacterName(tmpName);
            m_characterSheet = CharacterSheet(m_page->image().characterName());
          }
        }
        m_actorButton->clear();
      }
    }

    if (m_variableSwitchPicker) {
      if (auto [closed, confirmed] = m_variableSwitchPicker->draw(); closed) {
        if (confirmed) {
          switch (m_variableSwitchSelection) {
          case Switch1:
            m_page->conditions().setSwitch1Id(m_variableSwitchPicker->selection());
            break;
          case Switch2:
            m_page->conditions().setSwitch2Id(m_variableSwitchPicker->selection());
            break;
          case Variable:
            m_page->conditions().setVariableId(m_variableSwitchPicker->selection());
            break;
          }
        }
        m_variableSwitchPicker.reset();
      }
    }
    if (m_itemPicker) {
      if (auto [closed, confirmed] = m_itemPicker->draw(); closed) {
        if (confirmed) {
          m_page->conditions().setItemId(m_itemPicker->selection());
        }
        m_itemPicker.reset();
      }
    }
    if (m_actorPicker) {
      if (auto [closed, confirmed] = m_actorPicker->draw(); closed) {
        if (confirmed) {
          m_page->conditions().setActorId(m_actorPicker->selection());
        }
        m_actorPicker.reset();
      }
    }
    ImGui::EndTabItem();
  }

  return {!open, selected};
}

void EVPage::clearPage() const { m_page->clear(); }

void EVPage::pagePointerInvalidated() {
  m_commandEditor.setCommands(&m_page->list());
  m_actorButton->clear();
}

IPageEditor* IPageEditor::create(EventPage* page) { return new EVPage(page); }


code:
#pragma once
#include "Core/CommonUI/CharacterPicker.hpp"
#include "Core/CommonUI/EventCommandEditor.hpp"
#include "Core/CommonUI/ObjectPicker.hpp"
#include "Core/CommonUI/VariableSwitchPicker.hpp"
#include "Core/EventEditor.hpp"
#include "Core/Graphics/CharacterSheet.hpp"
#include "Core/Graphics/CheckeredCompositeTexture.hpp"

#include "Database/EventPage.hpp"

struct EventEditor;
struct Actor;
struct Item;
struct ImGuiTabItem;

class EVPage final : IPageEditor {
public:
  /* Returns true when closed, closing is the equivalent of deleting */
  std::tuple<bool, bool> draw(bool canDelete, int index) override;

  void clearPage() const;

  void setPage(EventPage* page) {
    m_page = page;
    m_commandEditor.setCommands(&m_page->list());
  }

  void setParent(EventEditor* parent) { m_parent = parent; }
  ImGuiTabItem* tabItem() const { return m_tabItem; }
  int layoutIndex() const { return m_layoutIndex; }

protected:
  friend IPageEditor* IPageEditor::create(EventPage* page);

  explicit EVPage(EventPage* page);

  void pagePointerInvalidated() override;

private:
  friend class EventEditor;

  enum VariableSwitchSelection {
    Variable,
    Switch1,
    Switch2,
  };

  EventEditor* m_parent = nullptr;
  char m_pageNameBuf[4096]{};
  EventCommandEditor m_commandEditor;
  CharacterSheet m_characterSheet;
  std::optional<Dialog_SetMovementRoute> m_routeDialog;
  VariableSwitchSelection m_variableSwitchSelection{Variable};
  std::optional<VariableSwitchPicker> m_variableSwitchPicker;
  std::optional<ObjectPicker<Actor>> m_actorPicker;
  std::optional<ObjectPicker<Item>> m_itemPicker;
  CharacterPicker m_characterPicker{CharacterPicker::PickerMode::PatternAndDirection, true};
  std::optional<CheckeredCompositeTexture> m_actorButton;
  ImGuiTabItem* m_tabItem = nullptr;
  int m_layoutIndex = 0;

  int m_uid;
  static int mNextID;
};

repo_name: Antidote/openrpgeditor

license: mit


code:
#include "Core/EventListView.hpp"
#include "Core/EventEditor.hpp"

#include "Database/Map.hpp"

#include "Core/MainWindow.hpp"

#include "imgui.h"
#include "imgui_internal.h"

void EventListView::draw() {
  char eventNameBuf[4096]{};
  if (ImGui::Begin(std::format("{}###events", trNOOP("Events")).c_str())) {
    Map* map = m_parent->currentMap();
    if (map) {
      ImGui::BeginChild("##event_list");
      {
        for (auto& event : map->events()) {
          if (event) {
            bool selectedHere = false;
            ImGui::BeginDisabled(event->id() == 0);
            sprintf(eventNameBuf, "%s (%i, %i)", event->id() == 0 ? trNOOP("Player Start") : Database::instance()->eventNameOrId(event->id()).c_str(), event->x(), event->y());
            if (ImGui::Selectable(eventNameBuf, m_parent->mapEditor()->selectedEvent() == &*event, ImGuiSelectableFlags_AllowDoubleClick | static_cast<int>(ImGuiSelectableFlags_SelectOnNav))) {
              m_parent->mapEditor()->setSelectedEvent(const_cast<Event*>(&*event));
              selectedHere = true;
              if (ImGui::GetMouseClickedCount(ImGuiMouseButton_Left) >= 2) {
                event->editor()->open();
              }
            }
            ImGui::EndDisabled();

            if (m_parent->mapEditor()->selectedEvent() == &event.value() && !selectedHere && !m_parent->mapEditor()->scrolledToEvent()) {
              ImGui::SetScrollHereY();
            }
          }
        }
      }
      ImGui::EndChild();
    }
  }
  ImGui::End();
}


code:
#pragma once

struct MainWindow;
struct EventListView {
  EventListView(MainWindow* parent)
  : m_parent(parent) {}

  void draw();

private:
  MainWindow* m_parent;
};


code:
#include "Core/EventSearcher.hpp"
#include "Core/MainWindow.hpp"
#include "Core/Settings.hpp"
#include "Database/EventCommands/MovementRoute/Script.hpp"
#include "imgui.h"
#include "ImGuiExt/ImGuiUtils.hpp"
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;
}
bool EventSearcher::getListing() { return true; }

void EventSearcher::draw() {
  if (!m_isOpen) {
    return;
  }
  if (picker) {
    auto [closed, confirmed] = picker->draw();
    if (closed) {
      if (confirmed) {
        m_pickedData = picker->selection();
        m_valueSelected = false;
      }
      picker.reset();
    }
  }
  if (m_animationPicker) {
    if (m_animationPicker->drawPicker(m_pickedData)) {
      m_valueSelected = false;
      m_animationPicker.reset();
    }
  }
  if (m_actorPicker) {
    if (m_actorPicker->drawPicker(m_pickedData)) {
      m_valueSelected = false;
      m_actorPicker.reset();
    }
  }
  if (m_statePicker) {
    if (m_statePicker->drawPicker(m_pickedData)) {
      m_valueSelected = false;
      m_statePicker.reset();
    }
  }
  if (m_classPicker) {
    if (m_classPicker->drawPicker(m_pickedData)) {
      m_valueSelected = false;
      m_classPicker.reset();
    }
  }
  if (m_enemyPicker) {
    if (m_enemyPicker->drawPicker(m_pickedData)) {
      m_valueSelected = false;
      m_enemyPicker.reset();
    }
  }
  if (m_itemPicker) {
    if (m_itemPicker->drawPicker(m_pickedData)) {
      m_valueSelected = false;
      m_itemPicker.reset();
    }
  }
  if (m_armorPicker) {
    if (m_armorPicker->drawPicker(m_pickedData)) {
      m_valueSelected = false;
      m_armorPicker.reset();
    }
  }
  if (m_weaponPicker) {
    if (m_weaponPicker->drawPicker(m_pickedData)) {
      m_valueSelected = false;
      m_weaponPicker.reset();
    }
  }
  if (m_skillPicker) {
    if (m_skillPicker->drawPicker(m_pickedData)) {
      m_valueSelected = false;
      m_skillPicker.reset();
    }
  }

  if (Database::instance()->commonEvents.isUpdating()) {
    m_commonPicker.reset();
    Database::instance()->commonEvents.setUpdate(false);
  } else {
    if (m_commonPicker) {
      if (m_commonPicker->drawPicker(m_pickedData)) {
        m_valueSelected = false;
        m_commonPicker.reset();
      }
    }
  }

  if (ImGui::Begin("Event Searcher", &m_isOpen)) {
    ImGui::Text("Search By:");
    ImGui::BeginGroup();
    {
      if (ImGui::BeginCombo("##orpg_search_combobox", DecodeEnumName(static_cast<SearchType>(m_pickedSearchType)).c_str())) {
        for (auto v : magic_enum::enum_values<SearchType>()) {
          if (ImGui::Selectable(DecodeEnumName(v).c_str(), static_cast<SearchType>(m_pickedSearchType) == v)) {
            m_pickedSearchType = static_cast<int>(v);
            m_valueSelected = false;
          }
        }
        ImGui::EndCombo();
      }
      pickedType = static_cast<SearchType>(m_pickedSearchType);
      if (pickedType == SearchType::Variable) { // Variable
        if (ImGui::Button(Database::instance()->variableNameAndId(m_valueSelected ? m_selectedData : m_pickedData).c_str(), ImVec2{290, 0})) {
          picker.emplace("Variables", Database::instance()->system.variables(), m_selectedData);
          picker->setOpen(true);
        }
      } else if (pickedType == SearchType::Switch) {
        // Switch
        if (ImGui::Button(Database::instance()->switchNameAndId(m_valueSelected ? m_selectedData : m_pickedData).c_str(), ImVec2{290, 0})) {
          picker.emplace("Switches", Database::instance()->system.switches(), m_selectedData);
          picker->setOpen(true);
        }
      } else if (pickedType == SearchType::Animation) {
        if (ImGui::Button(Database::instance()->animationNameAndId(m_valueSelected ? m_selectedData : m_pickedData).c_str(), ImVec2{290, 0})) {
          m_animationPicker.emplace("Animations"sv, Database::instance()->animations.animations(), m_selectedData);
          m_animationPicker->setOpen(true);
        }
      } else if (pickedType == SearchType::Armors) {
        if (ImGui::Button(Database::instance()->armorNameAndId(m_valueSelected ? m_selectedData : m_pickedData).c_str(), ImVec2{290, 0})) {
          m_armorPicker.emplace("Armors"sv, Database::instance()->armors.armors(), m_selectedData);
          m_armorPicker->setOpen(true);
        }
      } else if (pickedType == SearchType::Audio) {
        ImGui::InputText("##orpg_search_textInput_audio", &m_pickedString);
      } else if (pickedType == SearchType::CommonEvent) {
        if (ImGui::Button(Database::instance()->commonEventNameAndId(m_valueSelected ? m_selectedData : m_pickedData).c_str(), ImVec2{290, 0})) {
          m_commonPicker = ObjectPicker("Common Events"sv, Database::instance()->commonEvents.events(), m_selectedData);
          m_commonPicker->setOpen(true);
        }
      } else if (pickedType == SearchType::Class) {
        if (ImGui::Button(Database::instance()->classNameAndId(m_valueSelected ? m_selectedData : m_pickedData).c_str(), ImVec2{290, 0})) {
          m_classPicker.emplace("Classes"sv, Database::instance()->classes.classes(), m_selectedData);
          m_classPicker->setOpen(true);
        }
      } else if (pickedType == SearchType::Enemy) {
        if (ImGui::Button(Database::instance()->enemyNameAndId(m_valueSelected ? m_selectedData : m_pickedData).c_str(), ImVec2{290, 0})) {
          m_enemyPicker.emplace("Enemies"sv, Database::instance()->enemies.enemies(), m_selectedData);
          m_enemyPicker->setOpen(true);
        }
      } else if (pickedType == SearchType::Items) {
        if (ImGui::Button(Database::instance()->itemNameAndId(m_valueSelected ? m_selectedData : m_pickedData).c_str(), ImVec2{290, 0})) {
          m_itemPicker.emplace("Items"sv, Database::instance()->items.items(), m_selectedData);
          m_itemPicker->setOpen(true);
        }
      } else if (pickedType == SearchType::Skill) {
        if (ImGui::Button(Database::instance()->skillNameOrId(m_valueSelected ? m_selectedData : m_pickedData).c_str(), ImVec2{290, 0})) {
          m_skillPicker.emplace("Skills"sv, Database::instance()->skills.skills(), m_selectedData);
          m_skillPicker->setOpen(true);
        }
      } else if (pickedType == SearchType::State) {
        if (ImGui::Button(Database::instance()->stateNameAndId(m_valueSelected ? m_selectedData : m_pickedData).c_str(), ImVec2{290, 0})) {
          m_statePicker.emplace("States"sv, Database::instance()->states.states(), m_selectedData);
          m_statePicker->setOpen(true);
        }
      } else if (pickedType == SearchType::Weapons) {
        if (ImGui::Button(Database::instance()->weaponNameAndId(m_valueSelected ? m_selectedData : m_pickedData).c_str(), ImVec2{290, 0})) {
          m_weaponPicker.emplace("Weapons"sv, Database::instance()->weapons.weapons(), m_selectedData);
          m_weaponPicker->setOpen(true);
        }
      } else if (pickedType == SearchType::Actor) {

        if (ImGui::Button(Database::instance()->actorNameAndId(m_valueSelected ? m_selectedData : m_pickedData).c_str(), ImVec2{290, 0})) {
          m_actorPicker.emplace("Actors"sv, Database::instance()->actors.actorList(), m_selectedData);
          m_actorPicker->setOpen(true);
        }
      } else if (pickedType == SearchType::Script) {
        ImGui::InputText("##orpg_search_textInput_script", &m_pickedString);
      } else {
        ImGui::Text("No options available");
      }
      ImGui::EndGroup();
    }
    if (ImGui::Button("Search")) {
      m_valueSelected = true;
      type = pickedType;
      if (type == SearchType::Script || type == SearchType::Audio) {
        m_searchString = m_pickedString;
        reference.findAllReferences(m_searchString, type);
      } else {
        m_selectedData = m_pickedData;
        reference.findAllReferences(m_selectedData, type);
      }
      m_selectedSearchType = m_pickedSearchType;
      m_currentPage = 0;
      m_maxPage = reference.totalSize() / TOTAL_ENTRIES;
    }
    ImGui::SameLine();
    ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 500);
    if (ImGui::ArrowButton("##orpg_searchpage_left", ImGuiDir_Left)) {
      if (m_currentPage > 0) {
        m_currentPage--;
      }
    }
    ImGui::SameLine();
    ImGui::TextUnformatted(std::format("{}/{}", m_currentPage + 1, m_maxPage == 0 ? 1 : m_maxPage).c_str());
    ImGui::SameLine();
    if (ImGui::ArrowButton("##orpg_searchpage_right", ImGuiDir_Right)) {
      if (m_currentPage < m_maxPage - 1) {
        m_currentPage++;
      }
    }

    // Table with search results

    if (ImGui::BeginTable("##orpg_eventsearcher_eventlist", 6, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY,
                          ImVec2{0, ImGui::GetContentRegionAvail().y - 32 - ImGui::GetStyle().FramePadding.y})) {
      int tableIndex = 0;

      ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_WidthFixed);
      ImGui::TableSetupColumn("Map", ImGuiTableColumnFlags_WidthFixed);
      ImGui::TableSetupColumn("Event", ImGuiTableColumnFlags_WidthFixed);
      ImGui::TableSetupColumn("Page", ImGuiTableColumnFlags_WidthFixed);
      ImGui::TableSetupColumn("Step", ImGuiTableColumnFlags_WidthFixed);
      ImGui::TableSetupColumn("Tile", ImGuiTableColumnFlags_WidthFixed);
      ImGui::TableSetupScrollFreeze(5, 0);
      ImGui::TableHeadersRow();
      ImGui::TableNextRow();

      totalEntries = 0;
      int resultBegin = TOTAL_ENTRIES * m_currentPage;
      for (int i = 0; auto& results : reference.getEvents()) {
        ImGui::PushID(std::format("##event_condition_result_{}", i).c_str());
        if (totalEntries < TOTAL_ENTRIES && resultBegin == 0) {
          const auto& event = results.getEvent();
          drawTable("Event Condition", results.getMapId(), event.id(), event.name(), event.x(), event.y(), results.getPage() + 1, results.getStep() + 1);
          totalEntries++;
        }
        if (resultBegin > 0) {
          resultBegin--;
        }
        ImGui::PopID();
        ++i;
      }
      for (int i = 0; auto& results : reference.getCommands()) {
        if (totalEntries < TOTAL_ENTRIES && resultBegin == 0) {
          ImGui::PushID(std::format("##command_list_result_{}", i).c_str());
          const auto& event = results.getEvent();

          if (type == SearchType::Script) {
            drawStringCommand(results, type, totalEntries, results.getStep() + 1);
            totalEntries++;
          }
          if (type == SearchType::Audio) {
            if (results.getCommand()->hasStringReference(m_searchString, type)) {
              drawTable("Command List", results.getMapId(), event.id(), event.name(), event.x(), event.y(), results.getPage() + 1, results.getStep() + 1);
              totalEntries++;
            }
          } else {
            if (results.getCommand()->hasReference(m_selectedData, type)) {
              drawTable("Command List", results.getMapId(), event.id(), event.name(), event.x(), event.y(), results.getPage() + 1, results.getStep() + 1);
              totalEntries++;
            }
          }
          ImGui::PopID();
          ++i;
        }
        if (resultBegin > 0) {
          resultBegin--;
        }
      }
      for (int i = 0; auto& commonEv : reference.getCommons()) {
        if (totalEntries < TOTAL_ENTRIES && resultBegin == 0) {
          ImGui::PushID(std::format("##command_list_result_{}", i).c_str());
          if (type == SearchType::Script) {
            drawStringCommand(commonEv, type, tableIndex, commonEv.getStep() + 1);
            totalEntries++;
          } else {
            drawTable(commonEv.getCommonEvent().id(), totalEntries, commonEv.getStep() + 1);
            totalEntries++;
          }
          tableIndex++;
          ImGui::PopID();
          ++i;
        }
        if (resultBegin > 0) {
          resultBegin--;
        }
      }
      ImGui::EndTable();
    }
  }
  ImGui::End();
}
void EventSearcher::drawTable(std::string label, int mapId, int eventId, std::string eventName, int x, int y, int pageNo, int step) {
  ImGui::TableNextColumn();
  const bool isSelected = (m_selectedResult == totalEntries);

  if (ImGui::SelectableWithBorder(std::format("{}##orpg_table_event_type_entry{}_{}_{}_{}", label, mapId, eventId, pageNo, totalEntries).c_str(), isSelected,
                                  ImGuiSelectableFlags_AllowOverlap | ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowDoubleClick)) {
    m_selectedResult = totalEntries;
    if (isSelected)
      ImGui::SetItemDefaultFocus();
  }
  ImGui::TableNextColumn();
  ImGui::PushID(std::format("##orpg_table_map_entry{}_{}_{}", eventId, pageNo, totalEntries).c_str());
  ImGui::TextUnformatted(std::format("{:03}:{}", mapId, Database::instance()->mapName(mapId)).c_str());
  ImGui::PopID();

  ImGui::TableNextColumn();
  ImGui::PushID(std::format("##orpg_eventsearcher_event_data_{}_{}_{}", eventId, pageNo, totalEntries).c_str());
  ImGui::TextUnformatted(std::format("{:03}:{}", eventId, eventName).c_str());
  ImGui::PopID();

  ImGui::TableNextColumn();
  ImGui::PushID(std::format("##orpg_eventsearcher_event_pageNo_{}_{}_{}", eventId, pageNo, totalEntries).c_str());
  ImGui::TextUnformatted(std::to_string(pageNo).c_str());
  ImGui::PopID();

  ImGui::TableNextColumn();
  ImGui::PushID(std::format("##orpg_eventsearcher_event_step_{}_{}_{}", eventId, pageNo, totalEntries).c_str());
  ImGui::TextUnformatted(step == 0 ? "-" : std::to_string(step).c_str());
  ImGui::PopID();

  ImGui::TableNextColumn();
  ImGui::PushID(std::format("##orpg_eventsearcher_event_pos_{}_{}_{}", eventId, pageNo, totalEntries).c_str());
  ImGui::TextUnformatted(std::string(std::format("({},{})", x, y)).c_str());
  ImGui::PopID();
  ImGui::TableNextRow();
}
void EventSearcher::drawTable(int commonId, int tableIndex, int step) {
  const bool isSelected = (m_selectedResult == totalEntries);

  ImGui::TableNextColumn();
  if (ImGui::SelectableWithBorder(std::format("{}##orpg_table_type_common_entry{}", "Common Event", commonId).c_str(), isSelected,
                                  ImGuiSelectableFlags_AllowOverlap | ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowDoubleClick)) {
    m_selectedResult = totalEntries;
    if (isSelected)
      ImGui::SetItemDefaultFocus();
  }
  ImGui::TableNextColumn();
  ImGui::PushID(std::format("##orpg_table_commonev_entry{}_{}", commonId, totalEntries).c_str());
  ImGui::Text("-");
  ImGui::PopID();

  ImGui::TableNextColumn();
  ImGui::PushID(std::format("##orpg_eventsearcher_commonev_data_{}_{}", commonId, totalEntries).c_str());
  ImGui::TextUnformatted(std::format("{:03}:{}", commonId, Database::instance()->commonEventName(commonId)).c_str());
  ImGui::PopID();

  ImGui::TableNextColumn();
  ImGui::PushID(std::format("##orpg_eventsearcher_commonev_pageNo_{}_{}", commonId, totalEntries).c_str());
  ImGui::Text("-");
  ImGui::PopID();

  ImGui::TableNextColumn();
  ImGui::PushID(std::format("##orpg_eventsearcher_commonev_step_{}_{}", commonId, totalEntries).c_str());
  ImGui::TextUnformatted(step == 0 ? "-" : std::to_string(step).c_str());
  ImGui::PopID();

  ImGui::TableNextColumn();
  ImGui::PushID(std::format("##orpg_eventsearcher_commonev_pos_{}_{}", commonId, totalEntries).c_str());
  ImGui::Text("-");
  ImGui::PopID();
  ImGui::TableNextRow();
}
void EventSearcher::drawTable(std::string text, int tableIndex, int step, int eventId, int mapId, int pageId, int pos) {
  const bool isSelected = (m_selectedResult == totalEntries);
  ImGui::TableNextColumn();
  if (ImGui::SelectableWithBorder(std::format("{}##orpg_table_type_script_entry{}_{}", text, tableIndex, totalEntries).c_str(), isSelected,
                                  ImGuiSelectableFlags_AllowOverlap | ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowDoubleClick)) {
    m_selectedResult = totalEntries;
    if (isSelected)
      ImGui::SetItemDefaultFocus();
  }
  ImGui::TableNextColumn();
  ImGui::PushID(std::format("##orpg_table_script_entry{}_{}", tableIndex, totalEntries).c_str());
  ImGui::TextUnformatted(std::to_string(mapId).c_str());
  ImGui::PopID();

  ImGui::TableNextColumn();
  ImGui::PushID(std::format("##orpg_eventsearcher_script_data_{}_{}", tableIndex, totalEntries).c_str());
  ImGui::TextUnformatted(std::to_string(eventId).c_str());
  ImGui::PopID();

  ImGui::TableNextColumn();
  ImGui::PushID(std::format("##orpg_eventsearcher_script_pageNo_{}_{}", tableIndex, totalEntries).c_str());
  ImGui::TextUnformatted(std::to_string(pageId).c_str());
  ImGui::PopID();

  ImGui::TableNextColumn();
  ImGui::PushID(std::format("##orpg_eventsearcher_script_step_{}", tableIndex, totalEntries).c_str());
  ImGui::TextUnformatted(step == 0 ? "-" : std::to_string(step).c_str());
  ImGui::PopID();

  ImGui::TableNextColumn();
  ImGui::PushID(std::format("##orpg_eventsearcher_script_pos_{}_{}", tableIndex, totalEntries).c_str());
  ImGui::TextUnformatted(std::to_string(pos).c_str());
  ImGui::PopID();
  ImGui::TableNextRow();
}
void EventSearcher::drawStringCommand(const SearchResult& result, SearchType type, int tableIndex, int step) {
  if (result.getCommand()->hasStringReference(m_searchString, type)) {
    if (result.getCommand()->code() == EventCode::Script) {
      const auto cmd = std::dynamic_pointer_cast<const ScriptCommand>(result.getCommand());
      if (cmd->moreScript.size() > 0) {
        for (auto& more : cmd->moreScript) {
          if (result.resultSource() == SearchResult::ResultSource::Event) {
            drawTable(more->script, tableIndex, step, result.getEventId(), result.getMapId(), result.getPage(), result.getTile());
          } else if (result.resultSource() == SearchResult::ResultSource::CommonEvent) {
            drawTable(more->script, tableIndex, step, result.getCommonEventId(), -1, -1, -1);
          }
        }
      }
      drawTable(cmd->script, tableIndex, step, result.getEvent().id(), result.getMapId(), result.getPage(), result.getTile());
    } else if (result.getCommand()->code() == EventCode::Script_del_Movement) {
      auto cmd = std::dynamic_pointer_cast<const MovementScriptCommand>(result.getCommand());
      if (result.resultSource() == SearchResult::ResultSource::Event) {
        drawTable(cmd->script, tableIndex, step, result.getEventId(), result.getMapId(), result.getPage(), result.getTile());
      } else if (result.resultSource() == SearchResult::ResultSource::CommonEvent) {
        drawTable(cmd->script, tableIndex, step, result.getCommonEventId(), -1, -1, -1);
      }
    }
  }
}


code:
#pragma once

#include "Core/CommonUI/ObjectPicker.hpp"
#include "Core/CommonUI/VariableSwitchPicker.hpp"
#include "Database/Actors.hpp"
#include "Database/Animations.hpp"
#include "Database/Armors.hpp"
#include "Database/Audio.hpp"
#include "Database/Class.hpp"
#include "Database/CommonEvent.hpp"
#include "Database/Enemies.hpp"
#include "Database/Event.hpp"
#include "Database/Items.hpp"
#include "Database/Skills.hpp"
#include "Database/States.hpp"
#include "Database/Weapons.hpp"
#include "ReferenceSearch.hpp"
#include "imgui.h"

#include <vector>

struct MainWindow;
class EventSearcher {
public:
  explicit EventSearcher(MainWindow* parent)
  : m_parent(parent) {}

  bool getListing();

  void draw();
  void drawTable(std::string label, int mapId, int eventId, std::string eventName, int x, int y, int pageNo, int step);
  void drawTable(int commonId, int tableIndex, int step);
  void drawTable(std::string text, int tableIndex, int step, int eventId, int mapId, int pageId, int pos);
  void drawStringCommand(const SearchResult& result, SearchType type, int tableIndex, int step);

  [[nodiscard]] bool isOpen() const { return m_isOpen; }
  void open() { m_isOpen = true; }

private:
  ReferenceSearch reference;
  // std::vector<std::optional<Event>> m_events;
  std::optional<VariableSwitchPicker> picker;
  MainWindow* m_parent;
  int m_selectedVersion = -1;
  int m_pickedSearchType{0};

  int m_selectedSearchType{0};
  int m_selectedResult{0}; // For table selection
  int m_currentPage{0};
  int m_maxPage{1};
  int totalEntries{0};
  bool m_isOpen = false;
  const int TOTAL_ENTRIES = 29;

  // This is required so we don't update the data unless we ask to search
  int m_selectedData{1}; // Data set from picked data
  int m_pickedData{1};   // Data picked from selectors
  std::string m_searchString;
  std::string m_pickedString;

  bool m_valueSelected{false};

  SearchType type{SearchType::Variable};
  SearchType pickedType{SearchType::Variable};

  std::optional<ObjectPicker<Animation>> m_animationPicker;
  std::optional<ObjectPicker<Class>> m_classPicker;
  std::optional<ObjectPicker<State>> m_statePicker;
  std::optional<ObjectPicker<std::optional<CommonEvent>>> m_commonPicker;
  std::optional<ObjectPicker<Actor>> m_actorPicker;
  std::optional<ObjectPicker<Weapon>> m_weaponPicker;
  std::optional<ObjectPicker<Armor>> m_armorPicker;
  std::optional<ObjectPicker<Item>> m_itemPicker;
  std::optional<ObjectPicker<Enemy>> m_enemyPicker;
  std::optional<ObjectPicker<Skill>> m_skillPicker;
};


code:
#include "Core/FirstBootWizard.hpp"

#include "imgui.h"

bool FirstBootWizard::draw() {
  if (m_pages.empty()) {
    return true;
  }

  bool ret = false;

  const ImVec2 center = ImGui::GetMainViewport()->GetCenter();
  ImGui::SetNextWindowPos(center, ImGuiCond_Always, ImVec2(0.5f, 0.5f));
  ImGui::SetNextWindowSize(ImGui::GetMainViewport()->Size / 2, ImGuiCond_Once);
  ImGui::Begin((std::string(m_pages[m_currentPage]->pageTitle()) + " - First Boot Wizard").c_str(), nullptr,
               ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoScrollbar);
  {
    m_pages[m_currentPage]->draw();
    const auto textSize = ImGui::CalcTextSize("BACKFINISH");
    ImGui::SetCursorPosY(ImGui::GetContentRegionMax().y - (textSize.y + (ImGui::GetStyle().FramePadding.y * 2) + ImGui::GetStyle().ItemSpacing.y));
    ImGui::Separator();
    ImGui::SetCursorPosX(ImGui::GetContentRegionMax().x - (textSize.x + (ImGui::GetStyle().FramePadding.x * 2) + ImGui::GetStyle().ItemSpacing.x));
    ImGui::BeginDisabled(m_currentPage == 0);
    {
      if (ImGui::Button("Back")) {
        m_currentPage--;
        if (m_currentPage < 0) {
          m_currentPage = 0;
        }
      }
    }
    ImGui::EndDisabled();
    ImGui::SameLine();
    ImGui::BeginDisabled(!m_pages[m_currentPage]->allRequiredFieldsAreSet());
    {
      if (ImGui::Button(m_currentPage < m_pages.size() - 1 ? "Next" : "Finish") && m_pages[m_currentPage]->allRequiredFieldsAreSet()) {
        m_currentPage++;
        if (m_currentPage >= m_pages.size()) {
          ret = true;
        }
      }
    }
    ImGui::EndDisabled();
  }
  ImGui::End();

  return ret;
}

void FirstBootWizard::addPage(const std::shared_ptr<IWizardPage>& page) { m_pages.push_back(page); }


code:
#pragma once
#include "FirstBootWizard/IWizardPage.hpp"

#include <memory>
#include <vector>

class FirstBootWizard {
public:
  bool draw();
  void addPage(const std::shared_ptr<IWizardPage>& page);

private:
  std::vector<std::shared_ptr<IWizardPage>> m_pages;
  int m_currentPage = 0;
};


code:
#pragma once

#include <string>

using namespace std::string_view_literals;

class IWizardPage {
public:
  virtual ~IWizardPage() = default;
  virtual void draw() = 0;
  [[nodiscard]] virtual constexpr std::string_view pageTitle() const = 0;
  [[nodiscard]] virtual bool allRequiredFieldsAreSet() const { return true; }
};


code:
#include "Core/FirstBootWizard/ProjectLocationPage.hpp"
#include "Core/Application.hpp"
#include "Core/ImGuiExt/ImGuiUtils.hpp"
#include "Core/Settings.hpp"

#include <SDL3/SDL_dialog.h>
#include <imgui.h>

void ProjectLocationPage::draw() {
  ImGui::BeginChild("ProjectLocationPage", ImVec2(0, 0), ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY | ImGuiWindowFlags_AlwaysAutoResize, ImGuiWindowFlags_NoBackground);
  char location[4096];
  strncpy(location, Settings::instance()->projectBaseDirectory.c_str(), 4096);
  if (ImGui::LabelOverLineEdit("##project_location_line_edit", "Project Base Directory Location(*)", location, sizeof(location), 0.f, "Sets the directory where projects are stored")) {
    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* userdata, 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();
          },
          this, App::APP->getWindow()->getNativeWindow(), !Settings::instance()->projectBaseDirectory.empty() ? Settings::instance()->projectBaseDirectory.c_str() : nullptr, false);
    }
    ImGui::SetItemTooltip("Select a directory to store RPG Maker game projects");
  }
  ImGui::EndGroup();
  ImGui::EndChild();
  m_allRequiredFieldsAreSet = std::filesystem::is_directory(Settings::instance()->projectBaseDirectory);
}


code:
#pragma once
#include "Core/FirstBootWizard/IWizardPage.hpp"

class ProjectLocationPage : public IWizardPage {
public:
  void draw() override;
  [[nodiscard]] constexpr std::string_view pageTitle() const override { return "Specify Default Project Location"sv; }
  [[nodiscard]] bool allRequiredFieldsAreSet() const override { return m_allRequiredFieldsAreSet; }

private:
  bool m_allRequiredFieldsAreSet{false};
};


code:
#include "Core/FirstBootWizard/RPGMakerLocationAndVersionPage.hpp"

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

#include <Database/Versions.hpp>
#include <SDL3/SDL_dialog.h>
#include <filesystem>
#include <imgui.h>

void RPGMakerLocationAndVersionPage::draw() {
  ImGui::BeginChild("RPGMakerLocationAndVersionPage", ImVec2(0, 0), ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY | ImGuiWindowFlags_AlwaysAutoResize, ImGuiWindowFlags_NoBackground);
  char location[4096];
  strncpy(location, Settings::instance()->rpgMakerMVLocation.c_str(), 4096);
  if (ImGui::LabelOverLineEdit("##rpgmakermv_location_line_edit", trNOOP("RPG Maker MV Location(*)"), location, sizeof(location), 0.f)) {
    Settings::instance()->rpgMakerMVLocation = 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(std::format("{}##rpgmakermv", trNOOP("Choose...")).c_str())) {
      SDL_ShowOpenFolderDialog(
          [](void*, const char* const* fileList, int filter) {
            if (!fileList || !*fileList || filter >= 1) {
              return;
            }
            const std::filesystem::path path{fileList[0]};
            Settings::instance()->rpgMakerMVLocation = absolute(path).generic_string();
          },
          nullptr, App::APP->getWindow()->getNativeWindow(), !Settings::instance()->rpgMakerMVLocation.empty() ? Settings::instance()->rpgMakerMVLocation.c_str() : nullptr, false);
    }
  }
  ImGui::EndGroup();
  ImGui::BeginGroup();
  {
    ImGui::Text("RPG Maker MV Version");
    if (ImGui::BeginCombo("##rpgmakermv_version_combo", Settings::instance()->rpgMakerMVVersion != -1 ? KnownRPGMVVersions[Settings::instance()->rpgMakerMVVersion].data() : "Latest")) {
      if (ImGui::Selectable("Latest", Settings::instance()->rpgMakerMVVersion == -1)) {
        Settings::instance()->rpgMakerMVVersion = -1;
      }
      for (int i = 0; i < KnownRPGMVVersions.size(); ++i) {
        if (ImGui::Selectable(KnownRPGMVVersions[i].data(), i == Settings::instance()->rpgMakerMVVersion)) {
          Settings::instance()->rpgMakerMVVersion = i;
        }
        if (Settings::instance()->rpgMakerMVVersion == i) {
          ImGui::SetItemDefaultFocus();
        }
      }
      ImGui::EndCombo();
    }
  }
  ImGui::EndGroup();
  ImGui::Separator();
  char location2[4096];
  strncpy(location2, Settings::instance()->rpgMakerMZLocation.c_str(), 4096);
  if (ImGui::LabelOverLineEdit("##rpgmakermz_location_line_edit", "RPG Maker MZ Location(*)", location2, sizeof(location2), 0.f)) {
    Settings::instance()->rpgMakerMZLocation = location2;
  }
  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(std::format("{}##rpgmakermz", trNOOP("Choose...")).c_str())) {
      SDL_ShowOpenFolderDialog(
          [](void*, const char* const* fileList, int filter) {
            if (!fileList || !*fileList || filter >= 1) {
              return;
            }
            const std::filesystem::path path{fileList[0]};
            Settings::instance()->rpgMakerMZLocation = absolute(path).generic_string();
          },
          nullptr, App::APP->getWindow()->getNativeWindow(), !Settings::instance()->rpgMakerMZLocation.empty() ? Settings::instance()->rpgMakerMZLocation.c_str() : nullptr, false);
    }
  }
  ImGui::EndGroup();
  ImGui::BeginGroup();
  {
    ImGui::Text("RPG Maker MZ Version");
    if (ImGui::BeginCombo("##rpgmakermz_version_combo", Settings::instance()->rpgMakerMZVersion != -1 ? KnownRPGMZVersions[Settings::instance()->rpgMakerMZVersion].data() : "Latest")) {
      if (ImGui::Selectable("Latest", Settings::instance()->rpgMakerMZVersion == -1)) {
        Settings::instance()->rpgMakerMZVersion = -1;
      }
      for (int i = 0; i < KnownRPGMZVersions.size(); ++i) {
        if (ImGui::Selectable(KnownRPGMZVersions[i].data(), i == Settings::instance()->rpgMakerMZVersion)) {
          Settings::instance()->rpgMakerMZVersion = i;
        }
        if (Settings::instance()->rpgMakerMZVersion == i) {
          ImGui::SetItemDefaultFocus();
        }
      }
      ImGui::EndCombo();
    }
  }
  ImGui::EndGroup();
  ImGui::EndChild();

  m_allRequiredFieldsAreSet = std::filesystem::is_directory(Settings::instance()->rpgMakerMVLocation) || std::filesystem::is_directory(Settings::instance()->rpgMakerMZLocation);
}


code:
#pragma once
#include "Core/FirstBootWizard/IWizardPage.hpp"

class RPGMakerLocationAndVersionPage : public IWizardPage {
public:
  void draw() override;
  [[nodiscard]] constexpr std::string_view pageTitle() const override { return "Specify RPG Maker MV/MZ Location and Version"sv; }
  [[nodiscard]] bool allRequiredFieldsAreSet() const override { return m_allRequiredFieldsAreSet; }

private:
  bool m_allRequiredFieldsAreSet{false};
};


code:
#include "Core/FirstBootWizard/UISettingsPage.hpp"

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

#include <imgui.h>

static inline float roundToNearestQuarter(float num) { return static_cast<float>(static_cast<int>(num * 4)) / 4; }

void UISettingsPage::draw() {
  ImGui::BeginChild("UISettingsPage", ImVec2(0, 0), ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY | ImGuiWindowFlags_AlwaysAutoResize, ImGuiWindowFlags_NoBackground);
  ImGui::Text("Font Size");
  ImGui::SliderInt("##ui_font_size", &Settings::instance()->fontSize, 8, 48);
  if (ImGui::IsItemDeactivatedAfterEdit()) {
    App::APP->requestFontUpdate();
    m_fontSizeChanged = true;
  }
  ImGui::PushFont(App::APP->getMonoFont());
  ImGui::Text("Monospace Font Size");
  ImGui::SliderInt("##ui_mono_font_size", &Settings::instance()->monoFontSize, 8, 48);
  if (ImGui::IsItemDeactivatedAfterEdit()) {
    App::APP->requestFontUpdate();
    m_fontSizeChanged = true;
  }
  ImGui::PopFont();
  if (m_fontSizeChanged) {
    ImGui::Text("Reloading fonts....");
    m_fontSizeChanged = !App::APP->fontUpdateRequestPerformed();
  }
  ImGui::EndChild();
}


code:
#pragma once
#include "Core/FirstBootWizard/IWizardPage.hpp"

class UISettingsPage : public IWizardPage {
public:
  void draw() override;
  [[nodiscard]] constexpr std::string_view pageTitle() const override { return "UI Settings"sv; }

private:
  bool m_fontSizeChanged{false};
};


code:
#include "Core/FirstBootWizard/WelcomePage.hpp"

#include "imgui.h"
#include "imgui_internal.h"

void WelcomePage::draw() {
  ImGui::BeginGroup();
  ImGui::Text(
      "Welcome Open RPG Editor, the free Open Source\n"
      "drop-in replacement for RPG Maker MV/MZ.");
  ImGui::Text("License");
  static constexpr char license[] = R"(MIT License

Copyright (c) 2024 OpenRPGEditor Team

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.)";
  const auto textSize = ImGui::CalcTextSize("ABCDEFGHIJK");
  ImGui::InputTextMultiline("##ore_license_text", const_cast<char*>(license), sizeof(license),
                            {ImGui::GetContentRegionMax().x - ImGui::GetStyle().FramePadding.x * 2,
                             ImGui::GetContentRegionMax().y - (ImGui::GetCursorPosY() + textSize.y + (ImGui::GetStyle().FramePadding.x * 2) + ImGui::GetStyle().ItemSpacing.y)},
                            ImGuiInputTextFlags_ReadOnly);
  ImGui::EndGroup();
}


code:
#pragma once
#include "Core/FirstBootWizard/IWizardPage.hpp"

class WelcomePage : public IWizardPage {
public:
  void draw() override;
  [[nodiscard]] constexpr std::string_view pageTitle() const override { return "Welcome to Open RPG Editor"sv; }
};


code:
#include "Core/Graphics/CharacterSheet.hpp"

#include "Core/ResourceManager.hpp"

#include "Database/Database.hpp"

CharacterSheet::CharacterSheet(std::string_view sheetName, bool isTileId, int tileId)
: m_characterName(sheetName) {
  if (isTileId && tileId >= 0) {
    m_isTileId = isTileId;
    const int setId = 5 + floor(tileId / 256);
    const Map* map = nullptr;
    if (Database::instance()->mapInfos.currentMap() && Database::instance()->mapInfos.currentMap()->map()) {
      map = Database::instance()->mapInfos.currentMap()->map();
    }
    const Tileset* tileset = nullptr;
    if (map) {
      tileset = Database::instance()->tilesets.tileset(map->tilesetId());
    }
    if (tileset) {
      sheetName = tileset->tilesetName(setId);
    }
    m_sheetTexture = ResourceManager::instance()->loadTilesetImage(sheetName);
  } else {
    int count = 0;
    for (auto c : sheetName) {
      if (count >= 2) {
        break;
      }

      if (c == '$') {
        m_isSingleCharacter = true;
        ++count;
      } else if (c == '!') {
        m_noShift6_noHalfTransInBush = true;
        ++count;
      }
    }
    m_sheetTexture = ResourceManager::instance()->loadCharacterImage(sheetName);
  }
}

int CharacterSheet::characterWidth() const { return m_isTileId ? m_sheetTexture.width() / patternCountForTile() : m_sheetTexture.width() / patternCount(); }

int CharacterSheet::characterHeight() const { return m_isTileId ? m_sheetTexture.width() / directionCountForTile() : m_sheetTexture.height() / directionCount(); }

SimpleRect CharacterSheet::getRectForCharacter(int character, int pattern, Direction direction) const {
  if (!m_sheetTexture) {
    return {};
  }
  const int CharacterSpriteWidth = characterWidth();
  const int CharacterSpriteHeight = characterHeight();
  const int CharacterAtlasWidth = m_isSingleCharacter ? m_sheetTexture.width() : m_sheetTexture.width() / 4;
  const int CharacterAtlasHeight = m_isSingleCharacter ? m_sheetTexture.height() : m_sheetTexture.height() / 2;
  const float charX = static_cast<float>((character % (m_sheetTexture.width() / CharacterAtlasWidth)) * CharacterAtlasWidth);
  const float charY = static_cast<float>((character / (m_sheetTexture.width() / CharacterAtlasWidth)) * CharacterAtlasHeight);
  const float patternOffset = static_cast<float>(pattern * CharacterSpriteWidth);
  const float directionOffset = direction != Direction::Retain ? (((static_cast<int>(direction) - 2) / 2) * CharacterSpriteHeight) : 0.f;

  return {
      PointF{(charX + patternOffset) / m_sheetTexture.width(), (charY + directionOffset) / m_sheetTexture.height()},
      PointF{(charX + patternOffset + CharacterSpriteWidth) / m_sheetTexture.width(), (charY + directionOffset + CharacterSpriteHeight) / m_sheetTexture.height()},
  };
}

SimpleRect CharacterSheet::getRectForTile(int tileId) {
  if (!m_sheetTexture) {
    return {};
  }

  const int CharacterSpriteWidth = 48;
  const int CharacterSpriteHeight = 48;

  // Use 256-tile pages, assuming 16x16 tiles per page
  int tileX = fmod(floorf(tileId / 128), 2) * 8 + tileId % 8;
  int tileY = fmodf(floorf(tileId % 256 / 8), 16);

  // Compute pixel coordinates
  float tileU0 = tileX * CharacterSpriteWidth;
  float tileV0 = tileY * CharacterSpriteHeight;
  float tileU1 = tileU0 + static_cast<float>(CharacterSpriteWidth);
  float tileV1 = tileV0 + static_cast<float>(CharacterSpriteHeight);

  // Normalize UVs
  tileU0 /= m_sheetTexture.width();
  tileV0 /= m_sheetTexture.height();
  tileU1 /= m_sheetTexture.width();
  tileV1 /= m_sheetTexture.height();

  return {PointF{tileU0, tileV0}, PointF{tileU1, tileV1}};
}


code:
#pragma once

#include "Core/Graphics/SimpleRect.hpp"
#include "Core/Graphics/Texture.hpp"
#include "Database/Globals.hpp"

struct CharacterSheet {
  CharacterSheet() = default;

  explicit CharacterSheet(std::string_view sheetName, bool isTileId = false, int tileId = 0);

  int characterWidth() const;

  int characterHeight() const;

  int characterAtlasWidth() const { return characterWidth() * (m_isTileId ? patternCountForTile() : patternCountForCharacter()); }

  int characterAtlasHeight() const { return characterHeight() * (m_isTileId ? directionCountForTile() : directionCountForCharacter()); }

  [[nodiscard]] bool isSingleCharacter() const { return m_isSingleCharacter; }
  [[nodiscard]] bool isNoShift() const { return m_noShift6_noHalfTransInBush; }
  [[nodiscard]] bool isNoHalfTransparentInBush() const { return m_noShift6_noHalfTransInBush; }

  [[nodiscard]] SimpleRect getRectForCharacter(int character, int pattern = 0, Direction direction = Direction::Down) const;

  [[nodiscard]] SimpleRect getRectForTile(int tileId);

  explicit operator bool() const { return m_sheetTexture.operator bool(); }

  [[nodiscard]] int patternCount() const { return m_isSingleCharacter ? 3 : 12; }
  [[nodiscard]] static constexpr int patternCountForCharacter() { return 3; }
  [[nodiscard]] int directionCount() const { return m_isSingleCharacter ? 4 : 8; }
  [[nodiscard]] static constexpr int directionCountForCharacter() { return 4; }
  [[nodiscard]] static constexpr int patternCountForTile() { return 16; }
  [[nodiscard]] static constexpr int directionCountForTile() { return 16; }

  [[nodiscard]] bool isTileSheet() { return m_isTileId; }

  const Texture& texture() const { return m_sheetTexture; }

  int numCharacters() const { return (m_sheetTexture.width() / characterWidth()) + (m_sheetTexture.height() / characterHeight()); }

  std::string_view characterName() const { return m_characterName; }

  operator ImTextureRef() const { return m_sheetTexture.operator ImTextureRef(); }

private:
  bool m_isTileId{false};

  bool m_isSingleCharacter = false;
  bool m_noShift6_noHalfTransInBush = false;
  std::string m_characterName;
  Texture m_sheetTexture;
};


code:
#include "Core/Graphics/CheckerboardTexture.hpp"
#include "Core/Application.hpp"

CheckerboardTexture::CheckerboardTexture(const int width, const int height, const CellSizes cellSize, const uint8_t evenColor, const uint8_t oddColor)
: m_width(width)
, m_height(height)
, m_pendingWidth(width)
, m_pendingHeight(height)
, m_cellSize(cellSize)
, m_evenColor(evenColor)
, m_oddColor(oddColor) {}

CheckerboardTexture::CheckerboardTexture(const CheckerboardTexture& other) {
  m_width = other.m_width;
  m_height = other.m_height;
  m_pendingWidth = other.m_pendingWidth;
  m_pendingHeight = other.m_pendingHeight;
  m_cellSize = other.m_cellSize;
  m_evenColor = other.m_evenColor;
  m_oddColor = other.m_oddColor;
  m_texture = other.m_texture;
  other.m_texture = nullptr;
}

CheckerboardTexture& CheckerboardTexture::operator=(const CheckerboardTexture& other) {
  m_width = other.m_width;
  m_height = other.m_height;
  m_pendingWidth = other.m_pendingWidth;
  m_pendingHeight = other.m_pendingHeight;
  m_texture = other.m_texture;
  m_cellSize = other.m_cellSize;
  m_evenColor = other.m_evenColor;
  m_oddColor = other.m_oddColor;
  m_texture = other.m_texture;
  other.m_texture = nullptr;
  return *this;
}

void CheckerboardTexture::setWidth(const int width) {
  if (width == m_width) {
    return;
  }
  m_pendingWidth = width;
  m_workDone = false;
}

void CheckerboardTexture::setHeight(const int height) {
  if (height == m_height) {
    return;
  }
  m_pendingHeight = height;
  m_workDone = false;
}

void CheckerboardTexture::setSize(const int width, const int height) {
  if (width == m_width && m_height == height) {
    return;
  }
  m_pendingWidth = width;
  m_pendingHeight = height;
  m_workDone = false;
}

void CheckerboardTexture::update() const {
  if (m_workDone) {
    if (!m_workTexture) {
      return;
    }
    if (m_frameDelay > 0) {
      m_frameDelay--;
    }
    if (m_texture != m_workTexture && m_frameDelay <= 0) {
      if (m_texture != nullptr) {
        SDL_DestroyTexture(static_cast<SDL_Texture*>(m_texture));
      }
      m_texture = m_workTexture;
      m_workTexture = nullptr;
      m_width = m_pendingWidth;
      m_height = m_pendingHeight;
      m_frameDelay = 0;
    }
    return;
  }
  SDL_Renderer* renderer = App::APP->getWindow()->getNativeRenderer();

  if (!m_checkerTexture) {
    m_checkerSize = static_cast<int>(m_cellSize) * 2;
    const uint8_t colors[2]{m_oddColor, m_evenColor};
    auto* data = static_cast<uint8_t*>(malloc(m_checkerSize * m_checkerSize * 4));
    for (int y = 0; y < m_checkerSize * 4; y += 4) {
      for (int x = 0; x < m_checkerSize * 4; x += 4) {
        const uint8_t c = colors[y / static_cast<int>(m_cellSize) % 2 ^ x / static_cast<int>(m_cellSize) % 2];
        *(data + (y * m_checkerSize + x) + 0) = c;
        *(data + (y * m_checkerSize + x) + 1) = c;
        *(data + (y * m_checkerSize + x) + 2) = c;
        *(data + (y * m_checkerSize + x) + 3) = static_cast<uint8_t>(255);
      }
    }

    SDL_Surface* surface = SDL_CreateSurfaceFrom(m_checkerSize, m_checkerSize, SDL_PIXELFORMAT_ARGB8888, data, 4 * m_checkerSize);

    m_checkerTexture = SDL_CreateTextureFromSurface(renderer, surface);
    free(data);
  }

  m_workTexture = SDL_CreateTexture(App::APP->getWindow()->getNativeRenderer(), SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, m_pendingWidth, m_pendingHeight);
  SDL_SetTextureBlendMode(static_cast<SDL_Texture*>(m_workTexture), SDL_BLENDMODE_BLEND);
  auto* oldTarget = SDL_GetRenderTarget(renderer);
  SDL_SetRenderTarget(renderer, static_cast<SDL_Texture*>(m_workTexture));
  SDL_RenderTextureTiled(renderer, static_cast<SDL_Texture*>(m_checkerTexture), nullptr, 1.f, nullptr);

  SDL_SetRenderTarget(renderer, oldTarget);
  m_workDone = true;
  if (m_texture == nullptr) {
    m_texture = m_workTexture;
    m_workTexture = nullptr;
    m_width = m_pendingWidth;
    m_height = m_pendingHeight;
  } else {
    m_frameDelay = 1;
  }
}

CheckerboardTexture::~CheckerboardTexture() {
  SDL_DestroyTexture(static_cast<SDL_Texture*>(m_texture));
  m_texture = nullptr;
}


code:
#pragma once

#include "SDL3/SDL.h"
#include "imgui.h"
enum class CellSizes : uint8_t {
  _8 = 8,
  _16 = 16,
  _24 = 24,
  _32 = 32,
  _48 = 48,
  _64 = 64,
};
struct CheckerboardTexture {

  CheckerboardTexture() = default;
  CheckerboardTexture(int width, int height, CellSizes cellSize = CellSizes::_64, uint8_t evenColor = 255, uint8_t oddColor = 220);
  CheckerboardTexture(const CheckerboardTexture& other);
  CheckerboardTexture& operator=(const CheckerboardTexture& other);
  ~CheckerboardTexture();

  [[nodiscard]] void* get() const {
    update();
    return m_texture;
  }
  explicit operator bool() const { return m_texture != nullptr; }

  explicit operator ImTextureID() const { return reinterpret_cast<ImTextureID>(get()); }

  int width() const { return m_width; }
  int height() const { return m_height; }
  void setWidth(int width);
  void setHeight(int height);

  void setSize(int width, int height);
  void update() const;

private:
  mutable void* m_checkerTexture = nullptr;
  mutable void* m_texture = nullptr;
  mutable void* m_workTexture = nullptr;
  mutable bool m_workDone{false};
  mutable int m_width{0};
  mutable int m_height{0};
  int m_pendingWidth{0};
  int m_pendingHeight{0};
  CellSizes m_cellSize{CellSizes::_64};
  mutable int m_checkerSize{0};
  uint8_t m_evenColor{255};
  uint8_t m_oddColor{220};
  mutable int m_workX{0};
  mutable int m_workY{0};
  mutable int m_frameDelay{0};
};


code:
#include "Core/Graphics/CheckeredCompositeTexture.hpp"

#include "Core/Application.hpp"
#include <SDL3/SDL.h>

// static int roundUp(const int numToRound, const int multiple) {
//   if (multiple == 0)
//     return numToRound;
//
//   int remainder = abs(numToRound) % multiple;
//   if (remainder == 0)
//     return numToRound;
//
//   if (numToRound < 0)
//     return -(abs(numToRound) - remainder);
//   return numToRound + multiple - remainder;
// }

CheckeredCompositeTexture::CheckeredCompositeTexture()
: m_checker(32, 32) {}

ImTextureID CheckeredCompositeTexture::get() {
  if (m_isDirty) {
    update();
  }

  return reinterpret_cast<ImTextureID>(m_compositedTexture);
}

void CheckeredCompositeTexture::setSize(const int finalWidth, const int finalHeight) {
  const auto tmp = Point(finalWidth, finalHeight);
  if (tmp == m_finalSize) {
    return;
  }
  m_finalSize = tmp;
  m_isDirty = true;
}

void CheckeredCompositeTexture::setTexturesToComposite(const std::vector<CompositeTexture>& texturesToComposite) {
  m_texturesToComposite = texturesToComposite;
  m_isDirty = true;
}

void CheckeredCompositeTexture::update() {
  if (m_finalSize.isNull() || !m_isDirty) {
    return;
  }

  if (!m_compositedTexture) {
    m_compositedTexture = SDL_CreateTexture(App::APP->getWindow()->getNativeRenderer(), SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, m_finalSize.x(), m_finalSize.y());
    SDL_SetTextureBlendMode(static_cast<SDL_Texture*>(m_compositedTexture), SDL_BLENDMODE_BLEND);
  }
  const int cols = std::ceil(static_cast<float>(m_finalSize.x()) / static_cast<float>(m_checker.width()));
  const int rows = std::ceil(static_cast<float>(m_finalSize.y()) / static_cast<float>(m_checker.height()));

  auto* oldTarget = SDL_GetRenderTarget(App::APP->getWindow()->getNativeRenderer());
  SDL_SetRenderTarget(App::APP->getWindow()->getNativeRenderer(), static_cast<SDL_Texture*>(m_compositedTexture));
  uint8_t r, g, b, a;
  SDL_GetRenderDrawColor(App::APP->getWindow()->getNativeRenderer(), &r, &g, &b, &a);
  SDL_SetRenderDrawColor(App::APP->getWindow()->getNativeRenderer(), 0, 0, 0, 0);
  SDL_RenderClear(App::APP->getWindow()->getNativeRenderer());

  {
    const SDL_FRect screct{0, 0, static_cast<float>(m_checker.width()), static_cast<float>(m_checker.height())};
    for (int i = 0; i < rows; ++i) {
      for (int j = 0; j < cols; ++j) {
        const SDL_FRect dstrect{static_cast<float>(j * m_checker.width()), static_cast<float>(i * m_checker.height()), static_cast<float>(m_checker.width()), static_cast<float>(m_checker.height())};
        SDL_RenderTexture(App::APP->getWindow()->getNativeRenderer(), static_cast<SDL_Texture*>(m_checker.get()), &screct, &dstrect);
      }
    }
  }

  for (const auto& [texture, originalSize, offset, scaleToRect] : m_texturesToComposite) {
    const float aspect = static_cast<float>(originalSize.x()) / static_cast<float>(originalSize.y());
    int width = originalSize.x();
    int height = originalSize.y();

    if (scaleToRect) {
      width = m_finalSize.x();
      height = static_cast<int>(static_cast<float>(m_finalSize.x()) / aspect);
      if (height > m_finalSize.y()) {
        height = m_finalSize.y();
        width = static_cast<int>(static_cast<float>(m_finalSize.y()) * aspect);
      }
    }

    const SDL_FRect screct = {
        static_cast<float>(offset.x()),
        static_cast<float>(offset.y()),
        static_cast<float>(originalSize.x()),
        static_cast<float>(originalSize.y()),
    };
    const SDL_FRect dstrect = {
        std::abs(static_cast<float>(m_finalSize.x() / 2 - (width / 2))) + 1,
        std::abs(static_cast<float>(m_finalSize.y() / 2 - (height / 2))) + 1,
        static_cast<float>(width - 2),
        static_cast<float>(height - 2),
    };
    SDL_RenderTexture(App::APP->getWindow()->getNativeRenderer(), static_cast<SDL_Texture*>(texture.get()), &screct, &dstrect);
  }

  SDL_SetRenderTarget(App::APP->getWindow()->getNativeRenderer(), oldTarget);
  SDL_SetRenderDrawColor(App::APP->getWindow()->getNativeRenderer(), r, g, b, a);
  m_isDirty = false;
}


code:
#pragma once
#include "CheckerboardTexture.hpp"
#include "Core/Graphics/Texture.hpp"
#include "OREMath/Point.hpp"

#include <vector>

struct CompositeTexture {
  Texture m_texture;
  Point originalSize;
  Point offset;
  bool scaleToRect{true};
};

class CheckeredCompositeTexture {
public:
  CheckeredCompositeTexture();

  void setSize(int finalWidth, int finalHeight);
  void setTexturesToComposite(const std::vector<CompositeTexture>& texturesToComposite);
  bool hasCompositeTextures() const { return !m_texturesToComposite.empty(); }
  ImTextureID get();

  int width() const { return m_finalSize.x(); }
  int height() const { return m_finalSize.y(); }

  Point size() const { return m_finalSize; }

  void clear() {
    m_texturesToComposite.clear();
    m_isDirty = true;
  }

private:
  void update();
  Point m_finalSize;
  void* m_compositedTexture = nullptr;
  CheckerboardTexture m_checker;
  std::vector<CompositeTexture> m_texturesToComposite;
  bool m_isDirty{true};
};


code:
#include "Core/Graphics/ColorTonePreview.hpp"
#include "Core/Application.hpp"
#include "OREMath/Color.hpp"
#include "OREMath/Matrix4x4.hpp"
#include "OREMath/Vector3D.hpp"
#include "SDL3/SDL_render.h"

double fract(double x) { return x - floor(x); }

double hueToIntensity(double v1, double v2, double h) {
  h = fract(h);
  if (h < 1.0 / 6.0)
    return v1 + (v2 - v1) * 6.0 * h;
  if (h < 1.0 / 2.0)
    return v2;
  if (h < 2.0 / 3.0)
    return v1 + (v2 - v1) * 6.0 * (2.0 / 3.0 - h);
  return v1;
}

Vector3D HSLtoRGB(const Vector3D& color) {
  double h = color.x();
  double l = color.z();
  double s = color.y();
  if (s < 1.0 / 256.0)
    return {static_cast<float>(l), static_cast<float>(l), static_cast<float>(l)};
  double v1;
  double v2;
  if (l < 0.5)
    v2 = l * (1.0 + s);
  else
    v2 = (l + s) - (s * l);
  v1 = 2.0 * l - v2;
  double d = 1.0 / 3.0;
  double r = hueToIntensity(v1, v2, h + d);
  double g = hueToIntensity(v1, v2, h);
  double b = hueToIntensity(v1, v2, h - d);
  return Vector3D{static_cast<float>(r), static_cast<float>(g), static_cast<float>(b)};
}

ColorTonePreview::ColorTonePreview(const int width, const int height)
: m_textureWidth(width)
, m_textureHeight(height) {}

ColorTonePreview::~ColorTonePreview() {
  SDL_DestroyTexture(static_cast<SDL_Texture*>(m_texture));
  m_texture = nullptr;
}

void ColorTonePreview::update(float r, float g, float b, float gray) {
  const float s = 1 - gray;
  const float a00 = 0.213 + 0.787 * s;
  const float a01 = 0.715 - 0.715 * s;
  const float a02 = 0.072 - 0.072 * s;
  const float a10 = 0.213 - 0.213 * s;
  const float a11 = 0.715 + 0.285 * s;
  const float a12 = 0.072 - 0.072 * s;
  const float a20 = 0.213 - 0.213 * s;
  const float a21 = 0.715 - 0.715 * s;
  const float a22 = 0.072 + 0.928 * s;
  Matrix4x4 matrix(a00, a10, a20, 0, a01, a11, a21, 0, a02, a12, a22, 0, r, g, b, 1);
  if (!m_texture) {
    m_texture = SDL_CreateTexture(App::APP->getWindow()->getNativeRenderer(), SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, m_textureWidth, m_textureHeight);
  }

  uint32_t* pixels = nullptr;
  int pitch = 0;
  if (SDL_LockTexture(static_cast<SDL_Texture*>(m_texture), nullptr, reinterpret_cast<void**>(&pixels), &pitch)) {
    for (int y = 0; y < m_textureHeight; y++) {
      for (int x = 0; x < m_textureWidth; x++) {
        const float s = static_cast<float>(x) / static_cast<float>(m_textureWidth);
        const float t = static_cast<float>(y) / static_cast<float>(m_textureHeight);
        auto color = HSLtoRGB({t, 1.f, s}) * matrix;
        pixels[(y * m_textureWidth) + x] =
            Color::rgb(oRound(std::clamp(color.x(), 0.f, 1.f) * 255.f), oRound(std::clamp(color.y(), 0.f, 1.f) * 255.f), oRound(std::clamp(color.z(), 0.f, 1.f) * 255.f));
      }
    }
    SDL_UnlockTexture(static_cast<SDL_Texture*>(m_texture));
  }
}

void ColorTonePreview::setSize(const int width, const int height) {
  if (width != m_textureWidth || height != m_textureHeight) {
    SDL_DestroyTexture(static_cast<SDL_Texture*>(m_texture));
    m_texture = nullptr;
    m_textureWidth = width;
    m_textureHeight = height;
  }
}


code:
#pragma once
#include "imgui.h"

class ColorTonePreview {
public:
  ColorTonePreview() = default;
  ColorTonePreview(int width, int height);
  ~ColorTonePreview();
  void update(float r, float g, float b, float gray);

  operator ImTextureRef() { return ImTextureRef(m_texture); }

  int width() const { return m_textureWidth; }
  int height() const { return m_textureHeight; }

  void setSize(int width, int height);

private:
  int m_textureWidth{60};
  int m_textureHeight{100};
  void* m_texture{nullptr};
};


code:
#include "Core/Graphics/FaceSheet.hpp"
#include "Core/ResourceManager.hpp"

FaceSheet::FaceSheet(const std::string_view sheetName)
: m_texture(ResourceManager::instance()->loadFaceImage(sheetName)) {}

Face FaceSheet::getFaceRect(const int index) const {
  const int fx = index % 4;
  const int fy = index / 4;

  Face face;
  face.min.xr() = fx * faceWidth();
  face.min.yr() = fy * faceHeight();
  face.max.xr() = (fx * faceWidth()) + faceWidth();
  face.max.yr() = (fy * faceHeight()) + faceHeight();
  face.min.xr() /= m_texture.width();
  face.min.yr() /= m_texture.height();
  face.max.xr() /= m_texture.width();
  face.max.yr() /= m_texture.height();

  return face;
}


code:
#pragma once

#include "Core/Graphics/Texture.hpp"
#include "OREMath/Point.hpp"

struct Face {
  PointF min;
  PointF max;
};

struct FaceSheet {
  explicit FaceSheet(const std::string_view sheetName);

  Face getFaceRect(int index) const;
  [[nodiscard]] const Texture& texture() const { return m_texture; }

  static constexpr int faceWidth() { return 144; }
  static constexpr int faceHeight() { return 144; }

private:
  Texture m_texture;
};


code:
#include "Core/Graphics/GameWindowBackground.hpp"

#include "Core/Application.hpp"

#include <SDL3/SDL.h>

static int roundUp(const int numToRound, const int multiple) {
  if (multiple == 0)
    return numToRound;

  int remainder = abs(numToRound) % multiple;
  if (remainder == 0)
    return numToRound;

  if (numToRound < 0)
    return -(abs(numToRound) - remainder);
  return numToRound + multiple - remainder;
}

void GameWindowBackground::update(const int r, const int g, const int b) {
  if (!ResourceManager::instance()) {
    return;
  }

  if (!m_renderTexture) {
    m_renderTexture = SDL_CreateTexture(App::APP->getWindow()->getNativeRenderer(), SDL_PIXELFORMAT_ABGR8888, SDL_TEXTUREACCESS_TARGET, m_width, m_height);
    SDL_SetTextureBlendMode(static_cast<SDL_Texture*>(m_renderTexture), SDL_BLENDMODE_BLEND);
  }

  if (!m_resultTexture) {
    m_resultTexture = SDL_CreateTexture(App::APP->getWindow()->getNativeRenderer(), SDL_PIXELFORMAT_ABGR8888, SDL_TEXTUREACCESS_STREAMING, m_width, m_height);
    SDL_SetTextureBlendMode(static_cast<SDL_Texture*>(m_resultTexture), SDL_BLENDMODE_BLEND);
  }

  if (!m_windowTexture) {
    m_windowTexture = ResourceManager::instance()->loadSystemImage("Window");
  }

  if (!m_renderTexture || !m_windowTexture) {
    return;
  }

  auto oldRenderTarget = SDL_GetRenderTarget(App::APP->getWindow()->getNativeRenderer());
  SDL_SetRenderTarget(App::APP->getWindow()->getNativeRenderer(), static_cast<SDL_Texture*>(m_renderTexture));
  if (m_sizeChanged || !m_isInitialized) {
    SDL_SetRenderDrawColor(App::APP->getWindow()->getNativeRenderer(), 0, 0, 0, 0);
    SDL_RenderClear(App::APP->getWindow()->getNativeRenderer());
    int cols = roundUp(m_width, 96) / 96;
    int rows = roundUp(m_height, 96) / 96;

    SDL_FRect screct{0, 0, 96, 96};
    SDL_FRect dstrect{0, 0, static_cast<float>(m_width), static_cast<float>(m_height)};
    SDL_RenderTexture(App::APP->getWindow()->getNativeRenderer(), static_cast<SDL_Texture*>(m_windowTexture.get()), &screct, &dstrect);

    screct = {0, 96, 96, 96};
    SDL_RenderTextureTiled(App::APP->getWindow()->getNativeRenderer(), static_cast<SDL_Texture*>(m_windowTexture.get()), &screct, 1.f, nullptr);
    SDL_RenderPresent(App::APP->getWindow()->getNativeRenderer());
    m_sizeChanged = false;
    m_isInitialized = true;
  }

  const SDL_Rect screct = {0, 0, m_width, m_height};
  if (auto surface = SDL_RenderReadPixels(App::APP->getWindow()->getNativeRenderer(), &screct)) {
    uint8_t* resultPixels;
    if (SDL_LockSurface(surface)) {
      uint8_t* sourcePixels = static_cast<uint8_t*>(surface->pixels);
      int pitch = 0;
      if (SDL_LockTexture(static_cast<SDL_Texture*>(m_resultTexture), nullptr, reinterpret_cast<void**>(&resultPixels), &pitch)) {
        for (int k = 0; k < m_width * m_height * 4; k += 4) {
          resultPixels[k + 3] = std::clamp(sourcePixels[k + 3] + 0, 0, 255);
          resultPixels[k + 0] = std::clamp(sourcePixels[k + 0] + r, 0, 255);
          resultPixels[k + 1] = std::clamp(sourcePixels[k + 1] + g, 0, 255);
          resultPixels[k + 2] = std::clamp(sourcePixels[k + 2] + b, 0, 255);
        }
        SDL_UnlockTexture(static_cast<SDL_Texture*>(m_resultTexture));
      }
      SDL_UnlockSurface(surface);
    }
  }

  SDL_SetRenderTarget(App::APP->getWindow()->getNativeRenderer(), oldRenderTarget);
}

void GameWindowBackground::setSize(const int width, const int height) {
  if (width != m_width || height != m_height) {
    SDL_DestroyTexture(static_cast<SDL_Texture*>(m_resultTexture));
    SDL_DestroyTexture(static_cast<SDL_Texture*>(m_renderTexture));
    m_resultTexture = nullptr;
    m_renderTexture = nullptr;
    m_width = width;
    m_height = height;
    m_sizeChanged = true;
  }
}


code:
#pragma once

#include "Core/Graphics/Texture.hpp"

#include <imgui.h>

class GameWindowBackground {
public:
  GameWindowBackground() = default;
  GameWindowBackground(const int width, const int height)
  : m_width(width)
  , m_height(height) {}
  void update(int r, int g, int b);

  operator ImTextureRef() const { return ImTextureRef(m_resultTexture); }

  int width() const { return m_width; }
  int height() const { return m_height; }

  void setSize(int width, int height);

private:
  void* m_renderTexture = nullptr;
  void* m_resultTexture = nullptr;
  int m_width{96};
  int m_height{96};
  Texture m_windowTexture;
  bool m_sizeChanged = true;
  bool m_isInitialized = false;
};


code:
#include "Core/Graphics/IconSheet.hpp"

#include "Core/ResourceManager.hpp"
#include "OREMath/Size.hpp"

IconSheet::IconSheet(const std::string_view gridAtlasPath)
: m_texture(ResourceManager::instance()->loadImage(gridAtlasPath)) {}

SimpleRect IconSheet::rectForId(const int id) const {
  float u0 = static_cast<float>(id % columnCount() * iconWidth());
  float v0 = floorf(static_cast<float>(id / columnCount())) * iconHeight();
  float u1 = u0 + iconWidth();
  float v1 = v0 + iconHeight();
  u0 /= static_cast<float>(m_texture.width());
  v0 /= static_cast<float>(m_texture.height());
  u1 /= static_cast<float>(m_texture.width());
  v1 /= static_cast<float>(m_texture.height());
  return {PointF{u0, v0}, PointF{u1, v1}};
}


code:
#pragma once
#include <string_view>

#include "Core/Graphics/SimpleRect.hpp"
#include "Core/Graphics/Texture.hpp"

class IconSheet {
public:
  IconSheet() = delete;
  explicit IconSheet(std::string_view gridAtlasPath);

  [[nodiscard]] static constexpr int iconWidth() { return 32; }
  [[nodiscard]] static constexpr int iconHeight() { return 32; }
  [[nodiscard]] static constexpr int columnCount() { return 16; }

  [[nodiscard]] SimpleRect rectForId(int id) const;

  const Texture& texture() const { return m_texture; }

private:
  Texture m_texture;
};


code:
#include "Core/Graphics/Image.hpp"

#include "Core/ResourceManager.hpp"

Image::Image(const std::string_view imageName, const Mode pickType, const bool secondImage = false) {
  if (imageName.empty()) {
    return;
  }
  switch (pickType) {
  case Mode::Battleback:
    if (secondImage) {
      m_imageTexture = ResourceManager::instance()->loadBattlebacks2Image(imageName);
    } else {
      m_imageTexture = ResourceManager::instance()->loadBattlebacks1Image(imageName);
    }
    break;
  case Mode::Parallax:
    m_imageTexture = ResourceManager::instance()->loadParallaxImage(imageName);
    break;
  case Mode::Picture:
    m_imageTexture = ResourceManager::instance()->loadPictureImage(imageName);
    break;
  case Mode::Title:
    if (secondImage) {
      m_imageTexture = ResourceManager::instance()->loadTitle2Image(imageName);
    } else {
      m_imageTexture = ResourceManager::instance()->loadTitle1Image(imageName);
    }
    break;
  case Mode::Tileset:
    m_imageTexture = ResourceManager::instance()->loadTilesetImage(imageName);
    break;
  case Mode::Animation:
    m_imageTexture = ResourceManager::instance()->loadAnimationImage(imageName);
    break;
  case Mode::SVBattler:
    m_imageTexture = ResourceManager::instance()->loadSVActorImage(imageName);
    break;
  case Mode::Window:
    m_imageTexture = ResourceManager::instance()->loadSystemImage("Window.png");
    break;
  }
  m_imageName = imageName;
}

int Image::imageWidth() const { return m_imageTexture.width(); }
int Image::imageHeight() const { return m_imageTexture.height(); }
std::string Image::imageName() const { return m_imageName; }


code:
#pragma once

#include "Core/Graphics/Texture.hpp"
#include "Database/Globals.hpp"

struct Image {
  enum class Mode {
    Battleback,
    Parallax,
    Picture,
    Title,
    Tileset,
    Animation,
    SVBattler,
    Window,
  };

  Image() = delete;

  explicit Image(std::string_view imageName, Mode pickType, bool secondImage);
  int imageWidth() const;
  int imageHeight() const;
  std::string imageName() const;

  explicit operator bool() const { return m_imageTexture.operator bool(); }
  const Texture& texture() const { return m_imageTexture; }

private:
  std::string m_imageName;
  Texture m_imageTexture;
};


code:
#include "Core/Graphics/ParallaxTexture.hpp"

#include "Core/Application.hpp"
#include "Core/ResourceManager.hpp"
#include "Database/Database.hpp"

#include <SDL3/SDL.h>
#include <SDL3/SDL_render.h>

// TODO: When the new renderer class is implemented replace direct ImGui call with the appropriate delta call

#include <imgui.h>

void ParallaxTexture::setTexture(const std::string_view path) {
  m_texture = ResourceManager::instance()->loadParallaxImage(path);
  update();
}

int ParallaxTexture::pixelWidth() const { return m_size.width() * Database::instance()->system.tileSize(); }
int ParallaxTexture::pixelHeight() const { return m_size.height() * Database::instance()->system.tileSize(); }

void ParallaxTexture::update() const {
  if (!m_texture || (!m_renderTexture && m_workDone)) {
    return;
  }

  if (!m_renderTexture) {
    m_renderTexture = SDL_CreateTexture(App::APP->getWindow()->getNativeRenderer(), SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, pixelWidth(), pixelHeight());
  }

  if (m_updateScrolling) {
    m_frameTime += ImGui::GetIO().DeltaTime;
    if (m_loopX) {
      m_scrollPosition.xr() += m_scroll.x() * ImGui::GetIO().DeltaTime;
    }
    if (m_loopY) {
      m_scrollPosition.yr() += m_scroll.y() * ImGui::GetIO().DeltaTime;
    }

    if (m_scrollPosition.x() < 0) {
      m_scrollPosition.xr() = width();
    } else if (m_scrollPosition.x() > width()) {
      m_scrollPosition.xr() = 0;
    }

    if (m_scrollPosition.y() < 0) {
      m_scrollPosition.yr() = height();
    } else if (m_scrollPosition.y() > height()) {
      m_scrollPosition.yr() = 0;
    }

    if (m_frameTime >= 1.f / 60.f) {
      m_workDone = false;
      m_frameTime = 0.f;
    }
  } else {
    m_frameTime = 0.f;
  }

  if (m_workDone) {
    return;
  }
  SDL_Texture* target = SDL_GetRenderTarget(App::APP->getWindow()->getNativeRenderer());
  SDL_SetRenderTarget(App::APP->getWindow()->getNativeRenderer(), static_cast<SDL_Texture*>(m_renderTexture));

  if (m_updateScrolling && (m_loopX || m_loopY)) {
    const float sx = m_scrollPosition.x() * Database::instance()->system.tileSize();
    const float sy = m_scrollPosition.y() * Database::instance()->system.tileSize();
    for (float y = -pixelHeight(); y <= pixelHeight(); y += m_texture.height()) {
      for (float x = -pixelWidth(); x <= pixelWidth(); x += m_texture.width()) {
        SDL_FRect dst{x + sx, y + sy, static_cast<float>(m_texture.width()), static_cast<float>(m_texture.height())};
        SDL_RenderTexture(App::APP->getWindow()->getNativeRenderer(), static_cast<SDL_Texture*>(m_texture.get()), nullptr, &dst);
      }
    }
  } else {
    const SDL_FRect dst{0, 0, static_cast<float>(pixelWidth()), static_cast<float>(pixelHeight())};
    SDL_RenderTextureTiled(App::APP->getWindow()->getNativeRenderer(), static_cast<SDL_Texture*>(m_texture.get()), nullptr, 1.f, &dst);
  }

  SDL_SetRenderTarget(App::APP->getWindow()->getNativeRenderer(), target);
  m_workDone = true;
}


code:
#pragma once

#include "Core/Graphics/RenderImage.hpp"
#include "Core/Graphics/Texture.hpp"

#include "OREMath/Point.hpp"
#include "OREMath/Size.hpp"

/**
 * @class ParallaxTexture Core/Graphics/ParallaxTexture.hpp
 * @brief
 * <p>
 * ParallaxTexture describes a texture which can be rendered as with scroll and looping behavior to add more
 * visual flair to a scene.
 * </p>
 */
class ParallaxTexture {
public:
  /**
   * Constructs a ParallaxTexture with known size, scroll speed, and looping behavior
   * @param size The total size of the area to render the parallax map to
   * @param scroll The Speed at which the parallax moves, in tiles
   * @param loopX Whether to loop the texture along the X axis
   * @param loopY Whether to loop the texture along the Y axis
   */
  ParallaxTexture(const Size size, const Point& scroll, const bool loopX, const bool loopY)
  : m_size(size)
  , m_scroll(scroll)
  , m_loopX(loopX)
  , m_loopY(loopY) {}
  /**
   *
   * @param width Width of the area to render the parallax map to
   * @param height Height of the area to render the parallax map to
   * @param scrollX The Speed at which the parallax move along the X axis, in tiles
   * @param scrollY The Speed at which the parallax move along the Y axis, in tiles
   * @param loopX Whether to loop the texture along the X axis
   * @param loopY Whether to loop the texture along the Y axis
   */
  explicit ParallaxTexture(const int width = 0, const int height = 0, const int scrollX = 0, const int scrollY = 0, const bool loopX = false, const bool loopY = false)
  : ParallaxTexture(Size{width, height}, {scrollX, scrollY}, loopX, loopY) {}

  /**
   *
   * @param texture Texture object to render as a parallax
   * @param size The total size of the area to render the parallax map to
   * @param scroll The Speed at which the parallax moves, in tiles
   * @param loopX Whether to loop the texture along the X axis
   * @param loopY Whether to loop the texture along the Y axis
   */
  explicit ParallaxTexture(const Texture& texture, const Size& size = {}, const Point& scroll = {}, const bool loopX = false, const bool loopY = false)
  : ParallaxTexture(size, scroll, loopX, loopY) {
    m_texture = texture;
  }

  /**
   *
   * @param path Project relative path to the texture to render as a parallax
   * @param size The total size of the area to render the parallax map to, in pixels
   * @param scroll The Speed at which the parallax moves, in tiles
   * @param loopX Whether to loop the texture along the X axis
   * @param loopY Whether to loop the texture along the Y axis
   */
  explicit ParallaxTexture(const std::string_view path, const Size& size = {}, const Point& scroll = {}, const bool loopX = false, const bool loopY = false)
  : ParallaxTexture(size, scroll, loopX, loopY) {
    setTexture(path);
  }

  /**
   * The speed at which to scroll the texture
   * @return The current scroll speed
   */
  const Point& scroll() const { return m_scroll; }
  /**
   * Sets the scroll speed of the texture
   * @param scroll The speed at which to scroll the texture, as defined in tiles
   */
  void setScroll(const Point& scroll) {
    if (m_scroll == scroll) {
      return;
    }
    m_scroll = scroll;
    m_workDone = false;
  }
  /**
   * Sets the scroll speed of the texture
   * @param x The speed at which to scroll the texture along the X axis, as defined in tiles
   * @param y The speed at which to scroll the texture along the Y axis, as defined in tiles
   */
  void setScroll(const int x, const int y) { setScroll(Point{x, y}); }

  /**
   * The size of the rendered area, in pixels
   * @return The size of the rendered area, in pixels
   */
  const Size& size() const { return m_size; }

  int width() const { return m_size.width(); }
  int height() const { return m_size.height(); }

  int pixelWidth() const;
  int pixelHeight() const;

  /**
   * Sets the size of the area the texture is rendered to, in pixels
   * @param size The size
   */
  void setSize(const Size& size) {
    if (m_size == size) {
      return;
    }
    m_size = size;
    m_workDone = false;
  }
  void setSize(const int w, const int h) { setSize(Size{w, h}); }

  const Texture& texture() const { return m_texture; }
  void setTexture(const Texture& texture) { m_texture = texture; }
  void setTexture(const std::string_view path);

  bool loopX() const { return m_loopX; }
  void setLoopX(const bool loopX) {
    if (loopX) {
      return;
    }

    m_loopX = loopX;
    m_workDone = false;
  }

  bool loopY() const { return m_loopY; }
  void setLoopY(const bool loopY) {
    if (m_loopY == loopY) {
      return;
    }
    m_loopY = loopY;
    m_workDone = false;
  }

  void enableUpdates() {
    // We don't want to set the scroll offset here just in case the user set the setting two times in a row
    m_updateScrolling = true;
  }
  void disableUpdates() {
    m_updateScrolling = false;
    m_scrollPosition = {};
    m_workDone = false;
  }
  bool areUpdatesEnabled() const { return m_updateScrolling; }
  void setUpdatesEnabled(const bool updates) {
    // We don't simply set m_updateScrolling here and use a ternary to avoid setting m_scrollPosition unnecessarily
    // Also the fact that this is valid C++ is cursed as all hell lmao
    updates ? enableUpdates() : disableUpdates();
  }

  explicit operator ImTextureID() const {
    this->update();
    return reinterpret_cast<ImTextureID>(m_renderTexture);
  }
  explicit operator bool() const { return m_texture.operator bool(); }

private:
  void update() const;
  /* Project related information */
  Texture m_texture;
  Size m_size;
  Point m_scroll;
  bool m_loopX;
  bool m_loopY;

  /* Runtime status */
  bool m_updateScrolling{false};
  mutable void* m_renderTexture = nullptr;
  mutable void* m_workTexture = nullptr;
  mutable bool m_workDone{false};
  mutable PointF m_scrollPosition;
  mutable float m_frameTime{0.f};
};