Line | Branch | Exec | Source |
---|---|---|---|
1 | // This file is part of INSTINCT, the INS Toolkit for Integrated | ||
2 | // Navigation Concepts and Training by the Institute of Navigation of | ||
3 | // the University of Stuttgart, Germany. | ||
4 | // | ||
5 | // This Source Code Form is subject to the terms of the Mozilla Public | ||
6 | // License, v. 2.0. If a copy of the MPL was not distributed with this | ||
7 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||
8 | |||
9 | /// @file Colormap.cpp | ||
10 | /// @brief Colormap | ||
11 | /// @author T. Topp (topp@ins.uni-stuttgart.de) | ||
12 | /// @date 2023-09-22 | ||
13 | |||
14 | #include "Colormap.hpp" | ||
15 | |||
16 | #include <algorithm> | ||
17 | #include <chrono> | ||
18 | |||
19 | #include "internal/gui/widgets/imgui_ex.hpp" | ||
20 | #include "util/Logger.hpp" | ||
21 | #include "util/ImGui.hpp" | ||
22 | |||
23 | namespace NAV | ||
24 | { | ||
25 | |||
26 | std::vector<Colormap> ColormapsGlobal; | ||
27 | std::vector<Colormap> ColormapsFlow; | ||
28 | |||
29 |
2/4✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
|
4 | Colormap::Colormap() |
30 | { | ||
31 |
1/2✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
|
1 | id = std::chrono::duration_cast<std::chrono::microseconds>( |
32 | 1 | std::chrono::system_clock::now().time_since_epoch()) | |
33 | 1 | .count(); | |
34 | 1 | } | |
35 | |||
36 | 3 | void Colormap::addColor(double value, ImColor color) | |
37 | { | ||
38 |
2/4✓ Branch 3 taken 3 times.
✗ Branch 4 not taken.
✓ Branch 7 taken 3 times.
✗ Branch 8 not taken.
|
3 | colormap.insert(std::upper_bound(colormap.begin(), colormap.end(), value, // NOLINT(boost-use-ranges,modernize-use-ranges) // ranges::upper_bound with lambda is not supported yet |
39 | 4 | [](double value, const std::pair<double, ImColor>& item) { return value < item.first; }), | |
40 | 3 | std::make_pair(value, color)); | |
41 | 3 | version++; | |
42 | 3 | } | |
43 | |||
44 | 5 | void Colormap::removeColor(size_t idx) | |
45 | { | ||
46 |
6/6✓ Branch 1 taken 4 times.
✓ Branch 2 taken 1 times.
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 3 times.
✓ Branch 6 taken 2 times.
✓ Branch 7 taken 3 times.
|
5 | if (idx >= colormap.size() || colormap.size() == 1) { return; } |
47 |
1/2✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
|
3 | colormap.erase(colormap.begin() + static_cast<std::ptrdiff_t>(idx)); |
48 | 3 | version++; | |
49 | } | ||
50 | |||
51 | ✗ | ImColor Colormap::getColor(double value, const ImColor& defaultColor) const | |
52 | { | ||
53 | ✗ | for (size_t i = colormap.size() - 1;; i--) | |
54 | { | ||
55 | ✗ | const auto& item = colormap.at(i); | |
56 | ✗ | if (value >= item.first) | |
57 | { | ||
58 | ✗ | if (discrete || i == colormap.size() - 1) { return item.second; } | |
59 | |||
60 | ✗ | const auto& other = colormap.at(i + 1); | |
61 | |||
62 | ✗ | double t = (value - item.first) / (other.first - item.first); | |
63 | |||
64 | ✗ | return item.second.Value + static_cast<float>(t) * (other.second.Value - item.second.Value); | |
65 | } | ||
66 | |||
67 | ✗ | if (i == 0) { break; } | |
68 | ✗ | } | |
69 | |||
70 | ✗ | return defaultColor; | |
71 | } | ||
72 | |||
73 | ✗ | int64_t Colormap::getId() const | |
74 | { | ||
75 | ✗ | return id; | |
76 | } | ||
77 | |||
78 | 18 | const std::vector<std::pair<double, ImColor>>& Colormap::getColormap() const | |
79 | { | ||
80 | 18 | return colormap; | |
81 | } | ||
82 | |||
83 | ✗ | void Colormap::render() const | |
84 | { | ||
85 | ✗ | const ImVec2 pos = ImGui::GetCursorScreenPos(); | |
86 | ✗ | const float w = ImGui::CalcItemWidth(); | |
87 | ✗ | const float h = ImGui::GetFrameHeight(); | |
88 | ✗ | const ImRect bounds = ImRect(pos.x, pos.y, pos.x + w, pos.y + h); | |
89 | |||
90 | ✗ | render(bounds); | |
91 | ✗ | } | |
92 | |||
93 | ✗ | void Colormap::render(const ImRect& bounds) const | |
94 | { | ||
95 | ✗ | if (auto* drawList = ImGui::GetWindowDrawList()) | |
96 | { | ||
97 | ✗ | if (colormap.empty()) | |
98 | { | ||
99 | ✗ | drawList->AddRectFilled(bounds.Min, bounds.Max, IM_COL32(25, 25, 25, 255)); | |
100 | ✗ | return; | |
101 | } | ||
102 | |||
103 | ✗ | const size_t n = colormap.size() >= 2 ? (discrete ? colormap.size() : colormap.size() - 1) : colormap.size(); | |
104 | |||
105 | ✗ | const float step = bounds.GetWidth() / static_cast<float>(n); | |
106 | ✗ | ImRect rect(bounds.Min.x, bounds.Min.y, bounds.Min.x + step, bounds.Max.y); | |
107 | ✗ | for (size_t i = 0; i < n; ++i) | |
108 | { | ||
109 | ✗ | ImU32 col1 = colormap.at(i).second; | |
110 | ✗ | ImU32 col2 = discrete || colormap.size() == 1 ? col1 : ImU32(colormap.at(i + 1).second); | |
111 | |||
112 | ✗ | drawList->AddRectFilledMultiColor(rect.Min, rect.Max, col1, col2, col2, col1); | |
113 | ✗ | rect.TranslateX(step); | |
114 | } | ||
115 | } | ||
116 | } | ||
117 | |||
118 | ✗ | bool ColormapButton(const char* label, Colormap& cmap, const ImVec2& size_arg) | |
119 | { | ||
120 | ✗ | ImGuiContext& G = *GImGui; | |
121 | ✗ | const ImGuiStyle& style = G.Style; | |
122 | ✗ | ImGuiWindow* Window = G.CurrentWindow; | |
123 | ✗ | if (Window->SkipItems) { return false; } | |
124 | |||
125 | ✗ | const ImVec2 pos = ImGui::GetCurrentWindow()->DC.CursorPos; | |
126 | ✗ | const ImVec2 label_size = ImGui::CalcTextSize(label, nullptr, true); | |
127 | ✗ | ImVec2 size = ImGui::CalcItemSize(size_arg, label_size.x + style.FramePadding.x * 2.0F, label_size.y + style.FramePadding.y * 2.0F); | |
128 | ✗ | const ImRect rect = ImRect(pos.x, pos.y, pos.x + size.x, pos.y + size.y); | |
129 | ✗ | cmap.render(rect); | |
130 | ✗ | ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32_BLACK_TRANS); | |
131 | ✗ | ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1, 1, 1, 0.1F)); | |
132 | ✗ | ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1, 1, 1, 0.2F)); | |
133 | ✗ | ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 0); | |
134 | ✗ | const bool pressed = ImGui::Button(label, size); | |
135 | ✗ | ImGui::PopStyleColor(3); | |
136 | ✗ | ImGui::PopStyleVar(1); | |
137 | |||
138 | ✗ | bool changed = false; | |
139 | |||
140 | ✗ | if (pressed) { ImGui::OpenPopup(fmt::format("Colormap##{}", label).c_str()); } | |
141 | ✗ | if (ImGui::BeginPopup(fmt::format("Colormap##{}", label).c_str())) | |
142 | { | ||
143 | ✗ | if (ImGui::BeginTable(fmt::format("##{} colormap table", label).c_str(), 5, ImGuiTableFlags_SizingFixedFit, ImVec2(0.0F, 0.0F))) | |
144 | { | ||
145 | ✗ | ImGui::TableSetupColumn("Value"); | |
146 | ✗ | ImGui::TableSetupColumn("Color"); | |
147 | ✗ | ImGui::TableSetupColumn(""); | |
148 | ✗ | ImGui::TableSetupColumn(""); | |
149 | ✗ | ImGui::TableSetupColumn(""); | |
150 | |||
151 | ✗ | int colormapRemovalIdx = -1; | |
152 | ✗ | int insertIdx = -1; | |
153 | ✗ | ImGui::TableHeadersRow(); | |
154 | ✗ | for (size_t i = 0; i < cmap.colormap.size(); i++) | |
155 | { | ||
156 | ✗ | auto& [value, color] = cmap.colormap.at(i); | |
157 | ✗ | ImGui::TableNextColumn(); | |
158 | ✗ | ImGui::SetNextItemWidth(100.0F); | |
159 | ✗ | if (ImGui::InputDoubleL(fmt::format("##{} colormap value {}", label, i).c_str(), &value, | |
160 | ✗ | i != 0 ? cmap.colormap.at(i - 1).first : std::numeric_limits<double>::lowest(), | |
161 | ✗ | i != cmap.colormap.size() - 1 ? cmap.colormap.at(i + 1).first : std::numeric_limits<double>::max(), | |
162 | 0.0, 0.0, "%.6g", ImGuiInputTextFlags_CharsScientific)) | ||
163 | { | ||
164 | ✗ | cmap.version++; | |
165 | ✗ | changed = true; | |
166 | } | ||
167 | |||
168 | ✗ | ImGui::TableNextColumn(); | |
169 | ✗ | ImGui::SetNextItemWidth(300.0F); | |
170 | ✗ | if (ImGui::ColorEdit4(fmt::format("##{} colormap color {}", label, i).c_str(), &color.Value.x)) | |
171 | { | ||
172 | ✗ | cmap.version++; | |
173 | ✗ | changed = true; | |
174 | } | ||
175 | |||
176 | ✗ | ImGui::TableNextColumn(); | |
177 | ✗ | if (ImGui::Button(fmt::format("⌃##{} insert colormap above {}", label, i).c_str())) { insertIdx = static_cast<int>(i); } | |
178 | ✗ | if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Insert entry above?"); } | |
179 | |||
180 | ✗ | ImGui::TableNextColumn(); | |
181 | ✗ | if (ImGui::Button(fmt::format("⌄##{} insert colormap below {}", label, i).c_str())) { insertIdx = static_cast<int>(i) + 1; } | |
182 | ✗ | if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Insert entry below?"); } | |
183 | |||
184 | ✗ | ImGui::TableNextColumn(); | |
185 | ✗ | if (cmap.colormap.size() > 1) | |
186 | { | ||
187 | ✗ | if (ImGui::Button(fmt::format("X##{} remove colormap {}", label, i).c_str())) { colormapRemovalIdx = static_cast<int>(i); } | |
188 | ✗ | if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Remove?"); } | |
189 | } | ||
190 | } | ||
191 | ✗ | if (colormapRemovalIdx >= 0) | |
192 | { | ||
193 | ✗ | cmap.colormap.erase(cmap.colormap.begin() + static_cast<std::ptrdiff_t>(colormapRemovalIdx)); | |
194 | ✗ | cmap.version++; | |
195 | ✗ | changed = true; | |
196 | } | ||
197 | ✗ | if (insertIdx >= 0) | |
198 | { | ||
199 | ✗ | cmap.colormap.insert(cmap.colormap.begin() + static_cast<std::ptrdiff_t>(insertIdx), | |
200 | ✗ | std::make_pair(cmap.colormap.at(static_cast<size_t>(insertIdx != static_cast<int>(cmap.colormap.size()) ? insertIdx : insertIdx - 1)).first, | |
201 | ✗ | ImColor(1.0F, 1.0F, 1.0F, 1.0F))); | |
202 | ✗ | cmap.version++; | |
203 | ✗ | changed = true; | |
204 | } | ||
205 | |||
206 | ✗ | ImGui::EndTable(); | |
207 | } | ||
208 | |||
209 | ✗ | ImGui::EndPopup(); | |
210 | } | ||
211 | |||
212 | ✗ | return changed; | |
213 | } | ||
214 | |||
215 | ✗ | void to_json(json& j, const Colormap& cmap) | |
216 | { | ||
217 | ✗ | j = json{ | |
218 | ✗ | { "name", cmap.name }, | |
219 | ✗ | { "discrete", cmap.discrete }, | |
220 | ✗ | { "id", cmap.id }, | |
221 | ✗ | { "colormap", cmap.colormap }, | |
222 | ✗ | }; | |
223 | ✗ | } | |
224 | |||
225 | ✗ | void from_json(const json& j, Colormap& cmap) | |
226 | { | ||
227 | ✗ | if (j.contains("name")) | |
228 | { | ||
229 | ✗ | j.at("name").get_to(cmap.name); | |
230 | } | ||
231 | ✗ | if (j.contains("discrete")) | |
232 | { | ||
233 | ✗ | j.at("discrete").get_to(cmap.discrete); | |
234 | } | ||
235 | ✗ | if (j.contains("id")) | |
236 | { | ||
237 | ✗ | j.at("id").get_to(cmap.id); | |
238 | } | ||
239 | ✗ | if (j.contains("colormap")) | |
240 | { | ||
241 | ✗ | j.at("colormap").get_to(cmap.colormap); | |
242 | } | ||
243 | ✗ | } | |
244 | |||
245 | ✗ | bool ShowColormapSelector(ColormapMaskType& type, int64_t& id, const char* label) | |
246 | { | ||
247 | ✗ | bool changes = false; | |
248 | |||
249 | ✗ | auto activeColormap = ColormapSearch(type, id); | |
250 | |||
251 | ✗ | std::string colormapName = activeColormap ? activeColormap->get().name : ""; | |
252 | ✗ | if (ImGui::BeginCombo(fmt::format("{}Colormap Mask", label).c_str(), colormapName.c_str())) | |
253 | { | ||
254 | ✗ | if (ImGui::Selectable("##Empty colormap mask", colormapName.empty(), 0)) | |
255 | { | ||
256 | ✗ | if (id != -1 || type != ColormapMaskType::None) | |
257 | { | ||
258 | ✗ | id = -1; | |
259 | ✗ | type = ColormapMaskType::None; | |
260 | ✗ | changes = true; | |
261 | } | ||
262 | } | ||
263 | // Set the initial focus when opening the combo (scrolling + keyboard navigation focus) | ||
264 | ✗ | if (colormapName.empty()) { ImGui::SetItemDefaultFocus(); } | |
265 | |||
266 | ✗ | for (const auto& cmap : ColormapsGlobal) | |
267 | { | ||
268 | ✗ | const bool is_selected = (cmap.getId() == id); | |
269 | ✗ | if (ImGui::Selectable(fmt::format("G: {}##Global colormap", cmap.name).c_str(), is_selected, 0)) | |
270 | { | ||
271 | ✗ | if (id != cmap.getId() || type != ColormapMaskType::Global) | |
272 | { | ||
273 | ✗ | id = cmap.getId(); | |
274 | ✗ | type = ColormapMaskType::Global; | |
275 | ✗ | changes = true; | |
276 | } | ||
277 | } | ||
278 | // Set the initial focus when opening the combo (scrolling + keyboard navigation focus) | ||
279 | ✗ | if (is_selected) { ImGui::SetItemDefaultFocus(); } | |
280 | } | ||
281 | ✗ | for (const auto& cmap : ColormapsFlow) | |
282 | { | ||
283 | ✗ | const bool is_selected = (cmap.getId() == id); | |
284 | ✗ | if (ImGui::Selectable(fmt::format("F: {}##Flow colormap", cmap.name).c_str(), is_selected, 0)) | |
285 | { | ||
286 | ✗ | if (id != cmap.getId() || type != ColormapMaskType::Flow) | |
287 | { | ||
288 | ✗ | id = cmap.getId(); | |
289 | ✗ | type = ColormapMaskType::Flow; | |
290 | ✗ | changes = true; | |
291 | } | ||
292 | } | ||
293 | // Set the initial focus when opening the combo (scrolling + keyboard navigation focus) | ||
294 | ✗ | if (is_selected) { ImGui::SetItemDefaultFocus(); } | |
295 | } | ||
296 | ✗ | ImGui::EndCombo(); | |
297 | } | ||
298 | ✗ | return changes; | |
299 | ✗ | } | |
300 | |||
301 | ✗ | std::optional<std::reference_wrapper<const Colormap>> ColormapSearch(const ColormapMaskType& type, const int64_t& id) | |
302 | { | ||
303 | ✗ | switch (type) | |
304 | { | ||
305 | ✗ | case ColormapMaskType::None: | |
306 | ✗ | break; | |
307 | ✗ | case ColormapMaskType::Global: | |
308 | ✗ | if (auto iter = std::ranges::find_if(ColormapsGlobal, [&id](const Colormap& cmap) { return id == cmap.getId(); }); | |
309 | ✗ | iter != ColormapsGlobal.end()) | |
310 | { | ||
311 | ✗ | return std::cref(*iter); | |
312 | } | ||
313 | ✗ | break; | |
314 | ✗ | case ColormapMaskType::Flow: | |
315 | ✗ | if (auto iter = std::ranges::find_if(ColormapsFlow, [&id](const Colormap& cmap) { return id == cmap.getId(); }); | |
316 | ✗ | iter != ColormapsFlow.end()) | |
317 | { | ||
318 | ✗ | return std::cref(*iter); | |
319 | } | ||
320 | ✗ | break; | |
321 | } | ||
322 | ✗ | return {}; | |
323 | } | ||
324 | |||
325 | } // namespace NAV | ||
326 |