#include "UI.hpp" #include "Model/Project.hpp" #include "Model/Template/TableTemplate.hpp" #include "Model/Template/Template.hpp" #include "UI/Localization.hpp" #include "UI/States.hpp" #include #include #include #include #include #include namespace { class TemplateUI { public: virtual ~TemplateUI() = default; virtual void Draw() = 0; }; class TableTemplateUI : public TemplateUI { private: std::unique_ptr mTable; public: TableTemplateUI(std::unique_ptr table) : mTable{ std::move(table) } { } virtual void Draw() override { ImGui::Columns(2); DrawInspector(); ImGui::NextColumn(); DrawTable(); ImGui::NextColumn(); ImGui::Columns(1); } private: void DrawInspector() { } void DrawTable() { constexpr int kCellSpacing = 20; int colCount = mTable->GetTableWidth(); int rowCount = mTable->GetTableHeight(); float x = 0.0f; float y = 0.0f; for (int rowIdx = 0; rowIdx < rowCount; ++rowIdx) { int rowHeight = mTable->GetRowHeight(rowIdx); for (int colIdx = 0; colIdx < colCount; ++colIdx) { int colWidth = mTable->GetColumnWidth(colIdx); ImRect rect{ ImVec2(x, y), ImVec2(colWidth, rowHeight), }; // TODO x += colWidth; x += kCellSpacing; } y += rowHeight; y += kCellSpacing; } } }; struct DrawTemplateList_State { // Internal data // Items that are intended for user usage const TemplateInfo* SelectedTemplate = nullptr; }; void DrawTemplateList(DrawTemplateList_State& state) { auto& uis = UIState::GetInstance(); auto& templates = uis.CurrentProject->GetTemplates(); for (auto& info : templates) { if (ImGui::Selectable(info.Name.c_str(), state.SelectedTemplate == &info)) { state.SelectedTemplate = &info; } if (ImGui::IsItemHovered()) { ImGui::BeginTooltip(); ImGui::Text("Path: %s", info.PathStringCache.c_str()); ImGui::EndTooltip(); } } } } // namespace void UI::TemplatesTab() { auto ls = LocaleStrings::Instance.get(); auto& uis = UIState::GetInstance(); bool openedDummy = true; static std::unique_ptr openTemplate; static DrawTemplateList_State state; // Toolbar item: close if (ImGui::Button(ls->Close.Get(), openTemplate == nullptr)) { openTemplate = nullptr; } // Toolbar item: open... ImGui::SameLine(); if (ImGui::Button("Open...")) { ImGui::OpenPopup("Open template"); } if (ImGui::BeginPopupModal("Open template", &openedDummy, ImGuiWindowFlags_AlwaysAutoResize)) { DrawTemplateList(state); if (state.SelectedTemplate) { // TODO } ImGui::EndPopup(); } // Toolbar item: manage... ImGui::SameLine(); if (ImGui::Button("Manage...")) { ImGui::OpenPopup("Manage templates"); } if (ImGui::BeginPopupModal("Manage templates", &openedDummy, ImGuiWindowFlags_AlwaysAutoResize)) { DrawTemplateList(state); enum class NameSelectionError { None, Duplicated, Empty, }; static std::string newName; static NameSelectionError newNameError; auto ValidateNewName = [&]() -> void { if (newName.empty()) { newNameError = NameSelectionError::Empty; } auto& templates = uis.CurrentProject->GetTemplates(); if (templates.find(newName) != templates.end()) { newNameError = NameSelectionError::Duplicated; } }; auto ShowNewNameErrors = [&]() -> void { switch (newNameError) { case NameSelectionError::None: break; case NameSelectionError::Duplicated: ImGui::ErrorMessage("Duplicate template name"); break; case NameSelectionError::Empty: ImGui::ErrorMessage("Template name cannot be empty"); break; } }; static Template::Kind newKind = Template::InvalidKind; auto ShowNewKindErrors = [&]() -> void { if (newKind == Template::InvalidKind) { ImGui::ErrorMessage("Must select a valid template type"); } }; auto IsInputValid = [&]() -> bool { return newNameError == NameSelectionError::None && newKind != Template::InvalidKind; }; if (ImGui::Button(ls->Add.Get())) { ImGui::OpenPopup("Create template"); } if (ImGui::BeginPopupModal("Create template")) { if (ImGui::InputText("Name", &newName)) { ValidateNewName(); } if (ImGui::BeginCombo("Type", Template::FormatKind(newKind))) { for (int i = 0; i < Template::KindCount; ++i) { auto kind = static_cast(i); if (ImGui::Selectable(Template::FormatKind(kind), newKind == kind)) { newKind = kind; } } ImGui::EndCombo(); } if (ImGui::Button(ls->DialogConfirm.Get(), IsInputValid())) { auto& project = *uis.CurrentProject; auto& info = *project.InsertTemplate( newName, TemplateInfo{ .Path = project.GetTemplatePath(newName), .Name = newName, // Don't std::move here because evaluation order of `newName` (as parameter of InsertTemplate()) and this is unspecified .Kind = newKind, }); auto tmpl = Template::CreateByKind(newKind); std::ofstream ofs(info.Path); if (ofs) { tmpl->WriteTo(ofs); } else { // Writing to disk here isn't necessary for downstream operations to function successfully; // if the user makes any changes and don't save, those changes will not persist anyways // if the user doesn't make any changes, it doesn't matter that the empty template file isn't created yet } openTemplate = std::make_unique(std::move(tmpl)); } ImGui::SameLine(); if (ImGui::Button(ls->DialogCancel.Get())) { ImGui::CloseCurrentPopup(); } ShowNewNameErrors(); ShowNewKindErrors(); ImGui::EndPopup(); } if (ImGui::Button(ls->Rename.Get(), state.SelectedTemplate == nullptr)) { ImGui::OpenPopup("Rename template"); newName.clear(); } if (ImGui::BeginPopupModal("Rename template")) { if (ImGui::InputText("New name", &newName)) { ValidateNewName(); } if (ImGui::Button(ls->DialogConfirm.Get(), IsInputValid())) { auto& project = *uis.CurrentProject; project.RenameTemplate( state.SelectedTemplate->Name, newName); // We mutated the map, the pointer may be invalid now state.SelectedTemplate = &project.GetTemplates().at(newName); } ImGui::SameLine(); if (ImGui::Button(ls->DialogCancel.Get())) { ImGui::CloseCurrentPopup(); } ShowNewNameErrors(); ImGui::EndPopup(); } if (ImGui::Button(ls->Delete.Get(), state.SelectedTemplate == nullptr)) { ImGui::OpenPopup("Delete confirmation"); } if (ImGui::BeginPopupModal("Delete confirmation")) { assert(state.SelectedTemplate != nullptr); if (ImGui::Button(ls->DialogConfirm.Get())) { uis.CurrentProject->RemoveTemplate(state.SelectedTemplate->Name); } ImGui::SameLine(); if (ImGui::Button(ls->DialogCancel.Get())) { ImGui::CloseCurrentPopup(); } ImGui::EndPopup(); } ImGui::EndPopup(); } }