|
1 | 1 | #include "MeshCraft/Application/MeshCraftApplication.hpp" |
| 2 | +#include "MeshCraft/Application/UI/Registry.hpp" |
2 | 3 | #include "MeshCraft/ModelRegistry.hpp" |
3 | 4 |
|
4 | 5 | #include <MeshCraft/Mc3/Mc3Document.hpp> |
@@ -56,84 +57,36 @@ void MeshCraftApplication::drawRegistryPanel() { |
56 | 57 |
|
57 | 58 | ImGui::Spacing(); |
58 | 59 |
|
59 | | - // Results table |
60 | | - const float tableH = ImGui::GetContentRegionAvail().y - 36.0f; |
61 | | - if (ImGui::BeginTable("##regtable", 4, |
62 | | - ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | |
63 | | - ImGuiTableFlags_ScrollY | ImGuiTableFlags_SizingStretchProp, |
64 | | - ImVec2(0, tableH))) |
65 | | - { |
66 | | - ImGui::TableSetupScrollFreeze(0, 1); |
67 | | - ImGui::TableSetupColumn("Group", ImGuiTableColumnFlags_WidthStretch, 0.22f); |
68 | | - ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthStretch, 0.38f); |
69 | | - ImGui::TableSetupColumn("Variant", ImGuiTableColumnFlags_WidthStretch, 0.22f); |
70 | | - ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, 66.0f); |
71 | | - ImGui::TableHeadersRow(); |
72 | | - |
73 | | - for (auto& entry : regCachedResults_) { |
74 | | - ImGui::TableNextRow(); |
75 | | - ImGui::TableSetColumnIndex(0); ImGui::TextUnformatted(entry.group.c_str()); |
76 | | - ImGui::TableSetColumnIndex(1); ImGui::TextUnformatted(entry.name.c_str()); |
77 | | - ImGui::TableSetColumnIndex(2); ImGui::TextUnformatted(entry.variant.c_str()); |
78 | | - ImGui::TableSetColumnIndex(3); |
79 | | - ImGui::PushID(static_cast<int>(entry.id)); |
80 | | - if (ImGui::SmallButton("Insert")) { |
| 60 | + UI::RegistryResultsContext resultsContext{ |
| 61 | + .results = regCachedResults_, |
| 62 | + .insert = [this](const ModelRegistry::Entry& entry) { |
| 63 | + try { |
| 64 | + pushUndo(); |
| 65 | + std::string defId; |
81 | 66 | try { |
82 | | - // insertIntoScene() writes doc.textures/materials/definitions |
83 | | - // directly (F7): pushUndo() must snapshot the document BEFORE |
84 | | - // that call, not just before the objects.push_back() below, |
85 | | - // or Ctrl+Z after an Insert leaves the imported definition and |
86 | | - // its textures/materials permanently orphaned in the scene. |
87 | | - // insertIntoScene() only ever throws before touching doc (temp |
88 | | - // file I/O and the empty-definitions check both precede any |
89 | | - // doc mutation), so on failure the just-pushed snapshot is |
90 | | - // popped back off unapplied instead of leaving a no-op undo |
91 | | - // step, mirroring this codebase's established no-op-undo rule. |
92 | | - pushUndo(); |
93 | | - std::string defId; |
94 | | - try { |
95 | | - defId = registry_.insertIntoScene(document_, entry); |
96 | | - } catch (...) { |
97 | | - undoManager_.popUndoWithoutApplying(); |
98 | | - throw; |
99 | | - } |
100 | | - // Place an instance of the definition at the origin |
101 | | - auto obj = std::make_shared<Mc3::Mc3Object>(); |
102 | | - obj->type = Mc3::ObjectType::Instance; |
103 | | - obj->definition = defId; |
104 | | - obj->name = entry.name + |
105 | | - (entry.variant.empty() ? "" : "_" + entry.variant); |
106 | | - std::string base = "reg_" + defId; |
107 | | - obj->id = base; |
108 | | - int n = 1; |
109 | | - while (flatFindById(obj->id)) |
110 | | - obj->id = base + "_" + std::to_string(n++); |
111 | | - |
112 | | - document_.objects.push_back(obj); |
113 | | - modified_ = true; |
114 | | - setStatusMsg("Inserted '" + entry.name + "' from registry"); |
115 | | - } catch (const std::exception& ex) { |
116 | | - setStatusMsg(std::string("Insert failed: ") + ex.what(), true); |
| 67 | + defId = registry_.insertIntoScene(document_, entry); |
| 68 | + } catch (...) { |
| 69 | + undoManager_.popUndoWithoutApplying(); |
| 70 | + throw; |
117 | 71 | } |
| 72 | + auto obj = std::make_shared<Mc3::Mc3Object>(); |
| 73 | + obj->type = Mc3::ObjectType::Instance; |
| 74 | + obj->definition = defId; |
| 75 | + obj->name = entry.name + (entry.variant.empty() ? "" : "_" + entry.variant); |
| 76 | + std::string base = "reg_" + defId; |
| 77 | + obj->id = base; |
| 78 | + int n = 1; |
| 79 | + while (flatFindById(obj->id)) obj->id = base + "_" + std::to_string(n++); |
| 80 | + document_.objects.push_back(obj); |
| 81 | + modified_ = true; |
| 82 | + setStatusMsg("Inserted '" + entry.name + "' from registry"); |
| 83 | + } catch (const std::exception& ex) { |
| 84 | + setStatusMsg(std::string("Insert failed: ") + ex.what(), true); |
118 | 85 | } |
119 | | - // Delete button (right-click context or explicit — keep visible for power users) |
120 | | - ImGui::SameLine(0, 4); |
121 | | - if (ImGui::SmallButton("X")) { |
122 | | - registry_.remove(entry.id); |
123 | | - regResultsDirty_ = true; |
124 | | - } |
125 | | - if (ImGui::IsItemHovered()) |
126 | | - ImGui::SetTooltip("Remove from registry"); |
127 | | - ImGui::PopID(); |
128 | | - } |
129 | | - |
130 | | - if (regCachedResults_.empty()) { |
131 | | - ImGui::TableNextRow(); |
132 | | - ImGui::TableSetColumnIndex(0); |
133 | | - ImGui::TextDisabled("(empty)"); |
134 | | - } |
135 | | - ImGui::EndTable(); |
136 | | - } |
| 86 | + }, |
| 87 | + .remove = [this](int64_t id) { registry_.remove(id); regResultsDirty_ = true; }, |
| 88 | + }; |
| 89 | + UI::Registry::drawResults(resultsContext); |
137 | 90 |
|
138 | 91 | ImGui::Separator(); |
139 | 92 | if (ImGui::Button("Save Definition to Registry...")) |
@@ -239,3 +192,42 @@ void MeshCraftApplication::drawRegistryPanel() { |
239 | 192 | } |
240 | 193 |
|
241 | 194 | } // namespace MeshCraft::Application |
| 195 | + |
| 196 | +namespace MeshCraft::Application::UI { |
| 197 | + |
| 198 | +void Registry::drawResults(RegistryResultsContext& context) { |
| 199 | + const float tableH = ImGui::GetContentRegionAvail().y - 36.0f; |
| 200 | + if (!ImGui::BeginTable("##regtable", 4, |
| 201 | + ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | |
| 202 | + ImGuiTableFlags_ScrollY | ImGuiTableFlags_SizingStretchProp, |
| 203 | + ImVec2(0, tableH))) return; |
| 204 | + |
| 205 | + ImGui::TableSetupScrollFreeze(0, 1); |
| 206 | + ImGui::TableSetupColumn("Group", ImGuiTableColumnFlags_WidthStretch, 0.22f); |
| 207 | + ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthStretch, 0.38f); |
| 208 | + ImGui::TableSetupColumn("Variant", ImGuiTableColumnFlags_WidthStretch, 0.22f); |
| 209 | + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, 66.0f); |
| 210 | + ImGui::TableHeadersRow(); |
| 211 | + |
| 212 | + for (auto& entry : context.results) { |
| 213 | + ImGui::TableNextRow(); |
| 214 | + ImGui::TableSetColumnIndex(0); ImGui::TextUnformatted(entry.group.c_str()); |
| 215 | + ImGui::TableSetColumnIndex(1); ImGui::TextUnformatted(entry.name.c_str()); |
| 216 | + ImGui::TableSetColumnIndex(2); ImGui::TextUnformatted(entry.variant.c_str()); |
| 217 | + ImGui::TableSetColumnIndex(3); |
| 218 | + ImGui::PushID(static_cast<int>(entry.id)); |
| 219 | + if (ImGui::SmallButton("Insert")) context.insert(entry); |
| 220 | + ImGui::SameLine(0, 4); |
| 221 | + if (ImGui::SmallButton("X")) context.remove(entry.id); |
| 222 | + if (ImGui::IsItemHovered()) ImGui::SetTooltip("Remove from registry"); |
| 223 | + ImGui::PopID(); |
| 224 | + } |
| 225 | + if (context.results.empty()) { |
| 226 | + ImGui::TableNextRow(); |
| 227 | + ImGui::TableSetColumnIndex(0); |
| 228 | + ImGui::TextDisabled("(empty)"); |
| 229 | + } |
| 230 | + ImGui::EndTable(); |
| 231 | +} |
| 232 | + |
| 233 | +} // namespace MeshCraft::Application::UI |
0 commit comments