From e2c81e27e47e49240b19b3a8bca15e03bd954870 Mon Sep 17 00:00:00 2001 From: bb1950328 Date: Sat, 6 Jan 2024 00:31:38 +0100 Subject: [PATCH] improve part palette log time t=179.25h --- .idea/codeStyles/Project.xml | 14 - .idea/codeStyles/codeStyleConfig.xml | 1 + src/config/data.h | 41 +- src/graphics/camera.cpp | 10 +- src/graphics/mesh/mesh.cpp | 6 +- .../mesh/mesh_textured_triangle_data.cpp | 2 +- .../mesh/mesh_textured_triangle_data.h | 2 +- src/graphics/mesh/mesh_triangle_data.cpp | 2 +- src/graphics/mesh/mesh_triangle_data.h | 2 +- src/gui/gui_internal.cpp | 10 +- src/gui/windows/window_part_palette.cpp | 386 +++++++++++++----- src/gui/windows/window_settings.cpp | 1 + src/helpers/custom_hash.h | 7 + src/ldr/file_repo.cpp | 2 +- src/ldr/file_repo.h | 2 +- src/persistent_state.h | 41 +- 16 files changed, 379 insertions(+), 150 deletions(-) diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index b96f9923..de4a5e5d 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -8,33 +8,19 @@ diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml index 79ee123c..6e6eec11 100644 --- a/.idea/codeStyles/codeStyleConfig.xml +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -1,5 +1,6 @@ \ No newline at end of file diff --git a/src/config/data.h b/src/config/data.h index 910a0215..c78e6180 100644 --- a/src/config/data.h +++ b/src/config/data.h @@ -189,8 +189,40 @@ namespace bricksim::config { friend bool operator!=(const ElementTree& lhs, const ElementTree& rhs) { return !(lhs == rhs); } }; + struct PartCategoryTreeNode { + uint64_t id; + std::string name; + std::string ldrawCategory; + std::string nameFilter; + std::vector children; + + explicit PartCategoryTreeNode() { + json_helper::defaultInit(this); + } + + template + void json_io(JsonIo& io) { + io + & json_dto::optional("id", id, 0) + & json_dto::optional("name", name, "") + & json_dto::optional("ldrawCategory", ldrawCategory, "") + & json_dto::optional("nameFilter", nameFilter, "") + & json_dto::optional("children", children, decltype(children)()); + } + + friend bool operator==(const PartCategoryTreeNode& lhs, const PartCategoryTreeNode& rhs) { + return lhs.id == rhs.id + && lhs.name == rhs.name + && lhs.ldrawCategory == rhs.ldrawCategory + && lhs.nameFilter == rhs.nameFilter; + } + + friend bool operator!=(const PartCategoryTreeNode& lhs, const PartCategoryTreeNode& rhs) { return !(lhs == rhs); } + }; + struct PartPalette { uint16_t thumbnailSize; + std::vector customTrees; PartPalette() { defaultInit(this); @@ -199,10 +231,15 @@ namespace bricksim::config { template void json_io(JsonIo& io) { io - & json_dto::optional("thumbnailSize", thumbnailSize, 256, json_dto::min_max_constraint(4, 2048)); + & json_dto::optional("thumbnailSize", thumbnailSize, 256, json_dto::min_max_constraint(4, 2048)) + & json_dto::optional("customTrees", customTrees, decltype(customTrees)()); + } + + friend bool operator==(const PartPalette& lhs, const PartPalette& rhs) { + return lhs.thumbnailSize == rhs.thumbnailSize + && lhs.customTrees == rhs.customTrees; } - friend bool operator==(const PartPalette& lhs, const PartPalette& rhs) { return lhs.thumbnailSize == rhs.thumbnailSize; } friend bool operator!=(const PartPalette& lhs, const PartPalette& rhs) { return !(lhs == rhs); } }; diff --git a/src/graphics/camera.cpp b/src/graphics/camera.cpp index c7d37860..ec1e37bb 100644 --- a/src/graphics/camera.cpp +++ b/src/graphics/camera.cpp @@ -171,16 +171,16 @@ namespace bricksim::graphics { collectPoints(points, node, glm::mat4(1.f)); Seb::Smallest_enclosing_ball seb(3, points); - auto center = seb.center_begin(); + const auto center = seb.center_begin(); const auto sebCenter = glm::vec3(center[0], center[1], center[2]); - auto meshRadius = seb.radius() * constants::LDU_TO_OPENGL_SCALE; + const auto meshRadius = seb.radius() * constants::LDU_TO_OPENGL_SCALE; target = glm::vec4(sebCenter, 1.0f) * constants::LDU_TO_OPENGL; //todo calculate the distance from fov instead of this - auto distance = meshRadius * 2.45f; - auto s = glm::radians(45.0f);//todo make variable - auto t = glm::radians(45.0f); + const auto distance = meshRadius * 2.35f; + const auto s = glm::radians(45.0f);//todo make variable + const auto t = glm::radians(45.0f); cameraPos = glm::vec3( distance * std::cos(s) * std::cos(t), distance * std::sin(s) * std::cos(t), diff --git a/src/graphics/mesh/mesh.cpp b/src/graphics/mesh/mesh.cpp index 5e82f2d0..c15230cf 100644 --- a/src/graphics/mesh/mesh.cpp +++ b/src/graphics/mesh/mesh.cpp @@ -519,7 +519,7 @@ namespace bricksim::mesh { vertexCount += item.second.getVertexCount(); } if (vertexCount > 0) { - std::vector coords; + std::vector coords; coords.reserve(vertexCount); for (const auto& item: triangleData) { @@ -529,7 +529,7 @@ namespace bricksim::mesh { item.second.addVerticesForOuterDimensions(coords); } - Seb::Smallest_enclosing_ball seb(3, coords); + Seb::Smallest_enclosing_ball seb(3, coords); aabb::AABB aabb; @@ -540,7 +540,7 @@ namespace bricksim::mesh { outerDimensions = OuterDimensions{ .aabb = aabb, .minEnclosingBallCenter = {seb.center_begin()[0], seb.center_begin()[1], seb.center_begin()[2]}, - .minEnclosingBallRadius = seb.radius(), + .minEnclosingBallRadius = static_cast(seb.radius()), }; } else { outerDimensions = OuterDimensions{ diff --git a/src/graphics/mesh/mesh_textured_triangle_data.cpp b/src/graphics/mesh/mesh_textured_triangle_data.cpp index eac6c4f7..52e03930 100644 --- a/src/graphics/mesh/mesh_textured_triangle_data.cpp +++ b/src/graphics/mesh/mesh_textured_triangle_data.cpp @@ -108,7 +108,7 @@ namespace bricksim::mesh { return verticesAlreadyDeleted ? uploadedVertexCount : vertices.size(); } - void TexturedTriangleData::addVerticesForOuterDimensions(std::vector& coords) const { + void TexturedTriangleData::addVerticesForOuterDimensions(std::vector& coords) const { for (auto& item: vertices) { coords.push_back(item.position); } diff --git a/src/graphics/mesh/mesh_textured_triangle_data.h b/src/graphics/mesh/mesh_textured_triangle_data.h index 9181c7bc..c3f5b08d 100644 --- a/src/graphics/mesh/mesh_textured_triangle_data.h +++ b/src/graphics/mesh/mesh_textured_triangle_data.h @@ -15,7 +15,7 @@ namespace bricksim::mesh { void freeBuffers() const; void draw(const InstanceRange& sceneLayerInstanceRange) const; [[nodiscard]] size_t getVertexCount() const; - void addVerticesForOuterDimensions(std::vector& coords) const; + void addVerticesForOuterDimensions(std::vector& coords) const; void addVertex(const TexturedTriangleVertex& vertex); private: diff --git a/src/graphics/mesh/mesh_triangle_data.cpp b/src/graphics/mesh/mesh_triangle_data.cpp index d60574a9..1b6a1a34 100644 --- a/src/graphics/mesh/mesh_triangle_data.cpp +++ b/src/graphics/mesh/mesh_triangle_data.cpp @@ -170,7 +170,7 @@ namespace bricksim::mesh { vertices.push_back(vertex); } - void TriangleData::addVerticesForOuterDimensions(std::vector& coords) const { + void TriangleData::addVerticesForOuterDimensions(std::vector& coords) const { for (auto& item: vertices) { coords.push_back(item.position); } diff --git a/src/graphics/mesh/mesh_triangle_data.h b/src/graphics/mesh/mesh_triangle_data.h index da58c9f7..ccff6ff1 100644 --- a/src/graphics/mesh/mesh_triangle_data.h +++ b/src/graphics/mesh/mesh_triangle_data.h @@ -19,7 +19,7 @@ namespace bricksim::mesh { void rewriteInstanceBuffer(const std::vector& instances); [[nodiscard]] size_t getVertexCount() const; [[nodiscard]] size_t getIndexCount() const; - void addVerticesForOuterDimensions(std::vector& coords) const; + void addVerticesForOuterDimensions(std::vector& coords) const; [[nodiscard]] bool isDataAlreadyDeleted() const; [[nodiscard]] const std::vector& getVertices() const; diff --git a/src/gui/gui_internal.cpp b/src/gui/gui_internal.cpp index e9cf95a7..53f28427 100644 --- a/src/gui/gui_internal.cpp +++ b/src/gui/gui_internal.cpp @@ -7,21 +7,25 @@ namespace bricksim::gui_internal { bool drawPartThumbnail(const ImVec2& actualThumbSizeSquared, const std::shared_ptr& part, const ldr::ColorReference color) { - bool realThumbnailAvailable = false; + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(config::get().graphics.background)); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, {0, 0}); + bool realThumbnailDrawn = false; const bool visible = ImGui::IsRectVisible(actualThumbSizeSquared); if (visible) { auto optTexId = controller::getThumbnailGenerator()->getThumbnailNonBlocking({part, color}); if (optTexId.has_value()) { auto texId = convertTextureId(optTexId.value()->getID()); ImGui::ImageButton(part->metaInfo.name.c_str(), texId, actualThumbSizeSquared, ImVec2(0, 1), ImVec2(1, 0)); - realThumbnailAvailable = true; + realThumbnailDrawn = true; } } else { controller::getThumbnailGenerator()->removeFromRenderQueue({part, color}); } - if (!realThumbnailAvailable) { + if (!realThumbnailDrawn) { ImGui::Button(part->metaInfo.name.c_str(), actualThumbSizeSquared); } + ImGui::PopStyleVar(); + ImGui::PopStyleColor(); if (ImGui::IsItemHovered()) { auto availableColors = info_providers::part_color_availability::getAvailableColorsForPart(part); std::string availText; diff --git a/src/gui/windows/window_part_palette.cpp b/src/gui/windows/window_part_palette.cpp index eaddfab4..76fe3dc4 100644 --- a/src/gui/windows/window_part_palette.cpp +++ b/src/gui/windows/window_part_palette.cpp @@ -8,7 +8,163 @@ #include "window_part_palette.h" +#include "../../persistent_state.h" + namespace bricksim::gui::windows::part_palette { + namespace { + bool nodeSelectReverseRecursively(oset_t& set, const config::PartCategoryTreeNode& node) { + if (set.contains(node.id)) { + return true; + } + set.insert(node.id); + for (auto childIt = node.children.rbegin(); childIt != node.children.rend(); ++childIt) { + if (nodeSelectReverseRecursively(set, *childIt)) { + return true; + } + } + return false; + } + + ///iterates through the flattened tree in reverse, adds all ids to set, immediately returns true when it encounters a node which is already in set + bool nodeSelectReverse(oset_t& set, const std::vector& nodeStack, const std::size_t stackOffset = 0) { + const auto& node = nodeStack.back(); + set.insert(node.id); + if (stackOffset - 1 > nodeStack.size()) { + const auto& parent = nodeStack[nodeStack.size() - 1 - stackOffset]; + for (const auto& previousSibling: parent.children) { + if (previousSibling.id == node.id) { + break; + } + if (nodeSelectReverseRecursively(set, previousSibling)) { + return true; + } + } + if (nodeSelectReverse(set, nodeStack, stackOffset + 1)) { + return true; + } + } + return false; + } + + void drawCustomTreeNode(std::vector& nodeStack, persisted_state::PartPalette& state); + + void drawCustomTreeNodeChildren(std::vector& nodeStack, persisted_state::PartPalette& state) { + for (const auto& child: nodeStack.back().children) { + nodeStack.push_back(child); + drawCustomTreeNode(nodeStack, state); + nodeStack.pop_back(); + } + } + + void drawCustomTreeNode(std::vector& nodeStack, persisted_state::PartPalette& state) { + const auto& node = nodeStack.back(); + int flags = ImGuiTreeNodeFlags_None; + const auto initiallySelected = state.selectedTreeElements.contains(node.id); + if (initiallySelected) { + flags |= ImGuiTreeNodeFlags_Selected; + } + if (node.children.empty()) { + flags |= ImGuiTreeNodeFlags_Leaf; + } + if (ImGui::TreeNodeEx(reinterpret_cast(node.id), flags, "%s", node.name.c_str())) { + if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { + if (ImGui::GetIO().KeyCtrl) { + if (initiallySelected) { + state.selectedTreeElements.erase(node.id); + } else { + state.selectedTreeElements.insert(node.id); + } + } else if (ImGui::GetIO().KeyShift) { + nodeSelectReverse(state.selectedTreeElements, nodeStack); + } else { + state.selectedTreeElements = {node.id}; + } + } + drawCustomTreeNodeChildren(nodeStack, state); + ImGui::TreePop(); + } + } + + void drawDefaultCategoryTree(persisted_state::PartPalette& state) { + const auto& partCategories = ldr::file_repo::get().getAllCategories(); + uint64_t i = 0; + auto it = partCategories.begin(); + while (it != partCategories.end()) { + const auto initiallySelected = state.selectedTreeElements.contains(i); + if (ImGui::Selectable(it->c_str(), initiallySelected)) { + if (initiallySelected) { + if (state.selectedTreeElements.size() > 1) { + state.selectedTreeElements = {i}; + } else { + state.selectedTreeElements.clear(); + } + } else { + if (ImGui::GetIO().KeyCtrl) { + state.selectedTreeElements.insert(i); + } else if (ImGui::GetIO().KeyShift) { + uint64_t x = i; + while (x > 0 && !state.selectedTreeElements.contains(x)) { + state.selectedTreeElements.insert(x); + --x; + } + } else { + state.selectedTreeElements = {i}; + } + } + } + ++i; + ++it; + } + } + + //todo these algorithms can be optimized from O(n) to O(log(N)) by using binary search while looking for the next child + std::optional findNode(const config::PartCategoryTreeNode& root, const uint64_t id) { + if (root.id == id) { + return root; + } + if (!root.children.empty()) { + std::size_t childIdx = 0; + while (root.children.size() - 1 > childIdx && root.children[childIdx + 1].id < id) { + ++childIdx; + } + while (childIdx < root.children.size()) { + if (const auto res = findNode(root.children[childIdx], id)) { + return res; + } + ++childIdx; + } + } + return std::nullopt; + } + + std::optional findNode(std::vector& nodeStack, const uint64_t id) { + const auto& node = nodeStack.back(); + if (node.id > id) { + return std::nullopt; + } else if (node.id == id) { + return node; + } + std::size_t childIdx = 0; + while (childIdx < node.children.size() - 1 && node.children[childIdx + 1].id < id) { + ++childIdx; + } + while (childIdx < node.children.size()) { + nodeStack.push_back(node.children[childIdx]); + if (const auto res = findNode(nodeStack, id)) { + return res; + } + nodeStack.pop_back(); + ++childIdx; + } + if (nodeStack.size() > 1) { + nodeStack.pop_back(); + return findNode(nodeStack, id); + } + return std::nullopt; + } + } + + void draw(Data& data) { if (ImGui::Begin(data.name, &data.visible)) { collectWindowInfo(data.id); @@ -73,146 +229,156 @@ namespace bricksim::gui::windows::part_palette { ImGui::EndPopup(); } - static float categorySelectWidth = 250;//todo save - const auto totalWidth = ImGui::GetContentRegionAvail().x; - const auto itemSpacingX = ImGui::GetStyle().ItemSpacing.x; - float thumbnailContainerWidth = totalWidth - categorySelectWidth - itemSpacingX; - //static const auto partsGrouped = ldr::file_repo::getAllPartsGroupedByCategory(); - static const auto partCategories = ldr::file_repo::get().getAllCategories(); - static uoset_t selectedCategories = {*partCategories.begin()};//first category preselected - std::set editorNames; - std::transform(controller::getEditors().cbegin(), - controller::getEditors().cend(), - std::inserter(editorNames, editorNames.begin()), - [](const std::shared_ptr& editor) { - return editor->getFilename(); - }); - - ImGui::BeginChild("##categorySelectTree", ImVec2(categorySelectWidth, 0)); - for (const auto& set: {std::cref(editorNames), std::cref(partCategories)}) { - for (const auto& category: set.get()) { - int flags = selectedCategories.find(category) != selectedCategories.end() - ? ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_Selected - : ImGuiTreeNodeFlags_Leaf; - if (ImGui::TreeNodeEx(category.c_str(), flags)) { - if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { - if (ImGui::GetIO().KeyCtrl) { - if (selectedCategories.find(category) == selectedCategories.end()) { - selectedCategories.insert(category); - } else { - selectedCategories.erase(category); - } - } else if (ImGui::GetIO().KeyShift) { - auto groupIt = partCategories.find(category); - while (groupIt != partCategories.begin() && selectedCategories.find(*groupIt) == selectedCategories.end()) { - selectedCategories.insert(*groupIt); - groupIt--; - } - selectedCategories.insert(*groupIt); - } else { - bool wasOnlySelectionBefore = selectedCategories.size() == 1 && *selectedCategories.begin() == category; - selectedCategories.clear(); - if (!wasOnlySelectionBefore) { - selectedCategories.insert(category); - } - } + ImGui::BeginChild("##categorySelectTree", ImVec2(160.f * config::get().gui.scale, 0), ImGuiChildFlags_ResizeX); + + auto& customTrees = config::get().partPalette.customTrees; + auto& state = persisted_state::get().partPalette; + if (!customTrees.empty()) { + if (state.selectedCustomTree < -1 || state.selectedCustomTree >= customTrees.size()) { + state.selectedCustomTree = -1; + state.selectedTreeElements.clear(); + } + const char* currentTreeName = state.selectedCustomTree == -1 ? "Default" : customTrees[state.selectedCustomTree].name.c_str(); + ImGui::SetNextItemWidth(-1.f); + if (ImGui::BeginCombo("##customTreeCombo", currentTreeName)) { + if (ImGui::Selectable("Default", state.selectedCustomTree == -1)) { + state.selectedCustomTree = -1; + state.selectedTreeElements.clear(); + } + for (std::size_t i = 0; i < customTrees.size(); ++i) { + if (ImGui::Selectable(customTrees[i].name.c_str(), state.selectedCustomTree == i)) { + state.selectedCustomTree = i; + state.selectedTreeElements.clear(); } - ImGui::TreePop(); } + ImGui::EndCombo(); + } + } + + static std::optional> selectedEditor; + for (const auto& editor: controller::getEditors()) { + const auto label = fmt::format(ICON_FA_FILE" {}", editor->getDisplayName()); + if (ImGui::Selectable(label.c_str(), editor == selectedEditor)) { + selectedEditor = editor; + state.selectedTreeElements.clear(); + } + } + + if (state.selectedCustomTree == -1) { + drawDefaultCategoryTree(state); + } else { + std::vector nodeStack = {customTrees[state.selectedCustomTree]}; + drawCustomTreeNodeChildren(nodeStack, state); + if (!state.selectedTreeElements.empty()) { + selectedEditor = std::nullopt; } } ImGui::EndChild(); + ImGui::SameLine(); - ImGui::BeginChild("##thumbnailsContainer", ImVec2(thumbnailContainerWidth, 0), ImGuiChildFlags_None); - const static auto thumbnailSpacing = 4; + + ImGui::BeginChild("##thumbnailsContainer", ImGui::GetContentRegionAvail(), ImGuiChildFlags_None); + static constexpr auto thumbnailSpacing = 4; ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(thumbnailSpacing, thumbnailSpacing)); - auto actualThumbSize = std::floor(controller::getThumbnailGenerator()->size / 100.0 * thumbnailZoomPercent); - auto actualThumbSizeSquared = ImVec2(actualThumbSize, actualThumbSize); - int columns = std::max(1.0, std::floor((ImGui::GetContentRegionAvail().x + thumbnailSpacing) / (actualThumbSize + thumbnailSpacing))); - int currentCol = 0; + const auto actualThumbSize = std::floor(controller::getThumbnailGenerator()->size / 100.0 * thumbnailZoomPercent); + const auto actualThumbSizeSquared = ImVec2(actualThumbSize, actualThumbSize); const bool searchEmpty = searchTextBuffer[0] == '\0'; const auto& searchPredicate = part_finder::getPredicate(searchTextBuffer); - const auto drawPart = [&actualThumbSizeSquared, &columns, ¤tCol, &searchEmpty, &searchPredicate](const std::shared_ptr& file) { + const auto drawPart = [&actualThumbSizeSquared, &searchEmpty, &searchPredicate](const std::shared_ptr& file) { if (searchEmpty || searchPredicate.matches(*file)) { gui_internal::drawPartThumbnail(actualThumbSizeSquared, file, color->asReference()); - currentCol++; - if (currentCol == columns) { - currentCol = 0; - } else { - ImGui::SameLine(); + ImGui::SameLine(); + if (ImGui::GetContentRegionAvail().x < actualThumbSizeSquared.x) { + ImGui::NewLine(); } } }; - const auto drawCategory = [&editorNames, &drawPart](const std::string& category) { - if (editorNames.find(category) != editorNames.end()) { - const auto editor = std::find_if(controller::getEditors().begin(), - controller::getEditors().end(), - [&category](const std::shared_ptr& editor) { - return editor->getFilename() == category; - }) - ->get(); - for (const auto& item: editor->getRootNode()->getChildren()) { - const auto modelNode = std::dynamic_pointer_cast(item); - if (modelNode != nullptr) { - drawPart(modelNode->ldrFile); - if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { - editor->openContextMenuNodeSelectedOrClicked(modelNode); - } + const auto drawEditor = [&drawPart](const std::shared_ptr& editor) { + for (const auto& item: editor->getRootNode()->getChildren()) { + const auto modelNode = std::dynamic_pointer_cast(item); + if (modelNode != nullptr) { + drawPart(modelNode->ldrFile); + if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { + editor->openContextMenuNodeSelectedOrClicked(modelNode); } } + } + }; + + const auto drawCategory = [&drawPart](const std::string& category) { + for (const auto& part: ldr::file_repo::get().getAllFilesOfCategory(category)) { + drawPart(part); + } + ImGui::NewLine(); + }; + + static uomap_t, oset_t>> customCategoryCache; + const auto getCustomPartList = [](const std::string& ldrawCategory, const std::string& nameFilter) { + if (nameFilter.empty()) { + return ldr::file_repo::get().getAllFilesOfCategory(ldrawCategory); } else { - for (const auto& part: ldr::file_repo::get().getAllFilesOfCategory(category)) { - drawPart(part); + const auto key = std::make_pair(ldrawCategory, nameFilter); + if (const auto it = customCategoryCache.find(key); it != customCategoryCache.end()) { + return it->second; } + const auto& matcher = part_finder::getPredicate(nameFilter); + oset_t> result; + for (const auto& file: ldr::file_repo::get().getAllFilesOfCategory(ldrawCategory)) { + if (matcher.matches(*file)) { + result.insert(file); + } + } + return customCategoryCache.emplace(key, result).first->second; } }; - if (selectedCategories.size() > 1) { - for (const auto& category: selectedCategories) { - ImGui::Text("%s", category.c_str()); - drawCategory(category); - if (currentCol != 0) { - ImGui::NewLine(); - } - currentCol = 0; + const auto drawCustomCategory = [&drawPart, &getCustomPartList](const config::PartCategoryTreeNode& category) { + for (const auto& part: getCustomPartList(category.ldrawCategory, category.nameFilter)) { + drawPart(part); } - } else if (selectedCategories.size() == 1) { - drawCategory(*selectedCategories.begin()); - } else { - if (ldr::file_repo::get().areAllPartsLoaded()) { - for (const auto& category: editorNames) { - ImGui::Text("%s", category.c_str()); - drawCategory(category); - } - for (const auto& category: ldr::file_repo::get().getAllPartsGroupedByCategory()) { - bool textWritten = false; - for (const auto& part: category.second) { - if (!textWritten && (searchEmpty || searchPredicate.matches(*part))) { - ImGui::Text("%s", category.first.c_str()); - textWritten = true; - } - drawPart(part); + ImGui::NewLine(); + }; + + if (selectedEditor.has_value()) { + drawEditor(*selectedEditor); + } else if (!state.selectedTreeElements.empty()) { + const bool drawHeaders = state.selectedTreeElements.size() != 1; + if (state.selectedCustomTree == -1) { + static uomap_t categoryNamesByIndex; + if (categoryNamesByIndex.empty()) { + const auto& partCategories = ldr::file_repo::get().getAllCategories(); + uint64_t i = 0; + auto it = partCategories.begin(); + while (it != partCategories.end()) { + categoryNamesByIndex.emplace(i, *it); + ++i; + ++it; } - if (currentCol != 0) { - ImGui::NewLine(); + } + for (auto element: state.selectedTreeElements) { + const auto& categoryName = categoryNamesByIndex.find(element)->second; + if (drawHeaders) { + ImGui::Text("%s", categoryName.c_str()); } - currentCol = 0; + drawCategory(categoryName); } } else { - static bool taskAdded = false; - if (!taskAdded) { - controller::addBackgroundTask("Load remaining Parts", []() { - ldr::file_repo::get().getAllPartsGroupedByCategory(); - }); - taskAdded = true; + const auto& root = customTrees[state.selectedCustomTree]; + if (state.selectedTreeElements.size() == 1) { + drawCustomCategory(*findNode(root, *state.selectedTreeElements.begin())); + } else { + std::vector nodeStack = {root}; + for (const auto element: state.selectedTreeElements) { + const auto category = *findNode(nodeStack, element); + if (drawHeaders) { + ImGui::Text("%s", category.name.c_str()); + } + drawCustomCategory(category); + } } - const auto loaded = ldr::file_repo::get().getLoadedPartsGroupedByCategory().size(); - const auto all = ldr::file_repo::get().getAllCategories().size(); - ImGui::ProgressBar(loaded * 1.0f / all); - ImGui::Text("%c %lu of %lu categories loaded, please wait", gui_internal::getLoFiSpinner(), loaded, all); } } ImGui::PopStyleVar(); diff --git a/src/gui/windows/window_settings.cpp b/src/gui/windows/window_settings.cpp index 1de708df..c220a052 100644 --- a/src/gui/windows/window_settings.cpp +++ b/src/gui/windows/window_settings.cpp @@ -151,6 +151,7 @@ namespace bricksim::gui::windows::settings { if (ImGui::InputInt("Thumbnail Image Size", &thumbnailSize, 16, 64)) { data.thumbnailSize = std::clamp(thumbnailSize, 4, 2048); } + //todo implement editor for custom category trees } template<> diff --git a/src/helpers/custom_hash.h b/src/helpers/custom_hash.h index 4d70132c..69a1ff7d 100644 --- a/src/helpers/custom_hash.h +++ b/src/helpers/custom_hash.h @@ -22,4 +22,11 @@ namespace std { return value.red * 961 + value.green * 31 + value.blue; } }; + + template<> + struct hash> { + std::size_t operator()(const pair& value) const noexcept { + return hash()(value.first) * 31 + hash()(value.second); + } + }; } diff --git a/src/ldr/file_repo.cpp b/src/ldr/file_repo.cpp index b55e46d3..34efc09a 100644 --- a/src/ldr/file_repo.cpp +++ b/src/ldr/file_repo.cpp @@ -486,7 +486,7 @@ namespace bricksim::ldr::file_repo { return it->second; } - bool FileRepo::areAllPartsLoaded() { + bool FileRepo::areAllPartsLoaded() const { return getAllCategories().size() == partsByCategory.size(); } diff --git a/src/ldr/file_repo.h b/src/ldr/file_repo.h index e620ce67..6f20a1ac 100644 --- a/src/ldr/file_repo.h +++ b/src/ldr/file_repo.h @@ -78,7 +78,7 @@ namespace bricksim::ldr::file_repo { static std::string getPathRelativeToBase(FileType type, const std::string& name); oset_t> getAllFilesOfCategory(const std::string& categoryName); - bool areAllPartsLoaded(); + bool areAllPartsLoaded() const; void cleanup(); /** diff --git a/src/persistent_state.h b/src/persistent_state.h index 4f39c75c..1752b111 100644 --- a/src/persistent_state.h +++ b/src/persistent_state.h @@ -2,13 +2,13 @@ #include "helpers/json_helper.h" namespace bricksim::persisted_state { - struct SnappingState { + struct Snapping { bool enabled; uint64_t linearStepXZ; uint64_t linearStepY; float rotationalStep; - SnappingState() { + Snapping() { json_helper::defaultInit(this); } @@ -21,20 +21,45 @@ namespace bricksim::persisted_state { & json_dto::optional("rotationalStep", rotationalStep, 90.f); } - friend bool operator==(const SnappingState& lhs, const SnappingState& rhs) { + friend bool operator==(const Snapping& lhs, const Snapping& rhs) { return lhs.enabled == rhs.enabled && lhs.linearStepXZ == rhs.linearStepXZ && lhs.linearStepY == rhs.linearStepY && lhs.rotationalStep == rhs.rotationalStep; } - friend bool operator!=(const SnappingState& lhs, const SnappingState& rhs) { return !(lhs == rhs); } + friend bool operator!=(const Snapping& lhs, const Snapping& rhs) { return !(lhs == rhs); } + }; + + struct PartPalette { + ///-1 for default tree + int64_t selectedCustomTree; + oset_t selectedTreeElements; + + PartPalette() { + json_helper::defaultInit(this); + } + + template + void json_io(JsonIo& io) { + io + & json_dto::optional("selectedCustomTree", selectedCustomTree, -1) + & json_dto::optional("selectedTreeElements", selectedTreeElements, decltype(selectedTreeElements)()); + } + + friend bool operator==(const PartPalette& lhs, const PartPalette& rhs) { + return lhs.selectedCustomTree == rhs.selectedCustomTree + && lhs.selectedTreeElements == rhs.selectedTreeElements; + } + + friend bool operator!=(const PartPalette& lhs, const PartPalette& rhs) { return !(lhs == rhs); } }; struct PersistedState { uint16_t windowWidth; uint16_t windowHeight; - SnappingState snapping; + Snapping snapping; + PartPalette partPalette; PersistedState() { json_helper::defaultInit(this); @@ -45,13 +70,15 @@ namespace bricksim::persisted_state { io & json_dto::optional("windowWidth", windowWidth, 1280) & json_dto::optional("windowHeight", windowHeight, 720) - & json_dto::optional("snapping", snapping, SnappingState{}); + & json_dto::optional("snapping", snapping, Snapping{}) + & json_dto::optional("partPalette", partPalette, PartPalette{}); } friend bool operator==(const PersistedState& lhs, const PersistedState& rhs) { return lhs.windowWidth == rhs.windowWidth && lhs.windowHeight == rhs.windowHeight - && lhs.snapping == rhs.snapping; + && lhs.snapping == rhs.snapping + && lhs.partPalette == rhs.partPalette; } friend bool operator!=(const PersistedState& lhs, const PersistedState& rhs) { return !(lhs == rhs); }