blob: 936acb2d95ad8fe545b84c992a9538693b9afc81 [file] [log] [blame]
Austin Schuh812d0d12021-11-04 20:16:48 -07001// Copyright (c) FIRST and other WPILib contributors.
2// Open Source Software; you can modify and/or share it under the terms of
3// the WPILib BSD license file in the root directory of this project.
4
5#include "glass/Context.h"
6
7#include <algorithm>
8#include <cinttypes>
9#include <cstdio>
10
11#include <imgui.h>
12#include <imgui_internal.h>
13#include <imgui_stdlib.h>
14#include <wpi/StringExtras.h>
15#include <wpi/timestamp.h>
16#include <wpigui.h>
17
18#include "glass/ContextInternal.h"
19
20using namespace glass;
21
22Context* glass::gContext;
23
24static bool ConvertInt(Storage::Value* value) {
25 value->type = Storage::Value::kInt;
26 if (auto val = wpi::parse_integer<int>(value->stringVal, 10)) {
27 value->intVal = val.value();
28 return true;
29 }
30 return false;
31}
32
33static bool ConvertInt64(Storage::Value* value) {
34 value->type = Storage::Value::kInt64;
35 if (auto val = wpi::parse_integer<int64_t>(value->stringVal, 10)) {
36 value->int64Val = val.value();
37 return true;
38 }
39 return false;
40}
41
42static bool ConvertBool(Storage::Value* value) {
43 value->type = Storage::Value::kBool;
44 if (auto val = wpi::parse_integer<int>(value->stringVal, 10)) {
45 value->intVal = (val.value() != 0);
46 return true;
47 }
48 return false;
49}
50
51static bool ConvertFloat(Storage::Value* value) {
52 value->type = Storage::Value::kFloat;
53 if (auto val = wpi::parse_float<float>(value->stringVal)) {
54 value->floatVal = val.value();
55 return true;
56 }
57 return false;
58}
59
60static bool ConvertDouble(Storage::Value* value) {
61 value->type = Storage::Value::kDouble;
62 if (auto val = wpi::parse_float<double>(value->stringVal)) {
63 value->doubleVal = val.value();
64 return true;
65 }
66 return false;
67}
68
69static void* GlassStorageReadOpen(ImGuiContext*, ImGuiSettingsHandler* handler,
70 const char* name) {
71 auto ctx = static_cast<Context*>(handler->UserData);
72 auto& storage = ctx->storage[name];
73 if (!storage) {
74 storage = std::make_unique<Storage>();
75 }
76 return storage.get();
77}
78
79static void GlassStorageReadLine(ImGuiContext*, ImGuiSettingsHandler*,
80 void* entry, const char* line) {
81 auto storage = static_cast<Storage*>(entry);
82 auto [key, val] = wpi::split(line, '=');
83 auto& keys = storage->GetKeys();
84 auto& values = storage->GetValues();
85 auto it = std::find(keys.begin(), keys.end(), key);
86 if (it == keys.end()) {
87 keys.emplace_back(key);
88 values.emplace_back(std::make_unique<Storage::Value>(val));
89 } else {
90 auto& value = *values[it - keys.begin()];
91 value.stringVal = val;
92 switch (value.type) {
93 case Storage::Value::kInt:
94 ConvertInt(&value);
95 break;
96 case Storage::Value::kInt64:
97 ConvertInt64(&value);
98 break;
99 case Storage::Value::kBool:
100 ConvertBool(&value);
101 break;
102 case Storage::Value::kFloat:
103 ConvertFloat(&value);
104 break;
105 case Storage::Value::kDouble:
106 ConvertDouble(&value);
107 break;
108 default:
109 break;
110 }
111 }
112}
113
114static void GlassStorageWriteAll(ImGuiContext*, ImGuiSettingsHandler* handler,
115 ImGuiTextBuffer* out_buf) {
116 auto ctx = static_cast<Context*>(handler->UserData);
117
118 // sort for output
119 std::vector<wpi::StringMapConstIterator<std::unique_ptr<Storage>>> sorted;
120 for (auto it = ctx->storage.begin(); it != ctx->storage.end(); ++it) {
121 sorted.emplace_back(it);
122 }
123 std::sort(sorted.begin(), sorted.end(), [](const auto& a, const auto& b) {
124 return a->getKey() < b->getKey();
125 });
126
127 for (auto&& entryIt : sorted) {
128 auto& entry = *entryIt;
129 out_buf->append("[GlassStorage][");
130 out_buf->append(entry.first().data(),
131 entry.first().data() + entry.first().size());
132 out_buf->append("]\n");
133 auto& keys = entry.second->GetKeys();
134 auto& values = entry.second->GetValues();
135 for (size_t i = 0; i < keys.size(); ++i) {
136 out_buf->append(keys[i].data(), keys[i].data() + keys[i].size());
137 out_buf->append("=");
138 auto& value = *values[i];
139 switch (value.type) {
140 case Storage::Value::kInt:
141 out_buf->appendf("%d\n", value.intVal);
142 break;
143 case Storage::Value::kInt64:
144 out_buf->appendf("%" PRId64 "\n", value.int64Val);
145 break;
146 case Storage::Value::kBool:
147 out_buf->appendf("%d\n", value.boolVal ? 1 : 0);
148 break;
149 case Storage::Value::kFloat:
150 out_buf->appendf("%f\n", value.floatVal);
151 break;
152 case Storage::Value::kDouble:
153 out_buf->appendf("%f\n", value.doubleVal);
154 break;
155 case Storage::Value::kNone:
156 case Storage::Value::kString:
157 out_buf->append(value.stringVal.data(),
158 value.stringVal.data() + value.stringVal.size());
159 out_buf->append("\n");
160 break;
161 }
162 }
163 out_buf->append("\n");
164 }
165}
166
167static void Initialize(Context* ctx) {
168 wpi::gui::AddInit([=] {
169 ImGuiSettingsHandler ini_handler;
170 ini_handler.TypeName = "GlassStorage";
171 ini_handler.TypeHash = ImHashStr("GlassStorage");
172 ini_handler.ReadOpenFn = GlassStorageReadOpen;
173 ini_handler.ReadLineFn = GlassStorageReadLine;
174 ini_handler.WriteAllFn = GlassStorageWriteAll;
175 ini_handler.UserData = ctx;
176 ImGui::GetCurrentContext()->SettingsHandlers.push_back(ini_handler);
177
178 ctx->sources.Initialize();
179 });
180}
181
182static void Shutdown(Context* ctx) {}
183
184Context* glass::CreateContext() {
185 Context* ctx = new Context;
186 if (!gContext) {
187 SetCurrentContext(ctx);
188 }
189 Initialize(ctx);
190 return ctx;
191}
192
193void glass::DestroyContext(Context* ctx) {
194 if (!ctx) {
195 ctx = gContext;
196 }
197 Shutdown(ctx);
198 if (gContext == ctx) {
199 SetCurrentContext(nullptr);
200 }
201 delete ctx;
202}
203
204Context* glass::GetCurrentContext() {
205 return gContext;
206}
207
208void glass::SetCurrentContext(Context* ctx) {
209 gContext = ctx;
210}
211
212void glass::ResetTime() {
213 gContext->zeroTime = wpi::Now();
214}
215
216uint64_t glass::GetZeroTime() {
217 return gContext->zeroTime;
218}
219
220Storage::Value& Storage::GetValue(std::string_view key) {
221 auto it = std::find(m_keys.begin(), m_keys.end(), key);
222 if (it == m_keys.end()) {
223 m_keys.emplace_back(key);
224 m_values.emplace_back(std::make_unique<Value>());
225 return *m_values.back();
226 } else {
227 return *m_values[it - m_keys.begin()];
228 }
229}
230
231#define DEFUN(CapsName, LowerName, CType) \
232 CType Storage::Get##CapsName(std::string_view key, CType defaultVal) const { \
233 auto it = std::find(m_keys.begin(), m_keys.end(), key); \
234 if (it == m_keys.end()) \
235 return defaultVal; \
236 Value& value = *m_values[it - m_keys.begin()]; \
237 if (value.type != Value::k##CapsName) { \
238 if (!Convert##CapsName(&value)) \
239 value.LowerName##Val = defaultVal; \
240 } \
241 return value.LowerName##Val; \
242 } \
243 \
244 void Storage::Set##CapsName(std::string_view key, CType val) { \
245 auto it = std::find(m_keys.begin(), m_keys.end(), key); \
246 if (it == m_keys.end()) { \
247 m_keys.emplace_back(key); \
248 m_values.emplace_back(std::make_unique<Value>()); \
249 m_values.back()->type = Value::k##CapsName; \
250 m_values.back()->LowerName##Val = val; \
251 } else { \
252 Value& value = *m_values[it - m_keys.begin()]; \
253 value.type = Value::k##CapsName; \
254 value.LowerName##Val = val; \
255 } \
256 } \
257 \
258 CType* Storage::Get##CapsName##Ref(std::string_view key, CType defaultVal) { \
259 auto it = std::find(m_keys.begin(), m_keys.end(), key); \
260 if (it == m_keys.end()) { \
261 m_keys.emplace_back(key); \
262 m_values.emplace_back(std::make_unique<Value>()); \
263 m_values.back()->type = Value::k##CapsName; \
264 m_values.back()->LowerName##Val = defaultVal; \
265 return &m_values.back()->LowerName##Val; \
266 } else { \
267 Value& value = *m_values[it - m_keys.begin()]; \
268 if (value.type != Value::k##CapsName) { \
269 if (!Convert##CapsName(&value)) \
270 value.LowerName##Val = defaultVal; \
271 } \
272 return &value.LowerName##Val; \
273 } \
274 }
275
276DEFUN(Int, int, int)
277DEFUN(Int64, int64, int64_t)
278DEFUN(Bool, bool, bool)
279DEFUN(Float, float, float)
280DEFUN(Double, double, double)
281
282std::string Storage::GetString(std::string_view key,
283 std::string_view defaultVal) const {
284 auto it = std::find(m_keys.begin(), m_keys.end(), key);
285 if (it == m_keys.end()) {
286 return std::string{defaultVal};
287 }
288 Value& value = *m_values[it - m_keys.begin()];
289 value.type = Value::kString;
290 return value.stringVal;
291}
292
293void Storage::SetString(std::string_view key, std::string_view val) {
294 auto it = std::find(m_keys.begin(), m_keys.end(), key);
295 if (it == m_keys.end()) {
296 m_keys.emplace_back(key);
297 m_values.emplace_back(std::make_unique<Value>(val));
298 m_values.back()->type = Value::kString;
299 } else {
300 Value& value = *m_values[it - m_keys.begin()];
301 value.type = Value::kString;
302 value.stringVal = val;
303 }
304}
305
306std::string* Storage::GetStringRef(std::string_view key,
307 std::string_view defaultVal) {
308 auto it = std::find(m_keys.begin(), m_keys.end(), key);
309 if (it == m_keys.end()) {
310 m_keys.emplace_back(key);
311 m_values.emplace_back(std::make_unique<Value>(defaultVal));
312 m_values.back()->type = Value::kString;
313 return &m_values.back()->stringVal;
314 } else {
315 Value& value = *m_values[it - m_keys.begin()];
316 value.type = Value::kString;
317 return &value.stringVal;
318 }
319}
320
321Storage& glass::GetStorage() {
322 auto& storage = gContext->storage[gContext->curId];
323 if (!storage) {
324 storage = std::make_unique<Storage>();
325 }
326 return *storage;
327}
328
329Storage& glass::GetStorage(std::string_view id) {
330 auto& storage = gContext->storage[id];
331 if (!storage) {
332 storage = std::make_unique<Storage>();
333 }
334 return *storage;
335}
336
337static void PushIDStack(std::string_view label_id) {
338 gContext->idStack.emplace_back(gContext->curId.size());
339
340 auto [label, id] = wpi::split(label_id, "###");
341 // if no ###id, use label as id
342 if (id.empty()) {
343 id = label;
344 }
345 if (!gContext->curId.empty()) {
346 gContext->curId += "###";
347 }
348 gContext->curId += id;
349}
350
351static void PopIDStack() {
352 gContext->curId.resize(gContext->idStack.back());
353 gContext->idStack.pop_back();
354}
355
356bool glass::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) {
357 PushIDStack(name);
358 return ImGui::Begin(name, p_open, flags);
359}
360
361void glass::End() {
362 ImGui::End();
363 PopIDStack();
364}
365
366bool glass::BeginChild(const char* str_id, const ImVec2& size, bool border,
367 ImGuiWindowFlags flags) {
368 PushIDStack(str_id);
369 return ImGui::BeginChild(str_id, size, border, flags);
370}
371
372void glass::EndChild() {
373 ImGui::EndChild();
374 PopIDStack();
375}
376
377bool glass::CollapsingHeader(const char* label, ImGuiTreeNodeFlags flags) {
378 wpi::SmallString<64> openKey;
379 auto [name, id] = wpi::split(label, "###");
380 // if no ###id, use name as id
381 if (id.empty()) {
382 id = name;
383 }
384 openKey = id;
385 openKey += "###open";
386
387 bool* open = GetStorage().GetBoolRef(openKey.str());
388 *open = ImGui::CollapsingHeader(
389 label, flags | (*open ? ImGuiTreeNodeFlags_DefaultOpen : 0));
390 return *open;
391}
392
393bool glass::TreeNodeEx(const char* label, ImGuiTreeNodeFlags flags) {
394 PushIDStack(label);
395 bool* open = GetStorage().GetBoolRef("open");
396 *open = ImGui::TreeNodeEx(
397 label, flags | (*open ? ImGuiTreeNodeFlags_DefaultOpen : 0));
398 if (!*open) {
399 PopIDStack();
400 }
401 return *open;
402}
403
404void glass::TreePop() {
405 ImGui::TreePop();
406 PopIDStack();
407}
408
409void glass::PushID(const char* str_id) {
410 PushIDStack(str_id);
411 ImGui::PushID(str_id);
412}
413
414void glass::PushID(const char* str_id_begin, const char* str_id_end) {
415 PushIDStack(std::string_view(str_id_begin, str_id_end - str_id_begin));
416 ImGui::PushID(str_id_begin, str_id_end);
417}
418
419void glass::PushID(int int_id) {
420 char buf[16];
421 std::snprintf(buf, sizeof(buf), "%d", int_id);
422 PushIDStack(buf);
423 ImGui::PushID(int_id);
424}
425
426void glass::PopID() {
427 ImGui::PopID();
428 PopIDStack();
429}
430
431bool glass::PopupEditName(const char* label, std::string* name) {
432 bool rv = false;
433 if (ImGui::BeginPopupContextItem(label)) {
434 ImGui::Text("Edit name:");
435 if (ImGui::InputText("##editname", name)) {
436 rv = true;
437 }
438 if (ImGui::Button("Close") || ImGui::IsKeyPressedMap(ImGuiKey_Enter) ||
439 ImGui::IsKeyPressedMap(ImGuiKey_KeyPadEnter)) {
440 ImGui::CloseCurrentPopup();
441 }
442 ImGui::EndPopup();
443 }
444 return rv;
445}