code:
#include "Core/DatabaseEditor/DBSystemTab.hpp"
#include "Database/Class.hpp"

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

using namespace std::string_view_literals;

DBSystemTab::DBSystemTab(System& system, DatabaseEditor* parent)
: IDBEditorTab(parent)
, m_system(system) {}

void DBSystemTab::addAudioRow(const Audio& audio, const std::string& type, AudioType audioType) {
  ImGui::TableNextRow();
  {
    ImGui::TableNextColumn();
    if (ImGui::Selectable(type.c_str(), false, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowDoubleClick)) {}
    ImGui::TableNextColumn();
    ImGui::Text("%s", audio.name().c_str());
  }
}

void DBSystemTab::draw() {
  if (!m_characterPicker) {
    m_characterPicker.emplace();
  }

  if (!m_boatButtonTexture) {
    m_boatButtonTexture.emplace();
    m_boatButtonTexture->setSize(ImGui::GetDPIScaledValue(48), ImGui::GetDPIScaledValue(48));
    if (!m_boatSheet) {
      m_boatSheet.emplace(m_system.boat().characterName());
    }
    const auto& [uv0, uv1] = m_boatSheet->getRectForCharacter(m_system.boat().characterIndex(), 1);
    Point offset{static_cast<int>(uv0.x() * m_boatSheet->texture().width()), static_cast<int>(uv0.y() * m_boatSheet->texture().height())};
    m_boatButtonTexture->setTexturesToComposite({{m_boatSheet->texture(), {m_boatSheet->characterWidth(), m_boatSheet->characterHeight()}, offset}});
  }
  if (!m_shipButtonTexture) {
    m_shipButtonTexture.emplace();
    m_shipButtonTexture->setSize(ImGui::GetDPIScaledValue(48), ImGui::GetDPIScaledValue(48));
    if (!m_shipSheet) {
      m_shipSheet.emplace(m_system.ship().characterName());
    }
    const auto& [uv0, uv1] = m_shipSheet->getRectForCharacter(m_system.ship().characterIndex(), 1);
    const Point offset{static_cast<int>(uv0.x() * m_shipSheet->texture().width()), static_cast<int>(uv0.y() * m_shipSheet->texture().height())};
    m_shipButtonTexture->setTexturesToComposite({{m_shipSheet->texture(), {m_shipSheet->characterWidth(), m_shipSheet->characterHeight()}, offset}});
  }
  if (!m_airshipButtonTexture) {
    m_airshipButtonTexture.emplace();
    m_airshipButtonTexture->setSize(ImGui::GetDPIScaledValue(48), ImGui::GetDPIScaledValue(48));
    if (!m_airshipSheet) {
      m_airshipSheet.emplace(m_system.airship().characterName());
    }
    const auto& [uv0, uv1] = m_airshipSheet->getRectForCharacter(m_system.airship().characterIndex(), 1);
    const Point offset{static_cast<int>(uv0.x() * m_airshipSheet->texture().width()), static_cast<int>(uv0.y() * m_airshipSheet->texture().height())};
    m_airshipButtonTexture->setTexturesToComposite({{m_airshipSheet->texture(), {m_airshipSheet->characterWidth(), m_airshipSheet->characterHeight()}, offset}});
  }
  ImGui::BeginChild("#orpg_system_tab", ImVec2(0, 0), ImGuiChildFlags_None, ImGuiWindowFlags_HorizontalScrollbar);
  if (!m_gameWindowBackground) {
    m_gameWindowBackground.emplace();
  }

  if (m_gameWindowBackground->width() != ImGui::GetDPIScaledValue(94) || m_gameWindowBackground->height() != ImGui::GetDPIScaledValue(94)) {
    m_gameWindowBackground->setSize(ImGui::GetDPIScaledValue(94), ImGui::GetDPIScaledValue(94));
  }
  {
    ImGui::BeginGroup();
    {
      auto calc = ImGui::CalcTextSize("ABCDEFGKLMNOPQRSTUVW");
      calc.y *= 8;
      calc.y += ImGui::GetStyle().ItemSpacing.y * 16;
      ImGui::BeginChild("#orpg_system_starting_party", {}, ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_FrameStyle, ImGuiWindowFlags_NoResize);
      {
        ImGui::BeginGroup();
        {
          ImGui::TextUnformatted("Starting Party");
          if (ImGui::BeginTable("##starting_party_list", 1, ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY, calc)) {
            ImGui::TableSetupColumn("Actor");
            ImGui::TableSetupScrollFreeze(1, 1);
            ImGui::TableHeadersRow();

            const auto& actors = m_system.partyMembers();
            for (int i = 0; auto& actor : actors) {
              ImGui::TableNextRow();
              ImGui::TableNextColumn();
              if (ImGui::Selectable(Database::instance()->actorNameOrId(actor).c_str(), m_selectedActor == i, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowDoubleClick)) {
                m_selectedActor = i;
                if (ImGui::GetMouseClickedCount(ImGuiMouseButton_Left) >= 2) {
                  if (!m_actorsPicker) {
                    m_actorsPicker.emplace("Party Member"sv, Database::instance()->actors.actorList(), m_selectedActor);
                  }
                  m_actorsPicker->setOpen(true);
                }
              }
              ++i;
            }
            ImGui::TableNextRow();
            ImGui::TableNextColumn();
            if (ImGui::Selectable("##orpg_system_tab_party_list_dummy", m_selectedActor == -1, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowDoubleClick)) {
              m_selectedActor = -1;
              if (ImGui::GetMouseClickedCount(ImGuiMouseButton_Left) >= 2) {
                if (!m_actorsPicker) {
                  m_actorsPicker.emplace("Party Member"sv, Database::instance()->actors.actorList(), 1);
                }
                m_actorsPicker->setOpen(true);
              }
            }
            if (ImGui::IsKeyPressed(ImGuiKey_Delete) && ImGui::IsWindowFocused() && m_selectedActor != -1) {
              // m_system.partyMembers().erase(m_system.partyMembers().begin() + m_selectedActor);
              //--m_selectedActor;
            }

            ImGui::EndTable();
          }
        }
        ImGui::EndGroup();
      }
      ImGui::EndChild();
      ImGui::SameLine();
      ImGui::BeginChild("##starting_game_settings", ImVec2(), ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysAutoResize,
                        ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoBackground);
      {
        ImGui::BeginChild("##starting_game_title", ImVec2(), ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysAutoResize | ImGuiChildFlags_FrameStyle,
                          ImGuiWindowFlags_NoResize);
        {
          char tmp[4096]{};
          strncpy(tmp, m_system.gameTitle().c_str(), 4096);
          ImGui::PushStyleColor(ImGuiCol_FrameBg, ImGui::GetStyleColorVec4(ImGuiCol_ChildBg));
          if (ImGui::LabelOverLineEdit("##orpg_system_tab_game_title", "Game Title", tmp, 4096, 0, nullptr, ImGuiInputTextFlags_None)) {
            m_system.setGameTitle(tmp);
          }
          ImGui::PopStyleColor();
        }
        ImGui::EndChild();
        ImGui::SameLine();
        ImGui::BeginChild("##game_currency", ImVec2(), ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysAutoResize | ImGuiChildFlags_FrameStyle,
                          ImGuiWindowFlags_NoResize);
        {
          char tmp[4096]{};
          strncpy(tmp, m_system.currencyUnit().c_str(), 4096);
          ImGui::PushStyleColor(ImGuiCol_FrameBg, ImGui::GetStyleColorVec4(ImGuiCol_ChildBg));
          if (ImGui::LabelOverLineEdit("##orpg_system_tab_game_currency", "Currency", tmp, 4096, ImGui::CalcTextSize("ABCDEFGKLMNOPQRSTUVWXYZ01").x, nullptr, ImGuiInputTextFlags_None)) {
            m_system.setCurrencyUnit(tmp);
          }
          ImGui::PopStyleColor();
        }
        ImGui::EndChild();
        ImGui::BeginChild("##game_vehicle_images", ImVec2(), ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysAutoResize | ImGuiChildFlags_FrameStyle,
                          ImGuiWindowFlags_NoResize);
        {
          ImGui::TextUnformatted("Vehicle Images");
          if (!m_boatSheet || m_boatSheet->characterName() != m_system.boat().characterName()) {
            m_boatSheet = CharacterSheet(m_system.boat().characterName());
          }
          ImGui::BeginGroup();
          {
            ImGui::TextUnformatted("Boat:");
            if (ImGui::ImageButtonEx(ImGui::GetID("##system_boat_image"), m_boatButtonTexture->get(), ImVec2(m_boatButtonTexture->size()), {0.f, 0.f}, {1.f, 1.f}, {}, {1.f, 1.f, 1.f, 1.f},
                                     ImGuiButtonFlags_PressedOnDoubleClick)) {
              m_currentSheet = &m_boatSheet.value();
              m_characterPicker->setCharacterInfo(m_system.boat().characterName(), m_system.boat().characterIndex());
              m_characterPicker->setOpen(true);
            }
          }
          ImGui::EndGroup();
          ImGui::SameLine();
          ImGui::BeginGroup();
          {
            ImGui::TextUnformatted("Ship:");
            if (ImGui::ImageButtonEx(ImGui::GetID("##system_ship_image"), m_shipButtonTexture->get(), ImVec2(m_shipButtonTexture->size()), {0.f, 0.f}, {1.f, 1.f}, {}, {1.f, 1.f, 1.f, 1.f},
                                     ImGuiButtonFlags_PressedOnDoubleClick)) {
              m_currentSheet = &m_shipSheet.value();
              m_characterPicker->setCharacterInfo(m_system.ship().characterName(), m_system.ship().characterIndex());
              m_characterPicker->setOpen(true);
            }
          }
          ImGui::EndGroup();
          ImGui::SameLine();
          ImGui::BeginGroup();
          {
            ImGui::TextUnformatted("Airship:");
            if (ImGui::ImageButtonEx(ImGui::GetID("##system_airship_image"), m_airshipButtonTexture->get(), ImVec2(m_airshipButtonTexture->size()), ImVec2{0.f, 0.f}, ImVec2{1.f, 1.f}, {},
                                     {1.f, 1.f, 1.f, 1.f}, ImGuiButtonFlags_PressedOnDoubleClick)) {
              m_currentSheet = &m_airshipSheet.value();
              m_characterPicker->setCharacterInfo(m_system.airship().characterName(), m_system.airship().characterIndex());
              m_characterPicker->setOpen(true);
            }
          }
          ImGui::EndGroup();
        }
        ImGui::EndChild();
        ImGui::SameLine();
        ImGui::BeginChild("##game_window_background", ImVec2(), ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysAutoResize | ImGuiChildFlags_FrameStyle,
                          ImGuiWindowFlags_NoResize);
        {
          ImGui::TextUnformatted("Window Color");
          m_gameWindowBackground->update(m_system.windowTone()[0], m_system.windowTone()[1], m_system.windowTone()[2]);
          if (ImGui::ImageButtonEx(ImGui::GetID("##game_window_background_button"), *m_gameWindowBackground,
                                   ImVec2{static_cast<float>(m_gameWindowBackground->width()), static_cast<float>(m_gameWindowBackground->height())}, ImVec2{0.f, 0.f}, ImVec2{1.f, 1.f}, {},
                                   {1.f, 1.f, 1.f, 1.f}, ImGuiButtonFlags_PressedOnDoubleClick)) {
            // TODO: GameWindowColorPickerDialog imp
          }
        }
        ImGui::EndChild();
      }
      ImGui::EndChild();
      ImGui::SameLine();
      ImGui::BeginChild("##system_options", {}, ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysAutoResize | ImGuiChildFlags_FrameStyle, ImGuiWindowFlags_NoResize);
      {
        ImGui::TextUnformatted("Options");
        bool tmp = m_system.optSideView();
        if (ImGui::Checkbox("Use Side-view Battle", &tmp)) {
          m_system.setOptSideView(tmp);
        }
        tmp = m_system.optTransparent();
        if (ImGui::Checkbox("Start Transparent", &tmp)) {
          m_system.setOptTransparent(tmp);
        }
        tmp = m_system.optFollowers();

        if (ImGui::Checkbox("Show Player Followers", &tmp)) {
          m_system.setOptFollowers(tmp);
        }
        tmp = m_system.optSlipDeath();

        if (ImGui::Checkbox("Knockout by Slip Damage", &tmp)) {
          m_system.setOptSlipDeath(tmp);
        }
        tmp = m_system.optFloorDeath();

        if (ImGui::Checkbox("Knockout by Floor Damage", &tmp)) {
          m_system.setOptFloorDeath(tmp);
        }
        tmp = m_system.optDisplayTp();
        if (ImGui::Checkbox("Display TP in Battle", &tmp)) {
          m_system.setOptDisplayTp(tmp);
        }
        tmp = m_system.optExtraExp();
        if (ImGui::Checkbox("EXP for Reserve Members", &tmp)) {
          m_system.setOptExtraExp(tmp);
        }
      }
      ImGui::EndChild();
      ImGui::SameLine();
      ImGui::BeginChild("##system_sv_magic_skills", {}, ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysAutoResize | ImGuiChildFlags_FrameStyle,
                        ImGuiWindowFlags_NoResize);
      {
        ImGui::TextUnformatted("[SV] Magic Skills");
        if (ImGui::BeginTable("##starting_magic_skills_list", 1, ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY, calc)) {
          ImGui::TableSetupColumn("Skill Type");
          ImGui::TableSetupScrollFreeze(1, 1);
          ImGui::TableHeadersRow();

          const auto& skills = m_system.magicSkills();
          int i = 0;
          for (int skill : skills) {
            ImGui::TableNextRow();
            ImGui::TableNextColumn();
            if (ImGui::Selectable(Database::instance()->skillTypeNameOrId(skill).c_str(), m_selectedSkill == i, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowDoubleClick)) {
              m_selectedSkill = i;
              if (ImGui::GetMouseClickedCount(ImGuiMouseButton_Left) >= 2) {
                if (!m_skillTypePicker) {
                  m_skillTypePicker.emplace("Skill Type", m_system.skillTypes(), m_selectedSkill);
                }
                m_skillTypePicker->setOpen(true);
              }
            }
            ++i;
          }
          ImGui::TableNextRow();
          ImGui::TableNextColumn();
          if (ImGui::Selectable("##orpg_system_tab_party_list_dummy", m_selectedSkill == -1, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowDoubleClick)) {
            m_selectedSkill = -1;
            if (ImGui::GetMouseClickedCount(ImGuiMouseButton_Left) >= 2) {
              if (!m_skillTypePicker) {
                m_skillTypePicker.emplace("Skill Type", m_system.skillTypes(), 0);
              }
              m_skillTypePicker->setOpen(true);
            }
          }

          if (ImGui::IsKeyPressed(ImGuiKey_Delete) && ImGui::IsWindowFocused() && m_selectedSkill != -1) {
            // m_system.magicSkills().erase(m_system.magicSkills().begin() + m_selectedSkill);
            // m_selectedSkill--;
          }
          ImGui::EndTable();
        }
      }
      ImGui::EndChild();
    }
    ImGui::EndGroup();
    ImGui::BeginGroup();
    {
      auto calc = ImGui::CalcTextSize("ABCDEFGKLMNOPQRSTUVWABCDEFGKLMNOPQRSTUVW");
      calc.y *= 8;
      calc.y += ImGui::GetStyle().ItemSpacing.y * 16;
      ImGui::BeginChild("##orpg_system_music_files", {}, ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysAutoResize | ImGuiChildFlags_FrameStyle,
                        ImGuiWindowFlags_NoResize);
      {
        ImGui::TextUnformatted("Music");
        if (ImGui::BeginTable("##system_audio_music_list", 2, ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY, calc)) {
          ImGui::TableSetupColumn("Type");
          ImGui::TableSetupColumn("File");
          ImGui::TableSetupScrollFreeze(2, 1);
          ImGui::TableHeadersRow();

          addAudioRow(m_system.titleBgm(), "Title", AudioType::BackgroundMusic);
          addAudioRow(m_system.battleBgm(), "Battle", AudioType::BackgroundMusic);
          addAudioRow(m_system.victoryMe(), "Victory", AudioType::Melody);
          addAudioRow(m_system.defeatMe(), "Defeat", AudioType::Melody);
          addAudioRow(m_system.gameoverMe(), "Game Over", AudioType::Melody);
          addAudioRow(m_system.boat().bgm(), "Boat", AudioType::BackgroundMusic);
          addAudioRow(m_system.ship().bgm(), "Ship", AudioType::BackgroundMusic);
          addAudioRow(m_system.airship().bgm(), "Airship", AudioType::BackgroundMusic);
          ImGui::EndTable();
        }
      }
      ImGui::EndChild();
      ImGui::BeginChild("##orpg_system_sound_files", {}, ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysAutoResize | ImGuiChildFlags_FrameStyle,
                        ImGuiWindowFlags_NoResize);
      {
        ImGui::TextUnformatted("Sounds");
        if (ImGui::BeginTable("##system_audio_sound_list", 2, ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY, calc)) {
          ImGui::TableSetupColumn("Type");
          ImGui::TableSetupColumn("File");
          ImGui::TableSetupScrollFreeze(2, 1);
          ImGui::TableHeadersRow();

          for (int type = 0; const auto& sound : m_system.sounds()) {
            addAudioRow(sound, DecodeEnumName(static_cast<SoundType>(type)), AudioType::SoundEffect);
            ++type;
          }
          ImGui::EndTable();
        }
      }
      ImGui::EndChild();
    }
    ImGui::EndGroup();
    ImGui::SameLine();
    ImGui::BeginGroup();
    {
      auto calc = ImGui::CalcTextSize("ABCDEFGKLMNOPQRSTUVWABCDEFGKLMNOPQRSTUVW");
      calc.y *= 13;
      calc.y += ImGui::GetStyle().ItemSpacing.y * 26;
      ImGui::BeginChild("##system_menu_commands", {calc.x + (ImGui::GetStyle().FramePadding.x * 2), 0}, ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_FrameStyle,
                        ImGuiWindowFlags_NoResize);
      {
        ImGui::TextUnformatted("Menu Commands");
        ImGui::NewLine();
        ImGui::BeginChild("##menu_command_set1", {ImGui::GetContentRegionMax().x / 3, 0}, 0, ImGuiWindowFlags_NoBackground);
        {
          bool tmp = m_system.menuCommands()[0];
          if (ImGui::Checkbox("Item", &tmp)) {
            m_system.setMenuCommand(0, tmp);
          }
          tmp = m_system.menuCommands()[0];
          if (ImGui::Checkbox("Status", &tmp)) {
            m_system.setMenuCommand(3, tmp);
          }
        }
        ImGui::EndChild();
        ImGui::SameLine();
        ImGui::BeginChild("##menu_command_set2", {ImGui::GetContentRegionMax().x / 3, 0}, ImGuiChildFlags_AutoResizeY, ImGuiWindowFlags_NoBackground);
        {
          bool tmp = m_system.menuCommands()[1];
          if (ImGui::Checkbox("Skill", &tmp)) {
            m_system.setMenuCommand(1, tmp);
          }
          tmp = m_system.menuCommands()[4];
          if (ImGui::Checkbox("Formation", &tmp)) {
            m_system.setMenuCommand(4, tmp);
          }
        }
        ImGui::EndChild();
        ImGui::SameLine();
        ImGui::BeginChild("##menu_command_set3", {ImGui::GetContentRegionMax().x / 3, 0}, ImGuiChildFlags_AutoResizeY, ImGuiWindowFlags_NoBackground);
        {
          bool tmp = m_system.menuCommands()[2];
          if (ImGui::Checkbox("Equip", &tmp)) {
            m_system.setMenuCommand(2, tmp);
          }
          tmp = m_system.menuCommands()[5];
          if (ImGui::Checkbox("Save", &tmp)) {
            m_system.setMenuCommand(5, tmp);
          }
        }
        ImGui::EndChild();
      }
      ImGui::EndChild();
      ImGui::BeginChild("##system_sv_attack_motions", {}, ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_FrameStyle, ImGuiWindowFlags_NoResize);
      {
        ImGui::TextUnformatted("[SV] Attack Motions");
        if (ImGui::BeginTable("##system_sv_attack_motions_list", 3, ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY, calc)) {
          ImGui::TableSetupColumn("Type");
          ImGui::TableSetupColumn("Motion");
          ImGui::TableSetupColumn("Image");
          ImGui::TableSetupScrollFreeze(3, 1);
          ImGui::TableHeadersRow();

          const auto& attackMotions = m_system.attackMotions();
          for (int i = 0; const auto& attackMotion : attackMotions) {
            ImGui::TableNextRow();
            ImGui::TableNextColumn();
            if (ImGui::Selectable(i == 0 ? "Bare Hands" : Database::instance()->weaponTypeNameOrId(i).c_str(), i == m_selectedMotion,
                                  ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowDoubleClick)) {
              m_selectedMotion = i;
              if (ImGui::GetMouseClickedCount(ImGuiMouseButton_Left) >= 2) {
                // TODO: Implement Motion Type dialog
              }
            }
            ImGui::TableNextColumn();
            ImGui::TextUnformatted(DecodeEnumName(attackMotion.type()).c_str());
            ImGui::TableNextColumn();
            ImGui::TextUnformatted(DecodeEnumName(attackMotion.weaponImageId()).c_str());
            ++i;
          }
          ImGui::EndTable();
        }
      }
      ImGui::EndChild();
    }
    ImGui::EndGroup();
    ImGui::SameLine();
    ImGui::BeginGroup();
    {
      auto calc = ImGui::CalcTextSize("ABCDEFGKLMNOPQRSTUVWXYZABCDEFGKLMNOPQRSTUVWX").x;
      ImGui::BeginChild("##orpg_system_start_positions", {calc, 0}, ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysAutoResize | ImGuiChildFlags_FrameStyle,
                        ImGuiWindowFlags_NoResize);
      {
        ImGui::TextUnformatted("Starting Position");
        ImGui::BeginGroup();
        {
          ImGui::TextUnformatted("Player");
          const std::string label =
              std::format("{} ({},{})##player_start_button", m_system.startMapId() == 0 ? "None" : Database::instance()->mapNameOrId(m_system.startMapId()), m_system.startX(), m_system.startY());
          if (ImGui::Button(label.c_str(), {ImGui::GetContentRegionAvail().x, 0})) {
            // TODO: Map position picker
          }
        }
        ImGui::EndGroup();
        ImGui::BeginGroup();
        {
          ImGui::TextUnformatted("Boat");
          const std::string label = std::format("{} ({},{})##boat_start_button", m_system.boat().startMapId() == 0 ? "None" : Database::instance()->mapNameOrId(m_system.boat().startMapId()),
                                                m_system.boat().startX(), m_system.boat().startY());
          if (ImGui::Button(label.c_str(), {ImGui::GetContentRegionAvail().x, 0})) {
            // TODO: Map position picker
          }
        }
        ImGui::EndGroup();
        ImGui::BeginGroup();
        {
          ImGui::TextUnformatted("Ship");
          const std::string label = std::format("{} ({},{})##ship_start_button", m_system.ship().startMapId() == 0 ? "None" : Database::instance()->mapNameOrId(m_system.ship().startMapId()),
                                                m_system.ship().startX(), m_system.ship().startY());
          if (ImGui::Button(label.c_str(), {ImGui::GetContentRegionAvail().x, 0})) {
            // TODO: Map position picker
          }
        }
        ImGui::EndGroup();
        ImGui::BeginGroup();
        {
          ImGui::TextUnformatted("Airship");
          const std::string label = std::format("{} ({},{})##airship_start_button", m_system.airship().startMapId() == 0 ? "None" : Database::instance()->mapNameOrId(m_system.airship().startMapId()),
                                                m_system.airship().startX(), m_system.airship().startY());
          if (ImGui::Button(label.c_str(), {ImGui::GetContentRegionAvail().x, 0})) {
            // TODO: Map position picker
          }
        }
        ImGui::EndGroup();
      }
      ImGui::EndChild();
      ImGui::BeginChild("##orpg_system_title_image", {calc, 0}, ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysAutoResize | ImGuiChildFlags_FrameStyle,
                        ImGuiWindowFlags_NoResize);
      {
        ImGui::TextUnformatted("Title Screen");
        ImGui::BeginGroup();
        {
          ImGui::TextUnformatted("Images");
          const std::string label = std::format("{}##title_images_button", Database::dualImageText(m_system.title1Name(), m_system.title2Name()));
          if (ImGui::Button(label.c_str(), {ImGui::GetContentRegionAvail().x, 0})) {
            if (!m_titleImagePicker) {
              m_titleImagePicker.emplace(ImagePicker::PickerMode::Title, m_system.title1Name(), m_system.title2Name());
            } else {
              m_titleImagePicker->setImageInfo(m_system.title1Name(), m_system.title2Name());
            }
            m_titleImagePicker->setOpen(true);
          }
        }
        ImGui::EndGroup();
        bool tmp = m_system.optDrawTitle();
        if (ImGui::Checkbox("Draw Game Title", &tmp)) {
          m_system.setOptDrawTitle(tmp);
        }
      }
      ImGui::EndChild();
    }
    ImGui::EndGroup();
  }
  ImGui::EndChild();

  if (m_actorsPicker) {
    if (const auto [closed, confirmed] = m_actorsPicker->draw(); closed && confirmed) {
      if (m_selectedActor == -1) {
        int actor = m_actorsPicker->selection();
        // We only want on instance of each actor in the party, so check to make sure this actor isn't already in the party
        if (std::ranges::find_if(m_system.partyMembers(), [actor](const auto& partyMember) { return partyMember == actor; }) == m_system.partyMembers().end()) {
          // m_system.partyMembers().emplace_back(actor);
          // m_selectedActor = static_cast<int>(m_system.partyMembers.size() - 1);
        }
      } else {
        // m_system.partyMembers()[m_selectedActor] = m_actorsPicker->selection();
      }
      m_actorsPicker->accept();
    }
  }

  if (m_skillTypePicker) {
    if (const auto [closed, confirmed] = m_skillTypePicker->draw(); closed && confirmed) {
      if (m_selectedSkill == -1) {
        int magicSkill = m_skillTypePicker->selection();
        // We only want on instance of each skill type in the skills list, so check to make sure this type isn't already in the skills list
        if (std::ranges::find_if(m_system.magicSkills(), [magicSkill](const auto& skill) { return skill == magicSkill; }) == m_system.magicSkills().end()) {
          // m_system.magicSkills().emplace_back(magicSkill);
          //  m_selectedSkill = static_cast<int>(m_system.magicSkills().size() - 1);
        }
      } else {
        // m_system.magicSkills()[m_selectedSkill] = m_skillTypePicker->selection();
      }
      m_skillTypePicker->accept();
    }
  }

  if (m_titleImagePicker) {
    if (const auto& [closed, confirmed] = m_titleImagePicker->draw(); closed && confirmed) {
      m_system.setTitle1Name(m_titleImagePicker->selectedImage());
      m_system.setTitle2Name(m_titleImagePicker->selectedImage2());
      m_titleImagePicker->accept();
    }
  }

  if (const auto& [closed, confirmed] = m_characterPicker->draw(); closed) {
    if (confirmed && m_currentSheet != nullptr) {
      if (m_currentSheet == &m_boatSheet.value()) {
        m_system.boat().setCharacterName(m_characterPicker->selectedSheet());
        m_system.boat().setCharacterIndex(m_characterPicker->character());
        m_boatSheet = CharacterSheet(m_system.boat().characterName());
        const auto& [uv0, uv1] = m_boatSheet->getRectForCharacter(m_system.boat().characterIndex(), 1);
        const Point offset{static_cast<int>(uv0.x() * m_boatSheet->texture().width()), static_cast<int>(uv0.y() * m_boatSheet->texture().height())};
        m_boatButtonTexture->setTexturesToComposite({{m_boatSheet->texture(), {m_boatSheet->characterWidth(), m_boatSheet->characterHeight()}, offset}});
      } else if (m_currentSheet == &m_shipSheet.value()) {
        m_system.ship().setCharacterName(m_characterPicker->selectedSheet());
        m_system.ship().setCharacterIndex(m_characterPicker->character());
        m_shipSheet = CharacterSheet(m_system.ship().characterName());
        const auto& [uv0, uv1] = m_shipSheet->getRectForCharacter(m_system.ship().characterIndex(), 1);
        const Point offset{static_cast<int>(uv0.x() * m_shipSheet->texture().width()), static_cast<int>(uv0.y() * m_shipSheet->texture().height())};
        m_shipButtonTexture->setTexturesToComposite({{m_shipSheet->texture(), {m_shipSheet->characterWidth(), m_shipSheet->characterHeight()}, offset}});
      } else if (m_currentSheet == &m_airshipSheet.value()) {
        m_system.airship().setCharacterName(m_characterPicker->selectedSheet());
        m_system.airship().setCharacterIndex(m_characterPicker->character());
        m_airshipSheet = CharacterSheet(m_system.airship().characterName());
        const auto& [uv0, uv1] = m_airshipSheet->getRectForCharacter(m_system.airship().characterIndex(), 1);
        const Point offset{static_cast<int>(uv0.x() * m_airshipSheet->texture().width()), static_cast<int>(uv0.y() * m_airshipSheet->texture().height())};
        m_airshipButtonTexture->setTexturesToComposite({{m_airshipSheet->texture(), {m_airshipSheet->characterWidth(), m_airshipSheet->characterHeight()}, offset}});
      }
      m_currentSheet = nullptr;
    }
    m_characterPicker->setOpen(false);
  }
}


code:
#pragma once
#include "Core/CommonUI/CharacterPicker.hpp"
#include "Core/CommonUI/ImagePicker.hpp"
#include "Core/CommonUI/ObjectPicker.hpp"
#include "Core/CommonUI/VariableSwitchPicker.hpp"
#include "Core/DatabaseEditor/IDBEditorTab.hpp"
#include "Core/Graphics/CharacterSheet.hpp"
#include "Core/Graphics/CheckeredCompositeTexture.hpp"
#include "Core/Graphics/GameWindowBackground.hpp"
#include "DBCommonEventsTab.hpp"

#include "Database/Actor.hpp"
#include "Database/System.hpp"

/* TODO: Move this somewhere else */
enum class AudioType {
  BackgroundMusic,
  Melody,
  SoundEffect,
};

struct System;
struct DBSystemTab : IDBEditorTab {
  DBSystemTab() = delete;
  explicit DBSystemTab(System& system, DatabaseEditor* parent);
  void draw() override;
  std::vector<int>& getHeaders() override { return m_headers; }
  int getHeader(int index) override { return m_headers.at(index); }
  bool hasHeader() override { return !m_headers.empty(); }
  void setHeaderRange(int start, int end) override {
    m_categoryStart = start;
    m_categoryEnd = end;
  }

  std::string getName(int index) override { return ""; }
  int getCount() override { return 0; }

private:
  int m_categoryStart;
  int m_categoryEnd;
  std::vector<int> m_headers;
  void addAudioRow(const Audio& audio, const std::string& type, AudioType audioType);

  System& m_system;
  std::optional<CharacterSheet> m_boatSheet;
  std::optional<CharacterSheet> m_airshipSheet;
  std::optional<CharacterSheet> m_shipSheet;
  CharacterSheet* m_currentSheet = nullptr;
  std::optional<CharacterPicker> m_characterPicker;
  std::optional<GameWindowBackground> m_gameWindowBackground = GameWindowBackground(192, 192);
  std::optional<CheckeredCompositeTexture> m_boatButtonTexture;
  std::optional<CheckeredCompositeTexture> m_shipButtonTexture;
  std::optional<CheckeredCompositeTexture> m_airshipButtonTexture;
  std::optional<ObjectPicker<Actor>> m_actorsPicker;
  std::optional<VariableSwitchPicker> m_skillTypePicker;
  std::optional<ImagePicker> m_titleImagePicker;
  int m_selectedActor = -1;
  int m_selectedSkill = -1;
  int m_selectedMusic = -1;
  int m_selectedSound = -1;
  int m_selectedMotion = -1;
};

repo_name: Antidote/openrpgeditor

license: mit


code:
#include "Core/DatabaseEditor/DBTemplatesTab.hpp"
#include "Core/DatabaseEditor.hpp"
#include "Core/ImGuiExt/ImGuiNotify.hpp"
#include "Core/MainWindow.hpp"
#include "Database/Templates.hpp"
#include "imgui.h"
#include "misc/cpp/imgui_stdlib.h"
#include <cstring>

void DBTemplatesTab::draw() {

  ImGui::BeginChild("##orpg_templates_editor");
  {
    ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 10.f);
    ImGui::BeginGroup();
    {
      if (ImGui::Button("Add", ImVec2{300, 0})) {

        AddTemplate("New Template", Template::TemplateType::Command, "", {});
        m_selection = m_templates->templates.size() > 0 ? m_templates->templates.size() - 1 : 0;
        SetTemplate();
        SaveToFile();
      }
      ImGui::SameLine();
      ImGui::BeginDisabled(m_templates->templates.size() == 0);
      if (ImGui::Button("Delete", ImVec2{300, 0})) {
        const int start = m_templates->templates.at(m_selection).id();
        if (m_templates->templates.size() == 1) {
          m_templates->templates.clear();
        } else {
          if (start >= 0) {
            m_templates->templates.erase(m_templates->templates.begin() + (start - 1));
          }
        }

        for (int idx{1}; auto& templ : m_templates->templates) {
          templ.setId(idx);
          idx++; // Reorder the indexes
        }
        m_selection = m_selection == m_templates->templates.size() ? (m_selection - 1) : m_selection;
        if (m_templates->templates.size() > 0) {
          SetTemplate();
        }
        SaveToFile();
      }
      ImGui::EndDisabled();
      ImGui::SameLine();

      ImGui::BeginDisabled(!(m_hasChanges || m_currentTemplate->hasChanges() || m_templateType == 0) || m_templates->templates.size() < 1);
      if (ImGui::Button("Apply", ImVec2{300, 0})) {
        SaveChanges();
        if (m_templates->serialize(m_parent->project()->database().basePath + "data/Templates.json")) {
          ImGui::InsertNotification(ImGuiToast{ImGuiToastType::Success, "Serialized data/Templates.json successfully!"});
        } else {
          ImGui::InsertNotification(ImGuiToast{ImGuiToastType::Error, "Failed to serialize data/Templates.json!"});
        }
        m_hasChanges = false;
      }
      ImGui::EndDisabled();
      // Template List
      if (ImGui::BeginCombo("##orpg_database_templates_templatelist", m_selection < m_templates->templates.size() ? m_templates->templates.at(m_selection).name().c_str() : "")) {
        int index{0};
        for (auto& templates : m_templates->templates) {
          if (const bool is_selected = m_selection == index; ImGui::Selectable(m_templates->templates.at(index).name().c_str(), is_selected)) {
            m_selection = index;
            SetTemplate();
            if (is_selected)
              ImGui::SetItemDefaultFocus();
          }
          index++;
        }
        ImGui::EndCombo();
      }
    }
    ImGui::EndGroup(); // End Group (Add, Delete, Edit, TemplateList)

    if (!m_templates->templates.empty()) {
      ImGui::SeparatorText("Template Properties");
      ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 10);
      ImGui::Text("Name:");
      ImGui::SameLine();
      ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 700);
      ImGui::Text("Type:");
      // TemplateType List
      ImGui::BeginGroup();
      {
        ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 10);
        if (ImGui::InputText("##orpg_templates_name_input", &m_templateName)) {
          if (m_templateName == m_templates->templates.at(m_selection).name()) {
            m_hasChanges = false;
          } else {
            m_hasChanges = true;
          }
        }
        ImGui::SameLine();
        ImGui::SetNextItemWidth(200);
        ImGui::BeginDisabled(m_templateType == 2); // Shouldn't let event templates change
        if (ImGui::BeginCombo("##orpg_templatetypes_list", DecodeEnumName(magic_enum::enum_name(static_cast<Template::TemplateType>(m_templateType))).c_str())) {
          int index{0};
          for (auto& dir : magic_enum::enum_values<Template::TemplateType>()) {

            if (magic_enum::enum_index(dir).value() == 2) {
              continue;
            } // Skip event val

            bool is_selected = m_templateType == magic_enum::enum_index(dir).value();
            if (ImGui::Selectable(DecodeEnumName(magic_enum::enum_name(dir)).c_str(), is_selected)) {
              m_templateType = magic_enum::enum_index(dir).value();
              m_currentTemplate = CreateTemplateDialog(static_cast<Template::TemplateType>(m_templateType));
              if (m_templateType == static_cast<int>(m_templates->templates.at(m_selection).type())) {
                m_hasChanges = false;
              } else {
                m_hasChanges = true;
              }
              if (is_selected)
                ImGui::SetItemDefaultFocus();
            }
            index++;
          }
          ImGui::EndCombo();
        }
        ImGui::EndDisabled();
      }
      ImGui::EndGroup();
      ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 10.f);
      ImGui::BeginGroup();
      {
        ImGui::Text("Note:");
        char note[8192];
        strncpy(note, m_templateNote.c_str(), IM_ARRAYSIZE(note));
        if (ImGui::InputTextMultiline("##orpg_templates_note_input", note, IM_ARRAYSIZE(note), ImVec2{875, 150})) {
          m_templateNote = note;
        }
        ImGui::EndGroup();
      }
      ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 10);
      ImGui::BeginGroup();
      {
        if (m_templateType == 0) {
          m_commandEditor.draw();
        } else {
          m_currentTemplate->draw();
        }
        ImGui::EndGroup();
      }
    }
  }
  ImGui::EndChild();
}
void DBTemplatesTab::SetTemplate() {
  m_id = m_templates->templates.at(m_selection).id();
  m_templateName = m_templates->templates.at(m_selection).name();
  m_templateNote = m_templates->templates.at(m_selection).note();
  m_templateType = static_cast<int>(m_templates->templates.at(m_selection).type());
  m_currentTemplate = CreateTemplateDialog(static_cast<Template::TemplateType>(m_templateType));

  if (m_templates->templates.at(m_selection).commands() == "") {
    m_currentCommands.clear();
    m_currentCommands.emplace_back(std::make_shared<EventDummy>());
    m_currentCommands.back()->setIndent(0);
  } else {
    if (m_templateType == 0) {
      CommandParser parser;
      nlohmann::ordered_json cmdJson = nlohmann::ordered_json::parse(m_templates->templates.at(m_selection).commands());
      m_currentCommands = parser.parse(cmdJson);
    } else {
      m_currentCommands.clear(); // Everything else has no commands
    }
  }
  m_hasChanges = false;
}

void DBTemplatesTab::SaveChanges() {
  m_templates->templates.at(m_selection).setName(m_templateName);
  m_templates->templates.at(m_selection).setNote(m_templateNote);
  m_templates->templates.at(m_selection).setType(static_cast<Template::TemplateType>(m_templateType));
  if (m_templates->templates.at(m_selection).type() == Template::TemplateType::Command) {
    m_templates->templates.at(m_selection).setParameters({});
    nlohmann::ordered_json cmdJson;
    CommandParser::serialize(cmdJson, m_currentCommands);
    m_templates->templates.at(m_selection).setCommands(cmdJson.dump());
  } else if (m_templates->templates.at(m_selection).type() == Template::TemplateType::Tint) {
    nlohmann::ordered_json cmdJson;
    CommandParser::serialize(cmdJson, m_currentCommands);
    m_templates->templates.at(m_selection).setCommands(cmdJson.dump());
    m_templates->templates.at(m_selection).setParameters(m_currentTemplate->getData());
  }
}
void DBTemplatesTab::AddTemplate(const std::string& label, const Template::TemplateType type, const std::string& commandString, const std::vector<int>& params) {

  m_currentCommands.clear();
  m_currentCommands.emplace_back(std::make_shared<EventDummy>());
  m_currentCommands.back()->setIndent(0);
  m_commandEditor.setCommands(&m_currentCommands);

  m_templates->addTemplate(Template(m_templates->templates.size() + 1, label + " " + std::to_string(m_templates->templates.size() + 1), "", type, commandString, params));

  m_id = m_templates->templates.back().id();
  m_templateName = m_templates->templates.back().name();
  m_templateType = static_cast<int>(m_templates->templates.back().type());

  nlohmann::ordered_json cmdJson;
  CommandParser::serialize(cmdJson, m_currentCommands);
  m_templates->templates.at(m_templates->templates.size() - 1).setCommands(cmdJson.dump());
}

void DBTemplatesTab::SaveToFile() const {
  if (m_templates->serialize(Database::instance()->basePath + "data/Templates.json")) {
    ImGui::InsertNotification(ImGuiToast{ImGuiToastType::Success, "Serialized data/Templates.json successfully!"});
  } else {
    ImGui::InsertNotification(ImGuiToast{ImGuiToastType::Error, "Failed to serialize data/Templates.json!"});
  }
}


code:
#pragma once
#include "Core/DatabaseEditor/IDBEditorTab.hpp"
#include "Core/EventEditor.hpp"
#include "Core/TemplateEditor/IDBTemplates.hpp"
#include "Core/TemplateEditor/TemplatesCommand.hpp"
#include "Core/TemplateEditor/TemplatesEventProperties.hpp"
#include "Core/TemplateEditor/TemplatesTint.hpp"
#include "DBCommonEventsTab.hpp"
#include "Database/Templates.hpp"

#include <optional>

struct Templates;
struct DBTemplatesTab : IDBEditorTab {
  explicit DBTemplatesTab(Templates& templates, DatabaseEditor* parent)
  : IDBEditorTab(parent)
  , m_templates(&templates) {
    if (!m_templates->templates.empty()) {
      if (m_templates->templates.at(m_selection).commands().empty()) {
        m_currentCommands.emplace_back(std::make_shared<EventDummy>());
        m_currentCommands.back()->setIndent(0);
      } else {
        if (m_templates->templates.at(m_selection).type() == Template::TemplateType::Command) {
          CommandParser parser;
          nlohmann::ordered_json cmdJson = nlohmann::ordered_json::parse(m_templates->templates.at(m_selection).commands());
          m_currentCommands = parser.parse(cmdJson);
          m_commandEditor.setCommands(&m_currentCommands);
        } else {
          m_currentCommands.clear();
        }
      }
      m_templateNote = m_templates->templates.at(m_selection).note();
      m_templateName = m_templates->templates.at(m_selection).name();
      m_templateType = static_cast<int>(m_templates->templates.at(m_selection).type());
      m_currentTemplate = CreateTemplateDialog((m_templates->templates.at(m_selection).type()));
    } else {
      m_currentTemplate = CreateTemplateDialog(Template::TemplateType::Command);
      m_currentCommands.emplace_back(std::make_shared<EventDummy>());
      m_currentCommands.back()->setIndent(0);
      m_commandEditor.setCommands(&m_currentCommands);
    }
  }
  void draw() override;
  std::vector<Templates*>& templates() { return m_template; }
  std::vector<int>& getHeaders() override { return m_headers; }
  int getHeader(int index) override { return m_headers.at(index); }
  bool hasHeader() override { return !m_headers.empty(); }
  void setHeaderRange(int start, int end) override {
    m_categoryStart = start;
    m_categoryEnd = end;
  }
  std::string getName(int index) override { return ""; }
  int getCount() override { return 0; }

private:
  int m_categoryStart;
  int m_categoryEnd;
  std::vector<int> m_headers;
  Templates* m_templates = nullptr;
  std::vector<Templates*> m_template;
  std::vector<std::shared_ptr<IEventCommand>> m_currentCommands;
  int m_id{0};
  int m_selection{0};
  int m_templateType{0};
  std::string m_templateName;
  std::string m_templateNote;
  bool m_hasChanges = false;
  EventCommandEditor m_commandEditor;

  std::shared_ptr<IDBTemplates> m_currentTemplate;

  std::shared_ptr<IDBTemplates> CreateTemplateDialog(const Template::TemplateType type) {
    if (m_templates->templates.empty()) {
      return std::make_shared<TemplatesCommand>(nullptr, m_parent);
    }
    switch (type) {
    case Template::TemplateType::Command:
      return std::make_shared<TemplatesCommand>(&m_templates->templates.at(m_selection), m_parent);
    case Template::TemplateType::Tint:
      return std::make_shared<TemplatesTint>(&m_templates->templates.at(m_selection), m_parent);
    case Template::TemplateType::Event:
      return std::make_shared<TemplatesEventProperties>(&m_templates->templates.at(m_selection), m_parent);
    default:
      return nullptr;
    }
  }
  void AddTemplate(const std::string& label, Template::TemplateType type, const std::string& commandString, const std::vector<int>& params);
  void SetTemplate();
  void SaveToFile() const;
  void SaveChanges();
};


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

#include "Core/ImGuiExt/ImGuiUtils.hpp"
#include "Database/System.hpp"

using namespace std::string_view_literals;

#include <imgui.h>
#include <imgui_internal.h>
std::tuple<bool, bool> DBTermsTab::drawAbbreviatedString(const float width, std::string_view label, std::string& full, std::string& abbrv) {
  bool fullModified = false;
  bool abbrvModified = false;
  ImGui::BeginChild(std::format("##orpg_{}_term", label).c_str(), {width * 2 + (ImGui::GetStyle().ItemSpacing.x * 2) + (ImGui::GetStyle().FramePadding.x * 2), 0},
                    ImGuiChildFlags_FrameStyle | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysAutoResize);
  {
    ImGui::PushStyleColor(ImGuiCol_FrameBg, ImGui::GetStyleColorVec4(ImGuiCol_ChildBg));
    ImGui::TextUnformatted(label.data());
    char levelStr[256];
    strncpy(levelStr, full.c_str(), 256);
    if (ImGui::LabelOverLineEdit(std::format("##{}_term", label).c_str(), trNOOP("Full"), levelStr, 256, width, nullptr, ImGuiInputTextFlags_None)) {
      full = levelStr;
      fullModified = true;
    }
    ImGui::SameLine();
    ImGui::SetCursorPosY(ImGui::GetCursorPosY() - ImGui::GetStyle().ItemSpacing.y);
    char abvrStr[256];
    strncpy(abvrStr, abbrv.c_str(), 256);
    if (ImGui::LabelOverLineEdit(std::format("##{}_abbreviation_term", label).c_str(), trNOOP("Abbreviation"), abvrStr, 256, width, nullptr, ImGuiInputTextFlags_None)) {
      abbrv = abvrStr;
      abbrvModified = true;
    }
    ImGui::PopStyleColor();
  }
  ImGui::EndChild();
  return {fullModified, abbrvModified};
}

bool DBTermsTab::drawString(const float width, const std::string_view label, std::string& full) {
  bool result = false;
  ImGui::BeginChild(std::format("##orpg_{}_term", label.data()).c_str(), {width + ImGui::GetStyle().ItemSpacing.x + ImGui::GetStyle().FramePadding.x, 0},
                    ImGuiChildFlags_FrameStyle | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysAutoResize);
  {
    ImGui::PushStyleColor(ImGuiCol_FrameBg, ImGui::GetStyleColorVec4(ImGuiCol_ChildBg));
    char levelStr[256];
    strncpy(levelStr, full.c_str(), 256);
    if (ImGui::LabelOverLineEdit(std::format("##{}_term", label).c_str(), label.data(), levelStr, 256, width, nullptr, ImGuiInputTextFlags_None)) {
      full = levelStr;
      result = true;
    }
    ImGui::PopStyleColor();
  }
  ImGui::EndChild();
  return result;
}

void DBTermsTab::initializeParameterStrings() const {
  if (m_system.terms().basic().empty()) {
    m_system.terms().basic().resize(10);
  }
  if (!m_system.terms().basic()[0]) {
    m_system.terms().basic()[0] = trNOOP("Max HP");
  }
  if (!m_system.terms().basic()[1]) {
    m_system.terms().basic()[1] = trNOOP("Max MP");
  }
  if (!m_system.terms().basic()[2]) {
    m_system.terms().basic()[2] = trNOOP("Attack");
  }
  if (!m_system.terms().basic()[3]) {
    m_system.terms().basic()[3] = trNOOP("Defense");
  }
  if (!m_system.terms().basic()[4]) {
    m_system.terms().basic()[4] = trNOOP("M.Attack");
  }
  if (!m_system.terms().basic()[5]) {
    m_system.terms().basic()[5] = trNOOP("M.Defense");
  }
  if (!m_system.terms().basic()[6]) {
    m_system.terms().basic()[6] = trNOOP("Agility");
  }
  if (!m_system.terms().basic()[7]) {
    m_system.terms().basic()[7] = trNOOP("Luck");
  }
  if (!m_system.terms().basic()[8]) {
    m_system.terms().basic()[8] = trNOOP("Hit");
  }
  if (!m_system.terms().basic()[9]) {
    m_system.terms().basic()[9] = trNOOP("Evasion");
  }
}
void DBTermsTab::initializeBasicStrings() const {
  if (m_system.terms().params().empty()) {
    m_system.terms().params().resize(10);
  }
  if (!m_system.terms().params()[0]) {
    m_system.terms().params()[0] = trNOOP("Level");
  }
  if (!m_system.terms().params()[1]) {
    m_system.terms().params()[1] = trNOOP("Lv");
  }
  if (!m_system.terms().params()[2]) {
    m_system.terms().params()[2] = trNOOP("HP");
  }
  if (!m_system.terms().params()[3]) {
    m_system.terms().params()[3] = trNOOP("HP");
  }
  if (!m_system.terms().params()[4]) {
    m_system.terms().params()[4] = trNOOP("MP");
  }
  if (!m_system.terms().params()[5]) {
    m_system.terms().params()[5] = trNOOP("MP");
  }
  if (!m_system.terms().params()[6]) {
    m_system.terms().params()[6] = trNOOP("TP");
  }
  if (!m_system.terms().params()[7]) {
    m_system.terms().params()[7] = trNOOP("TP");
  }
  if (!m_system.terms().params()[8]) {
    m_system.terms().params()[8] = trNOOP("EXP");
  }
  if (!m_system.terms().params()[9]) {
    m_system.terms().params()[9] = trNOOP("EXP");
  }
}

void DBTermsTab::initializeCommandStrings() const {
  if (m_system.terms().commands().empty()) {
    m_system.terms().commands().resize(26);
  }

  if (!m_system.terms().commands()[0]) {
    m_system.terms().commands()[0] = trNOOP("Fight");
  }
  if (!m_system.terms().commands()[1]) {
    m_system.terms().commands()[1] = trNOOP("Escape");
  }
  if (!m_system.terms().commands()[2]) {
    m_system.terms().commands()[2] = trNOOP("Attack");
  }
  if (!m_system.terms().commands()[3]) {
    m_system.terms().commands()[3] = trNOOP("Guard");
  }
  if (!m_system.terms().commands()[4]) {
    m_system.terms().commands()[4] = trNOOP("Item");
  }
  if (!m_system.terms().commands()[5]) {
    m_system.terms().commands()[5] = trNOOP("Skill");
  }
  if (!m_system.terms().commands()[6]) {
    m_system.terms().commands()[6] = trNOOP("Equip");
  }
  if (!m_system.terms().commands()[7]) {
    m_system.terms().commands()[7] = trNOOP("Status");
  }
  if (!m_system.terms().commands()[8]) {
    m_system.terms().commands()[8] = trNOOP("Formation");
  }
  if (!m_system.terms().commands()[11]) {
    m_system.terms().commands()[11] = trNOOP("Options");
  }
  if (!m_system.terms().commands()[9]) {
    m_system.terms().commands()[9] = trNOOP("Save");
  }
  if (!m_system.terms().commands()[10]) {
    m_system.terms().commands()[10] = trNOOP("Game End");
  }
  if (!m_system.terms().commands()[12]) {
    m_system.terms().commands()[12] = trNOOP("Weapon");
  }
  if (!m_system.terms().commands()[13]) {
    m_system.terms().commands()[13] = trNOOP("Armor");
  }
  if (!m_system.terms().commands()[14]) {
    m_system.terms().commands()[14] = trNOOP("Key Item");
  }
  if (!m_system.terms().commands()[15]) {
    m_system.terms().commands()[15] = trNOOP("Equip");
  }
  if (!m_system.terms().commands()[16]) {
    m_system.terms().commands()[16] = trNOOP("Optimize");
  }
  if (!m_system.terms().commands()[17]) {
    m_system.terms().commands()[17] = trNOOP("Clear");
  }
  if (!m_system.terms().commands()[24]) {
    m_system.terms().commands()[24] = trNOOP("Buy");
  }
  if (!m_system.terms().commands()[25]) {
    m_system.terms().commands()[25] = trNOOP("Sell");
  }
  if (!m_system.terms().commands()[18]) {
    m_system.terms().commands()[18] = trNOOP("New Game");
  }
  if (!m_system.terms().commands()[19]) {
    m_system.terms().commands()[19] = trNOOP("Continue");
  }
  if (!m_system.terms().commands()[21]) {
    m_system.terms().commands()[21] = trNOOP("To Title");
  }
  if (!m_system.terms().commands()[22]) {
    m_system.terms().commands()[22] = trNOOP("Cancel");
  }
}
void DBTermsTab::draw() {

  initializeBasicStrings();

  initializeParameterStrings();

  initializeCommandStrings();

  ImGui::BeginChild("##orpg_terms");
  {
    const auto width = (ImGui::GetContentRegionAvail().x / 2) - ImGui::GetStyle().ItemSpacing.x - (ImGui::GetStyle().FramePadding.x * 2);
    if (ImGui::BeginTabBar("##orpg_terms_tab_bar")) {
      if (ImGui::BeginTabItem(trNOOP("Basic Statuses"))) {
        drawAbbreviatedString(width, trNOOP("Level"), *m_system.terms().basic()[0], *m_system.terms().basic()[1]);
        drawAbbreviatedString(width, trNOOP("HP"), *m_system.terms().basic()[2], *m_system.terms().basic()[3]);
        drawAbbreviatedString(width, trNOOP("MP"), *m_system.terms().basic()[4], *m_system.terms().basic()[5]);
        drawAbbreviatedString(width, trNOOP("TP"), *m_system.terms().basic()[6], *m_system.terms().basic()[7]);
        drawAbbreviatedString(width, trNOOP("EXP"), *m_system.terms().basic()[8], *m_system.terms().basic()[9]);
        ImGui::EndTabItem();
      }
      if (ImGui::BeginTabItem(trNOOP("Parameters"))) {
        drawString(width, trNOOP("Max HP"), *m_system.terms().params()[0]);
        ImGui::SameLine();
        drawString(width, trNOOP("Max MP"), *m_system.terms().params()[1]);

        drawString(width, trNOOP("Attack"), *m_system.terms().params()[2]);
        ImGui::SameLine();
        drawString(width, trNOOP("Defense"), *m_system.terms().params()[3]);

        drawString(width, trNOOP("M.Attack"), *m_system.terms().params()[4]);
        ImGui::SameLine();
        drawString(width, trNOOP("M.Defense"), *m_system.terms().params()[5]);

        drawString(width, trNOOP("Agility"), *m_system.terms().params()[6]);
        ImGui::SameLine();
        drawString(width, trNOOP("Luck"), *m_system.terms().params()[7]);

        drawString(width, trNOOP("Hit Rate"), *m_system.terms().params()[8]);
        ImGui::SameLine();
        drawString(width, trNOOP("Evasion"), *m_system.terms().params()[9]);
        ImGui::EndTabItem();
      }
      if (ImGui::BeginTabItem(trNOOP("Commands"))) {
        const auto commandWidth = (ImGui::GetContentRegionAvail().x / 4) - ImGui::GetStyle().ItemSpacing.x - (ImGui::GetStyle().FramePadding.x * 2);
        drawString(commandWidth, trNOOP("Fight"), *m_system.terms().commands()[0]);
        ImGui::SameLine();
        drawString(commandWidth, trNOOP("Escape"), *m_system.terms().commands()[1]);
        ImGui::SameLine();
        drawString(commandWidth, trNOOP("Attack"), *m_system.terms().commands()[2]);
        ImGui::SameLine();
        drawString(commandWidth, trNOOP("Guard"), *m_system.terms().commands()[3]);

        drawString(commandWidth, trNOOP("Item"), *m_system.terms().commands()[4]);
        ImGui::SameLine();
        drawString(commandWidth, trNOOP("Skill"), *m_system.terms().commands()[5]);
        ImGui::SameLine();
        drawString(commandWidth, trNOOP("Equip (Pause Screen)"), *m_system.terms().commands()[6]);
        ImGui::SameLine();
        drawString(commandWidth, trNOOP("Status"), *m_system.terms().commands()[7]);

        drawString(commandWidth, trNOOP("Formation"), *m_system.terms().commands()[8]);
        ImGui::SameLine();
        drawString(commandWidth, trNOOP("Options"), *m_system.terms().commands()[11]);
        ImGui::SameLine();
        drawString(commandWidth, trNOOP("Save"), *m_system.terms().commands()[9]);
        ImGui::SameLine();
        drawString(commandWidth, trNOOP("Game End"), *m_system.terms().commands()[10]);

        ImGui::SeparatorEx(ImGuiSeparatorFlags_Horizontal, 2.f);

        drawString(commandWidth, trNOOP("Weapon"), *m_system.terms().commands()[12]);
        ImGui::SameLine();
        drawString(commandWidth, trNOOP("Armor"), *m_system.terms().commands()[13]);
        ImGui::SameLine();
        drawString(commandWidth, trNOOP("Key Item"), *m_system.terms().commands()[14]);
        ImGui::SameLine();
        drawString(commandWidth, "Equip (Equipment Screen)", *m_system.terms().commands()[15]);

        drawString(commandWidth, trNOOP("Optimize"), *m_system.terms().commands()[16]);
        ImGui::SameLine();
        drawString(commandWidth, trNOOP("Clear"), *m_system.terms().commands()[17]);
        ImGui::SameLine();
        drawString(commandWidth, trNOOP("Buy"), *m_system.terms().commands()[24]);
        ImGui::SameLine();
        drawString(commandWidth, trNOOP("Sell"), *m_system.terms().commands()[25]);

        drawString(commandWidth, trNOOP("New Game"), *m_system.terms().commands()[18]);
        ImGui::SameLine();
        drawString(commandWidth, trNOOP("Continue"), *m_system.terms().commands()[19]);
        ImGui::SameLine();
        drawString(commandWidth, trNOOP("To Title"), *m_system.terms().commands()[21]);
        ImGui::SameLine();
        drawString(commandWidth, trNOOP("Cancel"), *m_system.terms().commands()[22]);

        ImGui::EndTabItem();
      }
      if (ImGui::BeginTabItem(trNOOP("Messages"))) {
        ImGui::BeginChild("##messages_child", {}, ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysAutoResize, ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_HorizontalScrollbar);
        {
          const auto messageWidth = (ImGui::GetContentRegionAvail().x / 3) - ImGui::GetStyle().ItemSpacing.x - (ImGui::GetStyle().FramePadding.x * 2);
          auto temp = m_system.terms().messages().alwaysDash();
          if (drawString(messageWidth, trNOOP("Always Dash"), temp)) {
            m_system.terms().messages().setAlwaysDash(temp);
          }
          ImGui::SameLine();
          temp = m_system.terms().messages().commandRemember();
          if (drawString(messageWidth, trNOOP("Command Remember"), temp)) {
            m_system.terms().messages().setCommandRemember(temp);
          }
          ImGui::SameLine();
          temp = m_system.terms().messages().bgmVolume();
          if (drawString(messageWidth, trNOOP("BGM Volume"), temp)) {
            m_system.terms().messages().setBgmVolume(temp);
          }
          temp = m_system.terms().messages().bgsVolume();
          if (drawString(messageWidth, trNOOP("BGS Volume"), temp)) {
            m_system.terms().messages().setBgsVolume(temp);
          }
          ImGui::SameLine();
          temp = m_system.terms().messages().meVolume();
          if (drawString(messageWidth, trNOOP("ME Volume"), temp)) {
            m_system.terms().messages().setMeVolume(temp);
          }
          ImGui::SameLine();
          temp = m_system.terms().messages().seVolume();
          if (drawString(messageWidth, trNOOP("SE Volume"), temp)) {
            m_system.terms().messages().setSeVolume(temp);
          }

          temp = m_system.terms().messages().possession();
          if (drawString(messageWidth, trNOOP("Possession"), temp)) {
            m_system.terms().messages().setPossession(temp);
          }
          ImGui::SameLine();
          temp = m_system.terms().messages().expTotal();
          if (drawString(messageWidth, trNOOP("EXP Total"), temp)) {
            m_system.terms().messages().setExpTotal(temp);
          }
          ImGui::SameLine();
          temp = m_system.terms().messages().expNext();
          if (drawString(messageWidth, trNOOP("EXP Next"), temp)) {
            m_system.terms().messages().setExpNext(temp);
          }
          temp = m_system.terms().messages().saveMessage();
          if (drawString(messageWidth, trNOOP("Save Message"), temp)) {
            m_system.terms().messages().setSaveMessage(temp);
          }
          ImGui::SameLine();
          temp = m_system.terms().messages().loadMessage();
          if (drawString(messageWidth, trNOOP("Load Message"), temp)) {
            m_system.terms().messages().setLoadMessage(temp);
          }
          ImGui::SameLine();
          temp = m_system.terms().messages().file();
          if (drawString(messageWidth, trNOOP("File"), temp)) {
            m_system.terms().messages().setFile(temp);
          }
          temp = m_system.terms().messages().partyName();
          if (drawString(messageWidth, trNOOP("Party"), temp)) {
            m_system.terms().messages().setPartyName(temp);
          }
          ImGui::SameLine();
          temp = m_system.terms().messages().emerge();
          if (drawString(messageWidth, trNOOP("Emerge"), temp)) {
            m_system.terms().messages().setEmerge(temp);
          }
          ImGui::SameLine();
          temp = m_system.terms().messages().preemptive();
          if (drawString(messageWidth, trNOOP("Preemptive"), temp)) {
            m_system.terms().messages().setPreemptive(temp);
          }
          temp = m_system.terms().messages().surprise();
          if (drawString(messageWidth, trNOOP("Surprise"), temp)) {
            m_system.terms().messages().setSurprise(temp);
          }
          ImGui::SameLine();
          temp = m_system.terms().messages().escapeStart();
          if (drawString(messageWidth, trNOOP("Escape Start"), temp)) {
            m_system.terms().messages().setEscapeStart(temp);
          }
          ImGui::SameLine();
          temp = m_system.terms().messages().escapeFailure();
          if (drawString(messageWidth, trNOOP("Escape Failure"), temp)) {
            m_system.terms().messages().setEscapeFailure(temp);
          }

          temp = m_system.terms().messages().victory();
          if (drawString(messageWidth, trNOOP("Victory"), temp)) {
            m_system.terms().messages().setVictory(temp);
          }

          ImGui::SameLine();
          temp = m_system.terms().messages().defeat();
          if (drawString(messageWidth, trNOOP("Defeat"), temp)) {
            m_system.terms().messages().setDefeat(temp);
          }
          ImGui::SameLine();
          temp = m_system.terms().messages().obtainExp();
          if (drawString(messageWidth, trNOOP("Obtain EXP"), temp)) {
            m_system.terms().messages().setObtainExp(temp);
          }
          temp = m_system.terms().messages().obtainGold();
          if (drawString(messageWidth, trNOOP("Obtain Gold"), temp)) {
            m_system.terms().messages().setObtainGold(temp);
          }
          ImGui::SameLine();
          temp = m_system.terms().messages().obtainItem();
          if (drawString(messageWidth, trNOOP("Obtain Item"), temp)) {
            m_system.terms().messages().setObtainItem(temp);
          }
          ImGui::SameLine();
          temp = m_system.terms().messages().levelUp();
          if (drawString(messageWidth, trNOOP("Level Up"), temp)) {
            m_system.terms().messages().setLevelUp(temp);
          }
          temp = m_system.terms().messages().obtainSkill();
          if (drawString(messageWidth, trNOOP("Obtain Skill"), temp)) {
            m_system.terms().messages().setObtainSkill(temp);
          }
          ImGui::SameLine();
          temp = m_system.terms().messages().useItem();
          if (drawString(messageWidth, trNOOP("Use Item"), temp)) {
            m_system.terms().messages().setUseItem(temp);
          }
          ImGui::SeparatorEx(ImGuiSeparatorFlags_Horizontal, 2.f);
          temp = m_system.terms().messages().criticalToEnemy();
          if (drawString(messageWidth, trNOOP("Critical To Enemy"), temp)) {
            m_system.terms().messages().setCriticalToEnemy(temp);
          }
          ImGui::SameLine();
          temp = m_system.terms().messages().criticalToActor();
          if (drawString(messageWidth, trNOOP("Critical To Actor"), temp)) {
            m_system.terms().messages().setCriticalToActor(temp);
          }

          ImGui::SeparatorEx(ImGuiSeparatorFlags_Horizontal, 2.f);
          temp = m_system.terms().messages().actorDamage();
          if (drawString(messageWidth, trNOOP("Actor Damage"), temp)) {
            m_system.terms().messages().setActorDamage(temp);
          }
          ImGui::SameLine();
          temp = m_system.terms().messages().actorRecovery();
          if (drawString(messageWidth, trNOOP("Actor Recovery"), temp)) {
            m_system.terms().messages().setActorRecovery(temp);
          }
          ImGui::SameLine();
          temp = m_system.terms().messages().actorGain();
          if (drawString(messageWidth, trNOOP("Actor Gain"), temp)) {
            m_system.terms().messages().setActorGain(temp);
          }
          temp = m_system.terms().messages().actorLoss();
          if (drawString(messageWidth, trNOOP("Actor Loss"), temp)) {
            m_system.terms().messages().setActorLoss(temp);
          }
          ImGui::SameLine();
          temp = m_system.terms().messages().actorDrain();
          if (drawString(messageWidth, trNOOP("Actor Drain"), temp)) {
            m_system.terms().messages().setActorDrain(temp);
          }
          ImGui::SameLine();
          temp = m_system.terms().messages().actorNoDamage();
          if (drawString(messageWidth, trNOOP("Actor No Damage"), temp)) {
            m_system.terms().messages().setActorNoDamage(temp);
          }
          temp = m_system.terms().messages().actorNoHit();
          if (drawString(messageWidth, trNOOP("Actor No Hit"), temp)) {
            m_system.terms().messages().setActorNoHit(temp);
          }
          ImGui::SeparatorEx(ImGuiSeparatorFlags_Horizontal, 2.f);
          temp = m_system.terms().messages().enemyDamage();
          if (drawString(messageWidth, trNOOP("Enemy Damage"), temp)) {
            m_system.terms().messages().setEnemyDamage(temp);
          }
          ImGui::SameLine();
          temp = m_system.terms().messages().enemyRecovery();
          if (drawString(messageWidth, trNOOP("Enemy Recovery"), temp)) {
            m_system.terms().messages().setEnemyRecovery(temp);
          }
          ImGui::SameLine();
          temp = m_system.terms().messages().enemyGain();
          if (drawString(messageWidth, trNOOP("Enemy Gain"), temp)) {
            m_system.terms().messages().setEnemyGain(temp);
          }
          temp = m_system.terms().messages().enemyLoss();
          if (drawString(messageWidth, trNOOP("Enemy Loss"), temp)) {
            m_system.terms().messages().setEnemyLoss(temp);
          }
          ImGui::SameLine();
          temp = m_system.terms().messages().enemyDrain();
          if (drawString(messageWidth, trNOOP("Enemy Drain"), temp)) {
            m_system.terms().messages().setEnemyDrain(temp);
          }
          ImGui::SameLine();
          temp = m_system.terms().messages().enemyNoDamage();
          if (drawString(messageWidth, trNOOP("Enemy No Damage"), temp)) {
            m_system.terms().messages().setEnemyNoDamage(temp);
          }
          temp = m_system.terms().messages().enemyNoHit();
          if (drawString(messageWidth, trNOOP("Enemy No Hit"), temp)) {
            m_system.terms().messages().setEnemyNoHit(temp);
          }
          ImGui::SeparatorEx(ImGuiSeparatorFlags_Horizontal, 2.f);
          temp = m_system.terms().messages().evasion();
          if (drawString(messageWidth, trNOOP("Evasion"), temp)) {
            m_system.terms().messages().setEvasion(temp);
          }
          ImGui::SameLine();
          temp = m_system.terms().messages().magicEvasion();
          if (drawString(messageWidth, trNOOP("Magic Evasion"), temp)) {
            m_system.terms().messages().setMagicEvasion(temp);
          }
          ImGui::SameLine();
          temp = m_system.terms().messages().magicReflection();
          if (drawString(messageWidth, trNOOP("Magic Reflection"), temp)) {
            m_system.terms().messages().setMagicReflection(temp);
          }
          temp = m_system.terms().messages().counterAttack();
          if (drawString(messageWidth, trNOOP("Counter Attack"), temp)) {
            m_system.terms().messages().setCounterAttack(temp);
          }
          ImGui::SameLine();
          temp = m_system.terms().messages().substitute();
          if (drawString(messageWidth, trNOOP("Substitute"), temp)) {
            m_system.terms().messages().setSubstitute(temp);
          }
          ImGui::SameLine();
          temp = m_system.terms().messages().buffAdd();
          if (drawString(messageWidth, trNOOP("Buff Add"), temp)) {
            m_system.terms().messages().setBuffAdd(temp);
          }
          temp = m_system.terms().messages().debuffAdd();
          if (drawString(messageWidth, trNOOP("Debuff Add"), temp)) {
            m_system.terms().messages().setDebuffAdd(temp);
          }
          ImGui::SameLine();
          temp = m_system.terms().messages().buffRemove();
          if (drawString(messageWidth, trNOOP("Buff Remove"), temp)) {
            m_system.terms().messages().setBuffRemove(temp);
          }
          ImGui::SameLine();
          temp = m_system.terms().messages().actionFailure();
          if (drawString(messageWidth, trNOOP("Action Failure"), temp)) {
            m_system.terms().messages().setActionFailure(temp);
          }
        }
        ImGui::EndChild();
        ImGui::EndTabItem();
      }
      ImGui::EndTabBar();
    }
  }
  ImGui::EndChild();
}


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

#include <string>

struct System;
class DBTermsTab final : public IDBEditorTab {
public:
  explicit DBTermsTab(System& system, DatabaseEditor* parent)
  : IDBEditorTab(parent)
  , m_system(system) {}
  void draw() override;
  std::vector<int>& getHeaders() override { return m_headers; }
  int getHeader(int index) override { return m_headers.at(index); }
  bool hasHeader() override { return !m_headers.empty(); }
  void setHeaderRange(int start, int end) override {
    m_categoryStart = start;
    m_categoryEnd = end;
  }

  std::string getName(int index) override { return ""; }
  int getCount() override { return 0; }

private:
  int m_categoryStart;
  int m_categoryEnd;
  std::vector<int> m_headers;
  static std::tuple<bool, bool> drawAbbreviatedString(float width, std::string_view label, std::string& full, std::string& abbrv);
  static bool drawString(float width, std::string_view label, std::string& full);
  void initializeParameterStrings() const;
  void initializeBasicStrings() const;
  void initializeCommandStrings() const;
  System& m_system;
};