#define IMGUI_DEFINE_MATH_OPERATORS #include "WelcomeWindow.h" #include #include "../Misc/InterfaceFonts.h" #include #include #include "Nuake/FileSystem/FileSystem.h" #include #include #include #include #include #include #include #include #include "EditorInterface.h" #include "FileSystemUI.h" #include namespace Nuake { ProjectPreview::ProjectPreview(const std::string& path) { Path = path; ReadProjectFile(); } void ProjectPreview::ReadProjectFile() { std::string projectFile = Nuake::FileSystem::ReadFile(Path); if (projectFile != "") { nlohmann::json projectJson = nlohmann::json::parse(projectFile); Name = projectJson["Name"]; Description = projectJson["Description"]; const std::string projectIconPath = Path + "/../icon.png"; if (FileSystem::FileExists(projectIconPath, true)) { ProjectIcon = TextureManager::Get()->GetTexture2(projectIconPath); } else { ProjectIcon = TextureManager::Get()->GetTexture2("Resources/Images/nuake-logo.png"); } } else { Name = Path; Description = "Error read project file"; } } json ProjectPreview::Serialize() { BEGIN_SERIALIZE(); j["Path"] = this->Path; END_SERIALIZE(); } bool ProjectPreview::Deserialize(const json& j) { if (!j.contains("Path")) return false; Path = j["Path"]; ReadProjectFile(); return true; } WelcomeWindow::WelcomeWindow(Nuake::EditorInterface* editor) : _Editor(editor) { _Projects = std::vector(); _RecentProjectFilePath = (FileSystem::GetConfigFolderPath() + "recent.json"); ParseRecentFile(); if (_Projects.size() > 0) { SelectedProject = 0; } // Load Nuake logo _NuakeLogo = TextureManager::Get()->GetTexture2(NUAKE_LOGO_PATH); } void WelcomeWindow::LoadQueuedProject() { FileSystem::SetRootDirectory(FileSystem::GetParentPath(queuedProjectPath)); auto project = Project::New(); auto projectFileData = FileSystem::ReadFile(queuedProjectPath, true); try { project->Deserialize(nlohmann::json::parse(projectFileData)); project->FullPath = queuedProjectPath; Engine::LoadProject(project); _Editor->filesystem->m_CurrentDirectory = Nuake::FileSystem::RootDirectory; } catch (std::exception exception) { Logger::Log("Error loading project: " + queuedProjectPath, "editor", CRITICAL); Logger::Log(exception.what()); return; } queuedProjectPath = ""; Engine::GetCurrentWindow()->SetTitle("Nuake Engine - Editing " + project->Name); } void WelcomeWindow::ImportProject(const std::string& file) { if (file != "" && String::EndsWith(file, ".project")) { // Prevent importing the same project twice in the list bool alreadyContainsProject = false; for (auto& p : _Projects) { if (p.Path == file) { alreadyContainsProject = true; } } if (!alreadyContainsProject) { _Projects.push_back(ProjectPreview(file)); SaveRecentFile(); } } } void WelcomeWindow::Draw() { if (!queuedRemovalPath.empty()) { std::erase_if(_Projects, [&](ProjectPreview preview) { return preview.Path == queuedRemovalPath; }); queuedRemovalPath = ""; SaveRecentFile(); } // Make viewport fullscreen ImGuiViewport* viewport = ImGui::GetMainViewport(); ImGui::SetNextWindowPos(viewport->Pos); ImGui::SetNextWindowSize(viewport->Size); ImGui::SetNextWindowViewport(viewport->ID); ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(32.0f, 32.0f)); if(ImGui::Begin("Welcome Screen", 0, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoResize)) { // Draw Nuake logo { const Vector2 logoSize = _NuakeLogo->GetSize(); const ImVec2 imguiSize = ImVec2(logoSize.x, logoSize.y); ImGui::Image((ImTextureID)_NuakeLogo->GetImGuiDescriptorSet(), imguiSize); } //ImGui::SameLine(); std::string versionInfo = "pre-alpha "; #ifdef NK_DEBUG versionInfo += "debug build"; #endif #ifdef NK_DIST versionInfo += "dist build"; #endif ImGui::TextColored({1, 1, 1, 0.5}, versionInfo.c_str()); // ImGui::TextColored({ 1, 1, 1, 0.5 }, "239d9479"); // Add padding under logo ImGui::Dummy(ImVec2(10, 16)); DrawRightControls(); ImGui::SameLine(); DrawRecentProjectsSection(); ImGui::End(); } ImGui::PopStyleVar(2); } void WelcomeWindow::DrawRecentProjectsSection() { // Recent projects section takes up 80% of the width ImVec2 projectsWindowSize = ImGui::GetContentRegionAvail(); ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0, 0, 0, 0)); ImGui::BeginChild("Projects", projectsWindowSize); { //ImGui::Dummy({ 4, 4 }); //ImGui::SameLine(); //{ // auto font = UIFont(Bold); // ImGui::Text("Recent Projects"); //} ImGui::PopStyleColor(); for (uint32_t i = 0; i < std::size(_Projects); i++) { DrawProjectItem(i); } ImGui::Dummy({ 1, 4 }); ImGui::SameLine(); const float itemHeight = 60.0f; ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); if (UI::SecondaryButton("Import or Drag a Project", Vector2(ImGui::GetContentRegionAvail().x - 8, itemHeight))) { const std::string path = FileDialog::OpenFile("Project file(.project)\0*.project\0"); ImportProject(path); } ImGui::PopStyleColor(); } ImGui::EndChild(); } void WelcomeWindow::DrawProjectItem(const uint32_t itemIndex) { ImGui::Dummy({ 4, 4 }); ImGui::SameLine(); const ProjectPreview& project = _Projects[itemIndex]; const uint32_t itemHeight = 56; const float cursorYStart = ImGui::GetCursorPosY(); const std::string selectableName = "##" + std::to_string(itemIndex); const bool isSelected = SelectedProject == itemIndex; //ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(10.0f / 255.f, 182.0f / 255.f, 255.f / 255.f, 1.0f)); ImDrawList* draw_list = ImGui::GetWindowDrawList(); draw_list->ChannelsSplit(2); // Channel number is like z-order. Widgets in higher channels are rendered above widgets in lower channels. draw_list->ChannelsSetCurrent(1); //ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0, 0, 0, 0)); ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 8.0f); ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0.25, 0.25, 0.5, 0.0)); ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImVec4(0.25, 0.25, 0.5, 0.0)); ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0.25, 0.25, 0.5, 0.0)); bool result = ImGui::Selectable(selectableName.c_str(), SelectedProject == itemIndex, ImGuiSelectableFlags_AllowOverlap | ImGuiSelectableFlags_AllowDoubleClick, ImVec2(ImGui::GetContentRegionAvail().x - 12, itemHeight)); ImGui::PopStyleVar(); if (ImGui::BeginPopupContextWindow(selectableName.c_str())) { if (ImGui::MenuItem("Remove from list")) { queuedRemovalPath = _Projects[itemIndex].Path; } ImGui::EndPopup(); } if (result) { SelectedProject = itemIndex; } if (result && ImGui::IsMouseDoubleClicked(0)) { SaveRecentFile(); queuedProjectPath = _Projects[itemIndex].Path; } ImU32 color = IM_COL32(63, 63, 66, 128); if (isSelected) { color = IM_COL32(63, 69, 79, 255); } else if (ImGui::IsItemHovered()) { color = IM_COL32(20, 20, 20, 128); } if (isSelected && ImGui::IsKeyPressed(ImGuiKey_Delete)) { queuedRemovalPath = _Projects[SelectedProject].Path; } if (result) { SelectedProject = itemIndex; } //ImGui::PopStyleColor(); draw_list->ChannelsSetCurrent(0); ImVec2 p_min = ImGui::GetItemRectMin(); ImVec2 p_max = ImGui::GetItemRectMax(); ImGui::GetWindowDrawList()->AddRectFilled(p_min, p_max, color, 4.0f); draw_list->ChannelsMerge(); ImGui::PopStyleColor(3); const ImVec2 padding = ImVec2(16.0f, 0.0f); const ImVec2 iconSize = ImVec2(54, 54.0f); ImGui::SetCursorPos(ImVec2(padding.x / 2.0, padding.y / 2.0) + ImVec2(4, cursorYStart)); if (project.ProjectIcon) { ImGui::Image((ImTextureID)project.ProjectIcon->GetImGuiDescriptorSet(), iconSize); } ImGui::SameLine(); ImGui::SetCursorPosX(padding.x + iconSize.x); ImGui::SetCursorPosY(cursorYStart + 8.0f); { UIFont boldfont = UIFont(Fonts::Normal); if (isSelected) { ImGui::TextColored(ImVec4(119.0f / 255.0f, 187.0f / 255.0f, 1, 255.0f), project.Name.c_str()); } else { ImGui::Text(project.Name.c_str()); } } ImGui::SetCursorPosY(cursorYStart + 28.f); { ImGui::SetCursorPosX(padding.x + iconSize.x); UIFont boldfont = UIFont(Fonts::Normal); if (isSelected) { ImGui::TextColored(ImVec4(119.0f / 255.0f, 187.0f / 255.0f, 1, 255.0f), (ICON_FA_INFO_CIRCLE + std::string(" ") + project.Description).c_str()); } else { ImGui::TextColored(ImVec4(1, 1, 1, 0.4), (ICON_FA_INFO_CIRCLE + std::string(" ") + project.Description).c_str()); } } ImGui::SetCursorPosY(cursorYStart + itemHeight + 4.0f); ImGui::Dummy({ 4, 2 }); } void WelcomeWindow::DrawRightControls() { const float buttonHeight = 36.0f; ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0, 0, 0, 0)); ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 16.0f); bool opened = ImGui::BeginChild("Controls", ImVec2(250.0f, ImGui::GetContentRegionAvail().y), false); if (opened) { ImGui::PopStyleVar(); const ImVec2 buttonSize = ImVec2(ImGui::GetContentRegionAvail().x, buttonHeight); ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 4.0f); ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0.14f, 0.5f)); const std::string buttonLabel = std::string(ICON_FA_FOLDER_PLUS) + " New Project..."; if (Nuake::UI::PrimaryButton(buttonLabel.c_str(), { buttonSize.x, buttonSize.y })) { _Editor->isCreatingNewProject = true; } ImGui::PopStyleVar(2); ImGui::Separator(); ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 4.0f); ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0.16f, 0.5f)); const std::string buttonLabelOpen = std::string(ICON_FA_FOLDER_OPEN) + " Load Project"; bool hasProjectSelected = false; if (SelectedProject < std::size(_Projects) && SelectedProject >= 0) { hasProjectSelected = true; } if (!hasProjectSelected) { ImGui::BeginDisabled(true); } if (Nuake::UI::SecondaryButton(buttonLabelOpen, { buttonSize.x, buttonSize.y })) { SaveRecentFile(); queuedProjectPath = _Projects[SelectedProject].Path;; } if (!hasProjectSelected) { ImGui::EndDisabled(); } ImGui::PopStyleVar(2); ImGui::Separator(); ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 4.0f); ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0.09f, 0.5f)); const std::string buttonLabelDoc = std::string(ICON_FA_EXTERNAL_LINK_SQUARE_ALT) + " Documentation"; if (Nuake::UI::SecondaryButton(buttonLabelDoc, { buttonSize.x, buttonSize.y })) { OS::OpenURL("https://docs.readthedocs.io/en/latest/index.html"); } ImGui::PopStyleVar(2); } if (!opened) { ImGui::PopStyleVar(); } ImGui::EndChild(); ImGui::PopStyleColor(); } void WelcomeWindow::ParseRecentFile() { if (!FileSystem::FileExists(_RecentProjectFilePath, true)) { FileSystem::BeginWriteFile(_RecentProjectFilePath, true); FileSystem::WriteLine(_RecentProjectFileDefaultContent); FileSystem::EndWriteFile(); } std::string content = Nuake::FileSystem::ReadFile(_RecentProjectFilePath); if (content != "") { nlohmann::json j = nlohmann::json::parse(content); for (auto& project : j["Projects"]) { auto projectPreview = ProjectPreview(); projectPreview.Deserialize(project); _Projects.push_back(projectPreview); } } } void WelcomeWindow::SaveRecentFile() { std::string str; BEGIN_SERIALIZE(); for (uint32_t i = 0; i < std::size(_Projects); i++) { ProjectPreview& project = _Projects[i]; j["Projects"][i] = project.Serialize(); } FileSystem::BeginWriteFile(_RecentProjectFilePath, true); FileSystem::WriteLine(j.dump(4)); FileSystem::EndWriteFile(); } }